diff --git a/.changeset/unlucky-paws-own.md b/.changeset/unlucky-paws-own.md new file mode 100644 index 0000000000000..e48bf31e535cd --- /dev/null +++ b/.changeset/unlucky-paws-own.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Correctly emit pre-rendered pages when `build.split` is set to `true` diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index b53360d125a51..73b21adfd2a17 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -17,6 +17,7 @@ import type { RouteType, SSRError, SSRLoadedRenderer, + SSRManifest, } from '../../@types/astro'; import { generateImage as generateImageInternal, @@ -148,9 +149,17 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) { if (pageData.route.prerender) { const ssrEntryURLPage = createEntryURL(filePath, outFolder); - const ssrEntryPage: SinglePageBuiltModule = await import(ssrEntryURLPage.toString()); - - await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths); + const ssrEntryPage = await import(ssrEntryURLPage.toString()); + // forcing to use undefined, so we fail in an expected way if the module is not even there. + const manifest: SSRManifest | undefined = ssrEntryPage.manifest; + const ssrEntry = manifest?.pageModule; + if (ssrEntry) { + await generatePage(opts, internals, pageData, ssrEntry, builtPaths); + } else { + throw new Error( + `Unable to find the manifest for the module ${ssrEntryURLPage.toString()}. This is unexpected and likely a bug in Astro, please report.` + ); + } } } for (const pageData of eachRedirectPageData(internals)) { diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index 8c7da07d4319a..565c2ec574680 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -3,9 +3,13 @@ import type { RouteData, SSRResult } from '../../@types/astro'; import type { PageOptions } from '../../vite-plugin-astro/types'; import { prependForwardSlash, removeFileExtension } from '../path.js'; import { viteID } from '../util.js'; -import { ASTRO_PAGE_MODULE_ID, getVirtualModulePageIdFromPath } from './plugins/plugin-pages.js'; +import { + ASTRO_PAGE_RESOLVED_MODULE_ID, + getVirtualModulePageIdFromPath, +} from './plugins/plugin-pages.js'; import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; import type { PageBuildData, StylesheetAsset, ViteID } from './types'; +import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; export interface BuildInternals { /** @@ -234,7 +238,13 @@ export function* eachPageDataFromEntryPoint( internals: BuildInternals ): Generator<[PageBuildData, string]> { for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) { - if (entryPoint.includes(ASTRO_PAGE_MODULE_ID)) { + // virtual pages can be emitted with different prefixes: + // - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages + // - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID + if ( + entryPoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) || + entryPoint.includes(RESOLVED_SPLIT_MODULE_ID) + ) { const [, pageName] = entryPoint.split(':'); const pageData = internals.pagesByComponent.get( `${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}` diff --git a/packages/astro/test/fixtures/ssr-split-manifest/src/pages/prerender.astro b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/prerender.astro new file mode 100644 index 0000000000000..2eec6dbf13c93 --- /dev/null +++ b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/prerender.astro @@ -0,0 +1,12 @@ +--- +export const prerender = true +--- + + + + Pre render me + + + + + diff --git a/packages/astro/test/ssr-split-manifest.test.js b/packages/astro/test/ssr-split-manifest.test.js index 6d3167bec1205..9e8a0981ed5ac 100644 --- a/packages/astro/test/ssr-split-manifest.test.js +++ b/packages/astro/test/ssr-split-manifest.test.js @@ -3,7 +3,8 @@ import { loadFixture } from './test-utils.js'; import testAdapter from './test-adapter.js'; import * as cheerio from 'cheerio'; import { fileURLToPath } from 'node:url'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; describe('astro:ssr-manifest, split', () => { /** @type {import('./test-utils').Fixture} */ @@ -35,15 +36,34 @@ describe('astro:ssr-manifest, split', () => { const html = await response.text(); const $ = cheerio.load(html); - expect($('#assets').text()).to.equal('["/_astro/index.a8a337e4.css"]'); + expect($('#assets').text()).to.equal('["/_astro/index.a8a337e4.css","/prerender/index.html"]'); }); it('should give access to entry points that exists on file system', async () => { // number of the pages inside src/ - expect(entryPoints.size).to.equal(4); + expect(entryPoints.size).to.equal(5); for (const fileUrl of entryPoints.values()) { let filePath = fileURLToPath(fileUrl); expect(existsSync(filePath)).to.be.true; } }); + + it('should correctly emit the the pre render page', async () => { + const text = readFileSync( + resolve('./test/fixtures/ssr-split-manifest/dist/client/prerender/index.html'), + { + encoding: 'utf8', + } + ); + expect(text.includes('Pre render me')).to.be.true; + }); + + it('should emit an entry point to request the pre-rendered page', async () => { + const pagePath = 'src/pages/prerender.astro'; + const app = await fixture.loadEntryPoint(pagePath, currentRoutes); + const request = new Request('http://example.com/'); + const response = await app.render(request); + const html = await response.text(); + expect(html.includes('Pre render me')).to.be.true; + }); }); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 11b181779bbc6..583a6c61e4d9a 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -252,7 +252,7 @@ export async function loadFixture(inlineConfig) { const virtualModule = getVirtualModulePageNameFromPath(RESOLVED_SPLIT_MODULE_ID, pagePath); const filePath = makeSplitEntryPointFileName(virtualModule, routes); const url = new URL(`./server/${filePath}?id=${fixtureId}`, config.outDir); - const { createApp, manifest, middleware } = await import(url); + const { createApp, manifest } = await import(url); const app = createApp(streaming); app.manifest = manifest; return app; diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index a018f0649e46b..f895a6458c4e6 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -29,7 +29,7 @@ describe('Prerendering', () => { adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; });