diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts index a667ac428234..c27b5caec0f0 100644 --- a/packages/astro/src/core/build/graph.ts +++ b/packages/astro/src/core/build/graph.ts @@ -6,6 +6,7 @@ import { resolvedPagesVirtualModuleId } from '../app/index.js'; export function* walkParentInfos( id: string, ctx: { getModuleInfo: GetModuleInfo }, + until?: (importer: string) => boolean, depth = 0, seen = new Set(), childId = '' @@ -16,12 +17,13 @@ export function* walkParentInfos( let order = childId ? info.importedIds.indexOf(childId) : 0; yield [info, depth, order]; } + if (until?.(id)) return; const importers = (info?.importers || []).concat(info?.dynamicImporters || []); for (const imp of importers) { if (seen.has(imp)) { continue; } - yield* walkParentInfos(imp, ctx, ++depth, seen, id); + yield* walkParentInfos(imp, ctx, until, ++depth, seen, id); } } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 78c14973fc88..c2d00f82c4ae 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -9,6 +9,7 @@ import { emptyDir, removeDir } from '../../core/fs/index.js'; import { prependForwardSlash } from '../../core/path.js'; import { isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; +import { assetSsrPlugin } from '../../vite-plugin-asset-ssr/index.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { info } from '../logger/core.js'; @@ -142,6 +143,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp target: 'server', }), ...(viteConfig.plugins || []), + assetSsrPlugin({ internals }), // SSR needs to be last settings.config.output === 'server' && vitePluginSSR(internals, settings.adapter!), vitePluginAnalyzer(internals), diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 24b2b8f7b75c..0be30b5f4f7b 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -19,6 +19,7 @@ export interface PageBuildData { route: RouteData; moduleSpecifier: string; css: Map; + delayedCss?: Set; hoistedScript: { type: 'inline' | 'external'; value: string } | undefined; } export type AllPagesData = Record; diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index 97715cb05a05..8e6bea63e4d3 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -14,7 +14,7 @@ import { getPageDatasByHoistedScriptId, isHoistedScript, } from './internal.js'; -import { FLAG } from '../../vite-plugin-asset-ssr/index.js'; +import { DELAYED_ASSET_FLAG } from '../../vite-plugin-asset-ssr/index.js'; interface PluginOptions { internals: BuildInternals; @@ -103,25 +103,15 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] } }; - let exclude: Set = new Set(); - for (const [_, chunk] of Object.entries(bundle)) { - if (chunk.type === 'chunk' && chunk.facadeModuleId?.includes(FLAG)) { - exclude.add(chunk.fileName); - for (const imp of chunk.imports) { - exclude.add(imp); - } - } - } - for (const [_, chunk] of Object.entries(bundle)) { if (chunk.type === 'chunk') { const c = chunk; - if ('viteMetadata' in chunk && !exclude.has((chunk as OutputChunk).fileName)) { + if ('viteMetadata' in chunk) { const meta = chunk['viteMetadata'] as ViteMetadata; // Chunks that have the viteMetadata.importedCss are CSS chunks - if (meta.importedCss.size && !(chunk as OutputChunk).facadeModuleId?.endsWith(FLAG)) { + if (meta.importedCss.size) { // In the SSR build, keep track of all CSS chunks' modules as the client build may // duplicate them, e.g. for `client:load` components that render in SSR and client // for hydation. @@ -157,8 +147,31 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] // For this CSS chunk, walk parents until you find a page. Add the CSS to that page. for (const id of Object.keys(c.modules)) { - for (const [pageInfo, depth, order] of walkParentInfos(id, this)) { - if (moduleIsTopLevelPage(pageInfo)) { + for (const [pageInfo, depth, order] of walkParentInfos( + id, + this, + function until(importer) { + return importer.endsWith(DELAYED_ASSET_FLAG); + } + )) { + if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { + for (const parent of walkParentInfos(id, this)) { + console.log('walking parent...'); + const parentInfo = parent[0]; + if (moduleIsTopLevelPage(parentInfo)) { + const pageViteID = parentInfo.id; + const pageData = getPageDataByViteID(internals, pageViteID); + if (pageData) { + if (!pageData.delayedCss) { + pageData.delayedCss = new Set(); + } + for (const css of meta.importedCss) { + pageData.delayedCss.add(css); + } + } + } + } + } else if (moduleIsTopLevelPage(pageInfo)) { const pageViteID = pageInfo.id; const pageData = getPageDataByViteID(internals, pageViteID); if (pageData) { diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 5c3aae7ff957..4fc64c6c1abb 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -22,6 +22,7 @@ import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; import { createCustomViteLogger } from './errors/dev/index.js'; import { resolveDependency } from './util.js'; import { astroContentPlugin } from '../content/vite-plugin.js'; +import { injectDelayedAssetPlugin } from '../vite-plugin-asset-ssr/index.js'; interface CreateViteOptions { settings: AstroSettings; @@ -116,6 +117,7 @@ export async function createVite( astroScriptsPageSSRPlugin({ settings }), astroHeadPropagationPlugin({ settings }), astroContentPlugin({ settings, logging }), + injectDelayedAssetPlugin(), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 4cbc8d8ce31a..95e4824f0c6f 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -1 +1,49 @@ -export const FLAG = '?astro-asset-ssr'; +import { Plugin } from 'vite'; +import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; +import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; + +const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; + +export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; + +export function injectDelayedAssetPlugin(): Plugin { + return { + name: 'astro-inject-delayed-asset-plugin', + enforce: 'post', + load(id) { + if (id.endsWith(DELAYED_ASSET_FLAG)) { + const code = ` + export { Content } from ${JSON.stringify(id.replace(DELAYED_ASSET_FLAG, ''))}; + export const collectedCss = ${assetPlaceholder} + `; + return code; + } + }, + }; +} + +export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Plugin { + return { + name: 'astro-asset-ssr-plugin', + async generateBundle(_options, bundle) { + for (const [_, chunk] of Object.entries(bundle)) { + if (chunk.type === 'chunk' && chunk.code.includes(assetPlaceholder)) { + for (const id of Object.keys(chunk.modules)) { + for (const [pageInfo, depth, order] of walkParentInfos(id, this)) { + if (moduleIsTopLevelPage(pageInfo)) { + const pageViteID = pageInfo.id; + const pageData = getPageDataByViteID(internals, pageViteID); + if (pageData) { + chunk.code = chunk.code.replace( + assetPlaceholder, + JSON.stringify([...(pageData.delayedCss ?? [])]) + ); + } + } + } + } + } + } + }, + }; +}