aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Bjorn Lu <bjornlu.dev@gmail.com> 2024-06-17 14:23:10 +0800
committerGravatar GitHub <noreply@github.com> 2024-06-17 14:23:10 +0800
commite22be22e5729e60220726e92b52d2833c937fd1c (patch)
tree887bbea465e90336d52d3d4e01660aa8123a55a0
parent68f1d0d13e828d77721010ca840c12dfb383c4e7 (diff)
downloadastro-e22be22e5729e60220726e92b52d2833c937fd1c.tar.gz
astro-e22be22e5729e60220726e92b52d2833c937fd1c.tar.zst
astro-e22be22e5729e60220726e92b52d2833c937fd1c.zip
Refactor prerendering chunk handling (#11245)
-rw-r--r--.changeset/three-boxes-sniff.md5
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--packages/astro/src/core/build/internal.ts6
-rw-r--r--packages/astro/src/core/build/page-data.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-chunks.ts9
-rw-r--r--packages/astro/src/core/build/plugins/plugin-prerender.ts160
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts31
-rw-r--r--packages/astro/src/core/build/static-build.ts81
-rw-r--r--packages/astro/src/core/build/types.ts1
-rw-r--r--packages/astro/test/astro-assets-prefix.test.js16
-rw-r--r--packages/astro/test/before-hydration.test.js20
-rw-r--r--packages/astro/test/core-image.test.js16
-rw-r--r--packages/astro/test/css-inline-stylesheets.test.js18
-rw-r--r--packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js9
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/astro.config.mjs10
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/index.js85
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/package.json10
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js65
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/package.json14
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/src/components/Counter.tsx26
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro13
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/tsconfig.json7
-rw-r--r--packages/astro/test/i18n-routing.test.js15
-rw-r--r--packages/astro/test/ssr-hoisted-script.test.js52
-rw-r--r--packages/astro/test/ssr-prerender-chunks.test.js21
-rw-r--r--packages/astro/test/ssr-prerender.test.js16
-rw-r--r--packages/astro/test/test-utils.js4
-rw-r--r--packages/integrations/node/test/node-middleware.test.js11
-rw-r--r--packages/integrations/node/test/prerender-404-500.test.js35
-rw-r--r--packages/integrations/node/test/prerender.test.js44
-rw-r--r--packages/integrations/node/test/trailing-slash.test.js49
-rw-r--r--packages/integrations/vercel/test/serverless-prerender.test.js2
-rw-r--r--pnpm-lock.yaml26
33 files changed, 692 insertions, 193 deletions
diff --git a/.changeset/three-boxes-sniff.md b/.changeset/three-boxes-sniff.md
new file mode 100644
index 000000000..50b99c08d
--- /dev/null
+++ b/.changeset/three-boxes-sniff.md
@@ -0,0 +1,5 @@
+---
+"astro": patch
+---
+
+Refactors prerendering chunk handling to correctly remove unused code during the SSR runtime
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 918fffaca..2f2e63786 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -176,6 +176,12 @@ Any tests for `astro build` output should use the main `mocha` tests rather than
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.
+#### Creating tests
+
+When creating new tests, it's best to reference other existing test files and replicate the same setup. Some other tips include:
+
+- 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.
+
### Other useful commands
```shell
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 615d36640..07ecf261d 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -113,6 +113,11 @@ export interface BuildInternals {
ssrSplitEntryChunks: Map<string, Rollup.OutputChunk>;
componentMetadata: SSRResult['componentMetadata'];
middlewareEntryPoint?: URL;
+
+ /**
+ * Chunks in the bundle that are only used in prerendering that we can delete later
+ */
+ prerenderOnlyChunks: Rollup.OutputChunk[];
}
/**
@@ -151,6 +156,7 @@ export function createBuildInternals(): BuildInternals {
ssrSplitEntryChunks: new Map(),
entryPoints: new Map(),
cacheManifestUsed: false,
+ prerenderOnlyChunks: [],
};
}
diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts
index 6358a6f55..c4c1e180c 100644
--- a/packages/astro/src/core/build/page-data.ts
+++ b/packages/astro/src/core/build/page-data.ts
@@ -57,7 +57,6 @@ export async function collectPagesData(
moduleSpecifier: '',
styles: [],
hoistedScript: undefined,
- hasSharedModules: false,
};
clearInterval(routeCollectionLogTimeout);
@@ -80,7 +79,6 @@ export async function collectPagesData(
moduleSpecifier: '',
styles: [],
hoistedScript: undefined,
- hasSharedModules: false,
};
}
diff --git a/packages/astro/src/core/build/plugins/plugin-chunks.ts b/packages/astro/src/core/build/plugins/plugin-chunks.ts
index 30b3e4938..bcd1d15bf 100644
--- a/packages/astro/src/core/build/plugins/plugin-chunks.ts
+++ b/packages/astro/src/core/build/plugins/plugin-chunks.ts
@@ -12,6 +12,15 @@ export function vitePluginChunks(): VitePlugin {
if (id.includes('astro/dist/runtime/server/')) {
return 'astro/server';
}
+ // Split the Astro runtime into a separate chunk for readability
+ if (id.includes('astro/dist/runtime')) {
+ return 'astro';
+ }
+ // Place `astro/env/setup` import in its own chunk to prevent Rollup's TLA bug
+ // https://github.com/rollup/rollup/issues/4708
+ if (id.includes('astro/dist/env/setup')) {
+ return 'astro/env-setup';
+ }
},
});
},
diff --git a/packages/astro/src/core/build/plugins/plugin-prerender.ts b/packages/astro/src/core/build/plugins/plugin-prerender.ts
index 171d998b7..b7feb70e3 100644
--- a/packages/astro/src/core/build/plugins/plugin-prerender.ts
+++ b/packages/astro/src/core/build/plugins/plugin-prerender.ts
@@ -1,87 +1,105 @@
-import path from 'node:path';
-import type { Plugin as VitePlugin } from 'vite';
+import type { Rollup, Plugin as VitePlugin } from 'vite';
import { getPrerenderMetadata } from '../../../prerender/metadata.js';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types.js';
-import { extendManualChunks } from './util.js';
+import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugin-pages.js';
+import { getPagesFromVirtualModulePageName } from './util.js';
-function vitePluginPrerender(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
+function vitePluginPrerender(internals: BuildInternals): VitePlugin {
return {
name: 'astro:rollup-plugin-prerender',
- outputOptions(outputOptions) {
- extendManualChunks(outputOptions, {
- after(id, meta) {
- // Split the Astro runtime into a separate chunk for readability
- if (id.includes('astro/dist/runtime')) {
- return 'astro';
- }
- const pageInfo = internals.pagesByViteID.get(id);
- let hasSharedModules = false;
- if (pageInfo) {
- // prerendered pages should be split into their own chunk
- // Important: this can't be in the `pages/` directory!
- if (getPrerenderMetadata(meta.getModuleInfo(id)!)) {
- const infoMeta = meta.getModuleInfo(id)!;
+ generateBundle(_, bundle) {
+ const moduleIds = this.getModuleIds();
+ for (const id of moduleIds) {
+ const pageInfo = internals.pagesByViteID.get(id);
+ if (!pageInfo) continue;
+ const moduleInfo = this.getModuleInfo(id);
+ if (!moduleInfo) continue;
- // Here, we check if this page is importing modules that are shared among other modules e.g. middleware, other SSR pages, etc.
- // we loop the modules that the current page imports
- for (const moduleId of infoMeta.importedIds) {
- // we retrieve the metadata of the module
- const moduleMeta = meta.getModuleInfo(moduleId)!;
- if (
- // a shared modules should be inside the `src/` folder, at least
- moduleMeta.id.startsWith(opts.settings.config.srcDir.pathname) &&
- // and has at least two importers: the current page and something else
- moduleMeta.importers.length > 1
- ) {
- // Now, we have to trace back the modules imported and analyze them;
- // understanding if a module is eventually shared between two pages isn't easy, because a module could
- // be imported by a page and a component that is eventually imported by a page.
- //
- // Given the previous statement, we only check if
- // - the module is a page, and it's not pre-rendered
- // - the module is the middleware
- // If one of these conditions is met, we need a separate chunk
- for (const importer of moduleMeta.importedIds) {
- // we don't want to analyze the same module again, so we skip it
- if (importer !== id) {
- const importerModuleMeta = meta.getModuleInfo(importer);
- if (importerModuleMeta) {
- // if the module is inside the pages
- if (importerModuleMeta.id.includes('/pages')) {
- // we check if it's not pre-rendered
- if (getPrerenderMetadata(importerModuleMeta) === false) {
- hasSharedModules = true;
- break;
- }
- }
- // module isn't an Astro route/page, it could be a middleware
- else if (importerModuleMeta.id.includes('/middleware')) {
- hasSharedModules = true;
- break;
- }
- }
- }
- }
- }
- }
+ const prerender = !!getPrerenderMetadata(moduleInfo);
+ pageInfo.route.prerender = prerender;
+ }
- pageInfo.hasSharedModules = hasSharedModules;
- pageInfo.route.prerender = true;
- return 'prerender';
- }
- pageInfo.route.prerender = false;
- // dynamic pages should all go in their own chunk in the pages/* directory
- return `pages/${path.basename(pageInfo.component)}`;
- }
- },
- });
+ // Find all chunks used in the SSR runtime (that aren't used for prerendering only), then use
+ // the Set to find the inverse, where chunks that are only used for prerendering. It's faster
+ // to compute `internals.prerenderOnlyChunks` this way. The prerendered chunks will be deleted
+ // after we finish prerendering.
+ const nonPrerenderOnlyChunks = getNonPrerenderOnlyChunks(bundle, internals);
+ internals.prerenderOnlyChunks = Object.values(bundle).filter((chunk) => {
+ return chunk.type === 'chunk' && !nonPrerenderOnlyChunks.has(chunk);
+ }) as Rollup.OutputChunk[];
},
};
}
+function getNonPrerenderOnlyChunks(bundle: Rollup.OutputBundle, internals: BuildInternals) {
+ const chunks = Object.values(bundle);
+
+ const prerenderOnlyEntryChunks = new Set<Rollup.OutputChunk>();
+ const nonPrerenderOnlyEntryChunks = new Set<Rollup.OutputChunk>();
+ for (const chunk of chunks) {
+ if (chunk.type === 'chunk' && (chunk.isEntry || chunk.isDynamicEntry)) {
+ // See if this entry chunk is prerendered, if so, skip it
+ if (chunk.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
+ const pageDatas = getPagesFromVirtualModulePageName(
+ internals,
+ ASTRO_PAGE_RESOLVED_MODULE_ID,
+ chunk.facadeModuleId
+ );
+ const prerender = pageDatas.every((pageData) => pageData.route.prerender);
+ if (prerender) {
+ prerenderOnlyEntryChunks.add(chunk);
+ continue;
+ }
+ }
+ // Ideally we should record entries when `functionPerRoute` is enabled, but this breaks some tests
+ // that expect the entrypoint to still exist even if it should be unused.
+ // TODO: Revisit this so we can delete additional unused chunks
+ // else if (chunk.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
+ // const pageDatas = getPagesFromVirtualModulePageName(
+ // internals,
+ // RESOLVED_SPLIT_MODULE_ID,
+ // chunk.facadeModuleId
+ // );
+ // const prerender = pageDatas.every((pageData) => pageData.route.prerender);
+ // if (prerender) {
+ // prerenderOnlyEntryChunks.add(chunk);
+ // continue;
+ // }
+ // }
+
+ nonPrerenderOnlyEntryChunks.add(chunk);
+ }
+ }
+
+ // From the `nonPrerenderedEntryChunks`, we crawl all the imports/dynamicImports to find all
+ // other chunks that are use by the non-prerendered runtime
+ const nonPrerenderOnlyChunks = new Set(nonPrerenderOnlyEntryChunks);
+ for (const chunk of nonPrerenderOnlyChunks) {
+ for (const importFileName of chunk.imports) {
+ const importChunk = bundle[importFileName];
+ if (importChunk?.type === 'chunk') {
+ nonPrerenderOnlyChunks.add(importChunk);
+ }
+ }
+ for (const dynamicImportFileName of chunk.dynamicImports) {
+ const dynamicImportChunk = bundle[dynamicImportFileName];
+ // The main server entry (entry.mjs) may import a prerender-only entry chunk, we skip in this case
+ // to prevent incorrectly marking it as non-prerendered.
+ if (
+ dynamicImportChunk?.type === 'chunk' &&
+ !prerenderOnlyEntryChunks.has(dynamicImportChunk)
+ ) {
+ nonPrerenderOnlyChunks.add(dynamicImportChunk);
+ }
+ }
+ }
+
+ return nonPrerenderOnlyChunks;
+}
+
export function pluginPrerender(
opts: StaticBuildOptions,
internals: BuildInternals
@@ -96,7 +114,7 @@ export function pluginPrerender(
hooks: {
'build:before': () => {
return {
- vitePlugin: vitePluginPrerender(opts, internals),
+ vitePlugin: vitePluginPrerender(internals),
};
},
},
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index 0a10110cd..880a4d6a8 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -27,7 +27,17 @@ function vitePluginSSR(
name: '@astrojs/vite-plugin-astro-ssr-server',
enforce: 'post',
options(opts) {
- return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
+ const inputs = new Set<string>();
+
+ for (const pageData of Object.values(options.allPages)) {
+ if (routeIsRedirect(pageData.route)) {
+ continue;
+ }
+ inputs.add(getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component));
+ }
+
+ inputs.add(SSR_VIRTUAL_MODULE_ID);
+ return addRollupInput(opts, Array.from(inputs));
},
resolveId(id) {
if (id === SSR_VIRTUAL_MODULE_ID) {
@@ -72,7 +82,6 @@ function vitePluginSSR(
contents.push(...ssrCode.contents);
return [...imports, ...contents, ...exports].join('\n');
}
- return void 0;
},
async generateBundle(_opts, bundle) {
// Add assets from this SSR chunk as well.
@@ -141,23 +150,20 @@ function vitePluginSSRSplit(
adapter: AstroAdapter,
options: StaticBuildOptions
): VitePlugin {
- const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
return {
name: '@astrojs/vite-plugin-astro-ssr-split',
enforce: 'post',
options(opts) {
- if (functionPerRouteEnabled) {
- const inputs = new Set<string>();
+ const inputs = new Set<string>();
- for (const pageData of Object.values(options.allPages)) {
- if (routeIsRedirect(pageData.route)) {
- continue;
- }
- inputs.add(getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component));
+ for (const pageData of Object.values(options.allPages)) {
+ if (routeIsRedirect(pageData.route)) {
+ continue;
}
-
- return addRollupInput(opts, Array.from(inputs));
+ inputs.add(getVirtualModulePageName(SPLIT_MODULE_ID, pageData.component));
}
+
+ return addRollupInput(opts, Array.from(inputs));
},
resolveId(id) {
if (id.startsWith(SPLIT_MODULE_ID)) {
@@ -185,7 +191,6 @@ function vitePluginSSRSplit(
return [...imports, ...contents, ...exports].join('\n');
}
- return void 0;
},
async generateBundle(_opts, bundle) {
// Add assets from this SSR chunk as well.
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 73f3a19c5..5b75dbc93 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -2,7 +2,6 @@ import fs from 'node:fs';
import path, { extname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { teardown } from '@astrojs/compiler';
-import * as eslexer from 'es-module-lexer';
import glob from 'fast-glob';
import { bgGreen, bgMagenta, black, green } from 'kleur/colors';
import * as vite from 'vite';
@@ -156,7 +155,7 @@ export async function staticBuild(
case isServerLikeOutput(settings.config): {
settings.timer.start('Server generate');
await generatePages(opts, internals);
- await cleanStaticOutput(opts, internals, ssrOutputChunkNames);
+ await cleanStaticOutput(opts, internals);
opts.logger.info(null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
await ssrMoveAssets(opts);
settings.timer.end('Server generate');
@@ -199,6 +198,8 @@ async function ssrBuild(
copyPublicDir: !ssr,
rollupOptions: {
...viteConfig.build?.rollupOptions,
+ // Setting as `exports-only` allows us to safely delete inputs that are only used during prerendering
+ preserveEntrySignatures: 'exports-only',
input: [],
output: {
hoistTransitiveImports: isContentCache,
@@ -381,65 +382,35 @@ 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.
- * If a component is shared between a statically rendered route and a SSR route, it will still be included in the SSR build.
+ * Remove chunks that are used for prerendering only
*/
-async function cleanStaticOutput(
- opts: StaticBuildOptions,
- internals: BuildInternals,
- ssrOutputChunkNames: string[]
-) {
- const prerenderedFiles = new Set();
- const onDemandsFiles = new Set();
- for (const pageData of internals.pagesByKeys.values()) {
- const { moduleSpecifier } = pageData;
- const bundleId =
- internals.pageToBundleMap.get(moduleSpecifier) ??
- internals.entrySpecifierToBundleMap.get(moduleSpecifier);
- if (pageData.route.prerender && !pageData.hasSharedModules && !onDemandsFiles.has(bundleId)) {
- prerenderedFiles.add(bundleId);
- } else {
- onDemandsFiles.add(bundleId);
- // Check if the component was not previously added to the static build by a statically rendered route
- if (prerenderedFiles.has(bundleId)) {
- prerenderedFiles.delete(bundleId);
- }
- }
- }
+async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInternals) {
const ssr = isServerLikeOutput(opts.settings.config);
const out = ssr
? opts.settings.config.build.server
: getOutDirWithinCwd(opts.settings.config.outDir);
- // The SSR output chunks for Astro are all .mjs files
- const files = ssrOutputChunkNames.filter((f) => f.endsWith('.mjs'));
-
- if (files.length) {
- await eslexer.init;
-
- // Cleanup prerendered chunks.
- // This has to happen AFTER the SSR build runs as a final step, because we need the code in order to generate the pages.
- // These chunks should only contain prerendering logic, so they are safe to modify.
- await Promise.all(
- files.map(async (filename) => {
- if (!prerenderedFiles.has(filename)) {
- return;
- }
- const url = new URL(filename, out);
- const text = await fs.promises.readFile(url, { encoding: 'utf8' });
- const [, exports] = eslexer.parse(text);
- // Replace exports (only prerendered pages) with a noop
- let value = 'const noop = () => {};';
- for (const e of exports) {
- if (e.n === 'default') value += `\n export default noop;`;
- else value += `\nexport const ${e.n} = noop;`;
+ await Promise.all(
+ internals.prerenderOnlyChunks.map(async (chunk) => {
+ const url = new URL(chunk.fileName, out);
+ try {
+ // Entry chunks may be referenced by non-deleted code, so we don't actually delete it
+ // but only empty its content. These chunks should never be executed in practice, but
+ // it should prevent broken import paths if adapters do a secondary bundle.
+ if (chunk.isEntry || chunk.isDynamicEntry) {
+ await fs.promises.writeFile(
+ url,
+ "// Contents removed by Astro as it's used for prerendering only",
+ 'utf-8'
+ );
+ } else {
+ await fs.promises.unlink(url);
}
- await fs.promises.writeFile(url, value, { encoding: 'utf8' });
- })
- );
-
- removeEmptyDirs(out);
- }
+ } catch {
+ // Best-effort only. Sometimes some chunks may be deleted by other plugins, like pure CSS chunks,
+ // so they may already not exist.
+ }
+ })
+ );
}
async function cleanServerOutput(
diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts
index 53c6dcb93..b75b6415c 100644
--- a/packages/astro/src/core/build/types.ts
+++ b/packages/astro/src/core/build/types.ts
@@ -29,7 +29,6 @@ export interface PageBuildData {
moduleSpecifier: string;
hoistedScript: HoistedScriptAsset | undefined;
styles: Array<{ depth: number; order: number; sheet: StylesheetAsset }>;
- hasSharedModules: boolean;
}
export type AllPagesData = Record<ComponentPath, PageBuildData>;
diff --git a/packages/astro/test/astro-assets-prefix.test.js b/packages/astro/test/astro-assets-prefix.test.js
index 08af026c1..4987c64e1 100644
--- a/packages/astro/test/astro-assets-prefix.test.js
+++ b/packages/astro/test/astro-assets-prefix.test.js
@@ -14,6 +14,11 @@ describe('Assets Prefix - Static', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-assets-prefix/',
+ outDir: './dist/static',
+ build: {
+ client: './dist/static/client',
+ server: './dist/static/server',
+ },
});
await fixture.build();
});
@@ -72,7 +77,10 @@ describe('Assets Prefix - with path prefix', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-assets-prefix/',
+ outDir: './dist/server',
build: {
+ client: './dist/server/client',
+ server: './dist/server/server',
assetsPrefix: '/starting-slash',
},
});
@@ -97,6 +105,11 @@ describe('Assets Prefix, server', () => {
root: './fixtures/astro-assets-prefix/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server',
+ build: {
+ client: './dist/server/client',
+ server: './dist/server/server',
+ },
});
await fixture.build();
app = await fixture.loadTestAdapterApp();
@@ -154,7 +167,10 @@ describe('Assets Prefix, with path prefix', () => {
root: './fixtures/astro-assets-prefix/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server-path-prefix',
build: {
+ client: './dist/server-path-prefix/client',
+ server: './dist/server-path-prefix/server',
assetsPrefix: '/starting-slash',
},
});
diff --git a/packages/astro/test/before-hydration.test.js b/packages/astro/test/before-hydration.test.js
index d14b347bf..75acafa00 100644
--- a/packages/astro/test/before-hydration.test.js
+++ b/packages/astro/test/before-hydration.test.js
@@ -14,6 +14,11 @@ describe('Astro Scripts before-hydration', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/before-hydration/',
+ outDir: './dist/static-integration',
+ build: {
+ client: './dist/static-integration/client',
+ server: './dist/static-integration/server',
+ },
integrations: [
preact(),
{
@@ -68,6 +73,11 @@ describe('Astro Scripts before-hydration', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/before-hydration/',
+ outDir: './dist/static-no-integration',
+ build: {
+ client: './dist/static-no-integration/client',
+ server: './dist/static-no-integration/server',
+ },
});
});
@@ -115,6 +125,11 @@ describe('Astro Scripts before-hydration', () => {
root: './fixtures/before-hydration/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server-integration',
+ build: {
+ client: './dist/server-integration/client',
+ server: './dist/server-integration/server',
+ },
integrations: [
preact(),
{
@@ -153,6 +168,11 @@ describe('Astro Scripts before-hydration', () => {
fixture = await loadFixture({
root: './fixtures/before-hydration/',
output: 'server',
+ outDir: './dist/static-no-integration',
+ build: {
+ client: './dist/static-no-integration/client',
+ server: './dist/static-no-integration/server',
+ },
adapter: testAdapter(),
});
});
diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js
index a4fd13fcb..bb4ac3127 100644
--- a/packages/astro/test/core-image.test.js
+++ b/packages/astro/test/core-image.test.js
@@ -799,6 +799,11 @@ describe('astro:image', () => {
const fixtureWithBase = await loadFixture({
root: './fixtures/core-image-ssr/',
output: 'server',
+ outDir: './dist/server-base-path',
+ build: {
+ client: './dist/server-base-path/client',
+ server: './dist/server-base-path/server',
+ },
adapter: testAdapter(),
image: {
service: testImageService(),
@@ -1080,6 +1085,11 @@ describe('astro:image', () => {
fixture = await loadFixture({
root: './fixtures/core-image-ssr/',
output: 'server',
+ outDir: './dist/server-dev',
+ build: {
+ client: './dist/server-dev/client',
+ server: './dist/server-dev/server',
+ },
adapter: testAdapter(),
base: 'some-base',
image: {
@@ -1114,6 +1124,11 @@ describe('astro:image', () => {
fixture = await loadFixture({
root: './fixtures/core-image-ssr/',
output: 'server',
+ outDir: './dist/server-prod',
+ build: {
+ client: './dist/server-prod/client',
+ server: './dist/server-prod/server',
+ },
adapter: testAdapter(),
image: {
endpoint: 'astro/assets/endpoint/node',
@@ -1127,6 +1142,7 @@ describe('astro:image', () => {
const app = await fixture.loadTestAdapterApp();
let request = new Request('http://example.com/');
let response = await app.render(request);
+ console.log
assert.equal(response.status, 200);
const html = await response.text();
const $ = cheerio.load(html);
diff --git a/packages/astro/test/css-inline-stylesheets.test.js b/packages/astro/test/css-inline-stylesheets.test.js
index d066f530a..66a3da11e 100644
--- a/packages/astro/test/css-inline-stylesheets.test.js
+++ b/packages/astro/test/css-inline-stylesheets.test.js
@@ -15,7 +15,10 @@ describe('Setting inlineStylesheets to never in static output', () => {
site: 'https://test.dev/',
root: './fixtures/css-inline-stylesheets/',
output: 'static',
+ outDir: './dist/static-inline-stylesheets-never',
build: {
+ client: './dist/static-inline-stylesheets-never/client',
+ server: './dist/static-inline-stylesheets-never/server',
inlineStylesheets: 'never',
},
});
@@ -53,7 +56,10 @@ describe('Setting inlineStylesheets to never in server output', () => {
root: './fixtures/css-inline-stylesheets/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server-inline-stylesheets-never',
build: {
+ client: './dist/server-inline-stylesheets-never/client',
+ server: './dist/server-inline-stylesheets-never/server',
inlineStylesheets: 'never',
},
});
@@ -92,7 +98,10 @@ describe('Setting inlineStylesheets to auto in static output', () => {
site: 'https://test.info/',
root: './fixtures/css-inline-stylesheets/',
output: 'static',
+ outDir: './dist/static-inline-stylesheets-auto',
build: {
+ client: './dist/static-inline-stylesheets-auto/client',
+ server: './dist/static-inline-stylesheets-auto/server',
inlineStylesheets: 'auto',
},
vite: {
@@ -137,7 +146,10 @@ describe('Setting inlineStylesheets to auto in server output', () => {
root: './fixtures/css-inline-stylesheets/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server-inline-stylesheets-auto',
build: {
+ client: './dist/server-inline-stylesheets-auto/client',
+ server: './dist/server-inline-stylesheets-auto/server',
inlineStylesheets: 'auto',
},
vite: {
@@ -184,7 +196,10 @@ describe('Setting inlineStylesheets to always in static output', () => {
site: 'https://test.net/',
root: './fixtures/css-inline-stylesheets/',
output: 'static',
+ outDir: './dist/static-inline-stylesheets-always',
build: {
+ client: './dist/static-inline-stylesheets-always/client',
+ server: './dist/static-inline-stylesheets-always/server',
inlineStylesheets: 'always',
},
});
@@ -221,7 +236,10 @@ describe('Setting inlineStylesheets to always in server output', () => {
root: './fixtures/css-inline-stylesheets/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/server-inline-stylesheets-always',
build: {
+ client: './dist/server-inline-stylesheets-always/client',
+ server: './dist/server-inline-stylesheets-always/server',
inlineStylesheets: 'always',
},
});
diff --git a/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js b/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js
index d6c509de5..5c02c66b5 100644
--- a/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js
+++ b/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js
@@ -59,7 +59,10 @@ describe('Experimental Content Collections cache - inlineStylesheets to never in
root: './fixtures/css-inline-stylesheets/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/inline-stylesheets-never',
build: {
+ client: './dist/inline-stylesheets-never/client',
+ server: './dist/inline-stylesheets-never/server',
inlineStylesheets: 'never',
},
experimental: {
@@ -103,7 +106,10 @@ describe('Experimental Content Collections cache - inlineStylesheets to auto in
site: 'https://test.info/',
root: './fixtures/css-inline-stylesheets/',
output: 'static',
+ outDir: './dist/inline-stylesheets-auto',
build: {
+ client: './dist/inline-stylesheets-auto/client',
+ server: './dist/inline-stylesheets-auto/server',
inlineStylesheets: 'auto',
},
vite: {
@@ -202,7 +208,10 @@ describe('Setting inlineStylesheets to always in server output', () => {
root: './fixtures/css-inline-stylesheets/',
output: 'server',
adapter: testAdapter(),
+ outDir: './dist/inline-stylesheets-always',
build: {
+ client: './dist/inline-stylesheets-always/client',
+ server: './dist/inline-stylesheets-always/server',
inlineStylesheets: 'always',
},
experimental: {
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/astro.config.mjs b/packages/astro/test/fixtures/ssr-prerender-chunks/astro.config.mjs
new file mode 100644
index 000000000..ad35a2317
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/astro.config.mjs
@@ -0,0 +1,10 @@
+import serverlessAdapter from '@test/ssr-prerender-chunks-test-adapter';
+ import { defineConfig } from 'astro/config';
+ import react from "@astrojs/react";
+
+ // https://astro.build/config
+ export default defineConfig({
+ adapter: serverlessAdapter(),
+ output: 'server',
+ integrations: [react()]
+ }) \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/index.js b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/index.js
new file mode 100644
index 000000000..82b7b64b1
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/index.js
@@ -0,0 +1,85 @@
+/**
+ *
+ * @returns {import('../src/@types/astro').AstroIntegration}
+ */
+export default function () {
+ return {
+ name: '@test/ssr-prerender-chunks-test-adapter',
+ hooks: {
+ 'astro:config:setup': ({ updateConfig, config }) => {
+ updateConfig({
+ build: {
+ client: config.outDir,
+ server: new URL('./_worker.js/', config.outDir),
+ serverEntry: 'index.js',
+ redirects: false,
+ }
+ });
+ },
+ 'astro:config:done': ({ setAdapter }) => {
+ setAdapter({
+ name: '@test/ssr-prerender-chunks-test-adapter',
+ serverEntrypoint: '@test/ssr-prerender-chunks-test-adapter/server.js',
+ exports: ['default'],
+ supportedAstroFeatures: {
+ serverOutput: 'stable',
+ },
+ });
+ },
+ 'astro:build:setup': ({ vite, target }) => {
+ if (target === 'server') {
+ vite.resolve ||= {};
+ vite.resolve.alias ||= {};
+
+ const aliases = [
+ {
+ find: 'react-dom/server',
+ replacement: 'react-dom/server.browser',
+ },
+ ];
+
+ if (Array.isArray(vite.resolve.alias)) {
+ vite.resolve.alias = [...vite.resolve.alias, ...aliases];
+ } else {
+ for (const alias of aliases) {
+ (vite.resolve.alias)[alias.find] = alias.replacement;
+ }
+ }
+
+ vite.resolve.conditions ||= [];
+ // We need those conditions, previous these conditions where applied at the esbuild step which we removed
+ // https://github.com/withastro/astro/pull/7092
+ vite.resolve.conditions.push('workerd', 'worker');
+
+ vite.ssr ||= {};
+ vite.ssr.target = 'webworker';
+ vite.ssr.noExternal = true;
+
+ vite.build ||= {};
+ vite.build.rollupOptions ||= {};
+ vite.build.rollupOptions.output ||= {};
+ vite.build.rollupOptions.output.banner ||=
+ 'globalThis.process ??= {}; globalThis.process.env ??= {};';
+
+ // Cloudflare env is only available per request. This isn't feasible for code that access env vars
+ // 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`
+ vite.define = {
+ 'process.env': 'process.env',
+ ...vite.define,
+ };
+ }
+ // we thought that vite config inside `if (target === 'server')` would not apply for client
+ // but it seems like the same `vite` reference is used for both
+ // so we need to reset the previous conflicting setting
+ // in the future we should look into a more robust solution
+ if (target === 'client') {
+ vite.resolve ||= {};
+ vite.resolve.conditions ||= [];
+ vite.resolve.conditions = vite.resolve.conditions.filter(
+ (c) => c !== 'workerd' && c !== 'worker'
+ );
+ }
+ },
+ },
+ };
+} \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/package.json b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/package.json
new file mode 100644
index 000000000..655ab8b54
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@test/ssr-prerender-chunks-test-adapter",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "exports": {
+ ".": "./index.js",
+ "./server.js": "./server.js"
+ }
+}
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js
new file mode 100644
index 000000000..0921fe782
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js
@@ -0,0 +1,65 @@
+import { App } from 'astro/app';
+
+ export function createExports(manifest) {
+ const app = new App(manifest);
+
+ const fetch = async (
+ request,
+ env,
+ context
+ ) => {
+ const { pathname } = new URL(request.url);
+
+ // static assets fallback, in case default _routes.json is not used
+ if (manifest.assets.has(pathname)) {
+ return env.ASSETS.fetch(request.url.replace(/\.html$/, ''));
+ }
+
+ const routeData = app.match(request);
+ if (!routeData) {
+ // https://developers.cloudflare.com/pages/functions/api-reference/#envassetsfetch
+ const asset = await env.ASSETS.fetch(
+ request.url.replace(/index.html$/, '').replace(/\.html$/, '')
+ );
+ if (asset.status !== 404) {
+ return asset;
+ }
+ }
+
+ Reflect.set(
+ request,
+ Symbol.for('astro.clientAddress'),
+ request.headers.get('cf-connecting-ip')
+ );
+
+ process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => {
+ if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') {
+ return env.ASTRO_STUDIO_APP_TOKEN;
+ }
+ })();
+
+ const locals = {
+ runtime: {
+ env: env,
+ cf: request.cf,
+ caches,
+ ctx: {
+ waitUntil: (promise) => context.waitUntil(promise),
+ passThroughOnException: () => context.passThroughOnException(),
+ },
+ },
+ };
+
+ const response = await app.render(request, { routeData, locals });
+
+ if (app.setCookieHeaders) {
+ for (const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+
+ return response;
+ };
+
+ return { default: { fetch } };
+ } \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/package.json b/packages/astro/test/fixtures/ssr-prerender-chunks/package.json
new file mode 100644
index 000000000..60896bdd6
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@test/ssr-prerender-chunks",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/react": "workspace:*",
+ "@test/ssr-prerender-chunks-test-adapter": "link:./deps/test-adapter",
+ "@types/react": "^18.2.75",
+ "@types/react-dom": "^18.2.24",
+ "astro": "workspace:*",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/src/components/Counter.tsx b/packages/astro/test/fixtures/ssr-prerender-chunks/src/components/Counter.tsx
new file mode 100644
index 000000000..c9fdcc2d9
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/src/components/Counter.tsx
@@ -0,0 +1,26 @@
+import React, { useState } from "react";
+
+ const Counter: React.FC = () => {
+ const [count, setCount] = useState<number>(0);
+
+ const increment = () => {
+ setCount((prevCount) => prevCount + 1);
+ };
+
+ const decrement = () => {
+ setCount((prevCount) => prevCount - 1);
+ };
+
+ return (
+ <div>
+ <h2>Counter</h2>
+ <div>
+ <button onClick={decrement}>-</button>
+ <span>{count}</span>
+ <button onClick={increment}>+</button>
+ </div>
+ </div>
+ );
+ };
+
+ export default Counter; \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro b/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro
new file mode 100644
index 000000000..21a503211
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/src/pages/index.astro
@@ -0,0 +1,13 @@
+---
+ export const prerender = true;
+ import Counter from "../components/Counter";
+ ---
+
+ <html>
+ <head>
+ <title>Static Page</title>
+ </head>
+ <body>
+ <Counter client:load />
+ </body>
+ </html> \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/tsconfig.json b/packages/astro/test/fixtures/ssr-prerender-chunks/tsconfig.json
new file mode 100644
index 000000000..7fb90fafc
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "astro/tsconfigs/base",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react"
+ }
+} \ No newline at end of file
diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js
index 09581c899..df8083b81 100644
--- a/packages/astro/test/i18n-routing.test.js
+++ b/packages/astro/test/i18n-routing.test.js
@@ -1336,6 +1336,11 @@ describe('[SSR] i18n routing', () => {
fixture = await loadFixture({
root: './fixtures/i18n-routing-prefix-always/',
output: 'server',
+ outDir: './dist/pathname-prefix-always-no-redirect',
+ build: {
+ client: './dist/pathname-prefix-always-no-redirect/client',
+ server: './dist/pathname-prefix-always-no-redirect/server',
+ },
adapter: testAdapter(),
i18n: {
routing: {
@@ -1622,6 +1627,11 @@ describe('[SSR] i18n routing', () => {
fixture = await loadFixture({
root: './fixtures/i18n-routing/',
output: 'server',
+ outDir: './dist/locales-underscore',
+ build: {
+ client: './dist/locales-underscore/client',
+ server: './dist/locales-underscore/server',
+ },
adapter: testAdapter(),
i18n: {
defaultLocale: 'en',
@@ -1891,6 +1901,11 @@ describe('SSR fallback from missing locale index to default locale index', () =>
fixture = await loadFixture({
root: './fixtures/i18n-routing-prefix-other-locales/',
output: 'server',
+ outDir: './dist/missing-locale-to-default',
+ build: {
+ client: './dist/missing-locale-to-default/client',
+ server: './dist/missing-locale-to-default/server',
+ },
adapter: testAdapter(),
i18n: {
defaultLocale: 'en',
diff --git a/packages/astro/test/ssr-hoisted-script.test.js b/packages/astro/test/ssr-hoisted-script.test.js
index 41bae1ef5..70852f1db 100644
--- a/packages/astro/test/ssr-hoisted-script.test.js
+++ b/packages/astro/test/ssr-hoisted-script.test.js
@@ -25,7 +25,14 @@ describe('Hoisted inline scripts in SSR', () => {
describe('without base path', () => {
before(async () => {
- fixture = await loadFixture(defaultFixtureOptions);
+ fixture = await loadFixture({
+ ...defaultFixtureOptions,
+ outDir: './dist/inline-scripts-without-base-path',
+ build: {
+ client: './dist/inline-scripts-without-base-path/client',
+ server: './dist/inline-scripts-without-base-path/server',
+ },
+ });
await fixture.build();
});
@@ -42,6 +49,11 @@ describe('Hoisted inline scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/inline-scripts-with-base-path',
+ build: {
+ client: './dist/inline-scripts-with-base-path/client',
+ server: './dist/inline-scripts-with-base-path/server',
+ },
base,
});
await fixture.build();
@@ -63,6 +75,11 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/external-scripts-without-base-path',
+ build: {
+ client: './dist/external-scripts-without-base-path/client',
+ server: './dist/external-scripts-without-base-path/server',
+ },
vite: {
build: {
assetsInlineLimit: 0,
@@ -83,6 +100,11 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/external-scripts-with-base-path',
+ build: {
+ client: './dist/external-scripts-with-base-path/client',
+ server: './dist/external-scripts-with-base-path/server',
+ },
vite: {
build: {
assetsInlineLimit: 0,
@@ -104,14 +126,17 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/with-assets-prefix',
+ build: {
+ client: './dist/with-assets-prefix/client',
+ server: './dist/with-assets-prefix/server',
+ assetsPrefix: 'https://cdn.example.com',
+ },
vite: {
build: {
assetsInlineLimit: 0,
},
},
- build: {
- assetsPrefix: 'https://cdn.example.com',
- },
});
await fixture.build();
});
@@ -130,6 +155,11 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/with-rollup-output-file-names',
+ build: {
+ client: './dist/with-rollup-output-file-names/client',
+ server: './dist/with-rollup-output-file-names/server',
+ },
vite: {
build: {
assetsInlineLimit: 0,
@@ -157,6 +187,11 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/with-rollup-output-file-names-and-base',
+ build: {
+ client: './dist/with-rollup-output-file-names-and-base/client',
+ server: './dist/with-rollup-output-file-names-and-base/server',
+ },
vite: {
build: {
assetsInlineLimit: 0,
@@ -185,6 +220,12 @@ describe('Hoisted external scripts in SSR', () => {
before(async () => {
fixture = await loadFixture({
...defaultFixtureOptions,
+ outDir: './dist/with-rollup-output-file-names-and-assets-prefix',
+ build: {
+ client: './dist/with-rollup-output-file-names-and-assets-prefix/client',
+ server: './dist/with-rollup-output-file-names-and-assets-prefix/server',
+ assetsPrefix: 'https://cdn.example.com',
+ },
vite: {
build: {
assetsInlineLimit: 0,
@@ -197,9 +238,6 @@ describe('Hoisted external scripts in SSR', () => {
},
},
},
- build: {
- assetsPrefix: 'https://cdn.example.com',
- },
});
await fixture.build();
});
diff --git a/packages/astro/test/ssr-prerender-chunks.test.js b/packages/astro/test/ssr-prerender-chunks.test.js
new file mode 100644
index 000000000..7bd916814
--- /dev/null
+++ b/packages/astro/test/ssr-prerender-chunks.test.js
@@ -0,0 +1,21 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import { loadFixture } from './test-utils.js';
+
+describe('Chunks', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/ssr-prerender-chunks/',
+ });
+ await fixture.build();
+ });
+
+ it('does not have wrong chunks', async () => {
+ const content = await fixture.readFile('_worker.js/renderers.mjs');
+ const hasImportFromPrerender = !content.includes(`React } from './chunks/prerender`);
+ assert.ok(hasImportFromPrerender);
+ });
+});
diff --git a/packages/astro/test/ssr-prerender.test.js b/packages/astro/test/ssr-prerender.test.js
index 2bf53cefa..a1620d752 100644
--- a/packages/astro/test/ssr-prerender.test.js
+++ b/packages/astro/test/ssr-prerender.test.js
@@ -12,6 +12,11 @@ describe('SSR: prerender', () => {
fixture = await loadFixture({
root: './fixtures/ssr-prerender/',
output: 'server',
+ outDir: './dist/normal',
+ build: {
+ client: './dist/normal/client',
+ server: './dist/normal/server',
+ },
adapter: testAdapter(),
});
await fixture.build();
@@ -61,7 +66,11 @@ describe('SSR: prerender', () => {
});
});
-describe('Integrations can hook into the prerendering decision', () => {
+// NOTE: This test doesn't make sense as it relies on the fact that on the client build,
+// you can change the prerender state of pages from the SSR build, however, the client build
+// is not always guaranteed to run. If we want to support this feature, we may want to only allow
+// editing `route.prerender` on the `astro:build:done` hook.
+describe.skip('Integrations can hook into the prerendering decision', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -83,6 +92,11 @@ describe('Integrations can hook into the prerendering decision', () => {
fixture = await loadFixture({
root: './fixtures/ssr-prerender/',
output: 'server',
+ outDir: './dist/integration-prerender',
+ build: {
+ client: './dist/integration-prerender/client',
+ server: './dist/integration-prerender/server',
+ },
integrations: [testIntegration],
adapter: testAdapter(),
});
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index 65968a0bc..a4a4e22f8 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -216,6 +216,10 @@ export async function loadFixture(inlineConfig) {
});
}
},
+ loadAdapterEntryModule: async () => {
+ const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
+ return await import(url);
+ },
loadNodeAdapterHandler: async () => {
const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
const { handler } = await import(url);
diff --git a/packages/integrations/node/test/node-middleware.test.js b/packages/integrations/node/test/node-middleware.test.js
index fa496e871..d1b016a51 100644
--- a/packages/integrations/node/test/node-middleware.test.js
+++ b/packages/integrations/node/test/node-middleware.test.js
@@ -9,13 +9,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
*/
-async function load() {
- const mod = await import(
- `./fixtures/node-middleware/dist/server/entry.mjs?dropcache=${Date.now()}`
- );
- return mod;
-}
-
describe('behavior from middleware, standalone', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -29,7 +22,7 @@ describe('behavior from middleware, standalone', () => {
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -69,7 +62,7 @@ describe('behavior from middleware, middleware', () => {
adapter: nodejs({ mode: 'middleware' }),
});
await fixture.build();
- const { handler } = await load();
+ const { handler } = await fixture.loadAdapterEntryModule();
const app = express();
app.use(handler);
server = app.listen(8888);
diff --git a/packages/integrations/node/test/prerender-404-500.test.js b/packages/integrations/node/test/prerender-404-500.test.js
index afa23a151..2535fcb35 100644
--- a/packages/integrations/node/test/prerender-404-500.test.js
+++ b/packages/integrations/node/test/prerender-404-500.test.js
@@ -8,13 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
*/
-async function load() {
- const mod = await import(
- `./fixtures/prerender-404-500/dist/server/entry.mjs?dropcache=${Date.now()}`
- );
- return mod;
-}
-
describe('Prerender 404', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -32,10 +25,15 @@ describe('Prerender 404', () => {
base: '/some-base',
root: './fixtures/prerender-404-500/',
output: 'server',
+ outDir: './dist/server-with-base',
+ build: {
+ client: './dist/server-with-base/client',
+ server: './dist/server-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -117,10 +115,15 @@ describe('Prerender 404', () => {
site: 'https://test.info/',
root: './fixtures/prerender-404-500/',
output: 'server',
+ outDir: './dist/server-without-base',
+ build: {
+ client: './dist/server-without-base/client',
+ server: './dist/server-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -181,10 +184,15 @@ describe('Hybrid 404', () => {
base: '/some-base',
root: './fixtures/prerender-404-500/',
output: 'hybrid',
+ outDir: './dist/hybrid-with-base',
+ build: {
+ client: './dist/hybrid-with-base/client',
+ server: './dist/hybrid-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -238,10 +246,15 @@ describe('Hybrid 404', () => {
site: 'https://test.net/',
root: './fixtures/prerender-404-500/',
output: 'hybrid',
+ outDir: './dist/hybrid-without-base',
+ build: {
+ client: './dist/hybrid-without-base/client',
+ server: './dist/hybrid-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js
index 29080981a..d856d9d3e 100644
--- a/packages/integrations/node/test/prerender.test.js
+++ b/packages/integrations/node/test/prerender.test.js
@@ -8,10 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
*/
-async function load() {
- const mod = await import(`./fixtures/prerender/dist/server/entry.mjs?dropcache=${Date.now()}`);
- return mod;
-}
describe('Prerendering', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -25,10 +21,15 @@ describe('Prerendering', () => {
base: '/some-base',
root: './fixtures/prerender/',
output: 'server',
+ outDir: './dist/with-base',
+ build: {
+ client: './dist/with-base/client',
+ server: './dist/with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -94,10 +95,15 @@ describe('Prerendering', () => {
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'server',
+ outDir: './dist/without-base',
+ build: {
+ client: './dist/without-base/client',
+ server: './dist/without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -155,6 +161,11 @@ describe('Prerendering', () => {
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'server',
+ outDir: './dist/dev',
+ build: {
+ client: './dist/dev/client',
+ server: './dist/dev/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
devServer = await fixture.startDevServer();
@@ -197,10 +208,15 @@ describe('Hybrid rendering', () => {
base: '/some-base',
root: './fixtures/prerender/',
output: 'hybrid',
+ outDir: './dist/hybrid-with-base',
+ build: {
+ client: './dist/hybrid-with-base/client',
+ server: './dist/hybrid-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -264,10 +280,15 @@ describe('Hybrid rendering', () => {
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'hybrid',
+ outDir: './dist/hybrid-without-base',
+ build: {
+ client: './dist/hybrid-without-base/client',
+ server: './dist/hybrid-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -323,10 +344,15 @@ describe('Hybrid rendering', () => {
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'hybrid',
+ outDir: './dist/hybrid-shared-modules',
+ build: {
+ client: './dist/hybrid-shared-modules/client',
+ server: './dist/hybrid-shared-modules/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
diff --git a/packages/integrations/node/test/trailing-slash.test.js b/packages/integrations/node/test/trailing-slash.test.js
index ad91a2759..9ea8fcddd 100644
--- a/packages/integrations/node/test/trailing-slash.test.js
+++ b/packages/integrations/node/test/trailing-slash.test.js
@@ -8,13 +8,6 @@ import { loadFixture, waitServerListen } from './test-utils.js';
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
*/
-async function load() {
- const mod = await import(
- `./fixtures/trailing-slash/dist/server/entry.mjs?dropcache=${Date.now()}`
- );
- return mod;
-}
-
describe('Trailing slash', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@@ -30,10 +23,15 @@ describe('Trailing slash', () => {
base: '/some-base',
output: 'hybrid',
trailingSlash: 'always',
+ outDir: './dist/always-with-base',
+ build: {
+ client: './dist/always-with-base/client',
+ server: './dist/always-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -96,10 +94,15 @@ describe('Trailing slash', () => {
root: './fixtures/trailing-slash/',
output: 'hybrid',
trailingSlash: 'always',
+ outDir: './dist/always-without-base',
+ build: {
+ client: './dist/always-without-base/client',
+ server: './dist/always-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -165,10 +168,15 @@ describe('Trailing slash', () => {
base: '/some-base',
output: 'hybrid',
trailingSlash: 'never',
+ outDir: './dist/never-with-base',
+ build: {
+ client: './dist/never-with-base/client',
+ server: './dist/never-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -224,10 +232,15 @@ describe('Trailing slash', () => {
root: './fixtures/trailing-slash/',
output: 'hybrid',
trailingSlash: 'never',
+ outDir: './dist/never-without-base',
+ build: {
+ client: './dist/never-without-base/client',
+ server: './dist/never-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -286,10 +299,15 @@ describe('Trailing slash', () => {
base: '/some-base',
output: 'hybrid',
trailingSlash: 'ignore',
+ outDir: './dist/ignore-with-base',
+ build: {
+ client: './dist/ignore-with-base/client',
+ server: './dist/ignore-with-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
@@ -363,10 +381,15 @@ describe('Trailing slash', () => {
root: './fixtures/trailing-slash/',
output: 'hybrid',
trailingSlash: 'ignore',
+ outDir: './dist/ignore-without-base',
+ build: {
+ client: './dist/ignore-without-base/client',
+ server: './dist/ignore-without-base/server',
+ },
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
- const { startServer } = await load();
+ const { startServer } = await fixture.loadAdapterEntryModule();
let res = startServer();
server = res.server;
await waitServerListen(server.server);
diff --git a/packages/integrations/vercel/test/serverless-prerender.test.js b/packages/integrations/vercel/test/serverless-prerender.test.js
index 537eef77c..5b75418c9 100644
--- a/packages/integrations/vercel/test/serverless-prerender.test.js
+++ b/packages/integrations/vercel/test/serverless-prerender.test.js
@@ -20,7 +20,7 @@ describe('Serverless prerender', () => {
it('outDir is tree-shaken if not needed', async () => {
const [file] = await fixture.glob(
- '../.vercel/output/functions/_render.func/packages/integrations/vercel/test/fixtures/serverless-prerender/.vercel/output/_functions/chunks/pages/generic_*.mjs'
+ '../.vercel/output/functions/_render.func/packages/integrations/vercel/test/fixtures/serverless-prerender/.vercel/output/_functions/pages/_image.astro.mjs'
);
const contents = await fixture.readFile(file);
assert.ok(!contents.includes('const outDir ='), "outDir is tree-shaken if it's not imported");
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1194721fb..b5aadde6d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3781,6 +3781,32 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/test/fixtures/ssr-prerender-chunks:
+ dependencies:
+ '@astrojs/react':
+ specifier: workspace:*
+ version: link:../../../../integrations/react
+ '@test/ssr-prerender-chunks-test-adapter':
+ specifier: link:./deps/test-adapter
+ version: link:deps/test-adapter
+ '@types/react':
+ specifier: ^18.2.75
+ version: 18.3.3
+ '@types/react-dom':
+ specifier: ^18.2.24
+ version: 18.3.0
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+
+ packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter: {}
+
packages/astro/test/fixtures/ssr-prerender-get-static-paths:
dependencies:
astro: