refactor: emit pages as physical entry points (#7193)
This commit is contained in:
parent
f5a8cffac2
commit
8b041bf57c
13 changed files with 354 additions and 94 deletions
6
.changeset/dry-taxis-suffer.md
Normal file
6
.changeset/dry-taxis-suffer.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Refactor how pages are emitted during the internal bundling. Now each
|
||||
page is emitted as a separate entry point.
|
|
@ -1691,7 +1691,7 @@ export interface APIContext<Props extends Record<string, any> = Record<string, a
|
|||
*
|
||||
* export const onRequest = defineMiddleware((context, next) => {
|
||||
* context.locals.greeting = "Hello!";
|
||||
* next();
|
||||
* return next();
|
||||
* });
|
||||
* ```
|
||||
* Inside a `.astro` file:
|
||||
|
|
|
@ -32,8 +32,6 @@ export { deserializeManifest } from './common.js';
|
|||
|
||||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||
|
||||
export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry';
|
||||
export const resolvedPagesVirtualModuleId = '\0' + pagesVirtualModuleId;
|
||||
const responseSentSymbol = Symbol.for('astro.responseSent');
|
||||
|
||||
export interface MatchOptions {
|
||||
|
@ -139,7 +137,8 @@ export class App {
|
|||
defaultStatus = 404;
|
||||
}
|
||||
|
||||
let mod = await this.#manifest.pageMap.get(routeData.component)!();
|
||||
let page = await this.#manifest.pageMap.get(routeData.component)!();
|
||||
let mod = await page.page();
|
||||
|
||||
if (routeData.type === 'page') {
|
||||
let response = await this.#renderPage(request, routeData, mod, defaultStatus);
|
||||
|
@ -148,7 +147,8 @@ export class App {
|
|||
if (response.status === 500 || response.status === 404) {
|
||||
const errorPageData = matchRoute('/' + response.status, this.#manifestData);
|
||||
if (errorPageData && errorPageData.route !== routeData.route) {
|
||||
mod = await this.#manifest.pageMap.get(errorPageData.component)!();
|
||||
page = await this.#manifest.pageMap.get(errorPageData.component)!();
|
||||
mod = await page.page();
|
||||
try {
|
||||
let errorResponse = await this.#renderPage(
|
||||
request,
|
||||
|
|
|
@ -8,6 +8,7 @@ import type {
|
|||
SSRLoadedRenderer,
|
||||
SSRResult,
|
||||
} from '../../@types/astro';
|
||||
import type { SinglePageBuiltModule } from '../build/types';
|
||||
|
||||
export type ComponentPath = string;
|
||||
|
||||
|
@ -31,7 +32,7 @@ export interface RouteInfo {
|
|||
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
|
||||
routeData: SerializedRouteData;
|
||||
};
|
||||
type ImportComponentInstance = () => Promise<ComponentInstance>;
|
||||
type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
|
||||
|
||||
export interface SSRManifest {
|
||||
adapterName: string;
|
||||
|
|
|
@ -20,7 +20,11 @@ import {
|
|||
generateImage as generateImageInternal,
|
||||
getStaticImageList,
|
||||
} from '../../assets/generate.js';
|
||||
import { hasPrerenderedPages, type BuildInternals } from '../../core/build/internal.js';
|
||||
import {
|
||||
hasPrerenderedPages,
|
||||
type BuildInternals,
|
||||
eachPageDataFromEntryPoint,
|
||||
} from '../../core/build/internal.js';
|
||||
import {
|
||||
prependForwardSlash,
|
||||
removeLeadingForwardSlash,
|
||||
|
@ -47,11 +51,12 @@ import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js';
|
|||
import { cssOrder, eachPageData, getPageDataByComponent, mergeInlineCss } from './internal.js';
|
||||
import type {
|
||||
PageBuildData,
|
||||
SingleFileBuiltModule,
|
||||
SinglePageBuiltModule,
|
||||
StaticBuildOptions,
|
||||
StylesheetAsset,
|
||||
} from './types';
|
||||
import { getTimeStat } from './util.js';
|
||||
import { ASTRO_PAGE_MODULE_ID } from './plugins/plugin-pages';
|
||||
|
||||
function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean {
|
||||
return (
|
||||
|
@ -99,18 +104,23 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
|
|||
const verb = ssr ? 'prerendering' : 'generating';
|
||||
info(opts.logging, null, `\n${bgGreen(black(` ${verb} static routes `))}`);
|
||||
|
||||
const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder);
|
||||
const ssrEntry = await import(ssrEntryURL.toString());
|
||||
const builtPaths = new Set<string>();
|
||||
|
||||
if (ssr) {
|
||||
for (const pageData of eachPageData(internals)) {
|
||||
if (pageData.route.prerender)
|
||||
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
|
||||
for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
|
||||
if (pageData.route.prerender) {
|
||||
const ssrEntryURLPage = new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
|
||||
const ssrEntryPage = await import(ssrEntryURLPage.toString());
|
||||
|
||||
await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const pageData of eachPageData(internals)) {
|
||||
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
|
||||
for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
|
||||
const ssrEntryURLPage = new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
|
||||
const ssrEntryPage = await import(ssrEntryURLPage.toString());
|
||||
|
||||
await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +163,7 @@ async function generatePage(
|
|||
opts: StaticBuildOptions,
|
||||
internals: BuildInternals,
|
||||
pageData: PageBuildData,
|
||||
ssrEntry: SingleFileBuiltModule,
|
||||
ssrEntry: SinglePageBuiltModule,
|
||||
builtPaths: Set<string>
|
||||
) {
|
||||
let timeStart = performance.now();
|
||||
|
@ -169,7 +179,7 @@ async function generatePage(
|
|||
.map(({ sheet }) => sheet)
|
||||
.reduce(mergeInlineCss, []);
|
||||
|
||||
const pageModulePromise = ssrEntry.pageMap?.get(pageData.component);
|
||||
const pageModulePromise = ssrEntry.page;
|
||||
const middleware = ssrEntry.middleware;
|
||||
|
||||
if (!pageModulePromise) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { GetModuleInfo, ModuleInfo } from 'rollup';
|
||||
|
||||
import { resolvedPagesVirtualModuleId } from '../app/index.js';
|
||||
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
|
||||
|
||||
// This walks up the dependency graph and yields out each ModuleInfo object.
|
||||
export function* walkParentInfos(
|
||||
|
@ -43,8 +43,8 @@ export function* walkParentInfos(
|
|||
// it is imported by the top-level virtual module.
|
||||
export function moduleIsTopLevelPage(info: ModuleInfo): boolean {
|
||||
return (
|
||||
info.importers[0] === resolvedPagesVirtualModuleId ||
|
||||
info.dynamicImporters[0] == resolvedPagesVirtualModuleId
|
||||
info.importers[0]?.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
|
||||
info.dynamicImporters[0]?.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { Rollup } from 'vite';
|
||||
import type { PageBuildData, StylesheetAsset, ViteID } from './types';
|
||||
|
||||
import type { 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 } from './plugins/plugin-pages.js';
|
||||
|
||||
export interface BuildInternals {
|
||||
/**
|
||||
|
@ -97,7 +97,6 @@ export function createBuildInternals(): BuildInternals {
|
|||
hoistedScriptIdToPagesMap,
|
||||
entrySpecifierToBundleMap: new Map<string, string>(),
|
||||
pageToBundleMap: new Map<string, string>(),
|
||||
|
||||
pagesByComponent: new Map(),
|
||||
pageOptionsByPage: new Map(),
|
||||
pagesByViteID: new Map(),
|
||||
|
@ -215,6 +214,26 @@ export function* eachPageData(internals: BuildInternals) {
|
|||
yield* internals.pagesByComponent.values();
|
||||
}
|
||||
|
||||
export function* eachPageDataFromEntryPoint(
|
||||
internals: BuildInternals
|
||||
): Generator<[PageBuildData, string]> {
|
||||
for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) {
|
||||
if (entryPoint.includes(ASTRO_PAGE_MODULE_ID)) {
|
||||
const [, pageName] = entryPoint.split(':');
|
||||
const pageData = internals.pagesByComponent.get(
|
||||
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
|
||||
);
|
||||
if (!pageData) {
|
||||
throw new Error(
|
||||
"Build failed. Astro couldn't find the emitted page from " + pageName + ' pattern'
|
||||
);
|
||||
}
|
||||
|
||||
yield [pageData, filePath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function hasPrerenderedPages(internals: BuildInternals) {
|
||||
for (const pageData of eachPageData(internals)) {
|
||||
if (pageData.route.prerender) {
|
||||
|
|
144
packages/astro/src/core/build/plugins/README.md
Normal file
144
packages/astro/src/core/build/plugins/README.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
# Plugin directory (WIP)
|
||||
|
||||
This file serves as developer documentation to explain how the internal plugins work
|
||||
|
||||
|
||||
## `plugin-middleware`
|
||||
|
||||
This plugin is responsible to retrieve the `src/middleware.{ts.js}` file and emit an entry point during the SSR build.
|
||||
|
||||
The final file is emitted only if the user has the middleware file. The final name of the file is `middleware.mjs`.
|
||||
|
||||
This is **not** a virtual module. The plugin will try to resolve the physical file.
|
||||
|
||||
## `plugin-renderers`
|
||||
|
||||
This plugin is responsible to collect all the renderers inside an Astro application and emit them in a single file.
|
||||
|
||||
The emitted file is called `renderers.mjs`.
|
||||
|
||||
The emitted file has content similar to:
|
||||
|
||||
```js
|
||||
const renderers = [Object.assign({"name":"astro:jsx","serverEntrypoint":"astro/jsx/server.js","jsxImportSource":"astro"}, { ssr: server_default }),];
|
||||
|
||||
export { renderers };
|
||||
```
|
||||
|
||||
## `plugin-pages`
|
||||
|
||||
This plugin is responsible to collect all pages inside an Astro application, and emit a single entry point file for each page.
|
||||
|
||||
This plugin **will emit code** only when building a static site.
|
||||
|
||||
In order to achieve that, the plugin emits these pages as **virtual modules**. Doing so allows us to bypass:
|
||||
- rollup resolution of the files
|
||||
- possible plugins that get triggered when the name of the module has an extension e.g. `.astro`
|
||||
|
||||
The plugin does the following operations:
|
||||
- loop through all the pages and collects their paths;
|
||||
- with each path, we create a new [string](#plugin-pages-mapping-resolution) that will serve and virtual module for that particular page
|
||||
- when resolving the page, we check if the `id` of the module starts with `@astro-page`
|
||||
- once the module is resolved, we emit [the code of the module](#plugin-pages-code-generation)
|
||||
|
||||
|
||||
### `plugin pages` mapping resolution
|
||||
|
||||
The mapping is as follows:
|
||||
|
||||
```
|
||||
src/pages/index.astro => @astro-page:src/pages/index@_@astro
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
This kind of patterns will then allow us to retrieve the path physical path of the
|
||||
file back from that string. This is important for the [code generation](#plugin-pages-code-generation)
|
||||
|
||||
|
||||
|
||||
### `plugin pages` code generation
|
||||
|
||||
When generating the code of the page, we will import and export the following modules:
|
||||
- the `renderers.mjs`
|
||||
- the `middleware.mjs`
|
||||
- the page, via dynamic import
|
||||
|
||||
The emitted code of each entry point will look like this:
|
||||
|
||||
```js
|
||||
export { renderers } from '../renderers.mjs';
|
||||
import { _ as _middleware } from '../middleware.mjs';
|
||||
import '../chunks/astro.540fbe4e.mjs';
|
||||
|
||||
const page = () => import('../chunks/pages/index.astro.8aad0438.mjs');
|
||||
const middleware = _middleware;
|
||||
|
||||
export { middleware, page };
|
||||
```
|
||||
|
||||
If we have a `pages/` folder that looks like this:
|
||||
```
|
||||
├── blog
|
||||
│ ├── first.astro
|
||||
│ └── post.astro
|
||||
├── first.astro
|
||||
├── index.astro
|
||||
├── issue.md
|
||||
└── second.astro
|
||||
```
|
||||
|
||||
The emitted entry points will be stored inside a `pages/` folder, and they
|
||||
will look like this:
|
||||
```
|
||||
├── _astro
|
||||
│ ├── first.132e69e0.css
|
||||
│ ├── first.49cbf029.css
|
||||
│ ├── post.a3e86c58.css
|
||||
│ └── second.d178d0b2.css
|
||||
├── chunks
|
||||
│ ├── astro.540fbe4e.mjs
|
||||
│ └── pages
|
||||
│ ├── first.astro.493fa853.mjs
|
||||
│ ├── index.astro.8aad0438.mjs
|
||||
│ ├── issue.md.535b7d3b.mjs
|
||||
│ ├── post.astro.26e892d9.mjs
|
||||
│ └── second.astro.76540694.mjs
|
||||
├── middleware.mjs
|
||||
├── pages
|
||||
│ ├── blog
|
||||
│ │ ├── first.astro.mjs
|
||||
│ │ └── post.astro.mjs
|
||||
│ ├── first.astro.mjs
|
||||
│ ├── index.astro.mjs
|
||||
│ ├── issue.md.mjs
|
||||
│ └── second.astro.mjs
|
||||
└── renderers.mjs
|
||||
```
|
||||
|
||||
Of course, all these files will be deleted by Astro at the end build.
|
||||
|
||||
## `plugin-ssr` (WIP)
|
||||
|
||||
This plugin is responsible to create a single `entry.mjs` file that will be used
|
||||
in SSR.
|
||||
|
||||
This plugin **will emit code** only when building an **SSR** site.
|
||||
|
||||
The plugin will collect all the [virtual pages](#plugin-pages) and create
|
||||
a JavaScript `Map`. These map will look like this:
|
||||
|
||||
```js
|
||||
const _page$0 = () => import("../chunks/<INDEX.ASTRO_CHUNK>.mjs")
|
||||
const _page$1 = () => import("../chunks/<ABOUT.ASTRO_CHUNK>.mjs")
|
||||
|
||||
const pageMap = new Map([
|
||||
["src/pages/index.astro", _page$0],
|
||||
["src/pages/about.astro", _page$1],
|
||||
])
|
||||
```
|
||||
|
||||
It will also import the [`renderers`](#plugin-renderers) virtual module
|
||||
and the [`middleware`](#plugin-middleware) virtual module.
|
||||
|
|
@ -6,9 +6,7 @@ import type { AstroBuildPlugin } from '../plugin';
|
|||
import type { StaticBuildOptions } from '../types';
|
||||
|
||||
export const MIDDLEWARE_MODULE_ID = '@astro-middleware';
|
||||
export const RESOLVED_MIDDLEWARE_MODULE_ID = '\0@astro-middleware';
|
||||
|
||||
let inputs: Set<string> = new Set();
|
||||
export function vitePluginMiddleware(
|
||||
opts: StaticBuildOptions,
|
||||
_internals: BuildInternals
|
||||
|
@ -21,26 +19,14 @@ export function vitePluginMiddleware(
|
|||
}
|
||||
},
|
||||
|
||||
resolveId(id) {
|
||||
async resolveId(id) {
|
||||
if (id === MIDDLEWARE_MODULE_ID && opts.settings.config.experimental.middleware) {
|
||||
return RESOLVED_MIDDLEWARE_MODULE_ID;
|
||||
}
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
if (id === RESOLVED_MIDDLEWARE_MODULE_ID && opts.settings.config.experimental.middleware) {
|
||||
const imports: string[] = [];
|
||||
const exports: string[] = [];
|
||||
let middlewareId = await this.resolve(
|
||||
const middlewareId = await this.resolve(
|
||||
`${opts.settings.config.srcDir.pathname}/${MIDDLEWARE_PATH_SEGMENT_NAME}`
|
||||
);
|
||||
if (middlewareId) {
|
||||
imports.push(`import { onRequest } from "${middlewareId.id}"`);
|
||||
exports.push(`export { onRequest }`);
|
||||
return middlewareId.id;
|
||||
}
|
||||
const result = [imports.join('\n'), exports.join('\n')];
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,11 +1,33 @@
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../../app/index.js';
|
||||
import { addRollupInput } from '../add-rollup-input.js';
|
||||
import { eachPageData, type BuildInternals } from '../internal.js';
|
||||
import { type BuildInternals } from '../internal.js';
|
||||
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 = '@_@';
|
||||
|
||||
/**
|
||||
* 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 path
|
||||
*/
|
||||
export function getVirtualModulePageNameFromPath(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 `${ASTRO_PAGE_MODULE_ID}${path.replace(
|
||||
extension,
|
||||
extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN)
|
||||
)}`;
|
||||
}
|
||||
|
||||
function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
|
||||
return {
|
||||
|
@ -13,42 +35,49 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
|
|||
|
||||
options(options) {
|
||||
if (opts.settings.config.output === 'static') {
|
||||
return addRollupInput(options, [pagesVirtualModuleId]);
|
||||
const inputs: Set<string> = new Set();
|
||||
|
||||
for (const path of Object.keys(opts.allPages)) {
|
||||
inputs.add(getVirtualModulePageNameFromPath(path));
|
||||
}
|
||||
|
||||
return addRollupInput(options, Array.from(inputs));
|
||||
}
|
||||
},
|
||||
|
||||
resolveId(id) {
|
||||
if (id === pagesVirtualModuleId) {
|
||||
return resolvedPagesVirtualModuleId;
|
||||
if (id.startsWith(ASTRO_PAGE_MODULE_ID)) {
|
||||
return '\0' + id;
|
||||
}
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
if (id === resolvedPagesVirtualModuleId) {
|
||||
let importMap = '';
|
||||
if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
|
||||
const imports: string[] = [];
|
||||
const exports: string[] = [];
|
||||
const content: string[] = [];
|
||||
let i = 0;
|
||||
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
|
||||
exports.push(`export { renderers };`);
|
||||
for (const pageData of eachPageData(internals)) {
|
||||
const variable = `_page${i}`;
|
||||
imports.push(
|
||||
`const ${variable} = () => import(${JSON.stringify(pageData.moduleSpecifier)});`
|
||||
);
|
||||
importMap += `[${JSON.stringify(pageData.component)}, ${variable}],`;
|
||||
i++;
|
||||
// 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, '.')}`
|
||||
);
|
||||
if (pageData) {
|
||||
const resolvedPage = await this.resolve(pageData.moduleSpecifier);
|
||||
if (resolvedPage) {
|
||||
imports.push(`const page = () => import(${JSON.stringify(pageData.moduleSpecifier)});`);
|
||||
exports.push(`export { page }`);
|
||||
|
||||
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
|
||||
exports.push(`export { renderers };`);
|
||||
|
||||
if (opts.settings.config.experimental.middleware) {
|
||||
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
|
||||
exports.push(`export const middleware = _middleware;`);
|
||||
}
|
||||
|
||||
return `${imports.join('\n')}${exports.join('\n')}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.settings.config.experimental.middleware) {
|
||||
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
|
||||
exports.push(`export const middleware = _middleware;`);
|
||||
}
|
||||
|
||||
content.push(`export const pageMap = new Map([${importMap}]);`);
|
||||
|
||||
return `${imports.join('\n')}${content.join('\n')}${exports.join('\n')}`;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,62 +1,90 @@
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import type { AstroAdapter, AstroConfig } from '../../../@types/astro';
|
||||
import type { AstroAdapter } from '../../../@types/astro';
|
||||
import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types';
|
||||
import type { StaticBuildOptions } from '../types';
|
||||
|
||||
import type { AstroBuildPlugin } from '../plugin';
|
||||
import glob from 'fast-glob';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { runHookBuildSsr } from '../../../integrations/index.js';
|
||||
import { isHybridOutput } from '../../../prerender/utils.js';
|
||||
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
|
||||
import { pagesVirtualModuleId } from '../../app/index.js';
|
||||
import { joinPaths, prependForwardSlash } from '../../path.js';
|
||||
import { serializeRouteData } from '../../routing/index.js';
|
||||
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 { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
|
||||
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
|
||||
import { getVirtualModulePageNameFromPath } from './plugin-pages.js';
|
||||
|
||||
export const virtualModuleId = '@astrojs-ssr-virtual-entry';
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
|
||||
const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
|
||||
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
|
||||
const replaceExp = new RegExp(`['"](${manifestReplace})['"]`, 'g');
|
||||
|
||||
function vitePluginSSR(
|
||||
internals: BuildInternals,
|
||||
adapter: AstroAdapter,
|
||||
config: AstroConfig
|
||||
options: StaticBuildOptions
|
||||
): VitePlugin {
|
||||
return {
|
||||
name: '@astrojs/vite-plugin-astro-ssr',
|
||||
enforce: 'post',
|
||||
options(opts) {
|
||||
return addRollupInput(opts, [virtualModuleId]);
|
||||
return addRollupInput(opts, [SSR_VIRTUAL_MODULE_ID]);
|
||||
},
|
||||
resolveId(id) {
|
||||
if (id === virtualModuleId) {
|
||||
return resolvedVirtualModuleId;
|
||||
if (id === SSR_VIRTUAL_MODULE_ID) {
|
||||
return RESOLVED_SSR_VIRTUAL_MODULE_ID;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
let middleware = '';
|
||||
async load(id) {
|
||||
if (id === RESOLVED_SSR_VIRTUAL_MODULE_ID) {
|
||||
const {
|
||||
settings: { config },
|
||||
allPages,
|
||||
} = options;
|
||||
const imports: string[] = [];
|
||||
const contents: string[] = [];
|
||||
const exports: string[] = [];
|
||||
let middleware;
|
||||
if (config.experimental?.middleware === true) {
|
||||
middleware = 'middleware: _main.middleware';
|
||||
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}"`);
|
||||
middleware = 'middleware: _middleware';
|
||||
}
|
||||
return `import * as adapter from '${adapter.serverEntrypoint}';
|
||||
let i = 0;
|
||||
const pageMap: string[] = [];
|
||||
|
||||
for (const path of Object.keys(allPages)) {
|
||||
const virtualModuleName = getVirtualModulePageNameFromPath(path);
|
||||
let module = await this.resolve(virtualModuleName);
|
||||
if (module) {
|
||||
const variable = `_page${i}`;
|
||||
// we need to use the non-resolved ID in order to resolve correctly the virtual module
|
||||
imports.push(`const ${variable} = () => import("${virtualModuleName}");`);
|
||||
|
||||
const pageData = internals.pagesByComponent.get(path);
|
||||
if (pageData) {
|
||||
pageMap.push(`[${JSON.stringify(pageData.component)}, ${variable}]`);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
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 * as _main from '${pagesVirtualModuleId}';
|
||||
import { deserializeManifest as _deserializeManifest } from 'astro/app';
|
||||
import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest';
|
||||
const _manifest = Object.assign(_deserializeManifest('${manifestReplace}'), {
|
||||
pageMap: _main.pageMap,
|
||||
renderers: _main.renderers,
|
||||
pageMap,
|
||||
renderers,
|
||||
${middleware}
|
||||
});
|
||||
_privateSetManifestDontUseThis(_manifest);
|
||||
const _args = ${adapter.args ? JSON.stringify(adapter.args) : 'undefined'};
|
||||
export * from '${pagesVirtualModuleId}';
|
||||
|
||||
${
|
||||
adapter.exports
|
||||
? `const _exports = adapter.createExports(_manifest, _args);
|
||||
|
@ -77,6 +105,7 @@ 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;
|
||||
},
|
||||
|
@ -92,7 +121,7 @@ if(_start in adapter) {
|
|||
if (chunk.type === 'asset') {
|
||||
continue;
|
||||
}
|
||||
if (chunk.modules[resolvedVirtualModuleId]) {
|
||||
if (chunk.modules[RESOLVED_SSR_VIRTUAL_MODULE_ID]) {
|
||||
internals.ssrEntryChunk = chunk;
|
||||
delete bundle[chunkName];
|
||||
}
|
||||
|
@ -250,7 +279,7 @@ export function pluginSSR(
|
|||
hooks: {
|
||||
'build:before': () => {
|
||||
let vitePlugin = ssr
|
||||
? vitePluginSSR(internals, options.settings.adapter!, options.settings.config)
|
||||
? vitePluginSSR(internals, options.settings.adapter!, options)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
|
|
|
@ -17,7 +17,6 @@ import { isModeServerWithNoAdapter } from '../../core/util.js';
|
|||
import { runHookBuildSetup } from '../../integrations/index.js';
|
||||
import { isHybridOutput } from '../../prerender/utils.js';
|
||||
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
|
||||
import { resolvedPagesVirtualModuleId } from '../app/index.js';
|
||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||
import { info } from '../logger/core.js';
|
||||
import { getOutDirWithinCwd } from './common.js';
|
||||
|
@ -25,10 +24,14 @@ import { generatePages } from './generate.js';
|
|||
import { trackPageData } from './internal.js';
|
||||
import { createPluginContainer, type AstroBuildPluginContainer } from './plugin.js';
|
||||
import { registerAllPlugins } from './plugins/index.js';
|
||||
import { RESOLVED_MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
|
||||
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js';
|
||||
import type { PageBuildData, StaticBuildOptions } from './types';
|
||||
import { getTimeStat } from './util.js';
|
||||
import {
|
||||
ASTRO_PAGE_EXTENSION_POST_PATTERN,
|
||||
ASTRO_PAGE_RESOLVED_MODULE_ID,
|
||||
} from './plugins/plugin-pages.js';
|
||||
import { SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
|
||||
|
||||
export async function viteBuild(opts: StaticBuildOptions) {
|
||||
const { allPages, settings } = opts;
|
||||
|
@ -172,10 +175,17 @@ async function ssrBuild(
|
|||
assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`,
|
||||
...viteConfig.build?.rollupOptions?.output,
|
||||
entryFileNames(chunkInfo) {
|
||||
if (chunkInfo.facadeModuleId === resolvedPagesVirtualModuleId) {
|
||||
return opts.buildConfig.serverEntry;
|
||||
} else if (chunkInfo.facadeModuleId === RESOLVED_MIDDLEWARE_MODULE_ID) {
|
||||
if (chunkInfo.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
|
||||
return makeAstroPageEntryPointFileName(chunkInfo.facadeModuleId);
|
||||
} else if (
|
||||
// checks if the path of the module we have middleware, e.g. middleware.js / middleware/index.js
|
||||
chunkInfo.facadeModuleId?.includes('middleware') &&
|
||||
// checks if the file actually export the `onRequest` function
|
||||
chunkInfo.exports.includes('onRequest')
|
||||
) {
|
||||
return 'middleware.mjs';
|
||||
} else if (chunkInfo.facadeModuleId === SSR_VIRTUAL_MODULE_ID) {
|
||||
return opts.settings.config.build.serverEntry;
|
||||
} else if (chunkInfo.facadeModuleId === RESOLVED_RENDERERS_MODULE_ID) {
|
||||
return 'renderers.mjs';
|
||||
} else {
|
||||
|
@ -408,3 +418,29 @@ async function ssrMoveAssets(opts: StaticBuildOptions) {
|
|||
removeEmptyDirs(serverAssets);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes as input the virtual module name of an astro page and transform
|
||||
* to generate an `.mjs` file:
|
||||
*
|
||||
* Input: `@astro-page:src/pages/index@_@astro`
|
||||
*
|
||||
* Output: `pages/index.astro.mjs`
|
||||
*
|
||||
* 1. We remove the module id prefix, `@astro-page:`
|
||||
* 2. We remove `src/`
|
||||
* 3. We replace square brackets with underscore, for example `[slug]`
|
||||
* 4. At last, we replace the extension pattern with a simple dot
|
||||
* 5. We append the `.mjs` string, so the file will always be a JS file
|
||||
*
|
||||
* @param facadeModuleId
|
||||
*/
|
||||
function makeAstroPageEntryPointFileName(facadeModuleId: string) {
|
||||
return `${facadeModuleId
|
||||
.replace(ASTRO_PAGE_RESOLVED_MODULE_ID, '')
|
||||
.replace('src/', '')
|
||||
.replaceAll('[', '_')
|
||||
.replaceAll(']', '_')
|
||||
// this must be last
|
||||
.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}.mjs`;
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ export interface StaticBuildOptions {
|
|||
|
||||
type ImportComponentInstance = () => Promise<ComponentInstance>;
|
||||
|
||||
export interface SingleFileBuiltModule {
|
||||
pageMap: Map<ComponentPath, ImportComponentInstance>;
|
||||
export interface SinglePageBuiltModule {
|
||||
page: ImportComponentInstance;
|
||||
middleware: AstroMiddlewareInstance<unknown>;
|
||||
renderers: SSRLoadedRenderer[];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue