Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic plugin for page-ssr injection #4049

Merged
merged 6 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/plenty-hats-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/mdx': patch
---

Improve `injectScript` handling for non-Astro pages
14 changes: 13 additions & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
removeTrailingForwardSlash,
} from '../../core/path.js';
import type { RenderOptions } from '../../core/render/core';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { debug, info } from '../logger/core.js';
import { render } from '../render/core.js';
Expand Down Expand Up @@ -272,6 +272,18 @@ async function generatePath(
const links = createLinkStylesheetElementSet(linkIds.reverse(), site);
const scripts = createModuleScriptsSet(hoistedScripts ? [hoistedScripts] : [], site);

if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID);
if (typeof hashedFilePath !== 'string') {
throw new Error(`Cannot find the built path for ${PAGE_SCRIPT_ID}`);
}
const src = prependForwardSlash(npath.posix.join(astroConfig.base, hashedFilePath));
scripts.add({
props: { type: 'module', src },
children: '',
})
}

// Add all injected scripts to the page.
for (const script of astroConfig._ctx.scripts) {
if (script.stage === 'head-inline') {
Expand Down
5 changes: 5 additions & 0 deletions packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { prependForwardSlash } from '../../core/path.js';
import { emptyDir, isModeServerWithNoAdapter, removeDir } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/index.js';
import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { ViteConfigWithSSR } from '../create-vite';
import { info } from '../logger/core.js';
import { generatePages } from './generate.js';
Expand Down Expand Up @@ -85,6 +86,10 @@ Example:
...internals.discoveredScripts,
]);

if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) {
clientInput.add(PAGE_SCRIPT_ID);
}

// Run client build first, so the assets can be fed into the SSR rendered version.
timer.clientBuild = performance.now();
await clientBuild(opts, internals, clientInput);
Expand Down
10 changes: 8 additions & 2 deletions packages/astro/src/core/build/vite-plugin-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import glob from 'fast-glob';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
import { runHookBuildSsr } from '../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { pagesVirtualModuleId } from '../app/index.js';
import { serializeRouteData } from '../routing/index.js';
import { addRollupInput } from './add-rollup-input.js';
Expand Down Expand Up @@ -123,12 +123,19 @@ function buildManifest(
const { astroConfig } = opts;

const routes: SerializedRouteInfo[] = [];
const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) {
staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
}

for (const pageData of eachPageData(internals)) {
const scripts: SerializedRouteInfo['scripts'] = [];
if (pageData.hoistedScript) {
scripts.unshift(pageData.hoistedScript);
}
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) {
scripts.push({ type: 'external', value: entryModules[PAGE_SCRIPT_ID] });
}

routes.push({
file: '',
Expand All @@ -144,7 +151,6 @@ function buildManifest(
}

// HACK! Patch this special one.
const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
if (!(BEFORE_HYDRATION_SCRIPT_ID in entryModules)) {
entryModules[BEFORE_HYDRATION_SCRIPT_ID] =
'data:text/javascript;charset=utf-8,//[no before-hydration script]';
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-contai
import jsxVitePlugin from '../vite-plugin-jsx/index.js';
import markdownVitePlugin from '../vite-plugin-markdown/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { createCustomViteLogger } from './errors.js';
import { resolveDependency } from './util.js';

Expand Down Expand Up @@ -80,6 +81,7 @@ export async function createVite(
jsxVitePlugin({ config: astroConfig, logging }),
astroPostprocessVitePlugin({ config: astroConfig }),
astroIntegrationsContainerPlugin({ config: astroConfig }),
astroScriptsPageSSRPlugin({ config: astroConfig }),
],
publicDir: fileURLToPath(astroConfig.publicDir),
root: fileURLToPath(astroConfig.root),
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/core/render/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
SSRElement,
SSRLoadedRenderer,
} from '../../../@types/astro';
import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import { prependForwardSlash } from '../../../core/path.js';
import { LogOptions } from '../../logger/core.js';
import { isPage } from '../../util.js';
Expand Down Expand Up @@ -124,13 +125,19 @@ export async function render(
children: '',
});
}

// TODO: We should allow adding generic HTML elements to the head, not just scripts
for (const script of astroConfig._ctx.scripts) {
if (script.stage === 'head-inline') {
scripts.add({
props: {},
children: script.content,
});
} else if (script.stage === 'page' && isPage(filePath, astroConfig)) {
scripts.add({
props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
children: '',
});
}
}

Expand Down
14 changes: 0 additions & 14 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import esbuild from 'esbuild';
import slash from 'slash';
import { fileURLToPath } from 'url';
import { isRelativePath, startsWithForwardSlash } from '../core/path.js';
import { resolvePages } from '../core/util.js';
import { PAGE_SCRIPT_ID, PAGE_SSR_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
import { getFileInfo } from '../vite-plugin-utils/index.js';
import { cachedCompilation, CompileProps, getCachedSource } from './compile.js';
import { handleHotUpdate, trackCSSDependencies } from './hmr.js';
Expand Down Expand Up @@ -215,14 +213,6 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}

const filename = normalizeFilename(parsedId.filename);
let isPage = false;
try {
const fileUrl = new URL(`file://${filename}`);
isPage = fileUrl.pathname.startsWith(resolvePages(config).pathname);
} catch {}
if (isPage && config._ctx.scripts.some((s) => s.stage === 'page')) {
source += `\n<script src="${PAGE_SCRIPT_ID}" />`;
natemoo-re marked this conversation as resolved.
Show resolved Hide resolved
}
const compileProps: CompileProps = {
config,
filename,
Expand Down Expand Up @@ -269,10 +259,6 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
i++;
}
}
// Add handling to inject scripts into each page JS bundle, if needed.
if (isPage) {
SUFFIX += `\nimport "${PAGE_SSR_SCRIPT_ID}";`;
}

// Prefer live reload to HMR in `.astro` files
if (!resolvedConfig.isProduction) {
Expand Down
8 changes: 2 additions & 6 deletions packages/astro/src/vite-plugin-markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ import { pagesVirtualModuleId } from '../core/app/index.js';
import { collectErrorMetadata } from '../core/errors.js';
import type { LogOptions } from '../core/logger/core.js';
import { warn } from '../core/logger/core.js';
import { resolvePages } from '../core/util.js';
import { cachedCompilation, CompileProps } from '../vite-plugin-astro/compile.js';
import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
import { PAGE_SSR_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
import { getFileInfo } from '../vite-plugin-utils/index.js';

interface AstroPluginOptions {
Expand Down Expand Up @@ -147,8 +145,6 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
const isAstroFlavoredMd = config.legacy.astroFlavoredMarkdown;

const fileUrl = new URL(`file://${filename}`);
const isPage = fileUrl.pathname.startsWith(resolvePages(config).pathname);
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');

// Extract special frontmatter keys
let { data: frontmatter, content: markdownContent } = safeMatter(source, filename);
Expand Down Expand Up @@ -187,7 +183,6 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
import Slugger from 'github-slugger';
${layout ? `import Layout from '${layout}';` : ''}
${isAstroFlavoredMd && components ? `import * from '${components}';` : ''}
${hasInjectedScript ? `import '${PAGE_SSR_SCRIPT_ID}';` : ''}
${isAstroFlavoredMd ? setup : ''}

const slugger = new Slugger();
Expand Down Expand Up @@ -224,7 +219,8 @@ ${isAstroFlavoredMd ? setup : ''}`.trim();
if (/\bLayout\b/.test(imports)) {
astroResult = `${prelude}\n<Layout content={$$content}>\n\n${astroResult}\n\n</Layout>`;
} else {
astroResult = `${prelude}\n${astroResult}`;
// Note: without a Layout, we need to inject `head` manually so `maybeRenderHead` runs
astroResult = `${prelude}\n<head></head>${astroResult}`;
}

// Transform from `.astro` to valid `.ts`
Expand Down
50 changes: 50 additions & 0 deletions packages/astro/src/vite-plugin-scripts/page-ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Plugin as VitePlugin } from 'vite';
import { AstroConfig } from '../@types/astro.js';
import { PAGE_SSR_SCRIPT_ID } from './index.js';

import { isPage } from '../core/util.js';
import ancestor from 'common-ancestor-path';
import MagicString from 'magic-string';

export default function astroScriptsPostPlugin({ config }: { config: AstroConfig }): VitePlugin {
function normalizeFilename(filename: string) {
if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length);
} else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) {
filename = new URL('.' + filename, config.root).pathname;
}
return filename;
}

return {
name: 'astro:scripts:page-ssr',
enforce: 'post',

transform(this, code, id, options) {
if (!options?.ssr) return;

const hasInjectedScript = config._ctx.scripts.some((s) => s.stage === 'page-ssr');
if (!hasInjectedScript) return;

const filename = normalizeFilename(id);
let fileURL: URL;
try {
fileURL = new URL(`file://${filename}`);
} catch (e) {
// If we can't construct a valid URL, exit early
return;
}

const fileIsPage = isPage(fileURL, config);
if (!fileIsPage) return;

const s = new MagicString(code, { filename });
s.prepend(`import '${PAGE_SSR_SCRIPT_ID}';\n`);

return {
code: s.toString(),
map: s.generateMap(),
}
},
};
}
7 changes: 0 additions & 7 deletions packages/integrations/mdx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,6 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
if (!id.endsWith('.mdx')) return;
const [, moduleExports] = parseESM(code);

// This adds support for injected "page-ssr" scripts in MDX files.
// TODO: This should only be happening on page entrypoints, not all imported MDX.
// TODO: This code is copy-pasted across all Astro/Vite plugins that deal with page
// entrypoints (.astro, .md, .mdx). This should be handled in some centralized place,
// or otherwise refactored to not require copy-paste handling logic.
code += `\nimport "${'astro:scripts/page-ssr.js'}";`;

const { fileUrl, fileId } = getFileInfo(id, config);
if (!moduleExports.includes('url')) {
code += `\nexport const url = ${JSON.stringify(fileUrl)};`;
Expand Down