diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index b572146dba04..95653b485e3a 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -844,22 +844,22 @@ export interface AstroUserConfig { /** * @docs - * @name build.mode - * @type {string} - * @default {'server' | 'serverless'} + * @name build.ssrMode + * @type {'server' | 'serverless'} + * @default {'server'} * @description - * Defines how the SSR should be bundled. SSR code for "server" + * Defines how the SSR code should be bundled. SSR code for "server" * will be built in one single file. * - * When "serverless" is specified, Astro will emit a file for each page. + * When "serverless" is passed, Astro will emit a file for each page. * Each file emitted will render only one page. The pages will be emitted - * inside a `pages/` directory, and emitted file will keep the same file paths + * inside a `dist/pages/` directory, and the emitted files will keep the same file paths * of the `src/pages` directory. * * Each emitted file will be prefixed with `entry`. You can use {@link build.serverlessEntryPrefix} * to change the prefix. * - * Inside the `dist/` directory, the pages + * Inside the `dist/` directory, the pages will look like this: * ```plaintext * ├── pages * │ ├── blog @@ -876,7 +876,7 @@ export interface AstroUserConfig { * } * ``` */ - mode?: 'server' | 'serverless'; + ssrMode?: 'server' | 'serverless'; }; /** diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 0b2ddd9fc824..7d8e7e3c0c56 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -6,7 +6,7 @@ import type { SSRElement, SSRBaseManifest, } from '../../@types/astro'; -import type { RouteInfo, SSRServerlessManifest, SSRServerManifest } from './types'; +import type { RouteInfo, SSRServerManifest } from './types'; import mime from 'mime'; import type { SinglePageBuiltModule } from '../build/types'; @@ -150,7 +150,6 @@ export class App { } let mod = await this.#getModuleForRoute(routeData); - let mod = await this.#retrievePage(routeData); if (routeData.type === 'page' || routeData.type === 'redirect') { let response = await this.#renderPage(request, routeData, mod, defaultStatus); @@ -159,7 +158,6 @@ export class App { if (response.status === 500 || response.status === 404) { const errorRouteData = matchRoute('/' + response.status, this.#manifestData); if (errorRouteData && errorRouteData.route !== routeData.route) { - mod = await this.#retrievePage(errorPageData); mod = await this.#getModuleForRoute(errorRouteData); try { let errorResponse = await this.#renderPage( @@ -188,14 +186,19 @@ export class App { if (route.type === 'redirect') { return RedirectSinglePageBuiltModule; } else { - const importComponentInstance = this.#manifest.pageMap.get(route.component); - if (!importComponentInstance) { - throw new Error( - `Unexpectedly unable to find a component instance for route ${route.route}` - ); + if (isSsrServerManifest(this.#manifest)) { + 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 { + const importComponentInstance = this.#manifest.pageModule; + return importComponentInstance; } - const built = await importComponentInstance(); - return built; } } diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index f0c2e183f4c5..582549e77a5e 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -31,7 +31,7 @@ export type SerializedRouteInfo = Omit & { routeData: SerializedRouteData; }; -type ImportComponentInstance = () => Promise; +export type ImportComponentInstance = () => Promise; export type SSRBaseManifest = SSRServerManifest | SSRServerlessManifest; @@ -50,7 +50,6 @@ export type SSRServerManifest = { entryModules: Record; assets: Set; componentMetadata: SSRResult['componentMetadata']; - middleware?: AstroMiddlewareInstance; pageModule?: undefined; pageMap: Map; }; diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 8a0e74bf9195..55c46f8272bd 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -1,4 +1,4 @@ -import { getVirtualModulePageNameFromPath, getPathFromVirtualModulePageName } from './util.js'; +import { getPathFromVirtualModulePageName, ASTRO_PAGE_EXTENSION_POST_PATTERN } from './util.js'; import type { Plugin as VitePlugin } from 'vite'; import { routeIsRedirect } from '../../redirects/index.js'; import { addRollupInput } from '../add-rollup-input.js'; @@ -7,10 +7,27 @@ 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_MODULE_ID; +/** + * 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) + )}`; +} + export function getVirtualModulePageIdFromPath(path: string) { const name = getVirtualModulePageNameFromPath(path); return '\x00' + name; @@ -28,7 +45,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V if (routeIsRedirect(pageData.route)) { continue; } - inputs.add(getVirtualModulePageNameFromPath(ASTRO_PAGE_MODULE_ID, path)); + inputs.add(getVirtualModulePageNameFromPath(path)); } return addRollupInput(options, Array.from(inputs)); diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 8eab1e4e861c..2b7c99d7eee6 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -13,8 +13,6 @@ import { addRollupInput } from '../add-rollup-input.js'; import { getOutFile, getOutFolder } from '../common.js'; import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin'; -import type { StaticBuildOptions } from '../types'; -import { getVirtualModulePageNameFromPath } from './plugin-pages.js'; import type { OutputChunk, StaticBuildOptions } from '../types'; import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js'; import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js'; @@ -107,15 +105,13 @@ export function pluginSSRServer( options: StaticBuildOptions, internals: BuildInternals ): AstroBuildPlugin { - const ssr = - options.settings.config.output === 'server' || isHybridOutput(options.settings.config); + const ssr = isServerLikeOutput(options.settings.config); return { build: 'ssr', hooks: { 'build:before': () => { let vitePlugin = - // config.build object is optional, so we check NOT EQUAL against "serverless" instead - ssr && options.settings.config.build?.mode !== 'serverless' + ssr && options.settings.config.build.ssrMode === 'server' ? vitePluginSSRServer(internals, options.settings.adapter!, options) : undefined; @@ -129,7 +125,7 @@ export function pluginSSRServer( return; } - if (options.settings.config.build?.mode === 'serverless') { + if (options.settings.config.build.ssrMode === 'serverless') { return; } @@ -158,7 +154,7 @@ function vitePluginSSRServerless( name: '@astrojs/vite-plugin-astro-ssr-serverless', enforce: 'post', options(opts) { - if (options.settings.config.build?.mode === 'serverless') { + if (options.settings.config.build.ssrMode === 'serverless') { const inputs: Set = new Set(); for (const path of Object.keys(options.allPages)) { @@ -232,14 +228,13 @@ export function pluginSSRServerless( options: StaticBuildOptions, internals: BuildInternals ): AstroBuildPlugin { - const ssr = - options.settings.config.output === 'server' || isHybridOutput(options.settings.config); + const ssr = isServerLikeOutput(options.settings.config); return { build: 'ssr', hooks: { 'build:before': () => { let vitePlugin = - ssr && options.settings.config.build.mode === 'serverless' + ssr && options.settings.config.build.ssrMode === 'serverless' ? vitePluginSSRServerless(internals, options.settings.adapter!, options) : undefined; @@ -252,7 +247,7 @@ export function pluginSSRServerless( if (!ssr) { return; } - if (options.settings.config.build?.mode === 'server') { + if (options.settings.config.build.ssrMode === 'server') { return; } @@ -275,13 +270,8 @@ export function pluginSSRServerless( function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) { const imports: string[] = []; const contents: string[] = []; - let middleware; - if (config.experimental?.middleware === true) { - imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`); - middleware = 'middleware: _middleware'; - } let pageMap; - if (config.build.mode === 'serverless') { + if (config.build.ssrMode === 'serverless') { pageMap = 'pageModule'; } else { pageMap = 'pageMap'; @@ -335,7 +325,7 @@ export async function injectManifest( internals: BuildInternals, chunk: Readonly ) { - if (buildOpts.settings.config.build.mode === 'serverless') { + if (buildOpts.settings.config.build.ssrMode === 'serverless') { if (internals.ssrServerlessEntryChunks.size === 0) { throw new Error(`Did not generate an entry chunk for SSR in serverless mode`); } @@ -475,39 +465,3 @@ function buildManifest( return ssrManifest; } - -export function pluginSSR( - options: StaticBuildOptions, - internals: BuildInternals -): AstroBuildPlugin { - const ssr = isServerLikeOutput(options.settings.config); - return { - build: 'ssr', - hooks: { - 'build:before': () => { - let vitePlugin = ssr - ? vitePluginSSR(internals, options.settings.adapter!, options) - : undefined; - - return { - enforce: 'after-user-plugins', - vitePlugin, - }; - }, - 'build:post': async ({ mutate }) => { - if (!ssr) { - return; - } - - if (!internals.ssrEntryChunk) { - throw new Error(`Did not generate an entry chunk for SSR`); - } - // Mutate the filename - internals.ssrEntryChunk.fileName = options.settings.config.build.serverEntry; - - const code = await injectManifest(options, internals); - mutate(internals.ssrEntryChunk, 'server', code); - }, - }, - }; -} diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index e03878c1d611..381e7b8eed2e 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -25,7 +25,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = { redirects: true, inlineStylesheets: 'never', serverlessEntryPrefix: 'entry', - mode: 'server', + ssrMode: 'server', }, compressHTML: false, server: { @@ -126,7 +126,10 @@ export const AstroConfigSchema = z.object({ .string() .optional() .default(ASTRO_CONFIG_DEFAULTS.build.serverlessEntryPrefix), - mode: z.enum(['server', 'serverless']).optional().default(ASTRO_CONFIG_DEFAULTS.build.server), + ssrMode: z + .enum(['server', 'serverless']) + .optional() + .default(ASTRO_CONFIG_DEFAULTS.build.ssrMode), }) .optional() .default({}), @@ -290,7 +293,10 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) { .string() .optional() .default(ASTRO_CONFIG_DEFAULTS.build.serverlessEntryPrefix), - mode: z.enum(['server', 'serverless']).optional().default(ASTRO_CONFIG_DEFAULTS.build.mode), + ssrMode: z + .enum(['server', 'serverless']) + .optional() + .default(ASTRO_CONFIG_DEFAULTS.build.ssrMode), }) .optional() .default({}), diff --git a/packages/astro/test/fixtures/ssr-request/astro.config.mjs b/packages/astro/test/fixtures/ssr-request/astro.config.mjs index d5d304da91be..ec813582afb7 100644 --- a/packages/astro/test/fixtures/ssr-request/astro.config.mjs +++ b/packages/astro/test/fixtures/ssr-request/astro.config.mjs @@ -3,6 +3,6 @@ import { defineConfig } from 'astro/config'; // https://astro.build/config export default defineConfig({ build: { - mode: "serverless" + ssrMode: "server" } }); diff --git a/packages/astro/test/fixtures/ssr-serverless-manifest/astro.config.mjs b/packages/astro/test/fixtures/ssr-serverless-manifest/astro.config.mjs index 59ffc57a69a2..6429a1c4039a 100644 --- a/packages/astro/test/fixtures/ssr-serverless-manifest/astro.config.mjs +++ b/packages/astro/test/fixtures/ssr-serverless-manifest/astro.config.mjs @@ -1,7 +1,7 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ build: { - mode: "serverless" + ssrMode: "serverless" }, output: "server" }) \ No newline at end of file diff --git a/packages/astro/test/fixtures/ssr-serverless/astro.config.mjs b/packages/astro/test/fixtures/ssr-serverless/astro.config.mjs index 59ffc57a69a2..6429a1c4039a 100644 --- a/packages/astro/test/fixtures/ssr-serverless/astro.config.mjs +++ b/packages/astro/test/fixtures/ssr-serverless/astro.config.mjs @@ -1,7 +1,7 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ build: { - mode: "serverless" + ssrMode: "serverless" }, output: "server" }) \ No newline at end of file diff --git a/packages/astro/test/ssr-serverless-manifest.test.js b/packages/astro/test/ssr-serverless-manifest.test.js index 98588ba5aee8..ed5488df4554 100644 --- a/packages/astro/test/ssr-serverless-manifest.test.js +++ b/packages/astro/test/ssr-serverless-manifest.test.js @@ -3,7 +3,7 @@ import { loadFixture } from './test-utils.js'; import testAdapter from './test-adapter.js'; import * as cheerio from 'cheerio'; -describe('astro:ssr-manifest', () => { +describe('astro:ssr-manifest, serverless', () => { /** @type {import('./test-utils').Fixture} */ let fixture; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82a7e337a9a2..ef335128075a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3323,6 +3323,18 @@ importers: specifier: ^10.11.0 version: 10.13.2 + packages/astro/test/fixtures/ssr-serverless: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + + packages/astro/test/fixtures/ssr-serverless-manifest: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/static-build: dependencies: '@astrojs/preact':