diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index 3be0c6d8f..2926e8270 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -1,4 +1,4 @@ -import type { RenderedChunk } from 'rollup'; +import type { OutputChunk, RenderedChunk } from 'rollup'; import type { PageBuildData, ViteID } from './types'; import { prependForwardSlash } from '../path.js'; @@ -47,6 +47,11 @@ export interface BuildInternals { * These will be used as the top-level entrypoints for the client build. */ discoveredScripts: Set; + + // A list of all static files created during the build. Used for SSR. + staticFiles: Set; + // The SSR entry chunk. Kept in internals to share between ssr/client build steps + ssrEntryChunk?: OutputChunk; } /** @@ -84,6 +89,7 @@ export function createBuildInternals(): BuildInternals { discoveredHydratedComponents: new Set(), discoveredClientOnlyComponents: new Set(), discoveredScripts: new Set(), + staticFiles: new Set(), }; } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 3bcc5f0f9..01cbe2d5d 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -7,7 +7,6 @@ import * as vite from 'vite'; import { BuildInternals, createBuildInternals, - trackClientOnlyPageDatas, } from '../../core/build/internal.js'; import { prependForwardSlash } from '../../core/path.js'; import { emptyDir, removeDir } from '../../core/util.js'; @@ -23,7 +22,7 @@ import { getTimeStat } from './util.js'; import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js'; import { vitePluginInternals } from './vite-plugin-internals.js'; import { vitePluginPages } from './vite-plugin-pages.js'; -import { vitePluginSSR } from './vite-plugin-ssr.js'; +import { vitePluginSSR, vitePluginSSRInject } from './vite-plugin-ssr.js'; import { vitePluginAnalyzer } from './vite-plugin-analyzer.js'; export async function staticBuild(opts: StaticBuildOptions) { @@ -220,6 +219,8 @@ async function clientBuild( target: 'client', }), ...(viteConfig.plugins || []), + // SSR needs to be last + isBuildingToSSR(opts.astroConfig) && vitePluginSSRInject(opts, internals), ], publicDir: viteConfig.publicDir, root: viteConfig.root, diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts index a7cd3ef4c..d42f738c3 100644 --- a/packages/astro/src/core/build/vite-plugin-ssr.ts +++ b/packages/astro/src/core/build/vite-plugin-ssr.ts @@ -12,6 +12,7 @@ import { pagesVirtualModuleId } from '../app/index.js'; import { serializeRouteData } from '../routing/index.js'; import { addRollupInput } from './add-rollup-input.js'; import { eachPageData } from './internal.js'; +import * as fs from 'fs'; export const virtualModuleId = '@astrojs-ssr-virtual-entry'; const resolvedVirtualModuleId = '\0' + virtualModuleId; @@ -69,7 +70,7 @@ if(_start in adapter) { return void 0; }, async generateBundle(_opts, bundle) { - const staticFiles = new Set( + internals.staticFiles = new Set( await glob('**/*', { cwd: fileURLToPath(buildOpts.buildConfig.client), }) @@ -78,28 +79,50 @@ if(_start in adapter) { // Add assets from this SSR chunk as well. for (const [_chunkName, chunk] of Object.entries(bundle)) { if (chunk.type === 'asset') { - staticFiles.add(chunk.fileName); + internals.staticFiles.add(chunk.fileName); } } - - const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles)); - await runHookBuildSsr({ config: buildOpts.astroConfig, manifest }); - - for (const [_chunkName, chunk] of Object.entries(bundle)) { + + for (const [chunkName, chunk] of Object.entries(bundle)) { if (chunk.type === 'asset') { continue; } if (chunk.modules[resolvedVirtualModuleId]) { - const code = chunk.code; - chunk.code = code.replace(replaceExp, () => { - return JSON.stringify(manifest); - }); + internals.ssrEntryChunk = chunk; + delete bundle[chunkName]; } } }, }; } +export function vitePluginSSRInject( + buildOpts: StaticBuildOptions, + internals: BuildInternals, +): VitePlugin { + return { + name: '@astrojs/vite-plugin-astro-ssr-inject', + async writeBundle(opts, bundle) { + if(!internals.ssrEntryChunk) { + throw new Error(`Did not generate an entry chunk for SSR`); + } + + const staticFiles = internals.staticFiles; + const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles)); + await runHookBuildSsr({ config: buildOpts.astroConfig, manifest }); + + const chunk = internals.ssrEntryChunk; + const code = chunk.code; + chunk.code = code.replace(replaceExp, () => { + return JSON.stringify(manifest); + }); + const serverEntryURL = new URL(buildOpts.buildConfig.serverEntry, buildOpts.buildConfig.server); + await fs.promises.mkdir(new URL('./', serverEntryURL), { recursive: true }); + await fs.promises.writeFile(serverEntryURL, chunk.code, 'utf-8'); + }, + } +} + function buildManifest( opts: StaticBuildOptions, internals: BuildInternals,