diff --git a/.changeset/shiny-glasses-care.md b/.changeset/shiny-glasses-care.md new file mode 100644 index 000000000000..07f60d3da0ee --- /dev/null +++ b/.changeset/shiny-glasses-care.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where prerendered route paths that end with `.mjs` were removed from the final build diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index fa45c9d6b6e3..551e686dc472 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -198,8 +198,8 @@ class AstroBuilder { viteConfig, }; - const { internals } = await viteBuild(opts); - await staticBuild(opts, internals); + const { internals, ssrOutputChunkNames } = await viteBuild(opts); + await staticBuild(opts, internals, ssrOutputChunkNames); // Write any additionally generated assets to disk. this.timer.assetsStart = performance.now(); diff --git a/packages/astro/src/core/build/plugin.ts b/packages/astro/src/core/build/plugin.ts index c611c5186ed5..68474b0576b7 100644 --- a/packages/astro/src/core/build/plugin.ts +++ b/packages/astro/src/core/build/plugin.ts @@ -1,4 +1,4 @@ -import type { Plugin as VitePlugin } from 'vite'; +import type { Plugin as VitePlugin, Rollup } from 'vite'; import type { BuildInternals } from './internal.js'; import type { StaticBuildOptions, ViteBuildReturn } from './types.js'; @@ -68,7 +68,7 @@ export function createPluginContainer(options: StaticBuildOptions, internals: Bu }; }, - async runPostHook(ssrReturn: ViteBuildReturn, clientReturn: ViteBuildReturn | null) { + async runPostHook(ssrOutputs: Rollup.RollupOutput[], clientOutputs: Rollup.RollupOutput[]) { const mutations = new Map< string, { @@ -76,20 +76,6 @@ export function createPluginContainer(options: StaticBuildOptions, internals: Bu code: string; } >(); - const ssrOutputs: RollupOutputArray = []; - const clientOutputs: RollupOutputArray = []; - - if (Array.isArray(ssrReturn)) { - ssrOutputs.push(...ssrReturn); - } else if ('output' in ssrReturn) { - ssrOutputs.push(ssrReturn); - } - - if (Array.isArray(clientReturn)) { - clientOutputs.push(...clientReturn); - } else if (clientReturn && 'output' in clientReturn) { - clientOutputs.push(clientReturn); - } const mutate: MutateChunk = (chunk, targets, newCode) => { chunk.code = newCode; diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index ac572cda3d8b..56910c434130 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -33,7 +33,7 @@ import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js'; import { RESOLVED_SPLIT_MODULE_ID, RESOLVED_SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js'; import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; import type { StaticBuildOptions } from './types.js'; -import { encodeName, getTimeStat } from './util.js'; +import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from './util.js'; export async function viteBuild(opts: StaticBuildOptions) { const { allPages, settings } = opts; @@ -103,7 +103,9 @@ export async function viteBuild(opts: StaticBuildOptions) { // Run client build first, so the assets can be fed into the SSR rendered version. const clientOutput = await clientBuild(opts, internals, clientInput, container); - await runPostBuildHooks(container, ssrOutput, clientOutput); + const ssrOutputs = viteBuildReturnToRollupOutputs(ssrOutput); + const clientOutputs = viteBuildReturnToRollupOutputs(clientOutput ?? []); + await runPostBuildHooks(container, ssrOutputs, clientOutputs); settings.timer.end('Client build'); @@ -113,23 +115,38 @@ export async function viteBuild(opts: StaticBuildOptions) { teardown(); } - return { internals }; + // For static builds, the SSR output output won't be needed anymore after page generation. + // We keep track of the names here so we only remove these specific files when finished. + const ssrOutputChunkNames: string[] = []; + for (const output of ssrOutputs) { + for (const chunk of output.output) { + if (chunk.type === 'chunk') { + ssrOutputChunkNames.push(chunk.fileName); + } + } + } + + return { internals, ssrOutputChunkNames }; } -export async function staticBuild(opts: StaticBuildOptions, internals: BuildInternals) { +export async function staticBuild( + opts: StaticBuildOptions, + internals: BuildInternals, + ssrOutputChunkNames: string[] +) { const { settings } = opts; switch (true) { case settings.config.output === 'static': { settings.timer.start('Static generate'); await generatePages(opts, internals); - await cleanServerOutput(opts); + await cleanServerOutput(opts, ssrOutputChunkNames); settings.timer.end('Static generate'); return; } case isServerLikeOutput(settings.config): { settings.timer.start('Server generate'); await generatePages(opts, internals); - await cleanStaticOutput(opts, internals); + await cleanStaticOutput(opts, internals, ssrOutputChunkNames); opts.logger.info(null, `\n${bgMagenta(black(' finalizing server assets '))}\n`); await ssrMoveAssets(opts); settings.timer.end('Server generate'); @@ -324,10 +341,10 @@ async function clientBuild( async function runPostBuildHooks( container: AstroBuildPluginContainer, - ssrReturn: Awaited>, - clientReturn: Awaited> + ssrOutputs: vite.Rollup.RollupOutput[], + clientOutputs: vite.Rollup.RollupOutput[] ) { - const mutations = await container.runPostHook(ssrReturn, clientReturn); + const mutations = await container.runPostHook(ssrOutputs, clientOutputs); const config = container.options.settings.config; const build = container.options.settings.config.build; for (const [fileName, mutation] of mutations) { @@ -347,7 +364,11 @@ async function runPostBuildHooks( * For each statically prerendered page, replace their SSR file with a noop. * This allows us to run the SSR build only once, but still remove dependencies for statically rendered routes. */ -async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInternals) { +async function cleanStaticOutput( + opts: StaticBuildOptions, + internals: BuildInternals, + ssrOutputChunkNames: string[] +) { const allStaticFiles = new Set(); for (const pageData of eachPageData(internals)) { if (pageData.route.prerender) { @@ -361,10 +382,8 @@ async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInter const out = ssr ? opts.settings.config.build.server : getOutDirWithinCwd(opts.settings.config.outDir); - // The SSR output is all .mjs files, the client output is not. - const files = await glob('**/*.mjs', { - cwd: fileURLToPath(out), - }); + // The SSR output chunks for Astro are all .mjs files + const files = ssrOutputChunkNames.filter((f) => f.endsWith('.mjs')); if (files.length) { await eslexer.init; @@ -394,14 +413,10 @@ async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInter } } -async function cleanServerOutput(opts: StaticBuildOptions) { +async function cleanServerOutput(opts: StaticBuildOptions, ssrOutputChunkNames: string[]) { const out = getOutDirWithinCwd(opts.settings.config.outDir); - // The SSR output is all .mjs files, the client output is not. - const files = await glob('**/*.mjs', { - cwd: fileURLToPath(out), - // Important! Also cleanup dotfiles like `node_modules/.pnpm/**` - dot: true, - }); + // The SSR output chunks for Astro are all .mjs files + const files = ssrOutputChunkNames.filter((f) => f.endsWith('.mjs')); if (files.length) { // Remove all the SSR generated .mjs files await Promise.all( diff --git a/packages/astro/src/core/build/util.ts b/packages/astro/src/core/build/util.ts index 3d59cf45ce9a..fde296a6d246 100644 --- a/packages/astro/src/core/build/util.ts +++ b/packages/astro/src/core/build/util.ts @@ -1,4 +1,6 @@ +import type { Rollup } from 'vite'; import type { AstroConfig } from '../../@types/astro.js'; +import type { ViteBuildReturn } from './types.js'; export function getTimeStat(timeStart: number, timeEnd: number) { const buildTime = timeEnd - timeStart; @@ -52,3 +54,15 @@ export function encodeName(name: string): string { return name; } + +export function viteBuildReturnToRollupOutputs( + viteBuildReturn: ViteBuildReturn +): Rollup.RollupOutput[] { + const result: Rollup.RollupOutput[] = []; + if (Array.isArray(viteBuildReturn)) { + result.push(...viteBuildReturn); + } else if ('output' in viteBuildReturn) { + result.push(viteBuildReturn); + } + return result; +} diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index 37a5693cf315..d9e76defb9f5 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -151,6 +151,13 @@ describe('Astro basics', () => { expect($('body > :nth-child(5)').prop('outerHTML')).to.equal(''); }); + it('Generates pages that end with .mjs', async () => { + const content1 = await fixture.readFile('/get-static-paths-with-mjs/example.mjs'); + expect(content1).to.be.ok; + const content2 = await fixture.readFile('/get-static-paths-with-mjs/example.js'); + expect(content2).to.be.ok; + }); + describe('preview', () => { it('returns 200 for valid URLs', async () => { const result = await fixture.fetch('/'); diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/get-static-paths-with-mjs/[...file].js b/packages/astro/test/fixtures/astro-basic/src/pages/get-static-paths-with-mjs/[...file].js new file mode 100644 index 000000000000..8aae81326907 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/get-static-paths-with-mjs/[...file].js @@ -0,0 +1,10 @@ +export function getStaticPaths() { + return [ + { params: { file: 'example.mjs' } }, + { params: { file: 'example.js' } }, + ]; +} + +export function GET() { + return new Response('console.log("fileContent");') +}