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';
|
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:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise<void>;
|
||||||
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
|
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
|
||||||
'astro:server:done'?: () => 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:start'?: () => void | Promise<void>;
|
||||||
'astro:build:setup'?: (options: {
|
'astro:build:setup'?: (options: {
|
||||||
vite: vite.InlineConfig;
|
vite: vite.InlineConfig;
|
||||||
|
|
|
@ -4,9 +4,9 @@ import type {
|
||||||
MiddlewareResponseHandler,
|
MiddlewareResponseHandler,
|
||||||
RouteData,
|
RouteData,
|
||||||
SSRElement,
|
SSRElement,
|
||||||
|
SSRManifest,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
import type { RouteInfo, SSRManifest as Manifest } from './types';
|
import type { RouteInfo } from './types';
|
||||||
|
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
import type { SinglePageBuiltModule } from '../build/types';
|
import type { SinglePageBuiltModule } from '../build/types';
|
||||||
import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js';
|
import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js';
|
||||||
|
@ -41,7 +41,7 @@ export interface MatchOptions {
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
#env: Environment;
|
#env: Environment;
|
||||||
#manifest: Manifest;
|
#manifest: SSRManifest;
|
||||||
#manifestData: ManifestData;
|
#manifestData: ManifestData;
|
||||||
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
||||||
#encoder = new TextEncoder();
|
#encoder = new TextEncoder();
|
||||||
|
@ -52,7 +52,7 @@ export class App {
|
||||||
#base: string;
|
#base: string;
|
||||||
#baseWithoutTrailingSlash: string;
|
#baseWithoutTrailingSlash: string;
|
||||||
|
|
||||||
constructor(manifest: Manifest, streaming = true) {
|
constructor(manifest: SSRManifest, streaming = true) {
|
||||||
this.#manifest = manifest;
|
this.#manifest = manifest;
|
||||||
this.#manifestData = {
|
this.#manifestData = {
|
||||||
routes: manifest.routes.map((route) => route.routeData),
|
routes: manifest.routes.map((route) => route.routeData),
|
||||||
|
@ -175,14 +175,23 @@ export class App {
|
||||||
if (route.type === 'redirect') {
|
if (route.type === 'redirect') {
|
||||||
return RedirectSinglePageBuiltModule;
|
return RedirectSinglePageBuiltModule;
|
||||||
} else {
|
} else {
|
||||||
const importComponentInstance = this.#manifest.pageMap.get(route.component);
|
if (this.#manifest.pageMap) {
|
||||||
if (!importComponentInstance) {
|
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(
|
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'> & {
|
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
|
||||||
routeData: SerializedRouteData;
|
routeData: SerializedRouteData;
|
||||||
};
|
};
|
||||||
type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
|
|
||||||
|
|
||||||
export interface SSRManifest {
|
export type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
|
||||||
|
|
||||||
|
export type SSRManifest = {
|
||||||
adapterName: string;
|
adapterName: string;
|
||||||
routes: RouteInfo[];
|
routes: RouteInfo[];
|
||||||
site?: string;
|
site?: string;
|
||||||
base?: string;
|
base?: string;
|
||||||
assetsPrefix?: string;
|
assetsPrefix?: string;
|
||||||
markdown: MarkdownRenderingOptions;
|
markdown: MarkdownRenderingOptions;
|
||||||
pageMap: Map<ComponentPath, ImportComponentInstance>;
|
|
||||||
renderers: SSRLoadedRenderer[];
|
renderers: SSRLoadedRenderer[];
|
||||||
/**
|
/**
|
||||||
* Map of directive name (e.g. `load`) to the directive script code
|
* Map of directive name (e.g. `load`) to the directive script code
|
||||||
|
@ -48,7 +48,9 @@ export interface SSRManifest {
|
||||||
entryModules: Record<string, string>;
|
entryModules: Record<string, string>;
|
||||||
assets: Set<string>;
|
assets: Set<string>;
|
||||||
componentMetadata: SSRResult['componentMetadata'];
|
componentMetadata: SSRResult['componentMetadata'];
|
||||||
}
|
pageModule?: SinglePageBuiltModule;
|
||||||
|
pageMap?: Map<ComponentPath, ImportComponentInstance>;
|
||||||
|
};
|
||||||
|
|
||||||
export type SerializedSSRManifest = Omit<
|
export type SerializedSSRManifest = Omit<
|
||||||
SSRManifest,
|
SSRManifest,
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import type { Rollup } from 'vite';
|
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 type { PageOptions } from '../../vite-plugin-astro/types';
|
||||||
import { prependForwardSlash, removeFileExtension } from '../path.js';
|
import { prependForwardSlash, removeFileExtension } from '../path.js';
|
||||||
import { viteID } from '../util.js';
|
import { viteID } from '../util.js';
|
||||||
import {
|
import { ASTRO_PAGE_MODULE_ID, getVirtualModulePageIdFromPath } from './plugins/plugin-pages.js';
|
||||||
ASTRO_PAGE_EXTENSION_POST_PATTERN,
|
|
||||||
ASTRO_PAGE_MODULE_ID,
|
|
||||||
getVirtualModulePageIdFromPath,
|
|
||||||
} from './plugins/plugin-pages.js';
|
|
||||||
import type { PageBuildData, StylesheetAsset, ViteID } from './types';
|
import type { PageBuildData, StylesheetAsset, ViteID } from './types';
|
||||||
|
import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
|
||||||
|
|
||||||
export interface BuildInternals {
|
export interface BuildInternals {
|
||||||
/**
|
/**
|
||||||
|
@ -84,6 +81,8 @@ export interface BuildInternals {
|
||||||
staticFiles: Set<string>;
|
staticFiles: Set<string>;
|
||||||
// The SSR entry chunk. Kept in internals to share between ssr/client build steps
|
// The SSR entry chunk. Kept in internals to share between ssr/client build steps
|
||||||
ssrEntryChunk?: Rollup.OutputChunk;
|
ssrEntryChunk?: Rollup.OutputChunk;
|
||||||
|
entryPoints: Map<RouteData, URL>;
|
||||||
|
ssrSplitEntryChunks: Map<string, Rollup.OutputChunk>;
|
||||||
componentMetadata: SSRResult['componentMetadata'];
|
componentMetadata: SSRResult['componentMetadata'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +113,8 @@ export function createBuildInternals(): BuildInternals {
|
||||||
discoveredScripts: new Set(),
|
discoveredScripts: new Set(),
|
||||||
staticFiles: new Set(),
|
staticFiles: new Set(),
|
||||||
componentMetadata: new Map(),
|
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 { pluginPages } from './plugin-pages.js';
|
||||||
import { pluginPrerender } from './plugin-prerender.js';
|
import { pluginPrerender } from './plugin-prerender.js';
|
||||||
import { pluginRenderers } from './plugin-renderers.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) {
|
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
|
||||||
register(pluginComponentEntry(internals));
|
register(pluginComponentEntry(internals));
|
||||||
|
@ -27,4 +27,5 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
|
||||||
register(astroConfigBuildPlugin(options, internals));
|
register(astroConfigBuildPlugin(options, internals));
|
||||||
register(pluginHoistedScripts(options, internals));
|
register(pluginHoistedScripts(options, internals));
|
||||||
register(pluginSSR(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 type { Plugin as VitePlugin } from 'vite';
|
||||||
import { routeIsRedirect } from '../../redirects/index.js';
|
import { routeIsRedirect } from '../../redirects/index.js';
|
||||||
import { addRollupInput } from '../add-rollup-input.js';
|
import { addRollupInput } from '../add-rollup-input.js';
|
||||||
|
@ -7,12 +7,10 @@ import type { AstroBuildPlugin } from '../plugin';
|
||||||
import type { StaticBuildOptions } from '../types';
|
import type { StaticBuildOptions } from '../types';
|
||||||
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
|
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
|
||||||
import { RENDERERS_MODULE_ID } from './plugin-renderers.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_MODULE_ID = '@astro-page:';
|
||||||
export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0@astro-page:';
|
export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0' + ASTRO_PAGE_MODULE_ID;
|
||||||
|
|
||||||
// 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;
|
* 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)) {
|
if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
|
||||||
const imports: string[] = [];
|
const imports: string[] = [];
|
||||||
const exports: string[] = [];
|
const exports: string[] = [];
|
||||||
|
const pageName = getPathFromVirtualModulePageName(ASTRO_PAGE_RESOLVED_MODULE_ID, id);
|
||||||
// we remove the module name prefix from id, this will result into a string that will start with "src/..."
|
const pageData = internals.pagesByComponent.get(pageName);
|
||||||
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, '.')}`
|
|
||||||
);
|
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
const resolvedPage = await this.resolve(pageData.moduleSpecifier);
|
const resolvedPage = await this.resolve(pageData.moduleSpecifier);
|
||||||
if (resolvedPage) {
|
if (resolvedPage) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import type { Plugin as VitePlugin } from 'vite';
|
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 { runHookBuildSsr } from '../../../integrations/index.js';
|
||||||
import { isServerLikeOutput } from '../../../prerender/utils.js';
|
import { isServerLikeOutput } from '../../../prerender/utils.js';
|
||||||
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.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 { getOutFile, getOutFolder } from '../common.js';
|
||||||
import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js';
|
import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js';
|
||||||
import type { AstroBuildPlugin } from '../plugin';
|
import type { AstroBuildPlugin } from '../plugin';
|
||||||
import type { StaticBuildOptions } from '../types';
|
import type { OutputChunk, StaticBuildOptions } from '../types';
|
||||||
import { getVirtualModulePageNameFromPath } from './plugin-pages.js';
|
import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js';
|
||||||
import { RENDERERS_MODULE_ID } from './plugin-renderers.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';
|
export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
|
||||||
const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
|
const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
|
||||||
|
@ -28,7 +30,7 @@ function vitePluginSSR(
|
||||||
options: StaticBuildOptions
|
options: StaticBuildOptions
|
||||||
): VitePlugin {
|
): VitePlugin {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vite-plugin-astro-ssr',
|
name: '@astrojs/vite-plugin-astro-ssr-server',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
options(opts) {
|
options(opts) {
|
||||||
return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
|
return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
|
||||||
|
@ -54,7 +56,7 @@ function vitePluginSSR(
|
||||||
if (routeIsRedirect(pageData.route)) {
|
if (routeIsRedirect(pageData.route)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const virtualModuleName = getVirtualModulePageNameFromPath(path);
|
const virtualModuleName = getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path);
|
||||||
let module = await this.resolve(virtualModuleName);
|
let module = await this.resolve(virtualModuleName);
|
||||||
if (module) {
|
if (module) {
|
||||||
const variable = `_page${i}`;
|
const variable = `_page${i}`;
|
||||||
|
@ -71,38 +73,10 @@ function vitePluginSSR(
|
||||||
|
|
||||||
contents.push(`const pageMap = new Map([${pageMap.join(',')}]);`);
|
contents.push(`const pageMap = new Map([${pageMap.join(',')}]);`);
|
||||||
exports.push(`export { pageMap }`);
|
exports.push(`export { pageMap }`);
|
||||||
const content = `import * as adapter from '${adapter.serverEntrypoint}';
|
const ssrCode = generateSSRCode(options.settings.config, adapter);
|
||||||
import { renderers } from '${RENDERERS_MODULE_ID}';
|
imports.push(...ssrCode.imports);
|
||||||
import { deserializeManifest as _deserializeManifest } from 'astro/app';
|
contents.push(...ssrCode.contents);
|
||||||
import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest';
|
return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
|
||||||
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')}`;
|
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
|
@ -127,15 +101,261 @@ if(_start in adapter) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function injectManifest(buildOpts: StaticBuildOptions, internals: BuildInternals) {
|
export function pluginSSR(
|
||||||
if (!internals.ssrEntryChunk) {
|
options: StaticBuildOptions,
|
||||||
throw new Error(`Did not generate an entry chunk for SSR`);
|
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.
|
// Add assets from the client build.
|
||||||
const clientStatics = new Set(
|
const clientStatics = new Set(
|
||||||
await glob('**/*', {
|
await glob('**/*', {
|
||||||
cwd: fileURLToPath(buildOpts.buildConfig.client),
|
cwd: fileURLToPath(buildOpts.settings.config.build.client),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
for (const file of clientStatics) {
|
for (const file of clientStatics) {
|
||||||
|
@ -143,19 +363,29 @@ export async function injectManifest(buildOpts: StaticBuildOptions, internals: B
|
||||||
}
|
}
|
||||||
|
|
||||||
const staticFiles = internals.staticFiles;
|
const staticFiles = internals.staticFiles;
|
||||||
const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles));
|
return buildManifest(buildOpts, internals, Array.from(staticFiles));
|
||||||
await runHookBuildSsr({
|
}
|
||||||
config: buildOpts.settings.config,
|
|
||||||
manifest,
|
|
||||||
logging: buildOpts.logging,
|
|
||||||
});
|
|
||||||
|
|
||||||
const chunk = internals.ssrEntryChunk;
|
/**
|
||||||
const code = chunk.code;
|
* 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.
|
||||||
return code.replace(replaceExp, () => {
|
* We use this hook instead.
|
||||||
return JSON.stringify(manifest);
|
*
|
||||||
});
|
* 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(
|
function buildManifest(
|
||||||
|
@ -254,7 +484,6 @@ function buildManifest(
|
||||||
base: settings.config.base,
|
base: settings.config.base,
|
||||||
assetsPrefix: settings.config.build.assetsPrefix,
|
assetsPrefix: settings.config.build.assetsPrefix,
|
||||||
markdown: settings.config.markdown,
|
markdown: settings.config.markdown,
|
||||||
pageMap: null as any,
|
|
||||||
componentMetadata: Array.from(internals.componentMetadata),
|
componentMetadata: Array.from(internals.componentMetadata),
|
||||||
renderers: [],
|
renderers: [],
|
||||||
clientDirectives: Array.from(settings.clientDirectives),
|
clientDirectives: Array.from(settings.clientDirectives),
|
||||||
|
@ -264,39 +493,3 @@ function buildManifest(
|
||||||
|
|
||||||
return ssrManifest;
|
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 type { Plugin as VitePlugin } from 'vite';
|
||||||
|
import { extname } from 'node:path';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;
|
type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;
|
||||||
|
@ -38,3 +39,33 @@ export function extendManualChunks(outputOptions: OutputOptions, hooks: ExtendMa
|
||||||
return null;
|
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 { createPluginContainer, type AstroBuildPluginContainer } from './plugin.js';
|
||||||
import { registerAllPlugins } from './plugins/index.js';
|
import { registerAllPlugins } from './plugins/index.js';
|
||||||
import { MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
|
import { MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
|
||||||
import {
|
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
|
||||||
ASTRO_PAGE_EXTENSION_POST_PATTERN,
|
|
||||||
ASTRO_PAGE_RESOLVED_MODULE_ID,
|
|
||||||
} from './plugins/plugin-pages.js';
|
|
||||||
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.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 type { AllPagesData, PageBuildData, StaticBuildOptions } from './types';
|
||||||
import { getTimeStat } from './util.js';
|
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) {
|
export async function viteBuild(opts: StaticBuildOptions) {
|
||||||
const { allPages, settings } = opts;
|
const { allPages, settings } = opts;
|
||||||
|
@ -147,7 +147,7 @@ async function ssrBuild(
|
||||||
const { allPages, settings, viteConfig } = opts;
|
const { allPages, settings, viteConfig } = opts;
|
||||||
const ssr = isServerLikeOutput(settings.config);
|
const ssr = isServerLikeOutput(settings.config);
|
||||||
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
|
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 { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);
|
||||||
|
|
||||||
const viteBuildConfig: vite.InlineConfig = {
|
const viteBuildConfig: vite.InlineConfig = {
|
||||||
|
@ -176,7 +176,13 @@ async function ssrBuild(
|
||||||
...viteConfig.build?.rollupOptions?.output,
|
...viteConfig.build?.rollupOptions?.output,
|
||||||
entryFileNames(chunkInfo) {
|
entryFileNames(chunkInfo) {
|
||||||
if (chunkInfo.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
|
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) {
|
} else if (chunkInfo.facadeModuleId === MIDDLEWARE_MODULE_ID) {
|
||||||
return 'middleware.mjs';
|
return 'middleware.mjs';
|
||||||
} else if (chunkInfo.facadeModuleId === SSR_VIRTUAL_MODULE_ID) {
|
} 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`
|
* Input: `@astro-page:../node_modules/my-dep/injected@_@astro`
|
||||||
* Output: `pages/injected.mjs`
|
* 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)
|
* 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_`).
|
* 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
|
* 4. We append the `.mjs` extension, so the file will always be an ESM module
|
||||||
*
|
*
|
||||||
|
* @param prefix string
|
||||||
* @param facadeModuleId string
|
* @param facadeModuleId string
|
||||||
* @param pages AllPagesData
|
* @param pages AllPagesData
|
||||||
*/
|
*/
|
||||||
function makeAstroPageEntryPointFileName(facadeModuleId: string, pages: AllPagesData) {
|
export function makeAstroPageEntryPointFileName(
|
||||||
|
prefix: string,
|
||||||
|
facadeModuleId: string,
|
||||||
|
routes: RouteData[]
|
||||||
|
) {
|
||||||
const pageModuleId = facadeModuleId
|
const pageModuleId = facadeModuleId
|
||||||
.replace(ASTRO_PAGE_RESOLVED_MODULE_ID, '')
|
.replace(prefix, '')
|
||||||
.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.');
|
.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';
|
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',
|
serverEntry: 'entry.mjs',
|
||||||
redirects: true,
|
redirects: true,
|
||||||
inlineStylesheets: 'never',
|
inlineStylesheets: 'never',
|
||||||
|
split: false,
|
||||||
},
|
},
|
||||||
compressHTML: false,
|
compressHTML: false,
|
||||||
server: {
|
server: {
|
||||||
|
@ -120,6 +121,8 @@ export const AstroConfigSchema = z.object({
|
||||||
.enum(['always', 'auto', 'never'])
|
.enum(['always', 'auto', 'never'])
|
||||||
.optional()
|
.optional()
|
||||||
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
|
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
|
||||||
|
|
||||||
|
split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({}),
|
.default({}),
|
||||||
|
@ -279,6 +282,8 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
|
||||||
.enum(['always', 'auto', 'never'])
|
.enum(['always', 'auto', 'never'])
|
||||||
.optional()
|
.optional()
|
||||||
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
|
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
|
||||||
|
|
||||||
|
split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({}),
|
.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 '/'.`,
|
`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.',
|
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
|
* @docs
|
||||||
* @kind heading
|
* @kind heading
|
||||||
|
|
|
@ -309,16 +309,18 @@ export async function runHookBuildSsr({
|
||||||
config,
|
config,
|
||||||
manifest,
|
manifest,
|
||||||
logging,
|
logging,
|
||||||
|
entryPoints,
|
||||||
}: {
|
}: {
|
||||||
config: AstroConfig;
|
config: AstroConfig;
|
||||||
manifest: SerializedSSRManifest;
|
manifest: SerializedSSRManifest;
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
|
entryPoints: Map<RouteData, URL>;
|
||||||
}) {
|
}) {
|
||||||
for (const integration of config.integrations) {
|
for (const integration of config.integrations) {
|
||||||
if (integration?.hooks?.['astro:build:ssr']) {
|
if (integration?.hooks?.['astro:build:ssr']) {
|
||||||
await withTakingALongTimeMsg({
|
await withTakingALongTimeMsg({
|
||||||
name: integration.name,
|
name: integration.name,
|
||||||
hookResult: integration.hooks['astro:build:ssr']({ manifest }),
|
hookResult: integration.hooks['astro:build:ssr']({ manifest, entryPoints }),
|
||||||
logging,
|
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}
|
* @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 {
|
return {
|
||||||
name: 'my-ssr-adapter',
|
name: 'my-ssr-adapter',
|
||||||
hooks: {
|
hooks: {
|
||||||
|
@ -70,6 +74,16 @@ export default function ({ provideAddress = true, extendAdapter } = { provideAdd
|
||||||
...extendAdapter,
|
...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 { nodeLogDestination } from '../dist/core/logger/node.js';
|
||||||
import preview from '../dist/core/preview/index.js';
|
import preview from '../dist/core/preview/index.js';
|
||||||
import { check } from '../dist/cli/check/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 WebAPIs to globalThis for Node v12, Node v14, and Node v16
|
||||||
polyfill(globalThis, {
|
polyfill(globalThis, {
|
||||||
|
@ -245,6 +248,15 @@ export async function loadFixture(inlineConfig) {
|
||||||
app.manifest = manifest;
|
app.manifest = manifest;
|
||||||
return app;
|
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) => {
|
editFile: async (filePath, newContentsOrCallback) => {
|
||||||
const fileUrl = new URL(filePath.replace(/^\//, ''), config.root);
|
const fileUrl = new URL(filePath.replace(/^\//, ''), config.root);
|
||||||
const contents = await fs.promises.readFile(fileUrl, 'utf-8');
|
const contents = await fs.promises.readFile(fileUrl, 'utf-8');
|
||||||
|
|
|
@ -3339,6 +3339,12 @@ importers:
|
||||||
specifier: ^10.11.0
|
specifier: ^10.11.0
|
||||||
version: 10.13.2
|
version: 10.13.2
|
||||||
|
|
||||||
|
packages/astro/test/fixtures/ssr-split-manifest:
|
||||||
|
dependencies:
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
|
||||||
packages/astro/test/fixtures/static-build:
|
packages/astro/test/fixtures/static-build:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/preact':
|
'@astrojs/preact':
|
||||||
|
@ -4421,7 +4427,7 @@ importers:
|
||||||
version: 9.2.2
|
version: 9.2.2
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.1
|
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:
|
packages/integrations/netlify/test/edge-functions/fixtures/dynimport:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4945,7 +4951,7 @@ importers:
|
||||||
version: 3.0.0(vite@4.3.1)(vue@3.2.47)
|
version: 3.0.0(vite@4.3.1)(vue@3.2.47)
|
||||||
'@vue/babel-plugin-jsx':
|
'@vue/babel-plugin-jsx':
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1(@babel/core@7.21.8)
|
version: 1.1.1
|
||||||
'@vue/compiler-sfc':
|
'@vue/compiler-sfc':
|
||||||
specifier: ^3.2.39
|
specifier: ^3.2.39
|
||||||
version: 3.2.39
|
version: 3.2.39
|
||||||
|
@ -9332,6 +9338,23 @@ packages:
|
||||||
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
|
resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==}
|
||||||
dev: false
|
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):
|
/@vue/babel-plugin-jsx@1.1.1(@babel/core@7.21.8):
|
||||||
resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==}
|
resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -17663,6 +17686,39 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
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):
|
/vite@4.3.1(@types/node@18.16.3)(sass@1.52.2):
|
||||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
Loading…
Reference in a new issue