summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Emanuele Stoppa <my.burning@gmail.com> 2023-06-21 13:32:20 +0100
committerGravatar GitHub <noreply@github.com> 2023-06-21 13:32:20 +0100
commit459b5bd05f562238f7250520efe3cf0fa156bb45 (patch)
tree1c1aa1bb0fd6aefc8ac4013b137cf900fa47180c
parentc8bccb47d373a1968c22586a976b578530e258b0 (diff)
downloadastro-459b5bd05f562238f7250520efe3cf0fa156bb45.tar.gz
astro-459b5bd05f562238f7250520efe3cf0fa156bb45.tar.zst
astro-459b5bd05f562238f7250520efe3cf0fa156bb45.zip
feat: SSR `split` mode (#7220)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
-rw-r--r--.changeset/wet-readers-join.md31
-rw-r--r--packages/astro/src/@types/astro.ts33
-rw-r--r--packages/astro/src/core/app/index.ts27
-rw-r--r--packages/astro/src/core/app/types.ts10
-rw-r--r--packages/astro/src/core/build/internal.ts13
-rw-r--r--packages/astro/src/core/build/plugins/index.ts3
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts17
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts361
-rw-r--r--packages/astro/src/core/build/plugins/util.ts31
-rw-r--r--packages/astro/src/core/build/static-build.ts76
-rw-r--r--packages/astro/src/core/config/schema.ts5
-rw-r--r--packages/astro/src/core/errors/errors-data.ts11
-rw-r--r--packages/astro/src/integrations/index.ts4
-rw-r--r--packages/astro/test/fixtures/ssr-request/astro.config.mjs8
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs7
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/package.json8
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro18
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro17
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md1
-rw-r--r--packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro17
-rw-r--r--packages/astro/test/ssr-split-manifest.test.js49
-rw-r--r--packages/astro/test/test-adapter.js16
-rw-r--r--packages/astro/test/test-utils.js12
-rw-r--r--pnpm-lock.yaml60
24 files changed, 702 insertions, 133 deletions
diff --git a/.changeset/wet-readers-join.md b/.changeset/wet-readers-join.md
new file mode 100644
index 000000000..abf77e6c9
--- /dev/null
+++ b/.changeset/wet-readers-join.md
@@ -0,0 +1,31 @@
+---
+'astro': minor
+---
+
+Shipped a new SSR build configuration mode: `split`.
+When enabled, Astro will "split" the single `entry.mjs` file and instead emit a separate file to render each individual page during the build process.
+
+These files will be emitted inside `dist/pages`, mirroring the directory structure of your page files in `src/pages/`, for example:
+
+```
+├── pages
+│ ├── blog
+│ │ ├── entry._slug_.astro.mjs
+│ │ └── entry.about.astro.mjs
+│ └── entry.index.astro.mjs
+```
+
+To enable, set `build.split: true` in your Astro config:
+
+```js
+// src/astro.config.mjs
+export default defineConfig({
+ output: "server",
+ adapter: node({
+ mode: "standalone"
+ }),
+ build: {
+ split: true
+ }
+})
+``` \ No newline at end of file
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index fea28d092..1a5679f03 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -838,6 +838,30 @@ export interface AstroUserConfig {
* ```
*/
inlineStylesheets?: 'always' | 'auto' | 'never';
+
+ /**
+ * @docs
+ * @name build.split
+ * @type {boolean}
+ * @default {false}
+ * @version 2.7.0
+ * @description
+ * Defines how the SSR code should be bundled when built.
+ *
+ * When `split` is `true`, Astro will emit a file for each page.
+ * Each file emitted will render only one page. The pages will be emitted
+ * inside a `dist/pages/` directory, and the emitted files will keep the same file paths
+ * of the `src/pages` directory.
+ *
+ * ```js
+ * {
+ * build: {
+ * split: true
+ * }
+ * }
+ * ```
+ */
+ split?: boolean;
};
/**
@@ -1824,7 +1848,14 @@ export interface AstroIntegration {
'astro:server:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise<void>;
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
'astro:server:done'?: () => void | Promise<void>;
- 'astro:build:ssr'?: (options: { manifest: SerializedSSRManifest }) => void | Promise<void>;
+ 'astro:build:ssr'?: (options: {
+ manifest: SerializedSSRManifest;
+ /**
+ * This maps a {@link RouteData} to an {@link URL}, this URL represents
+ * the physical file you should import.
+ */
+ entryPoints: Map<RouteData, URL>;
+ }) => void | Promise<void>;
'astro:build:start'?: () => void | Promise<void>;
'astro:build:setup'?: (options: {
vite: vite.InlineConfig;
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index ae83b3016..00b93cd09 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -4,9 +4,9 @@ import type {
MiddlewareResponseHandler,
RouteData,
SSRElement,
+ SSRManifest,
} from '../../@types/astro';
-import type { RouteInfo, SSRManifest as Manifest } from './types';
-
+import type { RouteInfo } from './types';
import mime from 'mime';
import type { SinglePageBuiltModule } from '../build/types';
import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js';
@@ -41,7 +41,7 @@ export interface MatchOptions {
export class App {
#env: Environment;
- #manifest: Manifest;
+ #manifest: SSRManifest;
#manifestData: ManifestData;
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
#encoder = new TextEncoder();
@@ -52,7 +52,7 @@ export class App {
#base: string;
#baseWithoutTrailingSlash: string;
- constructor(manifest: Manifest, streaming = true) {
+ constructor(manifest: SSRManifest, streaming = true) {
this.#manifest = manifest;
this.#manifestData = {
routes: manifest.routes.map((route) => route.routeData),
@@ -175,14 +175,23 @@ export class App {
if (route.type === 'redirect') {
return RedirectSinglePageBuiltModule;
} else {
- const importComponentInstance = this.#manifest.pageMap.get(route.component);
- if (!importComponentInstance) {
+ if (this.#manifest.pageMap) {
+ const importComponentInstance = this.#manifest.pageMap.get(route.component);
+ if (!importComponentInstance) {
+ throw new Error(
+ `Unexpectedly unable to find a component instance for route ${route.route}`
+ );
+ }
+ const pageModule = await importComponentInstance();
+ return pageModule;
+ } else if (this.#manifest.pageModule) {
+ const importComponentInstance = this.#manifest.pageModule;
+ return importComponentInstance;
+ } else {
throw new Error(
- `Unexpectedly unable to find a component instance for route ${route.route}`
+ "Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
);
}
- const built = await importComponentInstance();
- return built;
}
}
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index 1283f1a10..9af15bf50 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -30,16 +30,16 @@ export interface RouteInfo {
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
routeData: SerializedRouteData;
};
-type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
-export interface SSRManifest {
+export type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
+
+export type SSRManifest = {
adapterName: string;
routes: RouteInfo[];
site?: string;
base?: string;
assetsPrefix?: string;
markdown: MarkdownRenderingOptions;
- pageMap: Map<ComponentPath, ImportComponentInstance>;
renderers: SSRLoadedRenderer[];
/**
* Map of directive name (e.g. `load`) to the directive script code
@@ -48,7 +48,9 @@ export interface SSRManifest {
entryModules: Record<string, string>;
assets: Set<string>;
componentMetadata: SSRResult['componentMetadata'];
-}
+ pageModule?: SinglePageBuiltModule;
+ pageMap?: Map<ComponentPath, ImportComponentInstance>;
+};
export type SerializedSSRManifest = Omit<
SSRManifest,
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 4cf40cb9a..7a13e4d08 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -1,14 +1,11 @@
import type { Rollup } from 'vite';
-import type { SSRResult } from '../../@types/astro';
+import type { RouteData, SSRResult } from '../../@types/astro';
import type { PageOptions } from '../../vite-plugin-astro/types';
import { prependForwardSlash, removeFileExtension } from '../path.js';
import { viteID } from '../util.js';
-import {
- ASTRO_PAGE_EXTENSION_POST_PATTERN,
- ASTRO_PAGE_MODULE_ID,
- getVirtualModulePageIdFromPath,
-} from './plugins/plugin-pages.js';
+import { ASTRO_PAGE_MODULE_ID, getVirtualModulePageIdFromPath } from './plugins/plugin-pages.js';
import type { PageBuildData, StylesheetAsset, ViteID } from './types';
+import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
export interface BuildInternals {
/**
@@ -84,6 +81,8 @@ export interface BuildInternals {
staticFiles: Set<string>;
// The SSR entry chunk. Kept in internals to share between ssr/client build steps
ssrEntryChunk?: Rollup.OutputChunk;
+ entryPoints: Map<RouteData, URL>;
+ ssrSplitEntryChunks: Map<string, Rollup.OutputChunk>;
componentMetadata: SSRResult['componentMetadata'];
}
@@ -114,6 +113,8 @@ export function createBuildInternals(): BuildInternals {
discoveredScripts: new Set(),
staticFiles: new Set(),
componentMetadata: new Map(),
+ ssrSplitEntryChunks: new Map(),
+ entryPoints: new Map(),
};
}
diff --git a/packages/astro/src/core/build/plugins/index.ts b/packages/astro/src/core/build/plugins/index.ts
index f6fcacfb1..4563bb696 100644
--- a/packages/astro/src/core/build/plugins/index.ts
+++ b/packages/astro/src/core/build/plugins/index.ts
@@ -11,7 +11,7 @@ import { pluginMiddleware } from './plugin-middleware.js';
import { pluginPages } from './plugin-pages.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginRenderers } from './plugin-renderers.js';
-import { pluginSSR } from './plugin-ssr.js';
+import { pluginSSR, pluginSSRSplit } from './plugin-ssr.js';
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
register(pluginComponentEntry(internals));
@@ -27,4 +27,5 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(astroConfigBuildPlugin(options, internals));
register(pluginHoistedScripts(options, internals));
register(pluginSSR(options, internals));
+ register(pluginSSRSplit(options, internals));
}
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index c6f89a558..ca224d851 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -1,4 +1,4 @@
-import { extname } from 'node:path';
+import { getPathFromVirtualModulePageName, ASTRO_PAGE_EXTENSION_POST_PATTERN } from './util.js';
import type { Plugin as VitePlugin } from 'vite';
import { routeIsRedirect } from '../../redirects/index.js';
import { addRollupInput } from '../add-rollup-input.js';
@@ -7,12 +7,10 @@ import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
+import { extname } from 'node:path';
export const ASTRO_PAGE_MODULE_ID = '@astro-page:';
-export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0@astro-page:';
-
-// This is an arbitrary string that we are going to replace the dot of the extension
-export const ASTRO_PAGE_EXTENSION_POST_PATTERN = '@_@';
+export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0' + ASTRO_PAGE_MODULE_ID;
/**
* 1. We add a fixed prefix, which is used as virtual module naming convention;
@@ -64,13 +62,8 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
const imports: string[] = [];
const exports: string[] = [];
-
- // we remove the module name prefix from id, this will result into a string that will start with "src/..."
- const pageName = id.slice(ASTRO_PAGE_RESOLVED_MODULE_ID.length);
- // We replaced the `.` of the extension with ASTRO_PAGE_EXTENSION_POST_PATTERN, let's replace it back
- const pageData = internals.pagesByComponent.get(
- `${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
- );
+ const pageName = getPathFromVirtualModulePageName(ASTRO_PAGE_RESOLVED_MODULE_ID, id);
+ const pageData = internals.pagesByComponent.get(pageName);
if (pageData) {
const resolvedPage = await this.resolve(pageData.moduleSpecifier);
if (resolvedPage) {
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index 3c2825e4c..f9ba91909 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -1,7 +1,7 @@
import glob from 'fast-glob';
-import { fileURLToPath } from 'url';
+import { fileURLToPath, pathToFileURL } from 'node:url';
import type { Plugin as VitePlugin } from 'vite';
-import type { AstroAdapter } from '../../../@types/astro';
+import type { AstroAdapter, AstroConfig } from '../../../@types/astro';
import { runHookBuildSsr } from '../../../integrations/index.js';
import { isServerLikeOutput } from '../../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
@@ -13,9 +13,11 @@ import { addRollupInput } from '../add-rollup-input.js';
import { getOutFile, getOutFolder } from '../common.js';
import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin';
-import type { StaticBuildOptions } from '../types';
-import { getVirtualModulePageNameFromPath } from './plugin-pages.js';
+import type { OutputChunk, StaticBuildOptions } from '../types';
+import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
+import { ASTRO_PAGE_MODULE_ID } from './plugin-pages.js';
+import { join } from 'node:path';
export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
@@ -28,7 +30,7 @@ function vitePluginSSR(
options: StaticBuildOptions
): VitePlugin {
return {
- name: '@astrojs/vite-plugin-astro-ssr',
+ name: '@astrojs/vite-plugin-astro-ssr-server',
enforce: 'post',
options(opts) {
return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
@@ -54,7 +56,7 @@ function vitePluginSSR(
if (routeIsRedirect(pageData.route)) {
continue;
}
- const virtualModuleName = getVirtualModulePageNameFromPath(path);
+ const virtualModuleName = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path);
let module = await this.resolve(virtualModuleName);
if (module) {
const variable = `_page${i}`;
@@ -71,12 +73,226 @@ function vitePluginSSR(
contents.push(`const pageMap = new Map([${pageMap.join(',')}]);`);
exports.push(`export { pageMap }`);
- const content = `import * as adapter from '${adapter.serverEntrypoint}';
+ const ssrCode = generateSSRCode(options.settings.config, adapter);
+ imports.push(...ssrCode.imports);
+ contents.push(...ssrCode.contents);
+ return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
+ }
+ return void 0;
+ },
+ async generateBundle(_opts, bundle) {
+ // Add assets from this SSR chunk as well.
+ for (const [_chunkName, chunk] of Object.entries(bundle)) {
+ if (chunk.type === 'asset') {
+ internals.staticFiles.add(chunk.fileName);
+ }
+ }
+
+ for (const [chunkName, chunk] of Object.entries(bundle)) {
+ if (chunk.type === 'asset') {
+ continue;
+ }
+ if (chunk.modules[RESOLVED_SSR_VIRTUAL_MODULE_ID]) {
+ internals.ssrEntryChunk = chunk;
+ delete bundle[chunkName];
+ }
+ }
+ },
+ };
+}
+
+export function pluginSSR(
+ options: StaticBuildOptions,
+ internals: BuildInternals
+): AstroBuildPlugin {
+ const ssr = isServerLikeOutput(options.settings.config);
+ return {
+ build: 'ssr',
+ hooks: {
+ 'build:before': () => {
+ let vitePlugin =
+ ssr && !options.settings.config.build.split
+ ? vitePluginSSR(internals, options.settings.adapter!, options)
+ : undefined;
+
+ return {
+ enforce: 'after-user-plugins',
+ vitePlugin,
+ };
+ },
+ 'build:post': async ({ mutate }) => {
+ if (!ssr) {
+ return;
+ }
+
+ if (options.settings.config.build.split) {
+ return;
+ }
+
+ if (!internals.ssrEntryChunk) {
+ throw new Error(`Did not generate an entry chunk for SSR`);
+ }
+ // Mutate the filename
+ internals.ssrEntryChunk.fileName = options.settings.config.build.serverEntry;
+
+ const manifest = await createManifest(options, internals);
+ await runHookBuildSsr({
+ config: options.settings.config,
+ manifest,
+ logging: options.logging,
+ entryPoints: internals.entryPoints,
+ });
+ const code = injectManifest(manifest, internals.ssrEntryChunk);
+ mutate(internals.ssrEntryChunk, 'server', code);
+ },
+ },
+ };
+}
+
+export const SPLIT_MODULE_ID = '@astro-page-split:';
+export const RESOLVED_SPLIT_MODULE_ID = '\0@astro-page-split:';
+
+function vitePluginSSRSplit(
+ internals: BuildInternals,
+ adapter: AstroAdapter,
+ options: StaticBuildOptions
+): VitePlugin {
+ return {
+ name: '@astrojs/vite-plugin-astro-ssr-split',
+ enforce: 'post',
+ options(opts) {
+ if (options.settings.config.build.split) {
+ const inputs: Set<string> = new Set();
+
+ for (const path of Object.keys(options.allPages)) {
+ inputs.add(getVirtualModulePageNameFromPath(SPLIT_MODULE_ID, path));
+ }
+
+ return addRollupInput(opts, Array.from(inputs));
+ }
+ },
+ resolveId(id) {
+ if (id.startsWith(SPLIT_MODULE_ID)) {
+ return '\0' + id;
+ }
+ },
+ async load(id) {
+ if (id.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
+ const {
+ settings: { config },
+ allPages,
+ } = options;
+ const imports: string[] = [];
+ const contents: string[] = [];
+ const exports: string[] = [];
+
+ const path = getPathFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, id);
+ const virtualModuleName = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path);
+ let module = await this.resolve(virtualModuleName);
+ if (module) {
+ // we need to use the non-resolved ID in order to resolve correctly the virtual module
+ imports.push(`import * as pageModule from "${virtualModuleName}";`);
+ }
+
+ const ssrCode = generateSSRCode(options.settings.config, adapter);
+ imports.push(...ssrCode.imports);
+ contents.push(...ssrCode.contents);
+
+ return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
+ }
+ return void 0;
+ },
+ async generateBundle(_opts, bundle) {
+ // Add assets from this SSR chunk as well.
+ for (const [_chunkName, chunk] of Object.entries(bundle)) {
+ if (chunk.type === 'asset') {
+ internals.staticFiles.add(chunk.fileName);
+ }
+ }
+
+ for (const [chunkName, chunk] of Object.entries(bundle)) {
+ if (chunk.type === 'asset') {
+ continue;
+ }
+ let shouldDeleteBundle = false;
+ for (const moduleKey of Object.keys(chunk.modules)) {
+ if (moduleKey.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
+ internals.ssrSplitEntryChunks.set(moduleKey, chunk);
+ storeEntryPoint(moduleKey, options, internals, chunk.fileName);
+ shouldDeleteBundle = true;
+ }
+ }
+ if (shouldDeleteBundle) {
+ delete bundle[chunkName];
+ }
+ }
+ },
+ };
+}
+
+export function pluginSSRSplit(
+ options: StaticBuildOptions,
+ internals: BuildInternals
+): AstroBuildPlugin {
+ const ssr = isServerLikeOutput(options.settings.config);
+ return {
+ build: 'ssr',
+ hooks: {
+ 'build:before': () => {
+ let vitePlugin =
+ ssr && options.settings.config.build.split
+ ? vitePluginSSRSplit(internals, options.settings.adapter!, options)
+ : undefined;
+
+ return {
+ enforce: 'after-user-plugins',
+ vitePlugin,
+ };
+ },
+ 'build:post': async ({ mutate }) => {
+ if (!ssr) {
+ return;
+ }
+ if (!options.settings.config.build.split) {
+ return;
+ }
+
+ if (internals.ssrSplitEntryChunks.size === 0) {
+ throw new Error(`Did not generate an entry chunk for SSR serverless`);
+ }
+
+ const manifest = await createManifest(options, internals);
+ await runHookBuildSsr({
+ config: options.settings.config,
+ manifest,
+ logging: options.logging,
+ entryPoints: internals.entryPoints,
+ });
+ for (const [moduleName, chunk] of internals.ssrSplitEntryChunks) {
+ const code = injectManifest(manifest, chunk);
+ mutate(chunk, 'server', code);
+ }
+ },
+ },
+ };
+}
+
+function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) {
+ const imports: string[] = [];
+ const contents: string[] = [];
+ let pageMap;
+ if (config.build.split) {
+ pageMap = 'pageModule';
+ } else {
+ pageMap = 'pageMap';
+ }
+
+ contents.push(`import * as adapter from '${adapter.serverEntrypoint}';
import { renderers } from '${RENDERERS_MODULE_ID}';
import { deserializeManifest as _deserializeManifest } from 'astro/app';
import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest';
const _manifest = Object.assign(_deserializeManifest('${manifestReplace}'), {
- pageMap,
+ ${pageMap},
renderers,
});
_privateSetManifestDontUseThis(_manifest);
@@ -101,41 +317,45 @@ export { _default as default };`;
const _start = 'start';
if(_start in adapter) {
adapter[_start](_manifest, _args);
-}`;
- return `${imports.join('\n')}${contents.join('\n')}${content}${exports.join('\n')}`;
- }
- return void 0;
- },
- async generateBundle(_opts, bundle) {
- // Add assets from this SSR chunk as well.
- for (const [_chunkName, chunk] of Object.entries(bundle)) {
- if (chunk.type === 'asset') {
- internals.staticFiles.add(chunk.fileName);
- }
- }
-
- for (const [chunkName, chunk] of Object.entries(bundle)) {
- if (chunk.type === 'asset') {
- continue;
- }
- if (chunk.modules[RESOLVED_SSR_VIRTUAL_MODULE_ID]) {
- internals.ssrEntryChunk = chunk;
- delete bundle[chunkName];
- }
- }
- },
+}`);
+ return {
+ imports,
+ contents,
};
}
-export async function injectManifest(buildOpts: StaticBuildOptions, internals: BuildInternals) {
- if (!internals.ssrEntryChunk) {
- throw new Error(`Did not generate an entry chunk for SSR`);
+/**
+ * It injects the manifest in the given output rollup chunk. It returns the new emitted code
+ * @param buildOpts
+ * @param internals
+ * @param chunk
+ */
+export function injectManifest(manifest: SerializedSSRManifest, chunk: Readonly<OutputChunk>) {
+ const code = chunk.code;
+
+ return code.replace(replaceExp, () => {
+ return JSON.stringify(manifest);
+ });
+}
+
+export async function createManifest(
+ buildOpts: StaticBuildOptions,
+ internals: BuildInternals
+): Promise<SerializedSSRManifest> {
+ if (buildOpts.settings.config.build.split) {
+ if (internals.ssrSplitEntryChunks.size === 0) {
+ throw new Error(`Did not generate an entry chunk for SSR in serverless mode`);
+ }
+ } else {
+ if (!internals.ssrEntryChunk) {
+ throw new Error(`Did not generate an entry chunk for SSR`);
+ }
}
// Add assets from the client build.
const clientStatics = new Set(
await glob('**/*', {
- cwd: fileURLToPath(buildOpts.buildConfig.client),
+ cwd: fileURLToPath(buildOpts.settings.config.build.client),
})
);
for (const file of clientStatics) {
@@ -143,19 +363,29 @@ export async function injectManifest(buildOpts: StaticBuildOptions, internals: B
}
const staticFiles = internals.staticFiles;
- const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles));
- await runHookBuildSsr({
- config: buildOpts.settings.config,
- manifest,
- logging: buildOpts.logging,
- });
-
- const chunk = internals.ssrEntryChunk;
- const code = chunk.code;
+ return buildManifest(buildOpts, internals, Array.from(staticFiles));
+}
- return code.replace(replaceExp, () => {
- return JSON.stringify(manifest);
- });
+/**
+ * Because we delete the bundle from rollup at the end of this function,
+ * we can't use `writeBundle` hook to get the final file name of the entry point written on disk.
+ * We use this hook instead.
+ *
+ * We retrieve the {@link RouteData} that belongs the current moduleKey
+ */
+function storeEntryPoint(
+ moduleKey: string,
+ options: StaticBuildOptions,
+ internals: BuildInternals,
+ fileName: string
+) {
+ const componentPath = getPathFromVirtualModulePageName(RESOLVED_SPLIT_MODULE_ID, moduleKey);
+ for (const [page, pageData] of Object.entries(options.allPages)) {
+ if (componentPath == page) {
+ const publicPath = fileURLToPath(options.settings.config.outDir);
+ internals.entryPoints.set(pageData.route, pathToFileURL(join(publicPath, fileName)));
+ }
+ }
}
function buildManifest(
@@ -254,7 +484,6 @@ function buildManifest(
base: settings.config.base,
assetsPrefix: settings.config.build.assetsPrefix,
markdown: settings.config.markdown,
- pageMap: null as any,
componentMetadata: Array.from(internals.componentMetadata),
renderers: [],
clientDirectives: Array.from(settings.clientDirectives),
@@ -264,39 +493,3 @@ function buildManifest(
return ssrManifest;
}
-
-export function pluginSSR(
- options: StaticBuildOptions,
- internals: BuildInternals
-): AstroBuildPlugin {
- const ssr = isServerLikeOutput(options.settings.config);
- return {
- build: 'ssr',
- hooks: {
- 'build:before': () => {
- let vitePlugin = ssr
- ? vitePluginSSR(internals, options.settings.adapter!, options)
- : undefined;
-
- return {
- enforce: 'after-user-plugins',
- vitePlugin,
- };
- },
- 'build:post': async ({ mutate }) => {
- if (!ssr) {
- return;
- }
-
- if (!internals.ssrEntryChunk) {
- throw new Error(`Did not generate an entry chunk for SSR`);
- }
- // Mutate the filename
- internals.ssrEntryChunk.fileName = options.settings.config.build.serverEntry;
-
- const code = await injectManifest(options, internals);
- mutate(internals.ssrEntryChunk, 'server', code);
- },
- },
- };
-}
diff --git a/packages/astro/src/core/build/plugins/util.ts b/packages/astro/src/core/build/plugins/util.ts
index 50f5e0705..1fbf0fcdc 100644
--- a/packages/astro/src/core/build/plugins/util.ts
+++ b/packages/astro/src/core/build/plugins/util.ts
@@ -1,4 +1,5 @@
import type { Plugin as VitePlugin } from 'vite';
+import { extname } from 'node:path';
// eslint-disable-next-line @typescript-eslint/ban-types
type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;
@@ -38,3 +39,33 @@ export function extendManualChunks(outputOptions: OutputOptions, hooks: ExtendMa
return null;
};
}
+
+// This is an arbitrary string that we are going to replace the dot of the extension
+export const ASTRO_PAGE_EXTENSION_POST_PATTERN = '@_@';
+
+/**
+ * 1. We add a fixed prefix, which is used as virtual module naming convention;
+ * 2. We replace the dot that belongs extension with an arbitrary string.
+ *
+ * @param virtualModulePrefix
+ * @param path
+ */
+export function getVirtualModulePageNameFromPath(virtualModulePrefix: string, path: string) {
+ // we mask the extension, so this virtual file
+ // so rollup won't trigger other plugins in the process
+ const extension = extname(path);
+ return `${virtualModulePrefix}${path.replace(
+ extension,
+ extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN)
+ )}`;
+}
+
+/**
+ *
+ * @param virtualModulePrefix
+ * @param id
+ */
+export function getPathFromVirtualModulePageName(virtualModulePrefix: string, id: string) {
+ const pageName = id.slice(virtualModulePrefix.length);
+ return pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.');
+}
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 327ef5f16..e783c28b1 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -25,14 +25,14 @@ import { trackPageData } from './internal.js';
import { createPluginContainer, type AstroBuildPluginContainer } from './plugin.js';
import { registerAllPlugins } from './plugins/index.js';
import { MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
-import {
- ASTRO_PAGE_EXTENSION_POST_PATTERN,
- ASTRO_PAGE_RESOLVED_MODULE_ID,
-} from './plugins/plugin-pages.js';
+import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js';
-import { SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
+import { RESOLVED_SPLIT_MODULE_ID, SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
import type { AllPagesData, PageBuildData, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
+import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
+import { extname } from 'node:path';
+import type { RouteData } from '../../@types/astro';
export async function viteBuild(opts: StaticBuildOptions) {
const { allPages, settings } = opts;
@@ -147,7 +147,7 @@ async function ssrBuild(
const { allPages, settings, viteConfig } = opts;
const ssr = isServerLikeOutput(settings.config);
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
-
+ const routes = Object.values(allPages).map((pd) => pd.route);
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);
const viteBuildConfig: vite.InlineConfig = {
@@ -176,7 +176,13 @@ async function ssrBuild(
...viteConfig.build?.rollupOptions?.output,
entryFileNames(chunkInfo) {
if (chunkInfo.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
- return makeAstroPageEntryPointFileName(chunkInfo.facadeModuleId, allPages);
+ return makeAstroPageEntryPointFileName(
+ ASTRO_PAGE_RESOLVED_MODULE_ID,
+ chunkInfo.facadeModuleId,
+ routes
+ );
+ } else if (chunkInfo.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
+ return makeSplitEntryPointFileName(chunkInfo.facadeModuleId, routes);
} else if (chunkInfo.facadeModuleId === MIDDLEWARE_MODULE_ID) {
return 'middleware.mjs';
} else if (chunkInfo.facadeModuleId === SSR_VIRTUAL_MODULE_ID) {
@@ -422,19 +428,65 @@ async function ssrMoveAssets(opts: StaticBuildOptions) {
* Input: `@astro-page:../node_modules/my-dep/injected@_@astro`
* Output: `pages/injected.mjs`
*
- * 1. We clean the `facadeModuleId` by removing the `@astro-page:` prefix and `@_@` suffix
+ * 1. We clean the `facadeModuleId` by removing the `ASTRO_PAGE_MODULE_ID` prefix and `ASTRO_PAGE_EXTENSION_POST_PATTERN`.
* 2. We find the matching route pattern in the manifest (or fallback to the cleaned module id)
* 3. We replace square brackets with underscore (`[slug]` => `_slug_`) and `...` with `` (`[...slug]` => `_---slug_`).
* 4. We append the `.mjs` extension, so the file will always be an ESM module
*
+ * @param prefix string
* @param facadeModuleId string
* @param pages AllPagesData
*/
-function makeAstroPageEntryPointFileName(facadeModuleId: string, pages: AllPagesData) {
+export function makeAstroPageEntryPointFileName(
+ prefix: string,
+ facadeModuleId: string,
+ routes: RouteData[]
+) {
const pageModuleId = facadeModuleId
- .replace(ASTRO_PAGE_RESOLVED_MODULE_ID, '')
+ .replace(prefix, '')
.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.');
- let name = pages[pageModuleId]?.route?.route ?? pageModuleId;
+ let route = routes.find((routeData) => {
+ return routeData.route === pageModuleId;
+ });
+ let name = pageModuleId;
+ if (route) {
+ name = route.route;
+ }
if (name.endsWith('/')) name += 'index';
- return `pages${name.replaceAll('[', '_').replaceAll(']', '_').replaceAll('...', '---')}.mjs`;
+ const fileName = `${name.replaceAll('[', '_').replaceAll(']', '_').replaceAll('...', '---')}.mjs`;
+ if (name.startsWith('..')) {
+ return `pages${fileName}`;
+ }
+ return fileName;
+}
+
+/**
+ * The `facadeModuleId` has a shape like: \0@astro-serverless-page:src/pages/index@_@astro.
+ *
+ * 1. We call `makeAstroPageEntryPointFileName` which normalise its name, making it like a file path
+ * 2. We split the file path using the file system separator and attempt to retrieve the last entry
+ * 3. The last entry should be the file
+ * 4. We prepend the file name with `entry.`
+ * 5. We built the file path again, using the new entry built in the previous step
+ *
+ * @param facadeModuleId
+ * @param opts
+ */
+export function makeSplitEntryPointFileName(facadeModuleId: string, routes: RouteData[]) {
+ const filePath = `${makeAstroPageEntryPointFileName(
+ RESOLVED_SPLIT_MODULE_ID,
+ facadeModuleId,
+ routes
+ )}`;
+
+ const pathComponents = filePath.split(path.sep);
+ const lastPathComponent = pathComponents.pop();
+ if (lastPathComponent) {
+ const extension = extname(lastPathComponent);
+ if (extension.length > 0) {
+ const newFileName = `entry.${lastPathComponent}`;
+ return [...pathComponents, newFileName].join(path.sep);
+ }
+ }
+ return filePath;
}
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 3edabc5d5..99ef653d0 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -24,6 +24,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
serverEntry: 'entry.mjs',
redirects: true,
inlineStylesheets: 'never',
+ split: false,
},
compressHTML: false,
server: {
@@ -120,6 +121,8 @@ export const AstroConfigSchema = z.object({
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
+
+ split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
})
.optional()
.default({}),
@@ -279,6 +282,8 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
+
+ split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
})
.optional()
.default({}),
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index a47087336..b8babd91a 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -819,6 +819,17 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
},
/**
* @docs
+ * @description
+ * Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error.
+ */
+ FailedToFindPageMapSSR: {
+ title: "Astro couldn't find the correct page to render",
+ code: 4003,
+ message:
+ "Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error. Please file an issue.",
+ },
+ /**
+ * @docs
* @kind heading
* @name CSS Errors
*/
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index 53a75fbea..ae1ff9761 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -309,16 +309,18 @@ export async function runHookBuildSsr({
config,
manifest,
logging,
+ entryPoints,
}: {
config: AstroConfig;
manifest: SerializedSSRManifest;
logging: LogOptions;
+ entryPoints: Map<RouteData, URL>;
}) {
for (const integration of config.integrations) {
if (integration?.hooks?.['astro:build:ssr']) {
await withTakingALongTimeMsg({
name: integration.name,
- hookResult: integration.hooks['astro:build:ssr']({ manifest }),
+ hookResult: integration.hooks['astro:build:ssr']({ manifest, entryPoints }),
logging,
});
}
diff --git a/packages/astro/test/fixtures/ssr-request/astro.config.mjs b/packages/astro/test/fixtures/ssr-request/astro.config.mjs
new file mode 100644
index 000000000..3bd2a19a3
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-request/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ build: {
+ split: false
+ }
+});
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs b/packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs
new file mode 100644
index 000000000..171de39d9
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs
@@ -0,0 +1,7 @@
+import { defineConfig } from 'astro/config';
+export default defineConfig({
+ build: {
+ split: true
+ },
+ output: "server"
+}) \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/package.json b/packages/astro/test/fixtures/ssr-split-manifest/package.json
new file mode 100644
index 000000000..b980cc8a7
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/ssr-split-manifest",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro
new file mode 100644
index 000000000..8bac75eb9
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro
@@ -0,0 +1,18 @@
+---
+export async function getStaticPaths() {
+ return [
+ {
+ params: { page: 1 },
+ },
+ {
+ params: { page: 2 },
+ },
+ {
+ params: { page: 3 }
+ }
+ ]
+};
+---
+
+<html>
+</html> \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro
new file mode 100644
index 000000000..f189e711c
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro
@@ -0,0 +1,17 @@
+---
+import { manifest } from 'astro:ssr-manifest';
+---
+<html>
+ <head>
+ <title>Testing</title>
+ <style>
+ body {
+ background: green;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Testing</h1>
+ <div id="assets" set:html={JSON.stringify([...manifest.assets])}></div>
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md
new file mode 100644
index 000000000..8a38d58c1
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md
@@ -0,0 +1 @@
+# Title \ No newline at end of file
diff --git a/packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro
new file mode 100644
index 000000000..06d949d47
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro
@@ -0,0 +1,17 @@
+---
+import { manifest } from 'astro:ssr-manifest';
+---
+<html>
+<head>
+ <title>Testing</title>
+ <style>
+ body {
+ background: green;
+ }
+ </style>
+</head>
+<body>
+<h1>Testing</h1>
+<div id="assets" set:html={JSON.stringify([...manifest.assets])}></div>
+</body>
+</html> \ No newline at end of file
diff --git a/packages/astro/test/ssr-split-manifest.test.js b/packages/astro/test/ssr-split-manifest.test.js
new file mode 100644
index 000000000..5005f6279
--- /dev/null
+++ b/packages/astro/test/ssr-split-manifest.test.js
@@ -0,0 +1,49 @@
+import { expect } from 'chai';
+import { loadFixture } from './test-utils.js';
+import testAdapter from './test-adapter.js';
+import * as cheerio from 'cheerio';
+import { fileURLToPath } from 'node:url';
+import { existsSync } from 'node:fs';
+
+describe('astro:ssr-manifest, split', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let entryPoints;
+ let currentRoutes;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/ssr-split-manifest/',
+ output: 'server',
+ adapter: testAdapter({
+ setEntryPoints(entries) {
+ entryPoints = entries;
+ },
+ setRoutes(routes) {
+ currentRoutes = routes;
+ },
+ }),
+ });
+ await fixture.build();
+ });
+
+ it('should be able to render a specific entry point', async () => {
+ const pagePath = 'src/pages/index.astro';
+ const app = await fixture.loadEntryPoint(pagePath, currentRoutes);
+ const request = new Request('http://example.com/');
+ const response = await app.render(request);
+ const html = await response.text();
+
+ const $ = cheerio.load(html);
+ expect($('#assets').text()).to.equal('["/_astro/index.a8a337e4.css"]');
+ });
+
+ it('should give access to entry points that exists on file system', async () => {
+ // number of the pages inside src/
+ expect(entryPoints.size).to.equal(4);
+ for (const fileUrl in entryPoints.values()) {
+ let filePath = fileURLToPath(fileUrl);
+ expect(existsSync(filePath)).to.be.true;
+ }
+ });
+});
diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js
index d74cfaf81..af5a7777b 100644
--- a/packages/astro/test/test-adapter.js
+++ b/packages/astro/test/test-adapter.js
@@ -4,7 +4,11 @@ import { viteID } from '../dist/core/util.js';
*
* @returns {import('../src/@types/astro').AstroIntegration}
*/
-export default function ({ provideAddress = true, extendAdapter } = { provideAddress: true }) {
+export default function (
+ { provideAddress = true, extendAdapter, setEntryPoints = undefined, setRoutes = undefined } = {
+ provideAddress: true,
+ }
+) {
return {
name: 'my-ssr-adapter',
hooks: {
@@ -70,6 +74,16 @@ export default function ({ provideAddress = true, extendAdapter } = { provideAdd
...extendAdapter,
});
},
+ 'astro:build:ssr': ({ entryPoints }) => {
+ if (setEntryPoints) {
+ setEntryPoints(entryPoints);
+ }
+ },
+ 'astro:build:done': ({ routes }) => {
+ if (setRoutes) {
+ setRoutes(routes);
+ }
+ },
},
};
}
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index b618f8593..11b181779 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -13,6 +13,9 @@ import dev from '../dist/core/dev/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js';
import preview from '../dist/core/preview/index.js';
import { check } from '../dist/cli/check/index.js';
+import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js';
+import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js';
+import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js';
// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16
polyfill(globalThis, {
@@ -245,6 +248,15 @@ export async function loadFixture(inlineConfig) {
app.manifest = manifest;
return app;
},
+ loadEntryPoint: async (pagePath, routes, streaming) => {
+ const virtualModule = getVirtualModulePageNameFromPath(RESOLVED_SPLIT_MODULE_ID, pagePath);
+ const filePath = makeSplitEntryPointFileName(virtualModule, routes);
+ const url = new URL(`./server/${filePath}?id=${fixtureId}`, config.outDir);
+ const { createApp, manifest, middleware } = await import(url);
+ const app = createApp(streaming);
+ app.manifest = manifest;
+ return app;
+ },
editFile: async (filePath, newContentsOrCallback) => {
const fileUrl = new URL(filePath.replace(/^\//, ''), config.root);
const contents = await fs.promises.readFile(fileUrl, 'utf-8');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 29d581936..45c034404 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3339,6 +3339,12 @@ importers:
specifier: ^10.11.0
version: 10.13.2
+ packages/astro/test/fixtures/ssr-split-manifest:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/test/fixtures/static-build:
dependencies:
'@astrojs/preact':
@@ -4421,7 +4427,7 @@ importers:
version: 9.2.2
vite:
specifier: ^4.3.1
- version: 4.3.1(@types/node@18.16.3)(sass@1.52.2)
+ version: 4.3.1(@types/node@14.18.21)
packages/integrations/netlify/test/edge-functions/fixtures/dynimport:
dependencies:
@@ -4945,7 +4951,7 @@ importers:
version: 3.0.0(vite@4.3.1)(vue@3.2.47)
'@vue/babel-plugin-jsx':
specifier: ^1.1.1
- version: 1.1.1(@babel/core@7.21.8)
+ version: 1.1.1
'@vue/compiler-sfc':
specifier: ^3.2.39
version: 3.2.39
@@ -9332,6 +9338,23 @@ packages:
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
dev: false
+ /@vue/babel-plugin-jsx@1.1.1:
+ resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==}
+ dependencies:
+ '@babel/helper-module-imports': 7.21.4
+ '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.18.2)
+ '@babel/template': 7.20.7
+ '@babel/traverse': 7.18.2
+ '@babel/types': 7.21.5
+ '@vue/babel-helper-vue-transform-on': 1.0.2
+ camelcase: 6.3.0
+ html-tags: 3.3.1
+ svg-tags: 1.0.0
+ transitivePeerDependencies:
+ - '@babel/core'
+ - supports-color
+ dev: false
+
/@vue/babel-plugin-jsx@1.1.1(@babel/core@7.21.8):
resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==}
dependencies:
@@ -17663,6 +17686,39 @@ packages:
- supports-color
dev: false
+ /vite@4.3.1(@types/node@14.18.21):
+ resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>= 14'
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ '@types/node': 14.18.21
+ esbuild: 0.17.18
+ postcss: 8.4.23
+ rollup: 3.21.8
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/vite@4.3.1(@types/node@18.16.3)(sass@1.52.2):
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
engines: {node: ^14.18.0 || >=16.0.0}