feat: SSR split
mode (#7220)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
c8bccb47d3
commit
459b5bd05f
24 changed files with 709 additions and 140 deletions
31
.changeset/wet-readers-join.md
Normal file
31
.changeset/wet-readers-join.md
Normal file
|
@ -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
|
||||
}
|
||||
})
|
||||
```
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,38 +73,10 @@ function vitePluginSSR(
|
|||
|
||||
contents.push(`const pageMap = new Map([${pageMap.join(',')}]);`);
|
||||
exports.push(`export { pageMap }`);
|
||||
const content = `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,
|
||||
renderers,
|
||||
});
|
||||
_privateSetManifestDontUseThis(_manifest);
|
||||
const _args = ${adapter.args ? JSON.stringify(adapter.args) : 'undefined'};
|
||||
|
||||
${
|
||||
adapter.exports
|
||||
? `const _exports = adapter.createExports(_manifest, _args);
|
||||
${adapter.exports
|
||||
.map((name) => {
|
||||
if (name === 'default') {
|
||||
return `const _default = _exports['default'];
|
||||
export { _default as default };`;
|
||||
} else {
|
||||
return `export const ${name} = _exports['${name}'];`;
|
||||
}
|
||||
})
|
||||
.join('\n')}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
const _start = 'start';
|
||||
if(_start in adapter) {
|
||||
adapter[_start](_manifest, _args);
|
||||
}`;
|
||||
return `${imports.join('\n')}${contents.join('\n')}${content}${exports.join('\n')}`;
|
||||
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;
|
||||
},
|
||||
|
@ -127,15 +101,261 @@ if(_start in adapter) {
|
|||
};
|
||||
}
|
||||
|
||||
export async function injectManifest(buildOpts: StaticBuildOptions, internals: BuildInternals) {
|
||||
if (!internals.ssrEntryChunk) {
|
||||
throw new Error(`Did not generate an entry chunk for SSR`);
|
||||
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},
|
||||
renderers,
|
||||
});
|
||||
_privateSetManifestDontUseThis(_manifest);
|
||||
const _args = ${adapter.args ? JSON.stringify(adapter.args) : 'undefined'};
|
||||
|
||||
${
|
||||
adapter.exports
|
||||
? `const _exports = adapter.createExports(_manifest, _args);
|
||||
${adapter.exports
|
||||
.map((name) => {
|
||||
if (name === 'default') {
|
||||
return `const _default = _exports['default'];
|
||||
export { _default as default };`;
|
||||
} else {
|
||||
return `export const ${name} = _exports['${name}'];`;
|
||||
}
|
||||
})
|
||||
.join('\n')}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
const _start = 'start';
|
||||
if(_start in adapter) {
|
||||
adapter[_start](_manifest, _args);
|
||||
}`);
|
||||
return {
|
||||
imports,
|
||||
contents,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
return buildManifest(buildOpts, internals, Array.from(staticFiles));
|
||||
}
|
||||
|
||||
const chunk = internals.ssrEntryChunk;
|
||||
const code = chunk.code;
|
||||
|
||||
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);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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, '.');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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({}),
|
||||
|
|
|
@ -817,6 +817,17 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
|
|||
`Invalid glob pattern: \`${globPattern}\`. Glob patterns must start with './', '../' or '/'.`,
|
||||
hint: 'See https://docs.astro.build/en/guides/imports/#glob-patterns for more information on supported glob patterns.',
|
||||
},
|
||||
/**
|
||||
* @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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
8
packages/astro/test/fixtures/ssr-request/astro.config.mjs
vendored
Normal file
8
packages/astro/test/fixtures/ssr-request/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
build: {
|
||||
split: false
|
||||
}
|
||||
});
|
7
packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs
vendored
Normal file
7
packages/astro/test/fixtures/ssr-split-manifest/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
export default defineConfig({
|
||||
build: {
|
||||
split: true
|
||||
},
|
||||
output: "server"
|
||||
})
|
8
packages/astro/test/fixtures/ssr-split-manifest/package.json
vendored
Normal file
8
packages/astro/test/fixtures/ssr-split-manifest/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/ssr-split-manifest",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
18
packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro
vendored
Normal file
18
packages/astro/test/fixtures/ssr-split-manifest/src/pages/[...post].astro
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
export async function getStaticPaths() {
|
||||
return [
|
||||
{
|
||||
params: { page: 1 },
|
||||
},
|
||||
{
|
||||
params: { page: 2 },
|
||||
},
|
||||
{
|
||||
params: { page: 3 }
|
||||
}
|
||||
]
|
||||
};
|
||||
---
|
||||
|
||||
<html>
|
||||
</html>
|
17
packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro
vendored
Normal file
17
packages/astro/test/fixtures/ssr-split-manifest/src/pages/index.astro
vendored
Normal file
|
@ -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>
|
1
packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md
vendored
Normal file
1
packages/astro/test/fixtures/ssr-split-manifest/src/pages/lorem.md
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# Title
|
17
packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro
vendored
Normal file
17
packages/astro/test/fixtures/ssr-split-manifest/src/pages/zod.astro
vendored
Normal file
|
@ -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>
|
49
packages/astro/test/ssr-split-manifest.test.js
Normal file
49
packages/astro/test/ssr-split-manifest.test.js
Normal file
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue