summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/dry-taxis-suffer.md6
-rw-r--r--packages/astro/src/@types/astro.ts2
-rw-r--r--packages/astro/src/core/app/index.ts8
-rw-r--r--packages/astro/src/core/app/types.ts3
-rw-r--r--packages/astro/src/core/build/generate.ts32
-rw-r--r--packages/astro/src/core/build/graph.ts6
-rw-r--r--packages/astro/src/core/build/internal.ts23
-rw-r--r--packages/astro/src/core/build/plugins/README.md144
-rw-r--r--packages/astro/src/core/build/plugins/plugin-middleware.ts20
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts79
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts71
-rw-r--r--packages/astro/src/core/build/static-build.ts46
-rw-r--r--packages/astro/src/core/build/types.ts4
13 files changed, 352 insertions, 92 deletions
diff --git a/.changeset/dry-taxis-suffer.md b/.changeset/dry-taxis-suffer.md
new file mode 100644
index 000000000..b0cb68b24
--- /dev/null
+++ b/.changeset/dry-taxis-suffer.md
@@ -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.
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index c7b1c4f59..c494cf127 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -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:
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 1e2dd1d24..90e17f438 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -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,
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index 3747e96e3..0fa2c034b 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -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;
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 5a9f075c4..3c24aa4bc 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -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) {
diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts
index 68d264b10..3ce325309 100644
--- a/packages/astro/src/core/build/graph.ts
+++ b/packages/astro/src/core/build/graph.ts
@@ -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)
);
}
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 1d69849c9..f6025238a 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -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) {
diff --git a/packages/astro/src/core/build/plugins/README.md b/packages/astro/src/core/build/plugins/README.md
new file mode 100644
index 000000000..32ac8c448
--- /dev/null
+++ b/packages/astro/src/core/build/plugins/README.md
@@ -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.
+
diff --git a/packages/astro/src/core/build/plugins/plugin-middleware.ts b/packages/astro/src/core/build/plugins/plugin-middleware.ts
index 507c4ae71..dd9872da3 100644
--- a/packages/astro/src/core/build/plugins/plugin-middleware.ts
+++ b/packages/astro/src/core/build/plugins/plugin-middleware.ts
@@ -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');
}
},
};
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index 051c85583..3ea0a61c8 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -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 }`);
- if (opts.settings.config.experimental.middleware) {
- imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
- exports.push(`export const middleware = _middleware;`);
- }
+ imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
+ exports.push(`export { renderers };`);
- content.push(`export const pageMap = new Map([${importMap}]);`);
+ 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')}${content.join('\n')}${exports.join('\n')}`;
+ return `${imports.join('\n')}${exports.join('\n')}`;
+ }
+ }
}
},
};
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index a6d02b792..b40faf20d 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -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';
+ }
+ 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++;
+ }
}
- return `import * as adapter from '${adapter.serverEntrypoint}';
+
+ 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 {
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index ef6541b30..54e99e0f4 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -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`;
+}
diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts
index c0f38de45..772235697 100644
--- a/packages/astro/src/core/build/types.ts
+++ b/packages/astro/src/core/build/types.ts
@@ -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[];
}