fix: correctly handle prerender pages in split mode (#7509)

This commit is contained in:
Emanuele Stoppa 2023-06-28 13:06:16 +01:00 committed by GitHub
parent 1a9b644e20
commit f4fea3b02b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 10 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Correctly emit pre-rendered pages when `build.split` is set to `true`

View file

@ -17,6 +17,7 @@ import type {
RouteType, RouteType,
SSRError, SSRError,
SSRLoadedRenderer, SSRLoadedRenderer,
SSRManifest,
} from '../../@types/astro'; } from '../../@types/astro';
import { import {
generateImage as generateImageInternal, generateImage as generateImageInternal,
@ -148,9 +149,27 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) { for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
if (pageData.route.prerender) { if (pageData.route.prerender) {
const ssrEntryURLPage = createEntryURL(filePath, outFolder); const ssrEntryURLPage = createEntryURL(filePath, outFolder);
const ssrEntryPage: SinglePageBuiltModule = await import(ssrEntryURLPage.toString()); const ssrEntryPage = await import(ssrEntryURLPage.toString());
if (opts.settings.config.build.split) {
await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths); // 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.`
);
}
} else {
await generatePage(
opts,
internals,
pageData,
ssrEntryPage as SinglePageBuiltModule,
builtPaths
);
}
} }
} }
for (const pageData of eachRedirectPageData(internals)) { for (const pageData of eachRedirectPageData(internals)) {

View file

@ -3,9 +3,13 @@ import type { RouteData, SSRResult } from '../../@types/astro';
import type { PageOptions } from '../../vite-plugin-astro/types'; import type { PageOptions } from '../../vite-plugin-astro/types';
import { prependForwardSlash, removeFileExtension } from '../path.js'; import { prependForwardSlash, removeFileExtension } from '../path.js';
import { viteID } from '../util.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 { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
import type { PageBuildData, StylesheetAsset, ViteID } from './types'; import type { PageBuildData, StylesheetAsset, ViteID } from './types';
import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js';
export interface BuildInternals { export interface BuildInternals {
/** /**
@ -234,7 +238,13 @@ export function* eachPageDataFromEntryPoint(
internals: BuildInternals internals: BuildInternals
): Generator<[PageBuildData, string]> { ): Generator<[PageBuildData, string]> {
for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) { 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 [, pageName] = entryPoint.split(':');
const pageData = internals.pagesByComponent.get( const pageData = internals.pagesByComponent.get(
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}` `${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`

View file

@ -0,0 +1,12 @@
---
export const prerender = true
---
<html>
<head>
<title>Pre render me</title>
</head>
<body>
</body>
</html>

View file

@ -3,7 +3,8 @@ import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js'; import testAdapter from './test-adapter.js';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url'; 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', () => { describe('astro:ssr-manifest, split', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
@ -35,15 +36,34 @@ describe('astro:ssr-manifest, split', () => {
const html = await response.text(); const html = await response.text();
const $ = cheerio.load(html); 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 () => { it('should give access to entry points that exists on file system', async () => {
// number of the pages inside src/ // number of the pages inside src/
expect(entryPoints.size).to.equal(4); expect(entryPoints.size).to.equal(5);
for (const fileUrl of entryPoints.values()) { for (const fileUrl of entryPoints.values()) {
let filePath = fileURLToPath(fileUrl); let filePath = fileURLToPath(fileUrl);
expect(existsSync(filePath)).to.be.true; 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('<title>Pre render me</title>')).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('<title>Pre render me</title>')).to.be.true;
});
}); });

View file

@ -252,7 +252,7 @@ export async function loadFixture(inlineConfig) {
const virtualModule = getVirtualModulePageNameFromPath(RESOLVED_SPLIT_MODULE_ID, pagePath); const virtualModule = getVirtualModulePageNameFromPath(RESOLVED_SPLIT_MODULE_ID, pagePath);
const filePath = makeSplitEntryPointFileName(virtualModule, routes); const filePath = makeSplitEntryPointFileName(virtualModule, routes);
const url = new URL(`./server/${filePath}?id=${fixtureId}`, config.outDir); 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); const app = createApp(streaming);
app.manifest = manifest; app.manifest = manifest;
return app; return app;

View file

@ -29,7 +29,7 @@ describe('Prerendering', () => {
adapter: nodejs({ mode: 'standalone' }), adapter: nodejs({ mode: 'standalone' }),
}); });
await fixture.build(); await fixture.build();
const { startServer } = await await load(); const { startServer } = await load();
let res = startServer(); let res = startServer();
server = res.server; server = res.server;
}); });