full site update

This commit is contained in:
2025-07-24 18:46:24 +02:00
parent bfe2b90d8d
commit 37a6e0ab31
6912 changed files with 540482 additions and 361712 deletions

View File

@@ -1,4 +1,4 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
export declare function baseMiddleware(settings: AstroSettings, logger: Logger): vite.Connect.NextHandleFunction;

View File

@@ -2,7 +2,7 @@ import * as fs from "node:fs";
import path from "node:path";
import { appendForwardSlash } from "@astrojs/internal-helpers/path";
import { bold } from "kleur/colors";
import notFoundTemplate, { subpathNotUsedTemplate } from "../template/4xx.js";
import { notFoundTemplate, subpathNotUsedTemplate } from "../template/4xx.js";
import { writeHtmlResponse } from "./response.js";
function baseMiddleware(settings, logger) {
const { config } = settings;
@@ -27,12 +27,7 @@ function baseMiddleware(settings, logger) {
return writeHtmlResponse(res, 404, html);
}
if (req.headers.accept?.includes("text/html")) {
const html = notFoundTemplate({
statusCode: 404,
title: "Not found",
tabTitle: "404: Not Found",
pathname
});
const html = notFoundTemplate(pathname);
return writeHtmlResponse(res, 404, html);
}
const publicPath = new URL("." + req.url, config.publicDir);

View File

@@ -6,13 +6,13 @@ export interface DevServerController {
onFileChange: LoaderEvents['file-change'];
onHMRError: LoaderEvents['hmr-error'];
}
export type CreateControllerParams = {
type CreateControllerParams = {
loader: ModuleLoader;
} | {
reload: ReloadFn;
};
export declare function createController(params: CreateControllerParams): DevServerController;
export interface RunWithErrorHandlingParams {
interface RunWithErrorHandlingParams {
controller: DevServerController;
pathname: string;
run: () => Promise<any>;

View File

@@ -1,4 +1,4 @@
import { viteID } from "../core/util.js";
import { viteID, wrapId } from "../core/util.js";
import { isBuildableCSSRequest } from "./util.js";
import { crawlGraph } from "./vite.js";
const inlineQueryRE = /(?:\?|&)inline(?:$|&)/;
@@ -31,8 +31,8 @@ async function getStylesForURL(filePath, loader) {
}
}
importedStylesMap.set(importedModule.url, {
id: importedModule.id ?? importedModule.url,
url: importedModule.url,
id: wrapId(importedModule.id ?? importedModule.url),
url: wrapId(importedModule.url),
content: css
});
}

View File

@@ -1,5 +1,5 @@
import type { AstroConfig } from '../@types/astro.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
import type { AstroConfig } from '../types/public/config.js';
import type { DevPipeline } from './pipeline.js';
export declare function recordServerError(loader: ModuleLoader, config: AstroConfig, { logger }: DevPipeline, _err: unknown): {
error: Error;

View File

@@ -1,3 +1,3 @@
import type { SSRResult } from '../@types/astro.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
import type { SSRResult } from '../types/public/internal.js';
export declare function getComponentMetadata(filePath: URL, loader: ModuleLoader): Promise<SSRResult['componentMetadata']>;

View File

@@ -1,14 +1,16 @@
import type { AstroSettings, ComponentInstance, ManifestData, RewritePayload, RouteData, SSRLoadedRenderer, SSRManifest } from '../@types/astro.js';
import type { HeadElements, TryRewriteResult } from '../core/base-pipeline.js';
import type { Logger } from '../core/logger/core.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
import { Pipeline } from '../core/render/index.js';
import type { AstroSettings, ComponentInstance, RoutesList } from '../types/astro.js';
import type { RewritePayload } from '../types/public/common.js';
import type { RouteData, SSRLoadedRenderer, SSRManifest } from '../types/public/internal.js';
export declare class DevPipeline extends Pipeline {
readonly loader: ModuleLoader;
readonly logger: Logger;
readonly manifest: SSRManifest;
readonly settings: AstroSettings;
readonly config: import("../@types/astro.js").AstroConfig;
readonly config: import("../types/public/config.js").AstroConfig;
readonly defaultRoutes: {
instance: ComponentInstance;
matchesComponent(filePath: URL): boolean;
@@ -16,16 +18,15 @@ export declare class DevPipeline extends Pipeline {
component: string;
}[];
renderers: SSRLoadedRenderer[];
manifestData: ManifestData | undefined;
routesList: RoutesList | undefined;
componentInterner: WeakMap<RouteData, ComponentInstance>;
private constructor();
static create(manifestData: ManifestData, { loader, logger, manifest, settings, }: Pick<DevPipeline, 'loader' | 'logger' | 'manifest' | 'settings'>): DevPipeline;
static create(manifestData: RoutesList, { loader, logger, manifest, settings, }: Pick<DevPipeline, 'loader' | 'logger' | 'manifest' | 'settings'>): DevPipeline;
headElements(routeData: RouteData): Promise<HeadElements>;
componentMetadata(routeData: RouteData): Promise<Map<string, import("../@types/astro.js").SSRComponentMetadata>>;
componentMetadata(routeData: RouteData): Promise<Map<string, import("../types/public/internal.js").SSRComponentMetadata>>;
preload(routeData: RouteData, filePath: URL): Promise<ComponentInstance>;
clearRouteCache(): void;
getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
setManifestData(manifestData: ManifestData): void;
rewriteKnownRoute(route: string, sourceRoute: RouteData): ComponentInstance;
setManifestData(manifestData: RoutesList): void;
}

View File

@@ -3,23 +3,21 @@ import { getInfoOutput } from "../cli/info/index.js";
import { ASTRO_VERSION } from "../core/constants.js";
import { enhanceViteSSRError } from "../core/errors/dev/index.js";
import { AggregateError, CSSError, MarkdownError } from "../core/errors/index.js";
import { Pipeline, loadRenderer } from "../core/render/index.js";
import { loadRenderer, Pipeline } from "../core/render/index.js";
import { createDefaultRoutes } from "../core/routing/default.js";
import { findRouteToRewrite } from "../core/routing/rewrite.js";
import { isPage, isServerLikeOutput, viteID } from "../core/util.js";
import { isPage, viteID } from "../core/util.js";
import { resolveIdToUrl } from "../core/viteUtils.js";
import { PAGE_SCRIPT_ID } from "../vite-plugin-scripts/index.js";
import { getStylesForURL } from "./css.js";
import { getComponentMetadata } from "./metadata.js";
import { createResolve } from "./resolve.js";
import { getScriptsForURL } from "./scripts.js";
class DevPipeline extends Pipeline {
constructor(loader, logger, manifest, settings, config = settings.config, defaultRoutes = createDefaultRoutes(manifest)) {
const mode = "development";
const resolve = createResolve(loader, config.root);
const serverLike = isServerLikeOutput(config);
const serverLike = settings.buildOutput === "server";
const streaming = true;
super(logger, manifest, mode, [], resolve, serverLike, streaming);
super(logger, manifest, "development", [], resolve, serverLike, streaming);
this.loader = loader;
this.logger = logger;
this.manifest = manifest;
@@ -32,7 +30,7 @@ class DevPipeline extends Pipeline {
// renderers are loaded on every request,
// so it needs to be mutable here unlike in other environments
renderers = new Array();
manifestData;
routesList;
componentInterner = /* @__PURE__ */ new WeakMap();
static create(manifestData, {
loader,
@@ -41,19 +39,19 @@ class DevPipeline extends Pipeline {
settings
}) {
const pipeline = new DevPipeline(loader, logger, manifest, settings);
pipeline.manifestData = manifestData;
pipeline.routesList = manifestData;
return pipeline;
}
async headElements(routeData) {
const {
config: { root },
loader,
mode,
runtimeMode,
settings
} = this;
const filePath = new URL(`${routeData.component}`, root);
const { scripts } = settings.config.experimental.directRenderScript ? { scripts: /* @__PURE__ */ new Set() } : await getScriptsForURL(filePath, settings.config.root, loader);
if (isPage(filePath, settings) && mode === "development") {
const scripts = /* @__PURE__ */ new Set();
if (isPage(filePath, settings) && runtimeMode === "development") {
scripts.add({
props: { type: "module", src: "/@vite/client" },
children: ""
@@ -139,32 +137,23 @@ class DevPipeline extends Pipeline {
}
}
async tryRewrite(payload, request) {
if (!this.manifestData) {
if (!this.routesList) {
throw new Error("Missing manifest data. This is an internal error, please file an issue.");
}
const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,
routes: this.manifestData?.routes,
routes: this.routesList?.routes,
trailingSlash: this.config.trailingSlash,
buildFormat: this.config.build.format,
base: this.config.base
base: this.config.base,
outDir: this.manifest.outDir
});
const componentInstance = await this.getComponentByRoute(routeData);
return { newUrl, pathname, componentInstance, routeData };
}
setManifestData(manifestData) {
this.manifestData = manifestData;
}
rewriteKnownRoute(route, sourceRoute) {
if (isServerLikeOutput(this.config) && sourceRoute.prerender) {
for (let def of this.defaultRoutes) {
if (route === def.route) {
return def.instance;
}
}
}
throw new Error("Unknown route");
this.routesList = manifestData;
}
}
export {

View File

@@ -1,13 +1,16 @@
import type fs from 'node:fs';
import type * as vite from 'vite';
import type { AstroSettings, SSRManifest } from '../@types/astro.js';
import type { SSRManifest } from '../core/app/types.js';
import type { Logger } from '../core/logger/core.js';
export interface AstroPluginOptions {
import type { AstroSettings, RoutesList } from '../types/astro.js';
interface AstroPluginOptions {
settings: AstroSettings;
logger: Logger;
fs: typeof fs;
routesList: RoutesList;
manifest: SSRManifest;
}
export default function createVitePluginAstroServer({ settings, logger, fs: fsMod, }: AstroPluginOptions): vite.Plugin;
export default function createVitePluginAstroServer({ settings, logger, fs: fsMod, routesList, manifest, }: AstroPluginOptions): vite.Plugin;
/**
* It creates a `SSRManifest` from the `AstroSettings`.
*
@@ -15,3 +18,4 @@ export default function createVitePluginAstroServer({ settings, logger, fs: fsMo
* @param settings
*/
export declare function createDevelopmentManifest(settings: AstroSettings): SSRManifest;
export {};

View File

@@ -1,56 +1,89 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { IncomingMessage } from "node:http";
import { createKey } from "../core/encryption.js";
import { fileURLToPath } from "node:url";
import { normalizePath } from "vite";
import {
getAlgorithm,
getDirectives,
getScriptHashes,
getScriptResources,
getStrictDynamic,
getStyleHashes,
getStyleResources,
shouldTrackCspHashes
} from "../core/csp/common.js";
import { warnMissingAdapter } from "../core/dev/adapter-validation.js";
import { createKey, getEnvironmentKey, hasEnvironmentKey } from "../core/encryption.js";
import { getViteErrorPayload } from "../core/errors/dev/index.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { patchOverlay } from "../core/errors/overlay.js";
import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
import { createViteLoader } from "../core/module-loader/index.js";
import { injectDefaultRoutes } from "../core/routing/default.js";
import { createRouteManifest } from "../core/routing/index.js";
import { createRoutesList } from "../core/routing/index.js";
import { getRoutePrerenderOption } from "../core/routing/manifest/prerender.js";
import { toFallbackType, toRoutingStrategy } from "../i18n/utils.js";
import { runHookRoutesResolved } from "../integrations/hooks.js";
import { baseMiddleware } from "./base.js";
import { createController } from "./controller.js";
import { recordServerError } from "./error.js";
import { DevPipeline } from "./pipeline.js";
import { handleRequest } from "./request.js";
import { setRouteError } from "./server-state.js";
import { trailingSlashMiddleware } from "./trailing-slash.js";
function createVitePluginAstroServer({
settings,
logger,
fs: fsMod
fs: fsMod,
routesList,
manifest
}) {
return {
name: "astro:server",
configureServer(viteServer) {
async configureServer(viteServer) {
const loader = createViteLoader(viteServer);
const manifest = createDevelopmentManifest(settings);
let manifestData = injectDefaultRoutes(
const pipeline = DevPipeline.create(routesList, {
loader,
logger,
manifest,
createRouteManifest({ settings, fsMod }, logger)
);
const pipeline = DevPipeline.create(manifestData, { loader, logger, manifest, settings });
settings
});
const controller = createController({ loader });
const localStorage = new AsyncLocalStorage();
function rebuildManifest(needsManifestRebuild) {
async function rebuildManifest(path = null) {
pipeline.clearRouteCache();
if (needsManifestRebuild) {
manifestData = injectDefaultRoutes(manifest, createRouteManifest({ settings }, logger));
pipeline.setManifestData(manifestData);
if (path !== null) {
const route = routesList.routes.find(
(r) => normalizePath(path) === normalizePath(fileURLToPath(new URL(r.component, settings.config.root)))
);
if (!route) {
return;
}
if (route.type !== "page" && route.type !== "endpoint") return;
const routePath = fileURLToPath(new URL(route.component, settings.config.root));
try {
const content = await fsMod.promises.readFile(routePath, "utf-8");
await getRoutePrerenderOption(content, route, settings, logger);
await runHookRoutesResolved({ routes: routesList.routes, settings, logger });
} catch (_) {
}
} else {
routesList = await createRoutesList({ settings, fsMod }, logger, { dev: true });
}
warnMissingAdapter(logger, settings);
pipeline.manifest.checkOrigin = settings.config.security.checkOrigin && settings.buildOutput === "server";
pipeline.setManifestData(routesList);
}
viteServer.watcher.on("add", rebuildManifest.bind(null, true));
viteServer.watcher.on("unlink", rebuildManifest.bind(null, true));
viteServer.watcher.on("change", rebuildManifest.bind(null, false));
viteServer.watcher.on("add", rebuildManifest.bind(null, null));
viteServer.watcher.on("unlink", rebuildManifest.bind(null, null));
viteServer.watcher.on("change", rebuildManifest);
function handleUnhandledRejection(rejection) {
const error = new AstroError({
const error = AstroError.is(rejection) ? rejection : new AstroError({
...AstroErrorData.UnhandledRejection,
message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection)
});
const store = localStorage.getStore();
if (store instanceof IncomingMessage) {
const request = store;
setRouteError(controller.state, request.url, error);
setRouteError(controller.state, store.url, error);
}
const { errorWithMetadata } = recordServerError(loader, settings.config, pipeline, error);
setTimeout(
@@ -67,6 +100,10 @@ function createVitePluginAstroServer({
route: "",
handle: baseMiddleware(settings, logger)
});
viteServer.middlewares.stack.unshift({
route: "",
handle: trailingSlashMiddleware(settings)
});
viteServer.middlewares.use(async function astroDevHandler(request, response) {
if (request.url === void 0 || !request.method) {
response.writeHead(500, "Incomplete request");
@@ -76,7 +113,7 @@ function createVitePluginAstroServer({
localStorage.run(request, () => {
handleRequest({
pipeline,
manifestData,
routesList,
controller,
incomingRequest: request,
incomingResponse: response
@@ -93,7 +130,8 @@ function createVitePluginAstroServer({
};
}
function createDevelopmentManifest(settings) {
let i18nManifest = void 0;
let i18nManifest;
let csp;
if (settings.config.i18n) {
i18nManifest = {
fallback: settings.config.i18n.fallback,
@@ -104,31 +142,51 @@ function createDevelopmentManifest(settings) {
fallbackType: toFallbackType(settings.config.i18n.routing)
};
}
if (shouldTrackCspHashes(settings.config.experimental.csp)) {
csp = {
cspDestination: settings.adapter?.adapterFeatures?.experimentalStaticHeaders ? "adapter" : void 0,
scriptHashes: getScriptHashes(settings.config.experimental.csp),
scriptResources: getScriptResources(settings.config.experimental.csp),
styleHashes: getStyleHashes(settings.config.experimental.csp),
styleResources: getStyleResources(settings.config.experimental.csp),
algorithm: getAlgorithm(settings.config.experimental.csp),
directives: getDirectives(settings.config.experimental.csp),
isStrictDynamic: getStrictDynamic(settings.config.experimental.csp)
};
}
return {
hrefRoot: settings.config.root.toString(),
srcDir: settings.config.srcDir,
cacheDir: settings.config.cacheDir,
outDir: settings.config.outDir,
buildServerDir: settings.config.build.server,
buildClientDir: settings.config.build.client,
publicDir: settings.config.publicDir,
trailingSlash: settings.config.trailingSlash,
buildFormat: settings.config.build.format,
compressHTML: settings.config.compressHTML,
assets: /* @__PURE__ */ new Set(),
entryModules: {},
routes: [],
adapterName: settings?.adapter?.name || "",
adapterName: settings?.adapter?.name ?? "",
clientDirectives: settings.clientDirectives,
renderers: [],
base: settings.config.base,
userAssetsBase: settings.config?.vite?.base,
assetsPrefix: settings.config.build.assetsPrefix,
site: settings.config.site,
componentMetadata: /* @__PURE__ */ new Map(),
inlinedScripts: /* @__PURE__ */ new Map(),
i18n: i18nManifest,
checkOrigin: settings.config.security?.checkOrigin ?? false,
experimentalEnvGetSecretEnabled: false,
key: createKey(),
checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false,
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
middleware() {
return {
onRequest: NOOP_MIDDLEWARE_FN
};
}
},
sessionConfig: settings.config.session,
csp
};
}
export {

View File

@@ -1,14 +1,14 @@
import type http from 'node:http';
import type { ManifestData } from '../@types/astro.js';
import type { RoutesList } from '../types/astro.js';
import type { DevServerController } from './controller.js';
import type { DevPipeline } from './pipeline.js';
type HandleRequest = {
pipeline: DevPipeline;
manifestData: ManifestData;
routesList: RoutesList;
controller: DevServerController;
incomingRequest: http.IncomingMessage;
incomingResponse: http.ServerResponse;
};
/** The main logic to route dev server requests to pages in Astro. */
export declare function handleRequest({ pipeline, manifestData, controller, incomingRequest, incomingResponse, }: HandleRequest): Promise<void>;
export declare function handleRequest({ pipeline, routesList, controller, incomingRequest, incomingResponse, }: HandleRequest): Promise<void>;
export {};

View File

@@ -5,7 +5,7 @@ import { handle500Response } from "./response.js";
import { handleRoute, matchRoute } from "./route.js";
async function handleRequest({
pipeline,
manifestData,
routesList,
controller,
incomingRequest,
incomingResponse
@@ -17,7 +17,7 @@ async function handleRequest({
if (config.trailingSlash === "never" && !incomingRequest.url) {
pathname = "";
} else {
pathname = url.pathname;
pathname = decodeURI(url.pathname);
}
url.pathname = removeTrailingForwardSlash(config.base) + url.pathname;
let body = void 0;
@@ -35,7 +35,7 @@ async function handleRequest({
controller,
pathname,
async run() {
const matchedRoute = await matchRoute(pathname, manifestData, pipeline);
const matchedRoute = await matchRoute(pathname, routesList, pipeline);
const resolvedPathname = matchedRoute?.resolvedPathname ?? pathname;
return await handleRoute({
matchedRoute,
@@ -43,7 +43,7 @@ async function handleRequest({
pathname: resolvedPathname,
body,
pipeline,
manifestData,
routesList,
incomingRequest,
incomingResponse
});

View File

@@ -1,8 +1,8 @@
import type http from 'node:http';
import type { ErrorWithMetadata } from '../core/errors/index.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
export declare function handle404Response(origin: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
export declare function handle500Response(loader: ModuleLoader, res: http.ServerResponse, err: ErrorWithMetadata): Promise<void>;
export declare function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string): void;
export declare function writeRedirectResponse(res: http.ServerResponse, statusCode: number, location: string): void;
export declare function writeWebResponse(res: http.ServerResponse, webResponse: Response): Promise<void>;
export declare function writeSSRResult(webRequest: Request, webResponse: Response, res: http.ServerResponse): Promise<void>;

View File

@@ -2,17 +2,7 @@ import { Http2ServerResponse } from "node:http2";
import { Readable } from "node:stream";
import { getSetCookiesFromResponse } from "../core/cookies/index.js";
import { getViteErrorPayload } from "../core/errors/dev/index.js";
import notFoundTemplate from "../template/4xx.js";
async function handle404Response(origin, req, res) {
const pathname = decodeURI(new URL(origin + req.url).pathname);
const html = notFoundTemplate({
statusCode: 404,
title: "Not found",
tabTitle: "404: Not Found",
pathname
});
writeHtmlResponse(res, 404, html);
}
import { redirectTemplate } from "../core/routing/3xx.js";
async function handle500Response(loader, res, err) {
res.on(
"close",
@@ -31,7 +21,21 @@ async function handle500Response(loader, res, err) {
}
function writeHtmlResponse(res, statusCode, html) {
res.writeHead(statusCode, {
"Content-Type": "text/html; charset=utf-8",
"Content-Type": "text/html",
"Content-Length": Buffer.byteLength(html, "utf-8")
});
res.write(html);
res.end();
}
function writeRedirectResponse(res, statusCode, location) {
const html = redirectTemplate({
status: statusCode,
absoluteLocation: location,
relativeLocation: location
});
res.writeHead(statusCode, {
Location: location,
"Content-Type": "text/html",
"Content-Length": Buffer.byteLength(html, "utf-8")
});
res.write(html);
@@ -84,9 +88,9 @@ async function writeSSRResult(webRequest, webResponse, res) {
return writeWebResponse(res, webResponse);
}
export {
handle404Response,
handle500Response,
writeHtmlResponse,
writeRedirectResponse,
writeSSRResult,
writeWebResponse
};

View File

@@ -1,24 +1,25 @@
import type http from 'node:http';
import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro.js';
import type { ComponentInstance, RoutesList } from '../types/astro.js';
import type { RouteData } from '../types/public/internal.js';
import type { DevPipeline } from './pipeline.js';
type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (...args: any) => Promise<infer R> ? R : any;
export interface MatchedRoute {
interface MatchedRoute {
route: RouteData;
filePath: URL;
resolvedPathname: string;
preloadedComponent: ComponentInstance;
mod: ComponentInstance;
}
export declare function matchRoute(pathname: string, manifestData: ManifestData, pipeline: DevPipeline): Promise<MatchedRoute | undefined>;
export declare function matchRoute(pathname: string, routesList: RoutesList, pipeline: DevPipeline): Promise<MatchedRoute | undefined>;
type HandleRoute = {
matchedRoute: AsyncReturnType<typeof matchRoute>;
url: URL;
pathname: string;
body: ArrayBuffer | undefined;
manifestData: ManifestData;
routesList: RoutesList;
incomingRequest: http.IncomingMessage;
incomingResponse: http.ServerResponse;
pipeline: DevPipeline;
};
export declare function handleRoute({ matchedRoute, url, pathname, body, pipeline, manifestData, incomingRequest, incomingResponse, }: HandleRoute): Promise<void>;
export declare function handleRoute({ matchedRoute, url, pathname, body, pipeline, routesList, incomingRequest, incomingResponse, }: HandleRoute): Promise<void>;
export {};

View File

@@ -1,33 +1,36 @@
import { loadActions } from "../actions/loadActions.js";
import {
clientLocalsSymbol,
DEFAULT_404_COMPONENT,
NOOP_MIDDLEWARE_HEADER,
REROUTE_DIRECTIVE_HEADER,
REWRITE_DIRECTIVE_HEADER_KEY,
clientLocalsSymbol
REWRITE_DIRECTIVE_HEADER_KEY
} from "../core/constants.js";
import { AstroErrorData, isAstroError } from "../core/errors/index.js";
import { req } from "../core/messages.js";
import { loadMiddleware } from "../core/middleware/loadMiddleware.js";
import { RenderContext } from "../core/render-context.js";
import { routeIsRedirect } from "../core/redirects/index.js";
import { getProps } from "../core/render/index.js";
import { RenderContext } from "../core/render-context.js";
import { createRequest } from "../core/request.js";
import { redirectTemplate } from "../core/routing/3xx.js";
import { matchAllRoutes } from "../core/routing/index.js";
import { isRoute404, isRoute500 } from "../core/routing/match.js";
import { PERSIST_SYMBOL } from "../core/session.js";
import { getSortedPreloadedMatches } from "../prerender/routing.js";
import { writeSSRResult, writeWebResponse } from "./response.js";
function isLoggedRequest(url) {
return url !== "/favicon.ico";
}
function getCustom404Route(manifestData) {
const route404 = /^\/404\/?$/;
return manifestData.routes.find((r) => route404.test(r.route));
return manifestData.routes.find((r) => isRoute404(r.route));
}
function getCustom500Route(manifestData) {
const route500 = /^\/500\/?$/;
return manifestData.routes.find((r) => route500.test(r.route));
return manifestData.routes.find((r) => isRoute500(r.route));
}
async function matchRoute(pathname, manifestData, pipeline) {
async function matchRoute(pathname, routesList, pipeline) {
const { config, logger, routeCache, serverLike, settings } = pipeline;
const matches = matchAllRoutes(pathname, manifestData);
const matches = matchAllRoutes(pathname, routesList);
const preloadedMatches = await getSortedPreloadedMatches({ pipeline, matches, settings });
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
try {
@@ -37,7 +40,8 @@ async function matchRoute(pathname, manifestData, pipeline) {
routeCache,
pathname,
logger,
serverLike
serverLike,
base: config.base
});
return {
route: maybeRoute,
@@ -55,7 +59,7 @@ async function matchRoute(pathname, manifestData, pipeline) {
}
const altPathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
if (altPathname !== pathname) {
return await matchRoute(altPathname, manifestData, pipeline);
return await matchRoute(altPathname, routesList, pipeline);
}
if (matches.length) {
const possibleRoutes = matches.flatMap((route) => route.component);
@@ -68,7 +72,7 @@ async function matchRoute(pathname, manifestData, pipeline) {
${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
);
}
const custom404 = getCustom404Route(manifestData);
const custom404 = getCustom404Route(routesList);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, config.root);
const preloadedComponent = await pipeline.preload(custom404, filePath);
@@ -88,7 +92,7 @@ async function handleRoute({
pathname,
body,
pipeline,
manifestData,
routesList,
incomingRequest,
incomingResponse
}) {
@@ -101,19 +105,20 @@ async function handleRoute({
let renderContext;
let mod = void 0;
let route;
const actions = await loadActions(loader);
pipeline.setActions(actions);
const middleware = (await loadMiddleware(loader)).onRequest;
const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
const { preloadedComponent } = matchedRoute;
route = matchedRoute.route;
request = createRequest({
base: config.base,
url,
headers: incomingRequest.headers,
method: incomingRequest.method,
body,
logger,
clientAddress: incomingRequest.socket.remoteAddress,
staticLike: config.output === "static" || route.prerender
isPrerendered: route.prerender,
routePattern: route.component
});
for (const [name, value] of Object.entries(config.server.headers ?? {})) {
if (value) incomingResponse.setHeader(name, value);
@@ -125,7 +130,9 @@ async function handleRoute({
pathname,
middleware: isDefaultPrerendered404(matchedRoute.route) ? void 0 : middleware,
request,
routeData: route
routeData: route,
clientAddress: incomingRequest.socket.remoteAddress,
actions
});
let response;
let statusCode = 200;
@@ -145,7 +152,7 @@ async function handleRoute({
!response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute ? response.status : statusCodedMatched ?? response.status
);
} catch (err) {
const custom500 = getCustom500Route(manifestData);
const custom500 = getCustom500Route(routesList);
if (!custom500) {
throw err;
}
@@ -155,6 +162,8 @@ async function handleRoute({
renderContext.props.error = err;
response = await renderContext.render(preloaded500Component);
statusCode = 500;
} finally {
renderContext.session?.[PERSIST_SYMBOL]();
}
if (isLoggedRequest(pathname)) {
const timeEnd = performance.now();
@@ -169,8 +178,10 @@ async function handleRoute({
})
);
}
if (statusCode === 404 && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
const fourOhFourRoute = await matchRoute("/404", manifestData, pipeline);
if (statusCode === 404 && // If the body isn't null, that means the user sets the 404 status
// but uses the current route to handle the 404
response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
const fourOhFourRoute = await matchRoute("/404", routesList, pipeline);
if (fourOhFourRoute) {
renderContext = await RenderContext.create({
locals,
@@ -178,7 +189,8 @@ async function handleRoute({
pathname,
middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? void 0 : middleware,
request,
routeData: fourOhFourRoute.route
routeData: fourOhFourRoute.route,
clientAddress: incomingRequest.socket.remoteAddress
});
response = await renderContext.render(fourOhFourRoute.preloadedComponent);
}
@@ -198,6 +210,24 @@ async function handleRoute({
return;
}
if (response.status < 400 && response.status >= 300) {
if (response.status >= 300 && response.status < 400 && routeIsRedirect(route) && !config.build.redirects && pipeline.settings.buildOutput === "static") {
const location = response.headers.get("location");
response = new Response(
redirectTemplate({
status: response.status,
absoluteLocation: location,
relativeLocation: location,
from: pathname
}),
{
status: 200,
headers: {
...response.headers,
"content-type": "text/html"
}
}
);
}
await writeSSRResult(request, response, incomingResponse);
return;
}

View File

@@ -1,6 +0,0 @@
import type { SSRElement } from '../@types/astro.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
export declare function getScriptsForURL(filePath: URL, root: URL, loader: ModuleLoader): Promise<{
scripts: Set<SSRElement>;
crawledFiles: Set<string>;
}>;

View File

@@ -1,38 +0,0 @@
import { createModuleScriptElementWithSrc } from "../core/render/ssr-element.js";
import { viteID } from "../core/util.js";
import { rootRelativePath } from "../core/viteUtils.js";
import { crawlGraph } from "./vite.js";
async function getScriptsForURL(filePath, root, loader) {
const elements = /* @__PURE__ */ new Set();
const crawledFiles = /* @__PURE__ */ new Set();
const rootID = viteID(filePath);
const modInfo = loader.getModuleInfo(rootID);
addHoistedScripts(elements, modInfo, root);
for await (const moduleNode of crawlGraph(loader, rootID, true)) {
if (moduleNode.file) {
crawledFiles.add(moduleNode.file);
}
const id = moduleNode.id;
if (id) {
const info = loader.getModuleInfo(id);
addHoistedScripts(elements, info, root);
}
}
return { scripts: elements, crawledFiles };
}
function addHoistedScripts(set, info, root) {
if (!info?.meta?.astro) {
return;
}
let id = info.id;
const astro = info?.meta?.astro;
for (let i = 0; i < astro.scripts.length; i++) {
let scriptId = `${id}?astro&type=script&index=${i}&lang.ts`;
scriptId = rootRelativePath(root, scriptId);
const element = createModuleScriptElementWithSrc(scriptId);
set.add(element);
}
}
export {
getScriptsForURL
};

View File

@@ -1,5 +1,5 @@
export type ErrorState = 'fresh' | 'error';
export interface RouteState {
type ErrorState = 'fresh' | 'error';
interface RouteState {
state: ErrorState;
error?: Error;
}
@@ -9,7 +9,7 @@ export interface ServerState {
error?: Error;
}
export declare function createServerState(): ServerState;
export declare function hasAnyFailureState(serverState: ServerState): boolean;
export declare function setRouteError(serverState: ServerState, pathname: string, error: Error): void;
export declare function setServerError(serverState: ServerState, error: Error): void;
export declare function clearRouteError(serverState: ServerState, pathname: string): void;
export {};

View File

@@ -4,9 +4,6 @@ function createServerState() {
state: "fresh"
};
}
function hasAnyFailureState(serverState) {
return serverState.state !== "fresh";
}
function setRouteError(serverState, pathname, error) {
if (serverState.routes.has(pathname)) {
const routeState = serverState.routes.get(pathname);
@@ -36,7 +33,6 @@ function clearRouteError(serverState, pathname) {
export {
clearRouteError,
createServerState,
hasAnyFailureState,
setRouteError,
setServerError
};

View File

@@ -0,0 +1,3 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../types/astro.js';
export declare function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.NextHandleFunction;

View File

@@ -0,0 +1,30 @@
import { collapseDuplicateTrailingSlashes, hasFileExtension } from "@astrojs/internal-helpers/path";
import { trailingSlashMismatchTemplate } from "../template/4xx.js";
import { writeHtmlResponse, writeRedirectResponse } from "./response.js";
function trailingSlashMiddleware(settings) {
const { trailingSlash } = settings.config;
return function devTrailingSlash(req, res, next) {
const url = new URL(`http://localhost${req.url}`);
let pathname;
try {
pathname = decodeURI(url.pathname);
} catch (e) {
return next(e);
}
if (pathname.startsWith("/_") || pathname.startsWith("/@")) {
return next();
}
const destination = collapseDuplicateTrailingSlashes(pathname, true);
if (pathname && destination !== pathname) {
return writeRedirectResponse(res, 301, `${destination}${url.search}`);
}
if (trailingSlash === "never" && pathname.endsWith("/") && pathname !== "/" || trailingSlash === "always" && !pathname.endsWith("/") && !hasFileExtension(pathname)) {
const html = trailingSlashMismatchTemplate(pathname, trailingSlash);
return writeHtmlResponse(res, 404, html);
}
return next();
};
}
export {
trailingSlashMiddleware
};