Skip to content

Commit e22be22

Browse files
authoredJun 17, 2024··
Refactor prerendering chunk handling (#11245)
1 parent 68f1d0d commit e22be22

33 files changed

+692
-193
lines changed
 

‎.changeset/three-boxes-sniff.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"astro": patch
3+
---
4+
5+
Refactors prerendering chunk handling to correctly remove unused code during the SSR runtime

‎CONTRIBUTING.md

+6
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ Any tests for `astro build` output should use the main `mocha` tests rather than
176176

177177
If a test needs to validate what happens on the page after it's loading in the browser, that's a perfect use for E2E dev server tests, i.e. to verify that hot-module reloading works in `astro dev` or that components were client hydrated and are interactive.
178178

179+
#### Creating tests
180+
181+
When creating new tests, it's best to reference other existing test files and replicate the same setup. Some other tips include:
182+
183+
- When re-using a fixture multiple times with different configurations, you should also configure unique `outDir`, `build.client`, and `build.server` values so the build output runtime isn't cached and shared by ESM between test runs.
184+
179185
### Other useful commands
180186

181187
```shell

‎packages/astro/src/core/build/internal.ts

+6
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export interface BuildInternals {
113113
ssrSplitEntryChunks: Map<string, Rollup.OutputChunk>;
114114
componentMetadata: SSRResult['componentMetadata'];
115115
middlewareEntryPoint?: URL;
116+
117+
/**
118+
* Chunks in the bundle that are only used in prerendering that we can delete later
119+
*/
120+
prerenderOnlyChunks: Rollup.OutputChunk[];
116121
}
117122

118123
/**
@@ -151,6 +156,7 @@ export function createBuildInternals(): BuildInternals {
151156
ssrSplitEntryChunks: new Map(),
152157
entryPoints: new Map(),
153158
cacheManifestUsed: false,
159+
prerenderOnlyChunks: [],
154160
};
155161
}
156162

‎packages/astro/src/core/build/page-data.ts

-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ export async function collectPagesData(
5757
moduleSpecifier: '',
5858
styles: [],
5959
hoistedScript: undefined,
60-
hasSharedModules: false,
6160
};
6261

6362
clearInterval(routeCollectionLogTimeout);
@@ -80,7 +79,6 @@ export async function collectPagesData(
8079
moduleSpecifier: '',
8180
styles: [],
8281
hoistedScript: undefined,
83-
hasSharedModules: false,
8482
};
8583
}
8684

‎packages/astro/src/core/build/plugins/plugin-chunks.ts

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export function vitePluginChunks(): VitePlugin {
1212
if (id.includes('astro/dist/runtime/server/')) {
1313
return 'astro/server';
1414
}
15+
// Split the Astro runtime into a separate chunk for readability
16+
if (id.includes('astro/dist/runtime')) {
17+
return 'astro';
18+
}
19+
// Place `astro/env/setup` import in its own chunk to prevent Rollup's TLA bug
20+
// https://github.com/rollup/rollup/issues/4708
21+
if (id.includes('astro/dist/env/setup')) {
22+
return 'astro/env-setup';
23+
}
1524
},
1625
});
1726
},

‎packages/astro/src/core/build/plugins/plugin-prerender.ts

+89-71
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,105 @@
1-
import path from 'node:path';
2-
import type { Plugin as VitePlugin } from 'vite';
1+
import type { Rollup, Plugin as VitePlugin } from 'vite';
32
import { getPrerenderMetadata } from '../../../prerender/metadata.js';
43
import type { BuildInternals } from '../internal.js';
54
import type { AstroBuildPlugin } from '../plugin.js';
65
import type { StaticBuildOptions } from '../types.js';
7-
import { extendManualChunks } from './util.js';
6+
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugin-pages.js';
7+
import { getPagesFromVirtualModulePageName } from './util.js';
88

9-
function vitePluginPrerender(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
9+
function vitePluginPrerender(internals: BuildInternals): VitePlugin {
1010
return {
1111
name: 'astro:rollup-plugin-prerender',
1212

13-
outputOptions(outputOptions) {
14-
extendManualChunks(outputOptions, {
15-
after(id, meta) {
16-
// Split the Astro runtime into a separate chunk for readability
17-
if (id.includes('astro/dist/runtime')) {
18-
return 'astro';
19-
}
20-
const pageInfo = internals.pagesByViteID.get(id);
21-
let hasSharedModules = false;
22-
if (pageInfo) {
23-
// prerendered pages should be split into their own chunk
24-
// Important: this can't be in the `pages/` directory!
25-
if (getPrerenderMetadata(meta.getModuleInfo(id)!)) {
26-
const infoMeta = meta.getModuleInfo(id)!;
13+
generateBundle(_, bundle) {
14+
const moduleIds = this.getModuleIds();
15+
for (const id of moduleIds) {
16+
const pageInfo = internals.pagesByViteID.get(id);
17+
if (!pageInfo) continue;
18+
const moduleInfo = this.getModuleInfo(id);
19+
if (!moduleInfo) continue;
2720

28-
// Here, we check if this page is importing modules that are shared among other modules e.g. middleware, other SSR pages, etc.
29-
// we loop the modules that the current page imports
30-
for (const moduleId of infoMeta.importedIds) {
31-
// we retrieve the metadata of the module
32-
const moduleMeta = meta.getModuleInfo(moduleId)!;
33-
if (
34-
// a shared modules should be inside the `src/` folder, at least
35-
moduleMeta.id.startsWith(opts.settings.config.srcDir.pathname) &&
36-
// and has at least two importers: the current page and something else
37-
moduleMeta.importers.length > 1
38-
) {
39-
// Now, we have to trace back the modules imported and analyze them;
40-
// understanding if a module is eventually shared between two pages isn't easy, because a module could
41-
// be imported by a page and a component that is eventually imported by a page.
42-
//
43-
// Given the previous statement, we only check if
44-
// - the module is a page, and it's not pre-rendered
45-
// - the module is the middleware
46-
// If one of these conditions is met, we need a separate chunk
47-
for (const importer of moduleMeta.importedIds) {
48-
// we don't want to analyze the same module again, so we skip it
49-
if (importer !== id) {
50-
const importerModuleMeta = meta.getModuleInfo(importer);
51-
if (importerModuleMeta) {
52-
// if the module is inside the pages
53-
if (importerModuleMeta.id.includes('/pages')) {
54-
// we check if it's not pre-rendered
55-
if (getPrerenderMetadata(importerModuleMeta) === false) {
56-
hasSharedModules = true;
57-
break;
58-
}
59-
}
60-
// module isn't an Astro route/page, it could be a middleware
61-
else if (importerModuleMeta.id.includes('/middleware')) {
62-
hasSharedModules = true;
63-
break;
64-
}
65-
}
66-
}
67-
}
68-
}
69-
}
21+
const prerender = !!getPrerenderMetadata(moduleInfo);
22+
pageInfo.route.prerender = prerender;
23+
}
7024

71-
pageInfo.hasSharedModules = hasSharedModules;
72-
pageInfo.route.prerender = true;
73-
return 'prerender';
74-
}
75-
pageInfo.route.prerender = false;
76-
// dynamic pages should all go in their own chunk in the pages/* directory
77-
return `pages/${path.basename(pageInfo.component)}`;
78-
}
79-
},
80-
});
25+
// Find all chunks used in the SSR runtime (that aren't used for prerendering only), then use
26+
// the Set to find the inverse, where chunks that are only used for prerendering. It's faster
27+
// to compute `internals.prerenderOnlyChunks` this way. The prerendered chunks will be deleted
28+
// after we finish prerendering.
29+
const nonPrerenderOnlyChunks = getNonPrerenderOnlyChunks(bundle, internals);
30+
internals.prerenderOnlyChunks = Object.values(bundle).filter((chunk) => {
31+
return chunk.type === 'chunk' && !nonPrerenderOnlyChunks.has(chunk);
32+
}) as Rollup.OutputChunk[];
8133
},
8234
};
8335
}
8436

37+
function getNonPrerenderOnlyChunks(bundle: Rollup.OutputBundle, internals: BuildInternals) {
38+
const chunks = Object.values(bundle);
39+
40+
const prerenderOnlyEntryChunks = new Set<Rollup.OutputChunk>();
41+
const nonPrerenderOnlyEntryChunks = new Set<Rollup.OutputChunk>();
42+
for (const chunk of chunks) {
43+
if (chunk.type === 'chunk' && (chunk.isEntry || chunk.isDynamicEntry)) {
44+
// See if this entry chunk is prerendered, if so, skip it
45+
if (chunk.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
46+
const pageDatas = getPagesFromVirtualModulePageName(
47+
internals,
48+
ASTRO_PAGE_RESOLVED_MODULE_ID,
49+
chunk.facadeModuleId
50+
);
51+
const prerender = pageDatas.every((pageData) => pageData.route.prerender);
52+
if (prerender) {
53+
prerenderOnlyEntryChunks.add(chunk);
54+
continue;
55+
}
56+
}
57+
// Ideally we should record entries when `functionPerRoute` is enabled, but this breaks some tests
58+
// that expect the entrypoint to still exist even if it should be unused.
59+
// TODO: Revisit this so we can delete additional unused chunks
60+
// else if (chunk.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
61+
// const pageDatas = getPagesFromVirtualModulePageName(
62+
// internals,
63+
// RESOLVED_SPLIT_MODULE_ID,
64+
// chunk.facadeModuleId
65+
// );
66+
// const prerender = pageDatas.every((pageData) => pageData.route.prerender);
67+
// if (prerender) {
68+
// prerenderOnlyEntryChunks.add(chunk);
69+
// continue;
70+
// }
71+
// }
72+
73+
nonPrerenderOnlyEntryChunks.add(chunk);
74+
}
75+
}
76+
77+
// From the `nonPrerenderedEntryChunks`, we crawl all the imports/dynamicImports to find all
78+
// other chunks that are use by the non-prerendered runtime
79+
const nonPrerenderOnlyChunks = new Set(nonPrerenderOnlyEntryChunks);
80+
for (const chunk of nonPrerenderOnlyChunks) {
81+
for (const importFileName of chunk.imports) {
82+
const importChunk = bundle[importFileName];
83+
if (importChunk?.type === 'chunk') {
84+
nonPrerenderOnlyChunks.add(importChunk);
85+
}
86+
}
87+
for (const dynamicImportFileName of chunk.dynamicImports) {
88+
const dynamicImportChunk = bundle[dynamicImportFileName];
89+
// The main server entry (entry.mjs) may import a prerender-only entry chunk, we skip in this case
90+
// to prevent incorrectly marking it as non-prerendered.
91+
if (
92+
dynamicImportChunk?.type === 'chunk' &&
93+
!prerenderOnlyEntryChunks.has(dynamicImportChunk)
94+
) {
95+
nonPrerenderOnlyChunks.add(dynamicImportChunk);
96+
}
97+
}
98+
}
99+
100+
return nonPrerenderOnlyChunks;
101+
}
102+
85103
export function pluginPrerender(
86104
opts: StaticBuildOptions,
87105
internals: BuildInternals
@@ -96,7 +114,7 @@ export function pluginPrerender(
96114
hooks: {
97115
'build:before': () => {
98116
return {
99-
vitePlugin: vitePluginPrerender(opts, internals),
117+
vitePlugin: vitePluginPrerender(internals),
100118
};
101119
},
102120
},

‎packages/astro/src/core/build/plugins/plugin-ssr.ts

+18-13
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ function vitePluginSSR(
2727
name: '@astrojs/vite-plugin-astro-ssr-server',
2828
enforce: 'post',
2929
options(opts) {
30-
return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
30+
const inputs = new Set<string>();
31+
32+
for (const pageData of Object.values(options.allPages)) {
33+
if (routeIsRedirect(pageData.route)) {
34+
continue;
35+
}
36+
inputs.add(getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component));
37+
}
38+
39+
inputs.add(SSR_VIRTUAL_MODULE_ID);
40+
return addRollupInput(opts, Array.from(inputs));
3141
},
3242
resolveId(id) {
3343
if (id === SSR_VIRTUAL_MODULE_ID) {
@@ -72,7 +82,6 @@ function vitePluginSSR(
7282
contents.push(...ssrCode.contents);
7383
return [...imports, ...contents, ...exports].join('\n');
7484
}
75-
return void 0;
7685
},
7786
async generateBundle(_opts, bundle) {
7887
// Add assets from this SSR chunk as well.
@@ -141,23 +150,20 @@ function vitePluginSSRSplit(
141150
adapter: AstroAdapter,
142151
options: StaticBuildOptions
143152
): VitePlugin {
144-
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
145153
return {
146154
name: '@astrojs/vite-plugin-astro-ssr-split',
147155
enforce: 'post',
148156
options(opts) {
149-
if (functionPerRouteEnabled) {
150-
const inputs = new Set<string>();
157+
const inputs = new Set<string>();
151158

152-
for (const pageData of Object.values(options.allPages)) {
153-
if (routeIsRedirect(pageData.route)) {
154-
continue;
155-
}
156-
inputs.add(getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component));
159+
for (const pageData of Object.values(options.allPages)) {
160+
if (routeIsRedirect(pageData.route)) {
161+
continue;
157162
}
158-
159-
return addRollupInput(opts, Array.from(inputs));
163+
inputs.add(getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component));
160164
}
165+
166+
return addRollupInput(opts, Array.from(inputs));
161167
},
162168
resolveId(id) {
163169
if (id.startsWith(SPLIT_MODULE_ID)) {
@@ -185,7 +191,6 @@ function vitePluginSSRSplit(
185191

186192
return [...imports, ...contents, ...exports].join('\n');
187193
}
188-
return void 0;
189194
},
190195
async generateBundle(_opts, bundle) {
191196
// Add assets from this SSR chunk as well.

‎packages/astro/src/core/build/static-build.ts

+26-55
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs';
22
import path, { extname } from 'node:path';
33
import { fileURLToPath, pathToFileURL } from 'node:url';
44
import { teardown } from '@astrojs/compiler';
5-
import * as eslexer from 'es-module-lexer';
65
import glob from 'fast-glob';
76
import { bgGreen, bgMagenta, black, green } from 'kleur/colors';
87
import * as vite from 'vite';
@@ -156,7 +155,7 @@ export async function staticBuild(
156155
case isServerLikeOutput(settings.config): {
157156
settings.timer.start('Server generate');
158157
await generatePages(opts, internals);
159-
await cleanStaticOutput(opts, internals, ssrOutputChunkNames);
158+
await cleanStaticOutput(opts, internals);
160159
opts.logger.info(null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
161160
await ssrMoveAssets(opts);
162161
settings.timer.end('Server generate');
@@ -199,6 +198,8 @@ async function ssrBuild(
199198
copyPublicDir: !ssr,
200199
rollupOptions: {
201200
...viteConfig.build?.rollupOptions,
201+
// Setting as `exports-only` allows us to safely delete inputs that are only used during prerendering
202+
preserveEntrySignatures: 'exports-only',
202203
input: [],
203204
output: {
204205
hoistTransitiveImports: isContentCache,
@@ -381,65 +382,35 @@ async function runPostBuildHooks(
381382
}
382383

383384
/**
384-
* For each statically prerendered page, replace their SSR file with a noop.
385-
* This allows us to run the SSR build only once, but still remove dependencies for statically rendered routes.
386-
* If a component is shared between a statically rendered route and a SSR route, it will still be included in the SSR build.
385+
* Remove chunks that are used for prerendering only
387386
*/
388-
async function cleanStaticOutput(
389-
opts: StaticBuildOptions,
390-
internals: BuildInternals,
391-
ssrOutputChunkNames: string[]
392-
) {
393-
const prerenderedFiles = new Set();
394-
const onDemandsFiles = new Set();
395-
for (const pageData of internals.pagesByKeys.values()) {
396-
const { moduleSpecifier } = pageData;
397-
const bundleId =
398-
internals.pageToBundleMap.get(moduleSpecifier) ??
399-
internals.entrySpecifierToBundleMap.get(moduleSpecifier);
400-
if (pageData.route.prerender && !pageData.hasSharedModules && !onDemandsFiles.has(bundleId)) {
401-
prerenderedFiles.add(bundleId);
402-
} else {
403-
onDemandsFiles.add(bundleId);
404-
// Check if the component was not previously added to the static build by a statically rendered route
405-
if (prerenderedFiles.has(bundleId)) {
406-
prerenderedFiles.delete(bundleId);
407-
}
408-
}
409-
}
387+
async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInternals) {
410388
const ssr = isServerLikeOutput(opts.settings.config);
411389
const out = ssr
412390
? opts.settings.config.build.server
413391
: getOutDirWithinCwd(opts.settings.config.outDir);
414-
// The SSR output chunks for Astro are all .mjs files
415-
const files = ssrOutputChunkNames.filter((f) => f.endsWith('.mjs'));
416-
417-
if (files.length) {
418-
await eslexer.init;
419-
420-
// Cleanup prerendered chunks.
421-
// This has to happen AFTER the SSR build runs as a final step, because we need the code in order to generate the pages.
422-
// These chunks should only contain prerendering logic, so they are safe to modify.
423-
await Promise.all(
424-
files.map(async (filename) => {
425-
if (!prerenderedFiles.has(filename)) {
426-
return;
427-
}
428-
const url = new URL(filename, out);
429-
const text = await fs.promises.readFile(url, { encoding: 'utf8' });
430-
const [, exports] = eslexer.parse(text);
431-
// Replace exports (only prerendered pages) with a noop
432-
let value = 'const noop = () => {};';
433-
for (const e of exports) {
434-
if (e.n === 'default') value += `\n export default noop;`;
435-
else value += `\nexport const ${e.n} = noop;`;
392+
await Promise.all(
393+
internals.prerenderOnlyChunks.map(async (chunk) => {
394+
const url = new URL(chunk.fileName, out);
395+
try {
396+
// Entry chunks may be referenced by non-deleted code, so we don't actually delete it
397+
// but only empty its content. These chunks should never be executed in practice, but
398+
// it should prevent broken import paths if adapters do a secondary bundle.
399+
if (chunk.isEntry || chunk.isDynamicEntry) {
400+
await fs.promises.writeFile(
401+
url,
402+
"// Contents removed by Astro as it's used for prerendering only",
403+
'utf-8'
404+
);
405+
} else {
406+
await fs.promises.unlink(url);
436407
}
437-
await fs.promises.writeFile(url, value, { encoding: 'utf8' });
438-
})
439-
);
440-
441-
removeEmptyDirs(out);
442-
}
408+
} catch {
409+
// Best-effort only. Sometimes some chunks may be deleted by other plugins, like pure CSS chunks,
410+
// so they may already not exist.
411+
}
412+
})
413+
);
443414
}
444415

445416
async function cleanServerOutput(

‎packages/astro/src/core/build/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface PageBuildData {
2929
moduleSpecifier: string;
3030
hoistedScript: HoistedScriptAsset | undefined;
3131
styles: Array<{ depth: number; order: number; sheet: StylesheetAsset }>;
32-
hasSharedModules: boolean;
3332
}
3433

3534
export type AllPagesData = Record<ComponentPath, PageBuildData>;

‎packages/astro/test/astro-assets-prefix.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ describe('Assets Prefix - Static', () => {
1414
before(async () => {
1515
fixture = await loadFixture({
1616
root: './fixtures/astro-assets-prefix/',
17+
outDir: './dist/static',
18+
build: {
19+
client: './dist/static/client',
20+
server: './dist/static/server',
21+
},
1722
});
1823
await fixture.build();
1924
});
@@ -72,7 +77,10 @@ describe('Assets Prefix - with path prefix', () => {
7277
before(async () => {
7378
fixture = await loadFixture({
7479
root: './fixtures/astro-assets-prefix/',
80+
outDir: './dist/server',
7581
build: {
82+
client: './dist/server/client',
83+
server: './dist/server/server',
7684
assetsPrefix: '/starting-slash',
7785
},
7886
});
@@ -97,6 +105,11 @@ describe('Assets Prefix, server', () => {
97105
root: './fixtures/astro-assets-prefix/',
98106
output: 'server',
99107
adapter: testAdapter(),
108+
outDir: './dist/server',
109+
build: {
110+
client: './dist/server/client',
111+
server: './dist/server/server',
112+
},
100113
});
101114
await fixture.build();
102115
app = await fixture.loadTestAdapterApp();
@@ -154,7 +167,10 @@ describe('Assets Prefix, with path prefix', () => {
154167
root: './fixtures/astro-assets-prefix/',
155168
output: 'server',
156169
adapter: testAdapter(),
170+
outDir: './dist/server-path-prefix',
157171
build: {
172+
client: './dist/server-path-prefix/client',
173+
server: './dist/server-path-prefix/server',
158174
assetsPrefix: '/starting-slash',
159175
},
160176
});

‎packages/astro/test/before-hydration.test.js

+20
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ describe('Astro Scripts before-hydration', () => {
1414
before(async () => {
1515
fixture = await loadFixture({
1616
root: './fixtures/before-hydration/',
17+
outDir: './dist/static-integration',
18+
build: {
19+
client: './dist/static-integration/client',
20+
server: './dist/static-integration/server',
21+
},
1722
integrations: [
1823
preact(),
1924
{
@@ -68,6 +73,11 @@ describe('Astro Scripts before-hydration', () => {
6873
before(async () => {
6974
fixture = await loadFixture({
7075
root: './fixtures/before-hydration/',
76+
outDir: './dist/static-no-integration',
77+
build: {
78+
client: './dist/static-no-integration/client',
79+
server: './dist/static-no-integration/server',
80+
},
7181
});
7282
});
7383

@@ -115,6 +125,11 @@ describe('Astro Scripts before-hydration', () => {
115125
root: './fixtures/before-hydration/',
116126
output: 'server',
117127
adapter: testAdapter(),
128+
outDir: './dist/server-integration',
129+
build: {
130+
client: './dist/server-integration/client',
131+
server: './dist/server-integration/server',
132+
},
118133
integrations: [
119134
preact(),
120135
{
@@ -153,6 +168,11 @@ describe('Astro Scripts before-hydration', () => {
153168
fixture = await loadFixture({
154169
root: './fixtures/before-hydration/',
155170
output: 'server',
171+
outDir: './dist/static-no-integration',
172+
build: {
173+
client: './dist/static-no-integration/client',
174+
server: './dist/static-no-integration/server',
175+
},
156176
adapter: testAdapter(),
157177
});
158178
});

‎packages/astro/test/core-image.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,11 @@ describe('astro:image', () => {
799799
const fixtureWithBase = await loadFixture({
800800
root: './fixtures/core-image-ssr/',
801801
output: 'server',
802+
outDir: './dist/server-base-path',
803+
build: {
804+
client: './dist/server-base-path/client',
805+
server: './dist/server-base-path/server',
806+
},
802807
adapter: testAdapter(),
803808
image: {
804809
service: testImageService(),
@@ -1080,6 +1085,11 @@ describe('astro:image', () => {
10801085
fixture = await loadFixture({
10811086
root: './fixtures/core-image-ssr/',
10821087
output: 'server',
1088+
outDir: './dist/server-dev',
1089+
build: {
1090+
client: './dist/server-dev/client',
1091+
server: './dist/server-dev/server',
1092+
},
10831093
adapter: testAdapter(),
10841094
base: 'some-base',
10851095
image: {
@@ -1114,6 +1124,11 @@ describe('astro:image', () => {
11141124
fixture = await loadFixture({
11151125
root: './fixtures/core-image-ssr/',
11161126
output: 'server',
1127+
outDir: './dist/server-prod',
1128+
build: {
1129+
client: './dist/server-prod/client',
1130+
server: './dist/server-prod/server',
1131+
},
11171132
adapter: testAdapter(),
11181133
image: {
11191134
endpoint: 'astro/assets/endpoint/node',
@@ -1127,6 +1142,7 @@ describe('astro:image', () => {
11271142
const app = await fixture.loadTestAdapterApp();
11281143
let request = new Request('http://example.com/');
11291144
let response = await app.render(request);
1145+
console.log
11301146
assert.equal(response.status, 200);
11311147
const html = await response.text();
11321148
const $ = cheerio.load(html);

‎packages/astro/test/css-inline-stylesheets.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ describe('Setting inlineStylesheets to never in static output', () => {
1515
site: 'https://test.dev/',
1616
root: './fixtures/css-inline-stylesheets/',
1717
output: 'static',
18+
outDir: './dist/static-inline-stylesheets-never',
1819
build: {
20+
client: './dist/static-inline-stylesheets-never/client',
21+
server: './dist/static-inline-stylesheets-never/server',
1922
inlineStylesheets: 'never',
2023
},
2124
});
@@ -53,7 +56,10 @@ describe('Setting inlineStylesheets to never in server output', () => {
5356
root: './fixtures/css-inline-stylesheets/',
5457
output: 'server',
5558
adapter: testAdapter(),
59+
outDir: './dist/server-inline-stylesheets-never',
5660
build: {
61+
client: './dist/server-inline-stylesheets-never/client',
62+
server: './dist/server-inline-stylesheets-never/server',
5763
inlineStylesheets: 'never',
5864
},
5965
});
@@ -92,7 +98,10 @@ describe('Setting inlineStylesheets to auto in static output', () => {
9298
site: 'https://test.info/',
9399
root: './fixtures/css-inline-stylesheets/',
94100
output: 'static',
101+
outDir: './dist/static-inline-stylesheets-auto',
95102
build: {
103+
client: './dist/static-inline-stylesheets-auto/client',
104+
server: './dist/static-inline-stylesheets-auto/server',
96105
inlineStylesheets: 'auto',
97106
},
98107
vite: {
@@ -137,7 +146,10 @@ describe('Setting inlineStylesheets to auto in server output', () => {
137146
root: './fixtures/css-inline-stylesheets/',
138147
output: 'server',
139148
adapter: testAdapter(),
149+
outDir: './dist/server-inline-stylesheets-auto',
140150
build: {
151+
client: './dist/server-inline-stylesheets-auto/client',
152+
server: './dist/server-inline-stylesheets-auto/server',
141153
inlineStylesheets: 'auto',
142154
},
143155
vite: {
@@ -184,7 +196,10 @@ describe('Setting inlineStylesheets to always in static output', () => {
184196
site: 'https://test.net/',
185197
root: './fixtures/css-inline-stylesheets/',
186198
output: 'static',
199+
outDir: './dist/static-inline-stylesheets-always',
187200
build: {
201+
client: './dist/static-inline-stylesheets-always/client',
202+
server: './dist/static-inline-stylesheets-always/server',
188203
inlineStylesheets: 'always',
189204
},
190205
});
@@ -221,7 +236,10 @@ describe('Setting inlineStylesheets to always in server output', () => {
221236
root: './fixtures/css-inline-stylesheets/',
222237
output: 'server',
223238
adapter: testAdapter(),
239+
outDir: './dist/server-inline-stylesheets-always',
224240
build: {
241+
client: './dist/server-inline-stylesheets-always/client',
242+
server: './dist/server-inline-stylesheets-always/server',
225243
inlineStylesheets: 'always',
226244
},
227245
});

‎packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ describe('Experimental Content Collections cache - inlineStylesheets to never in
5959
root: './fixtures/css-inline-stylesheets/',
6060
output: 'server',
6161
adapter: testAdapter(),
62+
outDir: './dist/inline-stylesheets-never',
6263
build: {
64+
client: './dist/inline-stylesheets-never/client',
65+
server: './dist/inline-stylesheets-never/server',
6366
inlineStylesheets: 'never',
6467
},
6568
experimental: {
@@ -103,7 +106,10 @@ describe('Experimental Content Collections cache - inlineStylesheets to auto in
103106
site: 'https://test.info/',
104107
root: './fixtures/css-inline-stylesheets/',
105108
output: 'static',
109+
outDir: './dist/inline-stylesheets-auto',
106110
build: {
111+
client: './dist/inline-stylesheets-auto/client',
112+
server: './dist/inline-stylesheets-auto/server',
107113
inlineStylesheets: 'auto',
108114
},
109115
vite: {
@@ -202,7 +208,10 @@ describe('Setting inlineStylesheets to always in server output', () => {
202208
root: './fixtures/css-inline-stylesheets/',
203209
output: 'server',
204210
adapter: testAdapter(),
211+
outDir: './dist/inline-stylesheets-always',
205212
build: {
213+
client: './dist/inline-stylesheets-always/client',
214+
server: './dist/inline-stylesheets-always/server',
206215
inlineStylesheets: 'always',
207216
},
208217
experimental: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import serverlessAdapter from '@test/ssr-prerender-chunks-test-adapter';
2+
import { defineConfig } from 'astro/config';
3+
import react from "@astrojs/react";
4+
5+
// https://astro.build/config
6+
export default defineConfig({
7+
adapter: serverlessAdapter(),
8+
output: 'server',
9+
integrations: [react()]
10+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
*
3+
* @returns {import('../src/@types/astro').AstroIntegration}
4+
*/
5+
export default function () {
6+
return {
7+
name: '@test/ssr-prerender-chunks-test-adapter',
8+
hooks: {
9+
'astro:config:setup': ({ updateConfig, config }) => {
10+
updateConfig({
11+
build: {
12+
client: config.outDir,
13+
server: new URL('./_worker.js/', config.outDir),
14+
serverEntry: 'index.js',
15+
redirects: false,
16+
}
17+
});
18+
},
19+
'astro:config:done': ({ setAdapter }) => {
20+
setAdapter({
21+
name: '@test/ssr-prerender-chunks-test-adapter',
22+
serverEntrypoint: '@test/ssr-prerender-chunks-test-adapter/server.js',
23+
exports: ['default'],
24+
supportedAstroFeatures: {
25+
serverOutput: 'stable',
26+
},
27+
});
28+
},
29+
'astro:build:setup': ({ vite, target }) => {
30+
if (target === 'server') {
31+
vite.resolve ||= {};
32+
vite.resolve.alias ||= {};
33+
34+
const aliases = [
35+
{
36+
find: 'react-dom/server',
37+
replacement: 'react-dom/server.browser',
38+
},
39+
];
40+
41+
if (Array.isArray(vite.resolve.alias)) {
42+
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
43+
} else {
44+
for (const alias of aliases) {
45+
(vite.resolve.alias)[alias.find] = alias.replacement;
46+
}
47+
}
48+
49+
vite.resolve.conditions ||= [];
50+
// We need those conditions, previous these conditions where applied at the esbuild step which we removed
51+
// https://github.com/withastro/astro/pull/7092
52+
vite.resolve.conditions.push('workerd', 'worker');
53+
54+
vite.ssr ||= {};
55+
vite.ssr.target = 'webworker';
56+
vite.ssr.noExternal = true;
57+
58+
vite.build ||= {};
59+
vite.build.rollupOptions ||= {};
60+
vite.build.rollupOptions.output ||= {};
61+
vite.build.rollupOptions.output.banner ||=
62+
'globalThis.process ??= {}; globalThis.process.env ??= {};';
63+
64+
// Cloudflare env is only available per request. This isn't feasible for code that access env vars
65+
// in a global way, so we shim their access as `process.env.*`. This is not the recommended way for users to access environment variables. But we'll add this for compatibility for chosen variables. Mainly to support `@astrojs/db`
66+
vite.define = {
67+
'process.env': 'process.env',
68+
...vite.define,
69+
};
70+
}
71+
// we thought that vite config inside `if (target === 'server')` would not apply for client
72+
// but it seems like the same `vite` reference is used for both
73+
// so we need to reset the previous conflicting setting
74+
// in the future we should look into a more robust solution
75+
if (target === 'client') {
76+
vite.resolve ||= {};
77+
vite.resolve.conditions ||= [];
78+
vite.resolve.conditions = vite.resolve.conditions.filter(
79+
(c) => c !== 'workerd' && c !== 'worker'
80+
);
81+
}
82+
},
83+
},
84+
};
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@test/ssr-prerender-chunks-test-adapter",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"exports": {
7+
".": "./index.js",
8+
"./server.js": "./server.js"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { App } from 'astro/app';
2+
3+
export function createExports(manifest) {
4+
const app = new App(manifest);
5+
6+
const fetch = async (
7+
request,
8+
env,
9+
context
10+
) => {
11+
const { pathname } = new URL(request.url);
12+
13+
// static assets fallback, in case default _routes.json is not used
14+
if (manifest.assets.has(pathname)) {
15+
return env.ASSETS.fetch(request.url.replace(/\.html$/, ''));
16+
}
17+
18+
const routeData = app.match(request);
19+
if (!routeData) {
20+
// https://developers.cloudflare.com/pages/functions/api-reference/#envassetsfetch
21+
const asset = await env.ASSETS.fetch(
22+
request.url.replace(/index.html$/, '').replace(/\.html$/, '')
23+
);
24+
if (asset.status !== 404) {
25+
return asset;
26+
}
27+
}
28+
29+
Reflect.set(
30+
request,
31+
Symbol.for('astro.clientAddress'),
32+
request.headers.get('cf-connecting-ip')
33+
);
34+
35+
process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => {
36+
if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') {
37+
return env.ASTRO_STUDIO_APP_TOKEN;
38+
}
39+
})();
40+
41+
const locals = {
42+
runtime: {
43+
env: env,
44+
cf: request.cf,
45+
caches,
46+
ctx: {
47+
waitUntil: (promise) => context.waitUntil(promise),
48+
passThroughOnException: () => context.passThroughOnException(),
49+
},
50+
},
51+
};
52+
53+
const response = await app.render(request, { routeData, locals });
54+
55+
if (app.setCookieHeaders) {
56+
for (const setCookieHeader of app.setCookieHeaders(response)) {
57+
response.headers.append('Set-Cookie', setCookieHeader);
58+
}
59+
}
60+
61+
return response;
62+
};
63+
64+
return { default: { fetch } };
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@test/ssr-prerender-chunks",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"@astrojs/react": "workspace:*",
7+
"@test/ssr-prerender-chunks-test-adapter": "link:./deps/test-adapter",
8+
"@types/react": "^18.2.75",
9+
"@types/react-dom": "^18.2.24",
10+
"astro": "workspace:*",
11+
"react": "^18.2.0",
12+
"react-dom": "^18.2.0"
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useState } from "react";
2+
3+
const Counter: React.FC = () => {
4+
const [count, setCount] = useState<number>(0);
5+
6+
const increment = () => {
7+
setCount((prevCount) => prevCount + 1);
8+
};
9+
10+
const decrement = () => {
11+
setCount((prevCount) => prevCount - 1);
12+
};
13+
14+
return (
15+
<div>
16+
<h2>Counter</h2>
17+
<div>
18+
<button onClick={decrement}>-</button>
19+
<span>{count}</span>
20+
<button onClick={increment}>+</button>
21+
</div>
22+
</div>
23+
);
24+
};
25+
26+
export default Counter;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
export const prerender = true;
3+
import Counter from "../components/Counter";
4+
---
5+
6+
<html>
7+
<head>
8+
<title>Static Page</title>
9+
</head>
10+
<body>
11+
<Counter client:load />
12+
</body>
13+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "astro/tsconfigs/base",
3+
"compilerOptions": {
4+
"jsx": "react-jsx",
5+
"jsxImportSource": "react"
6+
}
7+
}

‎packages/astro/test/i18n-routing.test.js

+15
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,11 @@ describe('[SSR] i18n routing', () => {
13361336
fixture = await loadFixture({
13371337
root: './fixtures/i18n-routing-prefix-always/',
13381338
output: 'server',
1339+
outDir: './dist/pathname-prefix-always-no-redirect',
1340+
build: {
1341+
client: './dist/pathname-prefix-always-no-redirect/client',
1342+
server: './dist/pathname-prefix-always-no-redirect/server',
1343+
},
13391344
adapter: testAdapter(),
13401345
i18n: {
13411346
routing: {
@@ -1622,6 +1627,11 @@ describe('[SSR] i18n routing', () => {
16221627
fixture = await loadFixture({
16231628
root: './fixtures/i18n-routing/',
16241629
output: 'server',
1630+
outDir: './dist/locales-underscore',
1631+
build: {
1632+
client: './dist/locales-underscore/client',
1633+
server: './dist/locales-underscore/server',
1634+
},
16251635
adapter: testAdapter(),
16261636
i18n: {
16271637
defaultLocale: 'en',
@@ -1891,6 +1901,11 @@ describe('SSR fallback from missing locale index to default locale index', () =>
18911901
fixture = await loadFixture({
18921902
root: './fixtures/i18n-routing-prefix-other-locales/',
18931903
output: 'server',
1904+
outDir: './dist/missing-locale-to-default',
1905+
build: {
1906+
client: './dist/missing-locale-to-default/client',
1907+
server: './dist/missing-locale-to-default/server',
1908+
},
18941909
adapter: testAdapter(),
18951910
i18n: {
18961911
defaultLocale: 'en',

‎packages/astro/test/ssr-hoisted-script.test.js

+45-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ describe('Hoisted inline scripts in SSR', () => {
2525

2626
describe('without base path', () => {
2727
before(async () => {
28-
fixture = await loadFixture(defaultFixtureOptions);
28+
fixture = await loadFixture({
29+
...defaultFixtureOptions,
30+
outDir: './dist/inline-scripts-without-base-path',
31+
build: {
32+
client: './dist/inline-scripts-without-base-path/client',
33+
server: './dist/inline-scripts-without-base-path/server',
34+
},
35+
});
2936
await fixture.build();
3037
});
3138

@@ -42,6 +49,11 @@ describe('Hoisted inline scripts in SSR', () => {
4249
before(async () => {
4350
fixture = await loadFixture({
4451
...defaultFixtureOptions,
52+
outDir: './dist/inline-scripts-with-base-path',
53+
build: {
54+
client: './dist/inline-scripts-with-base-path/client',
55+
server: './dist/inline-scripts-with-base-path/server',
56+
},
4557
base,
4658
});
4759
await fixture.build();
@@ -63,6 +75,11 @@ describe('Hoisted external scripts in SSR', () => {
6375
before(async () => {
6476
fixture = await loadFixture({
6577
...defaultFixtureOptions,
78+
outDir: './dist/external-scripts-without-base-path',
79+
build: {
80+
client: './dist/external-scripts-without-base-path/client',
81+
server: './dist/external-scripts-without-base-path/server',
82+
},
6683
vite: {
6784
build: {
6885
assetsInlineLimit: 0,
@@ -83,6 +100,11 @@ describe('Hoisted external scripts in SSR', () => {
83100
before(async () => {
84101
fixture = await loadFixture({
85102
...defaultFixtureOptions,
103+
outDir: './dist/external-scripts-with-base-path',
104+
build: {
105+
client: './dist/external-scripts-with-base-path/client',
106+
server: './dist/external-scripts-with-base-path/server',
107+
},
86108
vite: {
87109
build: {
88110
assetsInlineLimit: 0,
@@ -104,14 +126,17 @@ describe('Hoisted external scripts in SSR', () => {
104126
before(async () => {
105127
fixture = await loadFixture({
106128
...defaultFixtureOptions,
129+
outDir: './dist/with-assets-prefix',
130+
build: {
131+
client: './dist/with-assets-prefix/client',
132+
server: './dist/with-assets-prefix/server',
133+
assetsPrefix: 'https://cdn.example.com',
134+
},
107135
vite: {
108136
build: {
109137
assetsInlineLimit: 0,
110138
},
111139
},
112-
build: {
113-
assetsPrefix: 'https://cdn.example.com',
114-
},
115140
});
116141
await fixture.build();
117142
});
@@ -130,6 +155,11 @@ describe('Hoisted external scripts in SSR', () => {
130155
before(async () => {
131156
fixture = await loadFixture({
132157
...defaultFixtureOptions,
158+
outDir: './dist/with-rollup-output-file-names',
159+
build: {
160+
client: './dist/with-rollup-output-file-names/client',
161+
server: './dist/with-rollup-output-file-names/server',
162+
},
133163
vite: {
134164
build: {
135165
assetsInlineLimit: 0,
@@ -157,6 +187,11 @@ describe('Hoisted external scripts in SSR', () => {
157187
before(async () => {
158188
fixture = await loadFixture({
159189
...defaultFixtureOptions,
190+
outDir: './dist/with-rollup-output-file-names-and-base',
191+
build: {
192+
client: './dist/with-rollup-output-file-names-and-base/client',
193+
server: './dist/with-rollup-output-file-names-and-base/server',
194+
},
160195
vite: {
161196
build: {
162197
assetsInlineLimit: 0,
@@ -185,6 +220,12 @@ describe('Hoisted external scripts in SSR', () => {
185220
before(async () => {
186221
fixture = await loadFixture({
187222
...defaultFixtureOptions,
223+
outDir: './dist/with-rollup-output-file-names-and-assets-prefix',
224+
build: {
225+
client: './dist/with-rollup-output-file-names-and-assets-prefix/client',
226+
server: './dist/with-rollup-output-file-names-and-assets-prefix/server',
227+
assetsPrefix: 'https://cdn.example.com',
228+
},
188229
vite: {
189230
build: {
190231
assetsInlineLimit: 0,
@@ -197,9 +238,6 @@ describe('Hoisted external scripts in SSR', () => {
197238
},
198239
},
199240
},
200-
build: {
201-
assetsPrefix: 'https://cdn.example.com',
202-
},
203241
});
204242
await fixture.build();
205243
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import assert from 'node:assert/strict';
2+
import { before, describe, it } from 'node:test';
3+
import { loadFixture } from './test-utils.js';
4+
5+
describe('Chunks', () => {
6+
/** @type {import('./test-utils').Fixture} */
7+
let fixture;
8+
9+
before(async () => {
10+
fixture = await loadFixture({
11+
root: './fixtures/ssr-prerender-chunks/',
12+
});
13+
await fixture.build();
14+
});
15+
16+
it('does not have wrong chunks', async () => {
17+
const content = await fixture.readFile('_worker.js/renderers.mjs');
18+
const hasImportFromPrerender = !content.includes(`React } from './chunks/prerender`);
19+
assert.ok(hasImportFromPrerender);
20+
});
21+
});

