diff options
-rw-r--r-- | .changeset/plenty-hats-grow.md | 6 | ||||
-rw-r--r-- | packages/astro/src/core/build/generate.ts | 14 | ||||
-rw-r--r-- | packages/astro/src/core/build/static-build.ts | 5 | ||||
-rw-r--r-- | packages/astro/src/core/build/vite-plugin-ssr.ts | 10 | ||||
-rw-r--r-- | packages/astro/src/core/create-vite.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/core/render/dev/index.ts | 7 | ||||
-rw-r--r-- | packages/astro/src/vite-plugin-astro/index.ts | 14 | ||||
-rw-r--r-- | packages/astro/src/vite-plugin-markdown/index.ts | 8 | ||||
-rw-r--r-- | packages/astro/src/vite-plugin-scripts/page-ssr.ts | 50 | ||||
-rw-r--r-- | packages/integrations/mdx/src/index.ts | 7 |
10 files changed, 93 insertions, 30 deletions
diff --git a/.changeset/plenty-hats-grow.md b/.changeset/plenty-hats-grow.md new file mode 100644 index 000000000..d7238f840 --- /dev/null +++ b/.changeset/plenty-hats-grow.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/mdx': patch +--- + +Improve `injectScript` handling for non-Astro pages diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index fb125dd28..cbd8e1c2e 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -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'; @@ -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') { diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 99b58318d..19778ae4b 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -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'; @@ -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); diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts index 5ce5640a2..e7fbfd995 100644 --- a/packages/astro/src/core/build/vite-plugin-ssr.ts +++ b/packages/astro/src/core/build/vite-plugin-ssr.ts @@ -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'; @@ -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: '', @@ -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]'; diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 7b3a8070f..631c1dc2b 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -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'; @@ -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), diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index a6643fc35..6cf75a6cc 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -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'; @@ -124,6 +125,7 @@ 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') { @@ -131,6 +133,11 @@ export async function render( props: {}, children: script.content, }); + } else if (script.stage === 'page' && isPage(filePath, astroConfig)) { + scripts.add({ + props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` }, + children: '', + }); } } diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index b4925d0fd..f38d5e5ca 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -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'; @@ -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}" />`; - } const compileProps: CompileProps = { config, filename, @@ -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) { diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index ab6f6a7ef..6794b499a 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -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 { @@ -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); @@ -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(); @@ -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` diff --git a/packages/astro/src/vite-plugin-scripts/page-ssr.ts b/packages/astro/src/vite-plugin-scripts/page-ssr.ts new file mode 100644 index 000000000..1f726912e --- /dev/null +++ b/packages/astro/src/vite-plugin-scripts/page-ssr.ts @@ -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(), + } + }, + }; +} diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 81187cc60..7a38643aa 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -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)};`; |