summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/small-horses-protect.md6
-rw-r--r--packages/astro/src/core/app/index.ts80
-rw-r--r--packages/astro/src/core/app/node.ts2
-rw-r--r--packages/astro/src/core/app/types.ts7
-rw-r--r--packages/astro/src/core/build/add-rollup-input.ts43
-rw-r--r--packages/astro/src/core/build/common.ts1
-rw-r--r--packages/astro/src/core/build/generate.ts76
-rw-r--r--packages/astro/src/core/build/internal.ts88
-rw-r--r--packages/astro/src/core/build/page-data.ts10
-rw-r--r--packages/astro/src/core/build/static-build.ts15
-rw-r--r--packages/astro/src/core/build/types.d.ts17
-rw-r--r--packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts11
-rw-r--r--packages/astro/src/core/build/vite-plugin-pages.ts58
-rw-r--r--packages/astro/src/core/build/vite-plugin-ssr.ts70
-rw-r--r--packages/astro/src/core/routing/manifest/serialization.ts8
-rw-r--r--packages/astro/src/core/util.ts5
-rw-r--r--packages/astro/src/vite-plugin-build-css/index.ts22
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js10
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/src/pages/index.astro6
-rw-r--r--packages/astro/test/ssr-api-route.test.js39
-rw-r--r--packages/astro/test/ssr-dynamic.test.js5
-rw-r--r--packages/astro/test/static-build.test.js14
-rw-r--r--packages/astro/test/test-adapter.js4
-rw-r--r--packages/astro/test/test-utils.js7
-rw-r--r--packages/integrations/node/src/server.ts12
25 files changed, 428 insertions, 188 deletions
diff --git a/.changeset/small-horses-protect.md b/.changeset/small-horses-protect.md
new file mode 100644
index 000000000..ba6ec4fd7
--- /dev/null
+++ b/.changeset/small-horses-protect.md
@@ -0,0 +1,6 @@
+---
+'astro': patch
+'@astrojs/node': patch
+---
+
+Improves the build by building to a single file for rendering
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index b14fad504..050802ee0 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -1,10 +1,12 @@
-import type { ComponentInstance, ManifestData, RouteData, SSRLoadedRenderer } from '../../@types/astro';
+import type { ComponentInstance, EndpointHandler, ManifestData, RouteData } from '../../@types/astro';
import type { SSRManifest as Manifest, RouteInfo } from './types';
+import mime from 'mime';
import { defaultLogOptions } from '../logger.js';
export { deserializeManifest } from './common.js';
import { matchRoute } from '../routing/match.js';
import { render } from '../render/core.js';
+import { call as callEndpoint } from '../endpoint/index.js';
import { RouteCache } from '../render/route-cache.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
import { prependForwardSlash } from '../path.js';
@@ -12,20 +14,17 @@ import { prependForwardSlash } from '../path.js';
export class App {
#manifest: Manifest;
#manifestData: ManifestData;
- #rootFolder: URL;
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
#routeCache: RouteCache;
- #renderersPromise: Promise<SSRLoadedRenderer[]>;
+ #encoder = new TextEncoder();
- constructor(manifest: Manifest, rootFolder: URL) {
+ constructor(manifest: Manifest) {
this.#manifest = manifest;
this.#manifestData = {
routes: manifest.routes.map((route) => route.routeData),
};
- this.#rootFolder = rootFolder;
this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route]));
this.#routeCache = new RouteCache(defaultLogOptions);
- this.#renderersPromise = this.#loadRenderers();
}
match(request: Request): RouteData | undefined {
const url = new URL(request.url);
@@ -42,11 +41,22 @@ export class App {
}
}
- const manifest = this.#manifest;
- const info = this.#routeDataToRouteInfo.get(routeData!)!;
- const [mod, renderers] = await Promise.all([this.#loadModule(info.file), this.#renderersPromise]);
+ const mod = this.#manifest.pageMap.get(routeData.component)!;
+
+ if(routeData.type === 'page') {
+ return this.#renderPage(request, routeData, mod);
+ } else if(routeData.type === 'endpoint') {
+ return this.#callEndpoint(request, routeData, mod);
+ } else {
+ throw new Error(`Unsupported route type [${routeData.type}].`);
+ }
+ }
+ async #renderPage(request: Request, routeData: RouteData, mod: ComponentInstance): Promise<Response> {
const url = new URL(request.url);
+ const manifest = this.#manifest;
+ const renderers = manifest.renderers;
+ const info = this.#routeDataToRouteInfo.get(routeData!)!;
const links = createLinkStylesheetElementSet(info.links, manifest.site);
const scripts = createModuleScriptElementWithSrcSet(info.scripts, manifest.site);
@@ -80,26 +90,44 @@ export class App {
}
let html = result.html;
- return new Response(html, {
+ let bytes = this.#encoder.encode(html);
+ return new Response(bytes, {
status: 200,
+ headers: {
+ 'Content-Type': 'text/html',
+ 'Content-Length': bytes.byteLength.toString()
+ }
});
}
- async #loadRenderers(): Promise<SSRLoadedRenderer[]> {
- return await Promise.all(
- this.#manifest.renderers.map(async (renderer) => {
- const mod = (await import(renderer.serverEntrypoint)) as { default: SSRLoadedRenderer['ssr'] };
- return { ...renderer, ssr: mod.default };
- })
- );
- }
- async #loadModule(rootRelativePath: string): Promise<ComponentInstance> {
- let modUrl = new URL(rootRelativePath, this.#rootFolder).toString();
- let mod: ComponentInstance;
- try {
- mod = await import(modUrl);
- return mod;
- } catch (err) {
- throw new Error(`Unable to import ${modUrl}. Does this file exist?`);
+
+ async #callEndpoint(request: Request, routeData: RouteData, mod: ComponentInstance): Promise<Response> {
+ const url = new URL(request.url);
+ const handler = mod as unknown as EndpointHandler;
+ const result = await callEndpoint(handler, {
+ headers: request.headers,
+ logging: defaultLogOptions,
+ method: request.method,
+ origin: url.origin,
+ pathname: url.pathname,
+ routeCache: this.#routeCache,
+ ssr: true,
+ });
+
+ if(result.type === 'response') {
+ return result.response;
+ } else {
+ const body = result.body;
+ const headers = new Headers();
+ const mimeType = mime.getType(url.pathname);
+ if(mimeType) {
+ headers.set('Content-Type', mimeType);
+ }
+ const bytes = this.#encoder.encode(body);
+ headers.set('Content-Length', bytes.byteLength.toString());
+ return new Response(bytes, {
+ status: 200,
+ headers
+ });
}
}
}
diff --git a/packages/astro/src/core/app/node.ts b/packages/astro/src/core/app/node.ts
index 310244c74..8eee93d0f 100644
--- a/packages/astro/src/core/app/node.ts
+++ b/packages/astro/src/core/app/node.ts
@@ -33,5 +33,5 @@ export async function loadManifest(rootFolder: URL): Promise<SSRManifest> {
export async function loadApp(rootFolder: URL): Promise<NodeApp> {
const manifest = await loadManifest(rootFolder);
- return new NodeApp(manifest, rootFolder);
+ return new NodeApp(manifest);
}
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index d78a94050..ea4bd9cc0 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -1,4 +1,6 @@
-import type { RouteData, SerializedRouteData, MarkdownRenderOptions, AstroRenderer } from '../../@types/astro';
+import type { RouteData, SerializedRouteData, MarkdownRenderOptions, ComponentInstance, SSRLoadedRenderer } from '../../@types/astro';
+
+export type ComponentPath = string;
export interface RouteInfo {
routeData: RouteData;
@@ -17,7 +19,8 @@ export interface SSRManifest {
markdown: {
render: MarkdownRenderOptions;
};
- renderers: AstroRenderer[];
+ pageMap: Map<ComponentPath, ComponentInstance>;
+ renderers: SSRLoadedRenderer[];
entryModules: Record<string, string>;
}
diff --git a/packages/astro/src/core/build/add-rollup-input.ts b/packages/astro/src/core/build/add-rollup-input.ts
new file mode 100644
index 000000000..79feb3a7d
--- /dev/null
+++ b/packages/astro/src/core/build/add-rollup-input.ts
@@ -0,0 +1,43 @@
+import { InputOptions } from 'rollup';
+
+function fromEntries<V>(entries: [string, V][]) {
+ const obj: Record<string, V> = {};
+ for (const [k, v] of entries) {
+ obj[k] = v;
+ }
+ return obj;
+}
+
+export function addRollupInput(inputOptions: InputOptions, newInputs: string[]): InputOptions {
+ // Add input module ids to existing input option, whether it's a string, array or object
+ // this way you can use multiple html plugins all adding their own inputs
+ if (!inputOptions.input) {
+ return { ...inputOptions, input: newInputs };
+ }
+
+ if (typeof inputOptions.input === 'string') {
+ return {
+ ...inputOptions,
+ input: [inputOptions.input, ...newInputs],
+ };
+ }
+
+ if (Array.isArray(inputOptions.input)) {
+ return {
+ ...inputOptions,
+ input: [...inputOptions.input, ...newInputs],
+ };
+ }
+
+ if (typeof inputOptions.input === 'object') {
+ return {
+ ...inputOptions,
+ input: {
+ ...inputOptions.input,
+ ...fromEntries(newInputs.map((i) => [i.split('/').slice(-1)[0].split('.')[0], i])),
+ },
+ };
+ }
+
+ throw new Error(`Unknown rollup input type. Supported inputs are string, array and object.`);
+}
diff --git a/packages/astro/src/core/build/common.ts b/packages/astro/src/core/build/common.ts
index fca513781..074170762 100644
--- a/packages/astro/src/core/build/common.ts
+++ b/packages/astro/src/core/build/common.ts
@@ -1,4 +1,5 @@
import type { AstroConfig, RouteType } from '../../@types/astro';
+import type { StaticBuildOptions } from './types';
import npath from 'path';
import { appendForwardSlash } from '../../core/path.js';
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index d3d2365b4..cbd5b3c2b 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -1,20 +1,21 @@
+import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
+import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
+import type { PageBuildData, StaticBuildOptions, SingleFileBuiltModule } from './types';
+import type { BuildInternals } from '../../core/build/internal.js';
+import type { RenderOptions } from '../../core/render/core';
+
import fs from 'fs';
-import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors';
import npath from 'path';
-import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
import { fileURLToPath } from 'url';
-import type { AstroConfig, AstroRenderer, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
-import type { BuildInternals } from '../../core/build/internal.js';
import { debug, error, info } from '../../core/logger.js';
import { prependForwardSlash } from '../../core/path.js';
-import type { RenderOptions } from '../../core/render/core';
-import { resolveDependency } from '../../core/util.js';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { render } from '../render/core.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
-import { getOutFile, getOutFolder, getOutRoot } from './common.js';
-import type { PageBuildData, StaticBuildOptions } from './types';
+import { getOutFile, getOutRoot, getOutFolder, getServerRoot } from './common.js';
+import { getPageDataByComponent, eachPageData } from './internal.js';
+import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors';
import { getTimeStat } from './util.js';
// Render is usually compute, which Node.js can't parallelize well.
@@ -23,24 +24,6 @@ import { getTimeStat } from './util.js';
// system, possibly one that parallelizes if async IO is detected.
const MAX_CONCURRENT_RENDERS = 1;
-// Utility functions
-async function loadRenderer(renderer: AstroRenderer, config: AstroConfig): Promise<SSRLoadedRenderer> {
- const mod = (await import(resolveDependency(renderer.serverEntrypoint, config))) as { default: SSRLoadedRenderer['ssr'] };
- return { ...renderer, ssr: mod.default };
-}
-
-async function loadRenderers(config: AstroConfig): Promise<SSRLoadedRenderer[]> {
- return Promise.all(config._ctx.renderers.map((r) => loadRenderer(r, config)));
-}
-
-export function getByFacadeId<T>(facadeId: string, map: Map<string, T>): T | undefined {
- return (
- map.get(facadeId) ||
- // Windows the facadeId has forward slashes, no idea why
- map.get(facadeId.replace(/\//g, '\\'))
- );
-}
-
// Throttle the rendering a paths to prevents creating too many Promises on the microtask queue.
function* throttle(max: number, inPaths: string[]) {
let tmp = [];
@@ -86,45 +69,42 @@ export function chunkIsPage(astroConfig: AstroConfig, output: OutputAsset | Outp
export async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
info(opts.logging, null, `\n${bgMagenta(black(' generating static routes '))}\n`);
- // Get renderers to be shared for each page generation.
- const renderers = await loadRenderers(opts.astroConfig);
+ const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint;
+ const outFolder = ssr ? getServerRoot(opts.astroConfig) : getOutRoot(opts.astroConfig);
+ const ssrEntryURL = new URL(`./entry.mjs?time=${Date.now()}`, outFolder);
+ const ssrEntry = await import(ssrEntryURL.toString());
- for (let output of result.output) {
- if (chunkIsPage(opts.astroConfig, output, internals)) {
- await generatePage(output as OutputChunk, opts, internals, facadeIdToPageDataMap, renderers);
- }
+ for(const pageData of eachPageData(internals)) {
+ await generatePage(opts, internals, pageData, ssrEntry);
}
}
async function generatePage(
- output: OutputChunk,
+ //output: OutputChunk,
opts: StaticBuildOptions,
internals: BuildInternals,
- facadeIdToPageDataMap: Map<string, PageBuildData>,
- renderers: SSRLoadedRenderer[]
+ pageData: PageBuildData,
+ ssrEntry: SingleFileBuiltModule
) {
- let timeStart = performance.now();
- const { astroConfig } = opts;
-
- let url = new URL('./' + output.fileName, getOutRoot(astroConfig));
- const facadeId: string = output.facadeModuleId as string;
- let pageData = getByFacadeId<PageBuildData>(facadeId, facadeIdToPageDataMap);
+ let timeStart = performance.now();
+ const renderers = ssrEntry.renderers;
- if (!pageData) {
- throw new Error(`Unable to find a PageBuildData for the Astro page: ${facadeId}. There are the PageBuildDatas we have ${Array.from(facadeIdToPageDataMap.keys()).join(', ')}`);
- }
+ const pageInfo = getPageDataByComponent(internals, pageData.route.component);
+ const linkIds: string[] = Array.from(pageInfo?.css ?? []);
+ const hoistedId = pageInfo?.hoistedScript ?? null;
- const linkIds = getByFacadeId<string[]>(facadeId, internals.facadeIdToAssetsMap) || [];
- const hoistedId = getByFacadeId<string>(facadeId, internals.facadeIdToHoistedEntryMap) || null;
+ const pageModule = ssrEntry.pageMap.get(pageData.component);
- let compiledModule = await import(url.toString());
+ if(!pageModule) {
+ throw new Error(`Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.`);
+ }
const generationOptions: Readonly<GeneratePathOptions> = {
pageData,
internals,
linkIds,
hoistedId,
- mod: compiledModule,
+ mod: pageModule,
renderers,
};
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 9185e7e89..62186f678 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -1,26 +1,47 @@
+import type { RouteData } from '../../@types/astro';
import type { RenderedChunk } from 'rollup';
+import type { PageBuildData, ViteID } from './types';
+
+import { viteID } from '../util.js';
export interface BuildInternals {
// Pure CSS chunks are chunks that only contain CSS.
pureCSSChunks: Set<RenderedChunk>;
- // chunkToReferenceIdMap maps them to a hash id used to find the final file.
- chunkToReferenceIdMap: Map<string, string>;
-
- // This is a mapping of pathname to the string source of all collected
- // inline <style> for a page.
- astroStyleMap: Map<string, string>;
- // This is a virtual JS module that imports all dependent styles for a page.
- astroPageStyleMap: Map<string, string>;
-
- // A mapping to entrypoints (facadeId) to assets (styles) that are added.
- facadeIdToAssetsMap: Map<string, string[]>;
+ // TODO document what this is
hoistedScriptIdToHoistedMap: Map<string, Set<string>>;
- facadeIdToHoistedEntryMap: Map<string, string>;
// A mapping of specifiers like astro/client/idle.js to the hashed bundled name.
// Used to render pages with the correct specifiers.
entrySpecifierToBundleMap: Map<string, string>;
+
+ /**
+ * A map for page-specific information.
+ */
+ pagesByComponent: Map<string, PageBuildData>;
+
+ /**
+ * A map for page-specific information by Vite ID (a path-like string)
+ */
+ pagesByViteID: Map<ViteID, PageBuildData>;
+
+ /**
+ * chunkToReferenceIdMap maps them to a hash id used to find the final file.
+ * @deprecated This Map is only used for the legacy build.
+ */
+ chunkToReferenceIdMap: Map<string, string>;
+
+ /**
+ * This is a mapping of pathname to the string source of all collected inline <style> for a page.
+ * @deprecated This Map is only used for the legacy build.
+ */
+ astroStyleMap: Map<string, string>;
+
+ /**
+ * This is a virtual JS module that imports all dependent styles for a page.
+ * @deprecated This Map is only used for the legacy build.
+ */
+ astroPageStyleMap: Map<string, string>;
}
/**
@@ -39,21 +60,52 @@ export function createBuildInternals(): BuildInternals {
// This is a virtual JS module that imports all dependent styles for a page.
const astroPageStyleMap = new Map<string, string>();
- // A mapping to entrypoints (facadeId) to assets (styles) that are added.
- const facadeIdToAssetsMap = new Map<string, string[]>();
-
// These are for tracking hoisted script bundling
const hoistedScriptIdToHoistedMap = new Map<string, Set<string>>();
- const facadeIdToHoistedEntryMap = new Map<string, string>();
return {
pureCSSChunks,
chunkToReferenceIdMap,
astroStyleMap,
astroPageStyleMap,
- facadeIdToAssetsMap,
hoistedScriptIdToHoistedMap,
- facadeIdToHoistedEntryMap,
entrySpecifierToBundleMap: new Map<string, string>(),
+
+ pagesByComponent: new Map(),
+ pagesByViteID: new Map(),
};
}
+
+export function trackPageData(internals: BuildInternals, component: string, pageData: PageBuildData, componentModuleId: string, componentURL: URL): void {
+ pageData.moduleSpecifier = componentModuleId;
+ internals.pagesByComponent.set(component, pageData);
+ internals.pagesByViteID.set(viteID(componentURL), pageData);
+}
+
+
+export function * getPageDatasByChunk(internals: BuildInternals, chunk: RenderedChunk): Generator<PageBuildData, void, unknown> {
+ const pagesByViteID = internals.pagesByViteID;
+ for(const [modulePath] of Object.entries(chunk.modules)) {
+ if(pagesByViteID.has(modulePath)) {
+ yield pagesByViteID.get(modulePath)!;
+ }
+ }
+}
+
+export function getPageDataByComponent(internals: BuildInternals, component: string): PageBuildData | undefined {
+ if(internals.pagesByComponent.has(component)) {
+ return internals.pagesByComponent.get(component);
+ }
+ return undefined;
+}
+
+export function getPageDataByViteID(internals: BuildInternals, viteid: ViteID): PageBuildData | undefined {
+ if(internals.pagesByViteID.has(viteid)) {
+ return internals.pagesByViteID.get(viteid);
+ }
+ return undefined;
+}
+
+export function * eachPageData(internals: BuildInternals) {
+ yield * internals.pagesByComponent.values();
+}
diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts
index 946ebbefc..7681c0664 100644
--- a/packages/astro/src/core/build/page-data.ts
+++ b/packages/astro/src/core/build/page-data.ts
@@ -58,8 +58,13 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
clearInterval(routeCollectionLogTimeout);
}, 10000);
allPages[route.component] = {
+ component: route.component,
route,
paths: [route.pathname],
+ moduleSpecifier: '',
+ css: new Set(),
+ hoistedScript: undefined,
+ scripts: new Set(),
preload: await ssrPreload({
astroConfig,
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
@@ -120,8 +125,13 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
}
const finalPaths = result.staticPaths.map((staticPath) => staticPath.params && route.generate(staticPath.params)).filter(Boolean);
allPages[route.component] = {
+ component: route.component,
route,
paths: finalPaths,
+ moduleSpecifier: '',
+ css: new Set(),
+ hoistedScript: undefined,
+ scripts: new Set(),
preload: await ssrPreload({
astroConfig,
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 62e148c50..64579c2c8 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -16,8 +16,11 @@ import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js'
import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js';
import { vitePluginInternals } from './vite-plugin-internals.js';
import { vitePluginSSR } from './vite-plugin-ssr.js';
+import { vitePluginPages } from './vite-plugin-pages.js';
import { generatePages } from './generate.js';
+import { trackPageData } from './internal.js';
import { getClientRoot, getServerRoot, getOutRoot } from './common.js';
+import { isBuildingToSSR } from '../util.js';
import { getTimeStat } from './util.js';
export async function staticBuild(opts: StaticBuildOptions) {
@@ -45,6 +48,9 @@ export async function staticBuild(opts: StaticBuildOptions) {
const astroModuleURL = new URL('./' + component, astroConfig.projectRoot);
const astroModuleId = prependForwardSlash(component);
+ // Track the page data in internals
+ trackPageData(internals, component, pageData, astroModuleId, astroModuleURL);
+
if (pageData.route.type === 'page') {
const [renderers, mod] = pageData.preload;
const metadata = mod.$$metadata;
@@ -96,7 +102,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
timer.generate = performance.now();
if (opts.buildConfig.staticMode) {
- console.log('huh?');
await generatePages(ssrResult, opts, internals, facadeIdToPageDataMap);
await cleanSsrOutput(opts);
} else {
@@ -122,10 +127,10 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
outDir: fileURLToPath(out),
ssr: true,
rollupOptions: {
- input: Array.from(input),
+ input: [],
output: {
format: 'esm',
- entryFileNames: 'entry.[hash].mjs',
+ entryFileNames: 'entry.mjs',
chunkFileNames: 'chunks/chunk.[hash].mjs',
assetFileNames: 'assets/asset.[hash][extname]',
},
@@ -139,12 +144,14 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
},
plugins: [
vitePluginInternals(input, internals),
+ vitePluginPages(opts, internals),
rollupPluginAstroBuildCSS({
internals,
}),
...(viteConfig.plugins || []),
// SSR needs to be last
- opts.astroConfig._ctx.adapter?.serverEntrypoint && vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter),
+ isBuildingToSSR(opts.astroConfig) &&
+ vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter!),
],
publicDir: ssr ? false : viteConfig.publicDir,
root: viteConfig.root,
diff --git a/packages/astro/src/core/build/types.d.ts b/packages/astro/src/core/build/types.d.ts
index 33278ea8b..9bdab7582 100644
--- a/packages/astro/src/core/build/types.d.ts
+++ b/packages/astro/src/core/build/types.d.ts
@@ -1,15 +1,23 @@
import type { ComponentPreload } from '../render/dev/index';
-import type { AstroConfig, BuildConfig, ManifestData, RouteData } from '../../@types/astro';
+import type { AstroConfig, BuildConfig, ManifestData, RouteData, ComponentInstance, SSRLoadedRenderer } from '../../@types/astro';
import type { ViteConfigWithSSR } from '../../create-vite';
import type { LogOptions } from '../../logger';
import type { RouteCache } from '../../render/route-cache.js';
+export type ComponentPath = string;
+export type ViteID = string;
+
export interface PageBuildData {
+ component: ComponentPath;
paths: string[];
preload: ComponentPreload;
route: RouteData;
+ moduleSpecifier: string;
+ css: Set<string>;
+ hoistedScript: string | undefined;
+ scripts: Set<string>;
}
-export type AllPagesData = Record<string, PageBuildData>;
+export type AllPagesData = Record<ComponentPath, PageBuildData>;
/** Options for the static build */
export interface StaticBuildOptions {
@@ -23,3 +31,8 @@ export interface StaticBuildOptions {
routeCache: RouteCache;
viteConfig: ViteConfigWithSSR;
}
+
+export interface SingleFileBuiltModule {
+ pageMap: Map<ComponentPath, ComponentInstance>;
+ renderers: SSRLoadedRenderer[];
+}
diff --git a/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts b/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts
index 13adf93f3..bad98209e 100644
--- a/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts
+++ b/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts
@@ -1,7 +1,8 @@
import type { AstroConfig } from '../../@types/astro';
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from '../../core/build/internal.js';
-import { fileURLToPath } from 'url';
+import { viteID } from '../util.js';
+import { getPageDataByViteID } from './internal.js';
function virtualHoistedEntry(id: string) {
return id.endsWith('.astro/hoisted.js') || id.endsWith('.md/hoisted.js');
@@ -37,8 +38,12 @@ export function vitePluginHoistedScripts(astroConfig: AstroConfig, internals: Bu
if (output.type === 'chunk' && output.facadeModuleId && virtualHoistedEntry(output.facadeModuleId)) {
const facadeId = output.facadeModuleId!;
const pathname = facadeId.slice(0, facadeId.length - '/hoisted.js'.length);
- const filename = fileURLToPath(new URL('.' + pathname, astroConfig.projectRoot));
- internals.facadeIdToHoistedEntryMap.set(filename, id);
+
+ const vid = viteID(new URL('.' + pathname, astroConfig.projectRoot));
+ const pageInfo = getPageDataByViteID(internals, vid);
+ if(pageInfo) {
+ pageInfo.hoistedScript = id;
+ }
}
}
},
diff --git a/packages/astro/src/core/build/vite-plugin-pages.ts b/packages/astro/src/core/build/vite-plugin-pages.ts
new file mode 100644
index 000000000..1a01e9672
--- /dev/null
+++ b/packages/astro/src/core/build/vite-plugin-pages.ts
@@ -0,0 +1,58 @@
+
+import type { Plugin as VitePlugin } from 'vite';
+import type { BuildInternals } from './internal.js';
+import type { StaticBuildOptions } from './types';
+import { addRollupInput } from './add-rollup-input.js';
+import { eachPageData } from './internal.js';
+import { isBuildingToSSR } from '../util.js';
+
+export const virtualModuleId = '@astrojs-pages-virtual-entry';
+const resolvedVirtualModuleId = '\0' + virtualModuleId;
+
+export function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
+ return {
+ name: '@astro/plugin-build-pages',
+
+ options(options) {
+ if(!isBuildingToSSR(opts.astroConfig)) {
+ return addRollupInput(options, [virtualModuleId]);
+ }
+ },
+
+ resolveId(id) {
+ if(id === virtualModuleId) {
+ return resolvedVirtualModuleId;
+ }
+ },
+
+ load(id) {
+ if(id === resolvedVirtualModuleId) {
+ let importMap = '';
+ let imports = [];
+ let i = 0;
+ for(const pageData of eachPageData(internals)) {
+ const variable = `_page${i}`;
+ imports.push(`import * as ${variable} from '${pageData.moduleSpecifier}';`);
+ importMap += `['${pageData.component}', ${variable}],`;
+ i++;
+ }
+
+ i = 0;
+ let rendererItems = '';
+ for(const renderer of opts.astroConfig._ctx.renderers) {
+ const variable = `_renderer${i}`;
+ imports.push(`import ${variable} from '${renderer.serverEntrypoint}';`);
+ rendererItems += `Object.assign(${JSON.stringify(renderer)}, { ssr: ${variable} }),`
+ i++;
+ }
+
+ const def = `${imports.join('\n')}
+
+export const pageMap = new Map([${importMap}]);
+export const renderers = [${rendererItems}];`;
+
+ return def;
+ }
+ }
+ };
+}
diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts
index 0ea9f1d84..989a0ceb8 100644
--- a/packages/astro/src/core/build/vite-plugin-ssr.ts
+++ b/packages/astro/src/core/build/vite-plugin-ssr.ts
@@ -5,11 +5,13 @@ import type { AstroAdapter } from '../../@types/astro';
import type { StaticBuildOptions } from './types';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../app/types';
-import { chunkIsPage, rootRelativeFacadeId, getByFacadeId } from './generate.js';
import { serializeRouteData } from '../routing/index.js';
+import { eachPageData } from './internal.js';
+import { addRollupInput } from './add-rollup-input.js';
+import { virtualModuleId as pagesVirtualModuleId } from './vite-plugin-pages.js';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
-const virtualModuleId = '@astrojs-ssr-virtual-entry';
+export const virtualModuleId = '@astrojs-ssr-virtual-entry';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
@@ -17,13 +19,7 @@ export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInt
return {
name: '@astrojs/vite-plugin-astro-ssr',
options(opts) {
- if (Array.isArray(opts.input)) {
- opts.input.push(virtualModuleId);
- } else {
- return {
- input: [virtualModuleId],
- };
- }
+ return addRollupInput(opts, [virtualModuleId]);
},
resolveId(id) {
if (id === virtualModuleId) {
@@ -33,8 +29,12 @@ export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInt
load(id) {
if (id === resolvedVirtualModuleId) {
return `import * as adapter from '${adapter.serverEntrypoint}';
+import * as _main from '${pagesVirtualModuleId}';
import { deserializeManifest as _deserializeManifest } from 'astro/app';
-const _manifest = _deserializeManifest('${manifestReplace}');
+const _manifest = Object.assign(_deserializeManifest('${manifestReplace}'), {
+ pageMap: _main.pageMap,
+ renderers: _main.renderers
+});
${
adapter.exports
@@ -52,57 +52,38 @@ if(_start in adapter) {
},
generateBundle(opts, bundle) {
- const manifest = buildManifest(bundle, buildOpts, internals);
-
- for (const [_chunkName, chunk] of Object.entries(bundle)) {
- if (chunk.type === 'asset') continue;
- if (chunk.modules[resolvedVirtualModuleId]) {
+ const manifest = buildManifest(buildOpts, internals);
+
+ for(const [_chunkName, chunk] of Object.entries(bundle)) {
+ if(chunk.type === 'asset') continue;
+ if(chunk.modules[resolvedVirtualModuleId]) {
const exp = new RegExp(`['"]${manifestReplace}['"]`);
const code = chunk.code;
chunk.code = code.replace(exp, () => {
return JSON.stringify(manifest);
});
- chunk.fileName = 'entry.mjs';
}
}
},
};
}
-function buildManifest(bundle: OutputBundle, opts: StaticBuildOptions, internals: BuildInternals): SerializedSSRManifest {
- const { astroConfig, manifest } = opts;
-
- const rootRelativeIdToChunkMap = new Map<string, OutputChunk>();
- for (const [_outputName, output] of Object.entries(bundle)) {
- if (chunkIsPage(astroConfig, output, internals)) {
- const chunk = output as OutputChunk;
- if (chunk.facadeModuleId) {
- const id = rootRelativeFacadeId(chunk.facadeModuleId, astroConfig);
- rootRelativeIdToChunkMap.set(id, chunk);
- }
- }
- }
+function buildManifest(opts: StaticBuildOptions, internals: BuildInternals): SerializedSSRManifest {
+ const { astroConfig } = opts;
const routes: SerializedRouteInfo[] = [];
- for (const routeData of manifest.routes) {
- const componentPath = routeData.component;
-
- if (!rootRelativeIdToChunkMap.has(componentPath)) {
- throw new Error('Unable to find chunk for ' + componentPath);
+ for(const pageData of eachPageData(internals)) {
+ const scripts = Array.from(pageData.scripts);
+ if(pageData.hoistedScript) {
+ scripts.unshift(pageData.hoistedScript);
}
- const chunk = rootRelativeIdToChunkMap.get(componentPath)!;
- const facadeId = chunk.facadeModuleId!;
- const links = getByFacadeId<string[]>(facadeId, internals.facadeIdToAssetsMap) || [];
- const hoistedScript = getByFacadeId<string>(facadeId, internals.facadeIdToHoistedEntryMap);
- const scripts = hoistedScript ? [hoistedScript] : [];
-
routes.push({
- file: chunk.fileName,
- links,
+ file: '',
+ links: Array.from(pageData.css),
scripts,
- routeData: serializeRouteData(routeData),
+ routeData: serializeRouteData(pageData.route),
});
}
@@ -116,7 +97,8 @@ function buildManifest(bundle: OutputBundle, opts: StaticBuildOptions, internals
markdown: {
render: astroConfig.markdownOptions.render,
},
- renderers: astroConfig._ctx.renderers,
+ pageMap: null as any,
+ renderers: [],
entryModules,
};
diff --git a/packages/astro/src/core/routing/manifest/serialization.ts b/packages/astro/src/core/routing/manifest/serialization.ts
index 521a31931..8e3a42204 100644
--- a/packages/astro/src/core/routing/manifest/serialization.ts
+++ b/packages/astro/src/core/routing/manifest/serialization.ts
@@ -1,8 +1,8 @@
import type { RouteData, SerializedRouteData } from '../../../@types/astro';
-function createRouteData(pattern: RegExp, params: string[], component: string, pathname: string | undefined): RouteData {
+function createRouteData(pattern: RegExp, params: string[], component: string, pathname: string | undefined, type: 'page' | 'endpoint'): RouteData {
return {
- type: 'page',
+ type,
pattern,
params,
component,
@@ -20,7 +20,7 @@ export function serializeRouteData(routeData: RouteData): SerializedRouteData {
}
export function deserializeRouteData(rawRouteData: SerializedRouteData) {
- const { component, params, pathname } = rawRouteData;
+ const { component, params, pathname, type } = rawRouteData;
const pattern = new RegExp(rawRouteData.pattern);
- return createRouteData(pattern, params, component, pathname);
+ return createRouteData(pattern, params, component, pathname, type);
}
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 17f06854d..ce5e307b9 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -133,6 +133,10 @@ export function emptyDir(_dir: URL, skip?: Set<string>): void {
}
}
+export function isBuildingToSSR(config: AstroConfig): boolean {
+ return !!config._ctx.adapter?.serverEntrypoint;
+}
+
// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl
/** Basic stylesheet for RSS feeds */
export const PRETTY_FEED_V3 = `<?xml version="1.0" encoding="utf-8"?>
@@ -235,3 +239,4 @@ This file is in BETA. Please test and contribute to the discussion:
</html>
</xsl:template>
</xsl:stylesheet>`;
+
diff --git a/packages/astro/src/vite-plugin-build-css/index.ts b/packages/astro/src/vite-plugin-build-css/index.ts
index e630cd578..e6f8b5fc6 100644
--- a/packages/astro/src/vite-plugin-build-css/index.ts
+++ b/packages/astro/src/vite-plugin-build-css/index.ts
@@ -4,6 +4,7 @@ import * as path from 'path';
import esbuild from 'esbuild';
import { Plugin as VitePlugin } from 'vite';
import { isCSSRequest } from '../core/render/dev/css.js';
+import { getPageDatasByChunk } from '../core/build/internal.js';
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
@@ -137,12 +138,8 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
internals.chunkToReferenceIdMap.set(chunk.fileName, referenceId);
if (chunk.type === 'chunk') {
const fileName = this.getFileName(referenceId);
- if (chunk.facadeModuleId) {
- const facadeId = chunk.facadeModuleId!;
- if (!internals.facadeIdToAssetsMap.has(facadeId)) {
- internals.facadeIdToAssetsMap.set(facadeId, []);
- }
- internals.facadeIdToAssetsMap.get(facadeId)!.push(fileName);
+ for(const pageData of getPageDatasByChunk(internals, chunk)) {
+ pageData.css.add(fileName);
}
}
@@ -161,22 +158,15 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
for (const [chunkId, chunk] of Object.entries(bundle)) {
if (chunk.type === 'chunk') {
- // If the chunk has a facadeModuleId it is an entrypoint chunk.
// This find shared chunks of CSS and adds them to the main CSS chunks,
// so that shared CSS is added to the page.
- if (chunk.facadeModuleId) {
- if (!internals.facadeIdToAssetsMap.has(chunk.facadeModuleId)) {
- internals.facadeIdToAssetsMap.set(chunk.facadeModuleId, []);
- }
- const assets = internals.facadeIdToAssetsMap.get(chunk.facadeModuleId)!;
- const assetSet = new Set(assets);
+ for(const { css: cssSet } of getPageDatasByChunk(internals, chunk)) {
for (const imp of chunk.imports) {
if (internals.chunkToReferenceIdMap.has(imp) && !pureChunkFilenames.has(imp)) {
const referenceId = internals.chunkToReferenceIdMap.get(imp)!;
const fileName = this.getFileName(referenceId);
- if (!assetSet.has(fileName)) {
- assetSet.add(fileName);
- assets.push(fileName);
+ if (!cssSet.has(fileName)) {
+ cssSet.add(fileName);
}
}
}
diff --git a/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js b/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js
new file mode 100644
index 000000000..0003f2ad4
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/src/pages/food.json.js
@@ -0,0 +1,10 @@
+
+export function get() {
+ return {
+ body: JSON.stringify([
+ { name: 'lettuce' },
+ { name: 'broccoli' },
+ { name: 'pizza' }
+ ])
+ };
+}
diff --git a/packages/astro/test/fixtures/ssr-api-route/src/pages/index.astro b/packages/astro/test/fixtures/ssr-api-route/src/pages/index.astro
new file mode 100644
index 000000000..53e029f04
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/src/pages/index.astro
@@ -0,0 +1,6 @@
+<html>
+<head><title>Testing</title></head>
+<body>
+ <h1>Testing</h1>
+</body>
+</html>
diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js
new file mode 100644
index 000000000..4555dd56d
--- /dev/null
+++ b/packages/astro/test/ssr-api-route.test.js
@@ -0,0 +1,39 @@
+import { expect } from 'chai';
+import { loadFixture } from './test-utils.js';
+import testAdapter from './test-adapter.js';
+
+// Asset bundling
+describe('API routes in SSR', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ projectRoot: './fixtures/ssr-api-route/',
+ buildOptions: {
+ experimentalSsr: true,
+ },
+ adapter: testAdapter()
+ });
+ await fixture.build();
+ });
+
+ it('Basic pages work', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/');
+ const response = await app.render(request);
+ const html = await response.text();
+ expect(html).to.not.be.empty;
+ });
+
+ it('Can load the API route too', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/food.json');
+ const response = await app.render(request);
+ expect(response.status).to.equal(200);
+ expect(response.headers.get('Content-Type')).to.equal('application/json');
+ expect(response.headers.get('Content-Length')).to.not.be.empty;
+ const body = await response.json();
+ expect(body.length).to.equal(3);
+ });
+});
diff --git a/packages/astro/test/ssr-dynamic.test.js b/packages/astro/test/ssr-dynamic.test.js
index 30d9fdd11..843243425 100644
--- a/packages/astro/test/ssr-dynamic.test.js
+++ b/packages/astro/test/ssr-dynamic.test.js
@@ -2,10 +2,10 @@ import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
-import { App } from '../dist/core/app/index.js';
// Asset bundling
describe('Dynamic pages in SSR', () => {
+ /** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
@@ -20,8 +20,7 @@ describe('Dynamic pages in SSR', () => {
});
it('Do not have to implement getStaticPaths', async () => {
- const { createApp } = await import('./fixtures/ssr-dynamic/dist/server/entry.mjs');
- const app = createApp(new URL('./fixtures/ssr-dynamic/dist/server/', import.meta.url));
+ const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/123');
const response = await app.render(request);
const html = await response.text();
diff --git a/packages/astro/test/static-build.test.js b/packages/astro/test/static-build.test.js
index 0f04724ca..89860505e 100644
--- a/packages/astro/test/static-build.test.js
+++ b/packages/astro/test/static-build.test.js
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import cheerio from 'cheerio';
+import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js';
function addLeadingSlash(path) {
@@ -23,7 +23,7 @@ describe('Static build', () => {
it('can build pages using fetchContent', async () => {
const html = await fixture.readFile('/index.html');
- const $ = cheerio.load(html);
+ const $ = cheerioLoad(html);
const link = $('.posts a');
const href = link.attr('href');
expect(href).to.be.equal('/subpath/posts/thoughts');
@@ -69,7 +69,7 @@ describe('Static build', () => {
function createFindEvidence(expected, prefix) {
return async function findEvidence(pathname) {
const html = await fixture.readFile(pathname);
- const $ = cheerio.load(html);
+ const $ = cheerioLoad(html);
const links = $('link[rel=stylesheet]');
for (const link of links) {
const href = $(link).attr('href').slice('/subpath'.length);
@@ -118,23 +118,23 @@ describe('Static build', () => {
describe('Hoisted scripts', () => {
it('Get bundled together on the page', async () => {
const html = await fixture.readFile('/hoisted/index.html');
- const $ = cheerio.load(html);
+ const $ = cheerioLoad(html);
expect($('script[type="module"]').length).to.equal(1, 'hoisted script added');
});
it('Do not get added to the wrong page', async () => {
const hoistedHTML = await fixture.readFile('/hoisted/index.html');
- const $ = cheerio.load(hoistedHTML);
+ const $ = cheerioLoad(hoistedHTML);
const href = $('script[type="module"]').attr('src');
const indexHTML = await fixture.readFile('/index.html');
- const $$ = cheerio.load(indexHTML);
+ const $$ = cheerioLoad(indexHTML);
expect($$(`script[src="${href}"]`).length).to.equal(0, 'no script added to different page');
});
});
it('honors ssr config', async () => {
const html = await fixture.readFile('/index.html');
- const $ = cheerio.load(html);
+ const $ = cheerioLoad(html);
expect($('#ssr-config').text()).to.equal('testing');
});
});
diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js
index 2665cc4c1..90ae3aa13 100644
--- a/packages/astro/test/test-adapter.js
+++ b/packages/astro/test/test-adapter.js
@@ -22,8 +22,8 @@ export default function () {
}
},
load(id) {
- if (id === '@my-ssr') {
- return `import { App } from 'astro/app';export function createExports(manifest) { return { manifest, createApp: (root) => new App(manifest, root) }; }`;
+ if(id === '@my-ssr') {
+ return `import { App } from 'astro/app';export function createExports(manifest) { return { manifest, createApp: () => new App(manifest) }; }`;
}
},
},
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index 2bceb2748..88d29cc5a 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -20,6 +20,7 @@ polyfill(globalThis, {
* @typedef {import('../src/core/dev/index').DevServer} DevServer
* @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
+ * @typedef {import('../src/core/app/index').App} App
*
*
* @typedef {Object} Fixture
@@ -30,6 +31,7 @@ polyfill(globalThis, {
* @property {() => Promise<DevServer>} startDevServer
* @property {() => Promise<PreviewServer>} preview
* @property {() => Promise<void>} clean
+ * @property {() => Promise<App>} loadTestAdapterApp
*/
/**
@@ -85,6 +87,11 @@ export async function loadFixture(inlineConfig) {
readFile: (filePath) => fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.dist), 'utf8'),
readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.dist)),
clean: () => fs.promises.rm(config.dist, { maxRetries: 10, recursive: true, force: true }),
+ loadTestAdapterApp: async () => {
+ const url = new URL('./server/entry.mjs', config.dist);
+ const {createApp} = await import(url);
+ return createApp();
+ }
};
}
diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts
index 79a51cdfe..d643e8367 100644
--- a/packages/integrations/node/src/server.ts
+++ b/packages/integrations/node/src/server.ts
@@ -1,5 +1,6 @@
import type { SSRManifest } from 'astro';
import type { IncomingMessage, ServerResponse } from 'http';
+import type { Readable } from 'stream';
import { NodeApp } from 'astro/app/node';
import { polyfill } from '@astrojs/webapi';
@@ -8,7 +9,7 @@ polyfill(globalThis, {
});
export function createExports(manifest: SSRManifest) {
- const app = new NodeApp(manifest, new URL(import.meta.url));
+ const app = new NodeApp(manifest);
return {
async handler(req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) {
const route = app.match(req);
@@ -35,13 +36,8 @@ async function writeWebResponse(res: ServerResponse, webResponse: Response) {
const { status, headers, body } = webResponse;
res.writeHead(status, Object.fromEntries(headers.entries()));
if (body) {
- const reader = body.getReader();
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- if (value) {
- res.write(value);
- }
+ for await(const chunk of (body as unknown as Readable)) {
+ res.write(chunk);
}
}
res.end();