‎packages/astro/test/ssr-prerender.test.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ describe('SSR: prerender', () => {
1212
fixture = await loadFixture({
1313
root: './fixtures/ssr-prerender/',
1414
output: 'server',
15+
outDir: './dist/normal',
16+
build: {
17+
client: './dist/normal/client',
18+
server: './dist/normal/server',
19+
},
1520
adapter: testAdapter(),
1621
});
1722
await fixture.build();
@@ -61,7 +66,11 @@ describe('SSR: prerender', () => {
6166
});
6267
});
6368

64-
describe('Integrations can hook into the prerendering decision', () => {
69+
// NOTE: This test doesn't make sense as it relies on the fact that on the client build,
70+
// you can change the prerender state of pages from the SSR build, however, the client build
71+
// is not always guaranteed to run. If we want to support this feature, we may want to only allow
72+
// editing `route.prerender` on the `astro:build:done` hook.
73+
describe.skip('Integrations can hook into the prerendering decision', () => {
6574
/** @type {import('./test-utils').Fixture} */
6675
let fixture;
6776

@@ -83,6 +92,11 @@ describe('Integrations can hook into the prerendering decision', () => {
8392
fixture = await loadFixture({
8493
root: './fixtures/ssr-prerender/',
8594
output: 'server',
95+
outDir: './dist/integration-prerender',
96+
build: {
97+
client: './dist/integration-prerender/client',
98+
server: './dist/integration-prerender/server',
99+
},
86100
integrations: [testIntegration],
87101
adapter: testAdapter(),
88102
});

‎packages/astro/test/test-utils.js

+4
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ export async function loadFixture(inlineConfig) {
216216
});
217217
}
218218
},
219+
loadAdapterEntryModule: async () => {
220+
const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
221+
return await import(url);
222+
},
219223
loadNodeAdapterHandler: async () => {
220224
const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
221225
const { handler } = await import(url);

‎packages/integrations/node/test/node-middleware.test.js

+2-9
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
99
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
1010
*/
1111

12-
async function load() {
13-
const mod = await import(
14-
`./fixtures/node-middleware/dist/server/entry.mjs?dropcache=${Date.now()}`
15-
);
16-
return mod;
17-
}
18-
1912
describe('behavior from middleware, standalone', () => {
2013
/** @type {import('./test-utils').Fixture} */
2114
let fixture;
@@ -29,7 +22,7 @@ describe('behavior from middleware, standalone', () => {
2922
adapter: nodejs({ mode: 'standalone' }),
3023
});
3124
await fixture.build();
32-
const { startServer } = await load();
25+
const { startServer } = await fixture.loadAdapterEntryModule();
3326
let res = startServer();
3427
server = res.server;
3528
await waitServerListen(server.server);
@@ -69,7 +62,7 @@ describe('behavior from middleware, middleware', () => {
6962
adapter: nodejs({ mode: 'middleware' }),
7063
});
7164
await fixture.build();
72-
const { handler } = await load();
65+
const { handler } = await fixture.loadAdapterEntryModule();
7366
const app = express();
7467
app.use(handler);
7568
server = app.listen(8888);

‎packages/integrations/node/test/prerender-404-500.test.js

+24-11
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
88
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
99
*/
1010

11-
async function load() {
12-
const mod = await import(
13-
`./fixtures/prerender-404-500/dist/server/entry.mjs?dropcache=${Date.now()}`
14-
);
15-
return mod;
16-
}
17-
1811
describe('Prerender 404', () => {
1912
/** @type {import('./test-utils').Fixture} */
2013
let fixture;
@@ -32,10 +25,15 @@ describe('Prerender 404', () => {
3225
base: '/some-base',
3326
root: './fixtures/prerender-404-500/',
3427
output: 'server',
28+
outDir: './dist/server-with-base',
29+
build: {
30+
client: './dist/server-with-base/client',
31+
server: './dist/server-with-base/server',
32+
},
3533
adapter: nodejs({ mode: 'standalone' }),
3634
});
3735
await fixture.build();
38-
const { startServer } = await load();
36+
const { startServer } = await fixture.loadAdapterEntryModule();
3937
let res = startServer();
4038
server = res.server;
4139
await waitServerListen(server.server);
@@ -117,10 +115,15 @@ describe('Prerender 404', () => {
117115
site: 'https://test.info/',
118116
root: './fixtures/prerender-404-500/',
119117
output: 'server',
118+
outDir: './dist/server-without-base',
119+
build: {
120+
client: './dist/server-without-base/client',
121+
server: './dist/server-without-base/server',
122+
},
120123
adapter: nodejs({ mode: 'standalone' }),
121124
});
122125
await fixture.build();
123-
const { startServer } = await load();
126+
const { startServer } = await fixture.loadAdapterEntryModule();
124127
let res = startServer();
125128
server = res.server;
126129
await waitServerListen(server.server);
@@ -181,10 +184,15 @@ describe('Hybrid 404', () => {
181184
base: '/some-base',
182185
root: './fixtures/prerender-404-500/',
183186
output: 'hybrid',
187+
outDir: './dist/hybrid-with-base',
188+
build: {
189+
client: './dist/hybrid-with-base/client',
190+
server: './dist/hybrid-with-base/server',
191+
},
184192
adapter: nodejs({ mode: 'standalone' }),
185193
});
186194
await fixture.build();
187-
const { startServer } = await load();
195+
const { startServer } = await fixture.loadAdapterEntryModule();
188196
let res = startServer();
189197
server = res.server;
190198
await waitServerListen(server.server);
@@ -238,10 +246,15 @@ describe('Hybrid 404', () => {
238246
site: 'https://test.net/',
239247
root: './fixtures/prerender-404-500/',
240248
output: 'hybrid',
249+
outDir: './dist/hybrid-without-base',
250+
build: {
251+
client: './dist/hybrid-without-base/client',
252+
server: './dist/hybrid-without-base/server',
253+
},
241254
adapter: nodejs({ mode: 'standalone' }),
242255
});
243256
await fixture.build();
244-
const { startServer } = await load();
257+
const { startServer } = await fixture.loadAdapterEntryModule();
245258
let res = startServer();
246259
server = res.server;
247260
await waitServerListen(server.server);

‎packages/integrations/node/test/prerender.test.js

+35-9
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
88
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
99
*/
1010

11-
async function load() {
12-
const mod = await import(`./fixtures/prerender/dist/server/entry.mjs?dropcache=${Date.now()}`);
13-
return mod;
14-
}
1511
describe('Prerendering', () => {
1612
/** @type {import('./test-utils').Fixture} */
1713
let fixture;
@@ -25,10 +21,15 @@ describe('Prerendering', () => {
2521
base: '/some-base',
2622
root: './fixtures/prerender/',
2723
output: 'server',
24+
outDir: './dist/with-base',
25+
build: {
26+
client: './dist/with-base/client',
27+
server: './dist/with-base/server',
28+
},
2829
adapter: nodejs({ mode: 'standalone' }),
2930
});
3031
await fixture.build();
31-
const { startServer } = await load();
32+
const { startServer } = await fixture.loadAdapterEntryModule();
3233
let res = startServer();
3334
server = res.server;
3435
await waitServerListen(server.server);
@@ -94,10 +95,15 @@ describe('Prerendering', () => {
9495
fixture = await loadFixture({
9596
root: './fixtures/prerender/',
9697
output: 'server',
98+
outDir: './dist/without-base',
99+
build: {
100+
client: './dist/without-base/client',
101+
server: './dist/without-base/server',
102+
},
97103
adapter: nodejs({ mode: 'standalone' }),
98104
});
99105
await fixture.build();
100-
const { startServer } = await await load();
106+
const { startServer } = await fixture.loadAdapterEntryModule();
101107
let res = startServer();
102108
server = res.server;
103109
await waitServerListen(server.server);
@@ -155,6 +161,11 @@ describe('Prerendering', () => {
155161
fixture = await loadFixture({
156162
root: './fixtures/prerender/',
157163
output: 'server',
164+
outDir: './dist/dev',
165+
build: {
166+
client: './dist/dev/client',
167+
server: './dist/dev/server',
168+
},
158169
adapter: nodejs({ mode: 'standalone' }),
159170
});
160171
devServer = await fixture.startDevServer();
@@ -197,10 +208,15 @@ describe('Hybrid rendering', () => {
197208
base: '/some-base',
198209
root: './fixtures/prerender/',
199210
output: 'hybrid',
211+
outDir: './dist/hybrid-with-base',
212+
build: {
213+
client: './dist/hybrid-with-base/client',
214+
server: './dist/hybrid-with-base/server',
215+
},
200216
adapter: nodejs({ mode: 'standalone' }),
201217
});
202218
await fixture.build();
203-
const { startServer } = await load();
219+
const { startServer } = await fixture.loadAdapterEntryModule();
204220
let res = startServer();
205221
server = res.server;
206222
await waitServerListen(server.server);
@@ -264,10 +280,15 @@ describe('Hybrid rendering', () => {
264280
fixture = await loadFixture({
265281
root: './fixtures/prerender/',
266282
output: 'hybrid',
283+
outDir: './dist/hybrid-without-base',
284+
build: {
285+
client: './dist/hybrid-without-base/client',
286+
server: './dist/hybrid-without-base/server',
287+
},
267288
adapter: nodejs({ mode: 'standalone' }),
268289
});
269290
await fixture.build();
270-
const { startServer } = await load();
291+
const { startServer } = await fixture.loadAdapterEntryModule();
271292
let res = startServer();
272293
server = res.server;
273294
await waitServerListen(server.server);
@@ -323,10 +344,15 @@ describe('Hybrid rendering', () => {
323344
fixture = await loadFixture({
324345
root: './fixtures/prerender/',
325346
output: 'hybrid',
347+
outDir: './dist/hybrid-shared-modules',
348+
build: {
349+
client: './dist/hybrid-shared-modules/client',
350+
server: './dist/hybrid-shared-modules/server',
351+
},
326352
adapter: nodejs({ mode: 'standalone' }),
327353
});
328354
await fixture.build();
329-
const { startServer } = await load();
355+
const { startServer } = await fixture.loadAdapterEntryModule();
330356
let res = startServer();
331357
server = res.server;
332358
await waitServerListen(server.server);

‎packages/integrations/node/test/trailing-slash.test.js

+36-13
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
88
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
99
*/
1010

11-
async function load() {
12-
const mod = await import(
13-
`./fixtures/trailing-slash/dist/server/entry.mjs?dropcache=${Date.now()}`
14-
);
15-
return mod;
16-
}
17-
1811
describe('Trailing slash', () => {
1912
/** @type {import('./test-utils').Fixture} */
2013
let fixture;
@@ -30,10 +23,15 @@ describe('Trailing slash', () => {
3023
base: '/some-base',
3124
output: 'hybrid',
3225
trailingSlash: 'always',
26+
outDir: './dist/always-with-base',
27+
build: {
28+
client: './dist/always-with-base/client',
29+
server: './dist/always-with-base/server',
30+
},
3331
adapter: nodejs({ mode: 'standalone' }),
3432
});
3533
await fixture.build();
36-
const { startServer } = await load();
34+
const { startServer } = await fixture.loadAdapterEntryModule();
3735
let res = startServer();
3836
server = res.server;
3937
await waitServerListen(server.server);
@@ -96,10 +94,15 @@ describe('Trailing slash', () => {
9694
root: './fixtures/trailing-slash/',
9795
output: 'hybrid',
9896
trailingSlash: 'always',
97+
outDir: './dist/always-without-base',
98+
build: {
99+
client: './dist/always-without-base/client',
100+
server: './dist/always-without-base/server',
101+
},
99102
adapter: nodejs({ mode: 'standalone' }),
100103
});
101104
await fixture.build();
102-
const { startServer } = await load();
105+
const { startServer } = await fixture.loadAdapterEntryModule();
103106
let res = startServer();
104107
server = res.server;
105108
await waitServerListen(server.server);
@@ -165,10 +168,15 @@ describe('Trailing slash', () => {
165168
base: '/some-base',
166169
output: 'hybrid',
167170
trailingSlash: 'never',
171+
outDir: './dist/never-with-base',
172+
build: {
173+
client: './dist/never-with-base/client',
174+
server: './dist/never-with-base/server',
175+
},
168176
adapter: nodejs({ mode: 'standalone' }),
169177
});
170178
await fixture.build();
171-
const { startServer } = await load();
179+
const { startServer } = await fixture.loadAdapterEntryModule();
172180
let res = startServer();
173181
server = res.server;
174182
await waitServerListen(server.server);
@@ -224,10 +232,15 @@ describe('Trailing slash', () => {
224232
root: './fixtures/trailing-slash/',
225233
output: 'hybrid',
226234
trailingSlash: 'never',
235+
outDir: './dist/never-without-base',
236+
build: {
237+
client: './dist/never-without-base/client',
238+
server: './dist/never-without-base/server',
239+
},
227240
adapter: nodejs({ mode: 'standalone' }),
228241
});
229242
await fixture.build();
230-
const { startServer } = await load();
243+
const { startServer } = await fixture.loadAdapterEntryModule();
231244
let res = startServer();
232245
server = res.server;
233246
await waitServerListen(server.server);
@@ -286,10 +299,15 @@ describe('Trailing slash', () => {
286299
base: '/some-base',
287300
output: 'hybrid',
288301
trailingSlash: 'ignore',
302+
outDir: './dist/ignore-with-base',
303+
build: {
304+
client: './dist/ignore-with-base/client',
305+
server: './dist/ignore-with-base/server',
306+
},
289307
adapter: nodejs({ mode: 'standalone' }),
290308
});
291309
await fixture.build();
292-
const { startServer } = await load();
310+
const { startServer } = await fixture.loadAdapterEntryModule();
293311
let res = startServer();
294312
server = res.server;
295313
await waitServerListen(server.server);
@@ -363,10 +381,15 @@ describe('Trailing slash', () => {
363381
root: './fixtures/trailing-slash/',
364382
output: 'hybrid',
365383
trailingSlash: 'ignore',
384+
outDir: './dist/ignore-without-base',
385+
build: {
386+
client: './dist/ignore-without-base/client',
387+
server: './dist/ignore-without-base/server',
388+
},
366389
adapter: nodejs({ mode: 'standalone' }),
367390
});
368391
await fixture.build();
369-
const { startServer } = await load();
392+
const { startServer } = await fixture.loadAdapterEntryModule();
370393
let res = startServer();
371394
server = res.server;
372395
await waitServerListen(server.server);

‎packages/integrations/vercel/test/serverless-prerender.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('Serverless prerender', () => {
2020

2121
it('outDir is tree-shaken if not needed', async () => {
2222
const [file] = await fixture.glob(
23-
'../.vercel/output/functions/_render.func/packages/integrations/vercel/test/fixtures/serverless-prerender/.vercel/output/_functions/chunks/pages/generic_*.mjs'
23+
'../.vercel/output/functions/_render.func/packages/integrations/vercel/test/fixtures/serverless-prerender/.vercel/output/_functions/pages/_image.astro.mjs'
2424
);
2525
const contents = await fixture.readFile(file);
2626
assert.ok(!contents.includes('const outDir ='), "outDir is tree-shaken if it's not imported");

‎pnpm-lock.yaml

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.