diff --git a/packages/astro/src/config/entrypoint.ts b/packages/astro/src/config/entrypoint.ts
index da57c538b4df..8c7efbc4c0c4 100644
--- a/packages/astro/src/config/entrypoint.ts
+++ b/packages/astro/src/config/entrypoint.ts
@@ -1,6 +1,7 @@
// IMPORTANT: this file is the entrypoint for "astro/config". Keep it as light as possible!
import type { SharpImageServiceConfig } from '../assets/services/sharp.js';
+
import type { ImageServiceConfig } from '../types/public/index.js';
export { defineAstroFontProvider, fontProviders } from '../assets/fonts/providers/index.js';
diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts
index 1879b1732f66..aa59b7671312 100644
--- a/packages/astro/src/container/index.ts
+++ b/packages/astro/src/container/index.ts
@@ -135,14 +135,15 @@ function createManifest(
};
}
+ const root = new URL(import.meta.url);
return {
- hrefRoot: import.meta.url,
- srcDir: manifest?.srcDir ?? ASTRO_CONFIG_DEFAULTS.srcDir,
- buildClientDir: manifest?.buildClientDir ?? ASTRO_CONFIG_DEFAULTS.build.client,
- buildServerDir: manifest?.buildServerDir ?? ASTRO_CONFIG_DEFAULTS.build.server,
- publicDir: manifest?.publicDir ?? ASTRO_CONFIG_DEFAULTS.publicDir,
- outDir: manifest?.outDir ?? ASTRO_CONFIG_DEFAULTS.outDir,
- cacheDir: manifest?.cacheDir ?? ASTRO_CONFIG_DEFAULTS.cacheDir,
+ rootDir: root,
+ srcDir: manifest?.srcDir ?? new URL(ASTRO_CONFIG_DEFAULTS.srcDir, root),
+ buildClientDir: manifest?.buildClientDir ?? new URL(ASTRO_CONFIG_DEFAULTS.build.client, root),
+ buildServerDir: manifest?.buildServerDir ?? new URL(ASTRO_CONFIG_DEFAULTS.build.server, root),
+ publicDir: manifest?.publicDir ?? new URL(ASTRO_CONFIG_DEFAULTS.publicDir, root),
+ outDir: manifest?.outDir ?? new URL(ASTRO_CONFIG_DEFAULTS.outDir, root),
+ cacheDir: manifest?.cacheDir ?? new URL(ASTRO_CONFIG_DEFAULTS.cacheDir, root),
trailingSlash: manifest?.trailingSlash ?? ASTRO_CONFIG_DEFAULTS.trailingSlash,
buildFormat: manifest?.buildFormat ?? ASTRO_CONFIG_DEFAULTS.build.format,
compressHTML: manifest?.compressHTML ?? ASTRO_CONFIG_DEFAULTS.compressHTML,
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index 7d2ccc17f09b..793ade64ff6d 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -5,6 +5,7 @@ import { bold, cyan } from 'kleur/colors';
import { glob } from 'tinyglobby';
import {
type DevEnvironment,
+ isRunnableDevEnvironment,
normalizePath,
type RunnableDevEnvironment,
type ViteDevServer,
@@ -347,6 +348,9 @@ export async function createContentTypesGenerator({
logger,
settings,
});
+ if (!isRunnableDevEnvironment(viteServer.environments.ssr)) {
+ return;
+ }
invalidateVirtualMod(viteServer.environments.ssr);
}
}
diff --git a/packages/astro/src/core/app/base.ts b/packages/astro/src/core/app/base.ts
index 51616c80028a..f33b2ee78f6b 100644
--- a/packages/astro/src/core/app/base.ts
+++ b/packages/astro/src/core/app/base.ts
@@ -186,7 +186,6 @@ export abstract class BaseApp
{
pathname = prependForwardSlash(this.removeBase(url.pathname));
}
let routeData = matchRoute(decodeURI(pathname), this.manifestData);
-
if (!routeData) return undefined;
if (allowPrerenderedRoutes) {
return routeData;
diff --git a/packages/astro/src/core/app/common.ts b/packages/astro/src/core/app/common.ts
index f07b6a8b5ef3..b88f471c2150 100644
--- a/packages/astro/src/core/app/common.ts
+++ b/packages/astro/src/core/app/common.ts
@@ -45,6 +45,13 @@ export function deserializeManifest(
return { onRequest: NOOP_MIDDLEWARE_FN };
},
...serializedManifest,
+ rootDir: new URL(serializedManifest.rootDir),
+ srcDir: new URL(serializedManifest.srcDir),
+ publicDir: new URL(serializedManifest.publicDir),
+ outDir: new URL(serializedManifest.outDir),
+ cacheDir: new URL(serializedManifest.cacheDir),
+ buildClientDir: new URL(serializedManifest.buildClientDir),
+ buildServerDir: new URL(serializedManifest.buildServerDir),
assets,
componentMetadata,
inlinedScripts,
diff --git a/packages/astro/src/core/app/dev/app.ts b/packages/astro/src/core/app/dev/app.ts
index 91b8a2f9e5c3..28266ad5cbcc 100644
--- a/packages/astro/src/core/app/dev/app.ts
+++ b/packages/astro/src/core/app/dev/app.ts
@@ -1,383 +1,40 @@
-import type http from 'node:http';
-import { prependForwardSlash, removeTrailingForwardSlash } from '@astrojs/internal-helpers/path';
-import { loadActions } from '../../../actions/loadActions.js';
-import { getSortedPreloadedMatches } from '../../../prerender/routing.js';
-import type { AstroSettings, RoutesList } from '../../../types/astro.js';
+import type { RoutesList } from '../../../types/astro.js';
import type { RouteData } from '../../../types/public/index.js';
-import type { DevServerController } from '../../../vite-plugin-astro-server/controller.js';
-import { recordServerError } from '../../../vite-plugin-astro-server/error.js';
-import { runWithErrorHandling } from '../../../vite-plugin-astro-server/index.js';
-import {
- handle500Response,
- writeSSRResult,
- writeWebResponse,
-} from '../../../vite-plugin-astro-server/response.js';
-import { shouldAppendForwardSlash } from '../../build/util.js';
-import {
- clientLocalsSymbol,
- DEFAULT_404_COMPONENT,
- NOOP_MIDDLEWARE_HEADER,
- REROUTE_DIRECTIVE_HEADER,
- REWRITE_DIRECTIVE_HEADER_KEY,
-} from '../../constants.js';
-import {
- MiddlewareNoDataOrNextCalled,
- MiddlewareNotAResponse,
- NoMatchingStaticPathFound,
-} from '../../errors/errors-data.js';
+import { MiddlewareNoDataOrNextCalled, MiddlewareNotAResponse } from '../../errors/errors-data.js';
import { type AstroError, isAstroError } from '../../errors/index.js';
import type { Logger } from '../../logger/core.js';
-import { req } from '../../messages.js';
-import { loadMiddleware } from '../../middleware/loadMiddleware.js';
-import type { ModuleLoader } from '../../module-loader/index.js';
-import { routeIsRedirect } from '../../redirects/index.js';
-import { getProps } from '../../render/index.js';
import type { CreateRenderContext, RenderContext } from '../../render-context.js';
-import { createRequest } from '../../request.js';
-import { redirectTemplate } from '../../routing/3xx.js';
-import { matchAllRoutes } from '../../routing/index.js';
-import { isRoute404, isRoute500 } from '../../routing/match.js';
-import { PERSIST_SYMBOL } from '../../session.js';
+import { isRoute500 } from '../../routing/match.js';
import { BaseApp, type RenderErrorOptions } from '../base.js';
import type { SSRManifest } from '../types.js';
import { DevPipeline } from './pipeline.js';
export class DevApp extends BaseApp {
- settings: AstroSettings;
logger: Logger;
- loader: ModuleLoader;
- manifestData: RoutesList;
currentRenderContext: RenderContext | undefined = undefined;
- constructor(
- manifest: SSRManifest,
- streaming = true,
- settings: AstroSettings,
- logger: Logger,
- loader: ModuleLoader,
- manifestData: RoutesList,
- ) {
- super(manifest, streaming, settings, logger, loader, manifestData);
- this.settings = settings;
+ constructor(manifest: SSRManifest, streaming = true, logger: Logger, routesList: RoutesList) {
+ super(manifest, streaming, logger);
this.logger = logger;
- this.loader = loader;
- this.manifestData = manifestData;
+ this.manifestData = routesList;
}
- static async create(
- manifest: SSRManifest,
- routesList: RoutesList,
- settings: AstroSettings,
- logger: Logger,
- loader: ModuleLoader,
- ): Promise {
- return new DevApp(manifest, true, settings, logger, loader, routesList);
- }
-
- createPipeline(
- _streaming: boolean,
- manifest: SSRManifest,
- settings: AstroSettings,
- logger: Logger,
- loader: ModuleLoader,
- manifestData: RoutesList,
- ): DevPipeline {
- return DevPipeline.create(manifestData, {
- loader,
+ createPipeline(streaming: boolean, manifest: SSRManifest, logger: Logger): DevPipeline {
+ return DevPipeline.create({
logger,
manifest,
- settings,
+ streaming,
});
}
+ match(request: Request): RouteData | undefined {
+ return super.match(request, true);
+ }
+
async createRenderContext(payload: CreateRenderContext): Promise {
this.currentRenderContext = await super.createRenderContext(payload);
return this.currentRenderContext;
}
- public clearRouteCache() {
- this.pipeline.clearRouteCache();
- }
-
- public async handleRequest({
- controller,
- incomingRequest,
- incomingResponse,
- isHttps,
- }: HandleRequest): Promise {
- const { config } = this.pipeline;
- const origin = `${isHttps ? 'https' : 'http'}://${
- incomingRequest.headers[':authority'] ?? incomingRequest.headers.host
- }`;
-
- const url = new URL(origin + incomingRequest.url);
- let pathname: string;
- if (config.trailingSlash === 'never' && !incomingRequest.url) {
- pathname = '';
- } else {
- // We already have a middleware that checks if there's an incoming URL that has invalid URI, so it's safe
- // to not handle the error: packages/astro/src/vite-plugin-astro-server/base.ts
- pathname = decodeURI(url.pathname);
- }
-
- // Add config.base back to url before passing it to SSR
- url.pathname = removeTrailingForwardSlash(config.base) + url.pathname;
- if (
- url.pathname.endsWith('/') &&
- !shouldAppendForwardSlash(config.trailingSlash, config.build.format)
- ) {
- url.pathname = url.pathname.slice(0, -1);
- }
-
- let body: BodyInit | undefined = undefined;
- if (!(incomingRequest.method === 'GET' || incomingRequest.method === 'HEAD')) {
- let bytes: Uint8Array[] = [];
- await new Promise((resolve) => {
- incomingRequest.on('data', (part) => {
- bytes.push(part);
- });
- incomingRequest.on('end', resolve);
- });
- body = Buffer.concat(bytes);
- }
-
- const self = this;
- await runWithErrorHandling({
- controller,
- pathname,
- async run() {
- const matchedRoute = await matchRoute(pathname, self.manifestData, self.pipeline);
- const resolvedPathname = matchedRoute?.resolvedPathname ?? pathname;
- return await self.handleRoute({
- matchedRoute,
- url,
- pathname: resolvedPathname,
- body,
- incomingRequest: incomingRequest,
- incomingResponse: incomingResponse,
- });
- },
- onError(_err) {
- const { error, errorWithMetadata } = recordServerError(
- self.loader,
- config,
- self.logger,
- _err,
- );
- handle500Response(self.loader, incomingResponse, errorWithMetadata);
- return error;
- },
- });
- }
-
- async handleRoute({
- matchedRoute,
- incomingRequest,
- incomingResponse,
- body,
- url,
- pathname,
- }: HandleRoute): Promise {
- const timeStart = performance.now();
- const { config, loader, logger } = this.pipeline;
-
- if (!matchedRoute) {
- // This should never happen, because ensure404Route will add a 404 route if none exists.
- throw new Error('No route matched, and default 404 route was not found.');
- }
-
- let request: Request;
- let renderContext: RenderContext;
- let route: RouteData = matchedRoute.route;
- const componentInstance = await this.pipeline.getComponentByRoute(route);
- const actions = await loadActions(loader);
- this.pipeline.setActions(actions);
- const middleware = (await loadMiddleware(loader)).onRequest;
- // This is required for adapters to set locals in dev mode. They use a dev server middleware to inject locals to the `http.IncomingRequest` object.
- const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
-
- // Allows adapters to pass in locals in dev mode.
- request = createRequest({
- url,
- headers: incomingRequest.headers,
- method: incomingRequest.method,
- body,
- logger,
- isPrerendered: route.prerender,
- routePattern: route.component,
- });
-
- // Set user specified headers to response object.
- for (const [name, value] of Object.entries(config.server.headers ?? {})) {
- if (value) incomingResponse.setHeader(name, value);
- }
-
- renderContext = await this.createRenderContext({
- locals,
- pipeline: this.pipeline,
- pathname,
- middleware: isDefaultPrerendered404(matchedRoute.route) ? undefined : middleware,
- request,
- routeData: route,
- clientAddress: incomingRequest.socket.remoteAddress,
- actions,
- shouldInjectCspMetaTags: false,
- });
-
- let response;
- let statusCode = 200;
- let isReroute = false;
- let isRewrite = false;
-
- try {
- response = await renderContext.render(componentInstance);
- isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
- isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
- const statusCodedMatched = getStatusByMatchedRoute(route);
- statusCode = isRewrite
- ? // Ignore `matchedRoute` status for rewrites
- response.status
- : // Our internal noop middleware sets a particular header. If the header isn't present, it means that the user have
- // their own middleware, so we need to return what the user returns.
- !response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute
- ? response.status
- : (statusCodedMatched ?? response.status);
- } catch (err: any) {
- response = await this.renderError(request, {
- skipMiddleware: false,
- locals,
- status: 500,
- prerenderedErrorPageFetch: fetch,
- clientAddress: incomingRequest.socket.remoteAddress,
- error: err,
- });
- statusCode = 500;
- } finally {
- this.currentRenderContext?.session?.[PERSIST_SYMBOL]();
- }
-
- if (isLoggedRequest(pathname)) {
- const timeEnd = performance.now();
- logger.info(
- null,
- req({
- url: pathname,
- method: incomingRequest.method,
- statusCode,
- isRewrite,
- reqTime: timeEnd - timeStart,
- }),
- );
- }
-
- 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', this.manifestData, this.pipeline);
- if (fourOhFourRoute) {
- renderContext = await this.createRenderContext({
- locals,
- pipeline: this.pipeline,
- pathname,
- middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? undefined : middleware,
- request,
- routeData: fourOhFourRoute.route,
- clientAddress: incomingRequest.socket.remoteAddress,
- status: 404,
- shouldInjectCspMetaTags: false,
- });
- const component = await this.pipeline.preload(
- fourOhFourRoute.route,
- fourOhFourRoute.filePath,
- );
- response = await renderContext.render(component);
- }
- }
-
- // We remove the internally-used header before we send the response to the user agent.
- if (isReroute) {
- response.headers.delete(REROUTE_DIRECTIVE_HEADER);
- }
- if (isRewrite) {
- response.headers.delete(REROUTE_DIRECTIVE_HEADER);
- }
-
- if (route.type === 'endpoint') {
- await writeWebResponse(incomingResponse, response);
- return;
- }
-
- // This check is important in case of rewrites.
- // A route can start with a 404 code, then the rewrite kicks in and can return a 200 status code
- if (isRewrite) {
- await writeSSRResult(request, response, incomingResponse);
- return;
- }
-
- // We are in a recursion, and it's possible that this function is called itself with a status code
- // By default, the status code passed via parameters is computed by the matched route.
- //
- // By default, we should give priority to the status code passed, although it's possible that
- // the `Response` emitted by the user is a redirect. If so, then return the returned response.
- if (response.status < 400 && response.status >= 300) {
- if (
- response.status >= 300 &&
- response.status < 400 &&
- routeIsRedirect(route) &&
- !config.build.redirects &&
- this.pipeline.settings.buildOutput === 'static'
- ) {
- // If we're here, it means that the calling static redirect that was configured by the user
- // We try to replicate the same behaviour that we provide during a static build
- 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;
- }
-
- // Apply the `status` override to the response object before responding.
- // Response.status is read-only, so a clone is required to override.
- if (response.status !== statusCode) {
- response = new Response(response.body, {
- status: statusCode,
- headers: response.headers,
- });
- }
- await writeSSRResult(request, response, incomingResponse);
- }
-
- match(request: Request, _allowPrerenderedRoutes: boolean): RouteData | undefined {
- const url = new URL(request.url);
- // ignore requests matching public assets
- if (this.manifest.assets.has(url.pathname)) return undefined;
- let pathname = prependForwardSlash(this.removeBase(url.pathname));
-
- return this.manifestData.routes.find((route) => {
- return (
- route.pattern.test(pathname) ||
- route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname))
- );
- });
- }
-
async renderError(
request: Request,
{ locals, skipMiddleware = false, error, clientAddress, status }: RenderErrorOptions,
@@ -397,8 +54,7 @@ export class DevApp extends BaseApp {
}
try {
- const filePath500 = new URL(`./${custom500.component}`, this.settings.config.root);
- const preloaded500Component = await this.pipeline.preload(custom500, filePath500);
+ const preloaded500Component = await this.pipeline.getComponentByRoute(custom500);
const renderContext = await this.createRenderContext({
locals,
pipeline: this.pipeline,
@@ -432,126 +88,6 @@ export class DevApp extends BaseApp {
}
}
-/** Check for /404 and /500 custom routes to compute status code */
-function getStatusByMatchedRoute(route: RouteData) {
- if (route.route === '/404') return 404;
- if (route.route === '/500') return 500;
- return undefined;
-}
-
-function isDefaultPrerendered404(route: RouteData) {
- return route.route === '/404' && route.prerender && route.component === DEFAULT_404_COMPONENT;
-}
-
-function isLoggedRequest(url: string) {
- return url !== '/favicon.ico';
-}
-
-type HandleRequest = {
- controller: DevServerController;
- incomingRequest: http.IncomingMessage;
- incomingResponse: http.ServerResponse;
- isHttps: boolean;
-};
-
-type AsyncReturnType Promise> = T extends (
- ...args: any
-) => Promise
- ? R
- : any;
-
-type HandleRoute = {
- matchedRoute: AsyncReturnType;
- url: URL;
- pathname: string;
- body: BodyInit | undefined;
- incomingRequest: http.IncomingMessage;
- incomingResponse: http.ServerResponse;
-};
-
-interface MatchedRoute {
- route: RouteData;
- filePath: URL;
- resolvedPathname: string;
-}
-
-async function matchRoute(
- pathname: string,
- routesList: RoutesList,
- pipeline: DevPipeline,
-): Promise {
- const { config, logger, routeCache, serverLike, settings } = pipeline;
- const matches = matchAllRoutes(pathname, routesList);
-
- const preloadedMatches = await getSortedPreloadedMatches({ pipeline, matches, settings });
-
- for await (const { route: maybeRoute, filePath } of preloadedMatches) {
- // attempt to get static paths
- // if this fails, we have a bad URL match!
- try {
- await getProps({
- mod: await pipeline.preload(maybeRoute, filePath),
- routeData: maybeRoute,
- routeCache,
- pathname: pathname,
- logger,
- serverLike,
- base: config.base,
- trailingSlash: config.trailingSlash,
- });
- return {
- route: maybeRoute,
- filePath,
- resolvedPathname: pathname,
- };
- } catch (e) {
- // Ignore error for no matching static paths
- if (isAstroError(e) && e.title === NoMatchingStaticPathFound.title) {
- continue;
- }
- throw e;
- }
- }
-
- // Try without `.html` extensions or `index.html` in request URLs to mimic
- // routing behavior in production builds. This supports both file and directory
- // build formats, and is necessary based on how the manifest tracks build targets.
- const altPathname = pathname.replace(/\/index\.html$/, '/').replace(/\.html$/, '');
-
- if (altPathname !== pathname) {
- return await matchRoute(altPathname, routesList, pipeline);
- }
-
- if (matches.length) {
- const possibleRoutes = matches.flatMap((route) => route.component);
-
- logger.warn(
- 'router',
- `${NoMatchingStaticPathFound.message(
- pathname,
- )}\n\n${NoMatchingStaticPathFound.hint(possibleRoutes)}`,
- );
- }
-
- const custom404 = getCustom404Route(routesList);
-
- if (custom404) {
- const filePath = new URL(`./${custom404.component}`, config.root);
-
- return {
- route: custom404,
- filePath,
- resolvedPathname: pathname,
- };
- }
-
- return undefined;
-}
-
-function getCustom404Route(manifestData: RoutesList): RouteData | undefined {
- return manifestData.routes.find((r) => isRoute404(r.route));
-}
-
function getCustom500Route(manifestData: RoutesList): RouteData | undefined {
return manifestData.routes.find((r) => isRoute500(r.route));
}
diff --git a/packages/astro/src/core/app/dev/pipeline.ts b/packages/astro/src/core/app/dev/pipeline.ts
index 40801eb3699e..bf3e83ca51b4 100644
--- a/packages/astro/src/core/app/dev/pipeline.ts
+++ b/packages/astro/src/core/app/dev/pipeline.ts
@@ -1,221 +1,97 @@
-import { fileURLToPath } from 'node:url';
-import { getInfoOutput } from '../../../cli/info/index.js';
-import type { AstroSettings, ComponentInstance, RoutesList } from '../../../types/astro.js';
+import type { ComponentInstance } from '../../../types/astro.js';
import type {
- DevToolbarMetadata,
RewritePayload,
RouteData,
SSRElement,
- SSRLoadedRenderer,
- SSRManifest,
+ SSRResult,
} from '../../../types/public/index.js';
-import { getStylesForURL } from '../../../vite-plugin-astro-server/css.js';
-import { getComponentMetadata } from '../../../vite-plugin-astro-server/metadata.js';
-import { createResolve } from '../../../vite-plugin-astro-server/resolve.js';
-import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
-import { type HeadElements, Pipeline, type TryRewriteResult } from '../../base-pipeline.js';
-import { ASTRO_VERSION } from '../../constants.js';
-import { enhanceViteSSRError } from '../../errors/dev/index.js';
-import { AggregateError, CSSError, MarkdownError } from '../../errors/index.js';
-import type { Logger } from '../../logger/core.js';
-import type { ModuleLoader } from '../../module-loader/index.js';
-import { RedirectComponentInstance, routeIsRedirect } from '../../redirects/index.js';
-import { loadRenderer } from '../../render/index.js';
-import { createDefaultRoutes } from '../../routing/default.js';
+import { Pipeline, type TryRewriteResult } from '../../base-pipeline.js';
+import {
+ createAssetLink,
+ createModuleScriptElement,
+ createStylesheetElementSet,
+} from '../../render/ssr-element.js';
import { findRouteToRewrite } from '../../routing/rewrite.js';
-import { isPage, viteID } from '../../util.js';
-import { resolveIdToUrl } from '../../viteUtils.js';
export class DevPipeline extends Pipeline {
- // renderers are loaded on every request,
- // so it needs to be mutable here unlike in other environments
- override renderers = new Array();
-
- routesList: RoutesList | undefined;
-
- componentInterner: WeakMap = new WeakMap<
- RouteData,
- ComponentInstance
- >();
-
- private constructor(
- readonly loader: ModuleLoader,
- readonly logger: Logger,
- readonly manifest: SSRManifest,
- readonly settings: AstroSettings,
- readonly config = settings.config,
- readonly defaultRoutes = createDefaultRoutes(manifest),
- ) {
- const resolve = createResolve(loader, config.root);
- const serverLike = settings.buildOutput === 'server';
- const streaming = true;
- super(logger, manifest, 'development', [], resolve, serverLike, streaming);
- manifest.serverIslandMap = settings.serverIslandMap;
- manifest.serverIslandNameMap = settings.serverIslandNameMap;
- }
-
- static create(
- manifestData: RoutesList,
- {
- loader,
+ static create({
+ logger,
+ manifest,
+ streaming,
+ }: Pick) {
+ const resolve = async function resolve(specifier: string) {
+ if (!(specifier in manifest.entryModules)) {
+ throw new Error(`Unable to resolve [${specifier}]`);
+ }
+ const bundlePath = manifest.entryModules[specifier];
+ if (bundlePath.startsWith('data:') || bundlePath.length === 0) {
+ return bundlePath;
+ } else {
+ return createAssetLink(bundlePath, manifest.base, manifest.assetsPrefix);
+ }
+ };
+ const pipeline = new DevPipeline(
logger,
manifest,
- settings,
- }: Pick,
- ) {
- const pipeline = new DevPipeline(loader, logger, manifest, settings);
- pipeline.routesList = manifestData;
+ 'production',
+ manifest.renderers,
+ resolve,
+ true,
+ streaming,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ );
return pipeline;
}
- async headElements(routeData: RouteData): Promise {
- const {
- config: { root },
- loader,
- runtimeMode,
- settings,
- } = this;
- const filePath = new URL(`${routeData.component}`, root);
+ headElements(routeData: RouteData): Pick {
+ const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
+ // may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc.
+ const links = new Set();
const scripts = new Set();
-
- // Inject HMR scripts
- if (isPage(filePath, settings) && runtimeMode === 'development') {
- scripts.add({
- props: { type: 'module', src: '/@vite/client' },
- children: '',
- });
-
- if (
- settings.config.devToolbar.enabled &&
- (await settings.preferences.get('devToolbar.enabled'))
- ) {
- const src = await resolveIdToUrl(loader, 'astro/runtime/client/dev-toolbar/entrypoint.js');
- scripts.add({ props: { type: 'module', src }, children: '' });
-
- const additionalMetadata: DevToolbarMetadata['__astro_dev_toolbar__'] = {
- root: fileURLToPath(settings.config.root),
- version: ASTRO_VERSION,
- latestAstroVersion: settings.latestAstroVersion,
- debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }),
- };
-
- // Additional data for the dev overlay
- const children = `window.__astro_dev_toolbar__ = ${JSON.stringify(additionalMetadata)}`;
- scripts.add({ props: {}, children });
- }
- }
-
- // TODO: We should allow adding generic HTML elements to the head, not just scripts
- for (const script of settings.scripts) {
- if (script.stage === 'head-inline') {
- scripts.add({
- props: {},
- children: script.content,
- });
- } else if (script.stage === 'page' && isPage(filePath, settings)) {
- scripts.add({
- props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
- children: '',
- });
- }
- }
-
- // Pass framework CSS in as style tags to be appended to the page.
- const links = new Set();
- const { urls, styles: _styles } = await getStylesForURL(filePath, loader);
- for (const href of urls) {
- links.add({ props: { rel: 'stylesheet', href }, children: '' });
- }
-
- const styles = new Set();
- for (const { id, url: src, content } of _styles) {
- // Vite handles HMR for styles injected as scripts
- scripts.add({ props: { type: 'module', src }, children: '' });
- // But we still want to inject the styles to avoid FOUC. The style tags
- // should emulate what Vite injects so further HMR works as expected.
- styles.add({ props: { 'data-vite-dev-id': id }, children: content });
- }
-
- return { scripts, styles, links };
- }
-
- componentMetadata(routeData: RouteData) {
- const {
- config: { root },
- loader,
- } = this;
- const filePath = new URL(`${routeData.component}`, root);
- return getComponentMetadata(filePath, loader);
- }
-
- async preload(routeData: RouteData, filePath: URL) {
- if (routeIsRedirect(routeData)) {
- return RedirectComponentInstance;
- }
-
- const { loader } = this;
-
- // First check built-in routes
- for (const route of this.defaultRoutes) {
- if (route.matchesComponent(filePath)) {
- return route.instance;
+ const styles = createStylesheetElementSet(routeInfo?.styles ?? []);
+
+ for (const script of routeInfo?.scripts ?? []) {
+ if ('stage' in script) {
+ if (script.stage === 'head-inline') {
+ scripts.add({
+ props: {},
+ children: script.children,
+ });
+ }
+ } else {
+ scripts.add(createModuleScriptElement(script));
}
}
-
- // Important: This needs to happen first, in case a renderer provides polyfills.
- const renderers__ = this.settings.renderers.map((r) => loadRenderer(r, loader));
- const renderers_ = await Promise.all(renderers__);
- this.renderers = renderers_.filter((r): r is SSRLoadedRenderer => Boolean(r));
-
- try {
- // Load the module from the Vite SSR Runtime.
- const componentInstance = (await loader.import(viteID(filePath))) as ComponentInstance;
- this.componentInterner.set(routeData, componentInstance);
- return componentInstance;
- } catch (error) {
- // If the error came from Markdown or CSS, we already handled it and there's no need to enhance it
- if (MarkdownError.is(error) || CSSError.is(error) || AggregateError.is(error)) {
- throw error;
- }
-
- throw enhanceViteSSRError({ error, filePath, loader });
- }
+ return { links, styles, scripts };
}
- clearRouteCache() {
- this.routeCache.clearAll();
- this.componentInterner = new WeakMap();
- }
+ componentMetadata() {}
async getComponentByRoute(routeData: RouteData): Promise {
- const component = this.componentInterner.get(routeData);
- if (component) {
- return component;
- } else {
- const filePath = new URL(`${routeData.component}`, this.config.root);
- return await this.preload(routeData, filePath);
- }
+ const url = new URL(routeData.component, this.manifest.rootDir);
+ const module = await import(url.toString());
+ return module;
}
async tryRewrite(payload: RewritePayload, request: Request): Promise {
- if (!this.routesList) {
- throw new Error('Missing manifest data. This is an internal error, please file an issue.');
- }
- const { routeData, pathname, newUrl } = findRouteToRewrite({
+ const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,
- routes: this.routesList?.routes,
- trailingSlash: this.config.trailingSlash,
- buildFormat: this.config.build.format,
- base: this.config.base,
- outDir: this.manifest.outDir,
+ routes: this.manifest?.routes.map((r) => r.routeData),
+ trailingSlash: this.manifest.trailingSlash,
+ buildFormat: this.manifest.buildFormat,
+ base: this.manifest.base,
+ outDir: this.serverLike ? this.manifest.buildClientDir : this.manifest.outDir,
});
const componentInstance = await this.getComponentByRoute(routeData);
return { newUrl, pathname, componentInstance, routeData };
}
-
- setManifestData(manifestData: RoutesList) {
- this.routesList = manifestData;
- }
}
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 21e5d5638f43..f8908e264856 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -1,6 +1,8 @@
export { App } from './app.js';
export { BaseApp, type RenderErrorOptions, type RenderOptions } from './base.js';
export { deserializeManifest, fromRoutingStrategy, toRoutingStrategy } from './common.js';
+export { DevApp } from './dev/app.js';
+export { createConsoleLogger } from './logging.js';
export {
deserializeRouteData,
deserializeRouteInfo,
diff --git a/packages/astro/src/core/app/logging.ts b/packages/astro/src/core/app/logging.ts
new file mode 100644
index 000000000000..4b73d1c72e49
--- /dev/null
+++ b/packages/astro/src/core/app/logging.ts
@@ -0,0 +1,10 @@
+import type { AstroInlineConfig } from '../../types/public/index.js';
+import { consoleLogDestination } from '../logger/console.js';
+import { Logger } from '../logger/core.js';
+
+export function createConsoleLogger(level: AstroInlineConfig['logLevel']): Logger {
+ return new Logger({
+ dest: consoleLogDestination,
+ level: level ?? 'info',
+ });
+}
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index 6f25695ffcf7..04716ab07d13 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -51,7 +51,6 @@ export type AssetsPrefix =
| undefined;
export type SSRManifest = {
- hrefRoot: string;
adapterName: string;
routes: RouteInfo[];
site?: string;
@@ -86,12 +85,13 @@ export type SSRManifest = {
actions?: () => Promise | SSRActions;
checkOrigin: boolean;
sessionConfig?: ResolvedSessionConfig;
- cacheDir: string | URL;
- srcDir: string | URL;
- outDir: string | URL;
- publicDir: string | URL;
- buildClientDir: string | URL;
- buildServerDir: string | URL;
+ cacheDir: URL;
+ srcDir: URL;
+ outDir: URL;
+ rootDir: URL;
+ publicDir: URL;
+ buildClientDir: URL;
+ buildServerDir: URL;
csp: SSRManifestCSP | undefined;
};
@@ -130,7 +130,21 @@ export type SerializedSSRManifest = Omit<
| 'clientDirectives'
| 'serverIslandNameMap'
| 'key'
+ | 'rootDir'
+ | 'srcDir'
+ | 'cacheDir'
+ | 'outDir'
+ | 'publicDir'
+ | 'buildClientDir'
+ | 'buildServerDir'
> & {
+ rootDir: string;
+ srcDir: string;
+ cacheDir: string;
+ outDir: string;
+ publicDir: string;
+ buildClientDir: string;
+ buildServerDir: string;
routes: SerializedRouteInfo[];
assets: string[];
componentMetadata: [string, SSRComponentMetadata][];
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 9bc1c7a29b8c..7aee6097becd 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -710,7 +710,7 @@ async function createBuildManifest(
};
}
return {
- hrefRoot: settings.config.root.toString(),
+ rootDir: settings.config.root,
srcDir: settings.config.srcDir,
buildClientDir: settings.config.build.client,
buildServerDir: settings.config.build.server,
diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts
index 0440992776be..0c390e177667 100644
--- a/packages/astro/src/core/build/plugins/plugin-manifest.ts
+++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts
@@ -342,7 +342,7 @@ async function buildManifest(
}
return {
- hrefRoot: opts.settings.config.root.toString(),
+ rootDir: opts.settings.config.root.toString(),
cacheDir: opts.settings.config.cacheDir.toString(),
outDir: opts.settings.config.outDir.toString(),
srcDir: opts.settings.config.srcDir.toString(),
diff --git a/packages/astro/src/core/config/index.ts b/packages/astro/src/core/config/index.ts
index 00832e84733d..617d66768d30 100644
--- a/packages/astro/src/core/config/index.ts
+++ b/packages/astro/src/core/config/index.ts
@@ -3,7 +3,7 @@ export {
resolveConfigPath,
resolveRoot,
} from './config.js';
-export { createNodeLogger } from './logging.js';
+export { createConsoleLogger, createNodeLogger } from './logging.js';
export { mergeConfig } from './merge.js';
export { createSettings } from './settings.js';
export { loadTSConfig, updateTSConfigForFramework } from './tsconfig.js';
diff --git a/packages/astro/src/core/config/logging.ts b/packages/astro/src/core/config/logging.ts
index bd72f8b5e978..417ec87fb358 100644
--- a/packages/astro/src/core/config/logging.ts
+++ b/packages/astro/src/core/config/logging.ts
@@ -1,4 +1,5 @@
import type { AstroInlineConfig } from '../../types/public/config.js';
+import { consoleLogDestination } from '../logger/console.js';
import { Logger } from '../logger/core.js';
import { nodeLogDestination } from '../logger/node.js';
@@ -10,3 +11,10 @@ export function createNodeLogger(inlineConfig: AstroInlineConfig): Logger {
level: inlineConfig.logLevel ?? 'info',
});
}
+
+export function createConsoleLogger(level: AstroInlineConfig['logLevel']): Logger {
+ return new Logger({
+ dest: consoleLogDestination,
+ level: level ?? 'info',
+ });
+}
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 8a37377f2e34..5fbfc5015fbc 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -22,6 +22,7 @@ import astroPrefetch from '../prefetch/vite-plugin-prefetch.js';
import astroDevToolbar from '../toolbar/vite-plugin-dev-toolbar.js';
import astroTransitions from '../transitions/vite-plugin-transitions.js';
import type { AstroSettings } from '../types/astro.js';
+import { vitePluginApp } from '../vite-plugin-app/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
@@ -151,6 +152,7 @@ export async function createVite(
astroScriptsPlugin({ settings }),
// The server plugin is for dev only and having it run during the build causes
// the build to run very slow as the filewatcher is triggered often.
+ command === 'dev' && vitePluginApp(),
command === 'dev' && vitePluginAstroServer({ settings, logger }),
importMetaEnv({ envLoader }),
astroEnv({ settings, sync, envLoader }),
diff --git a/packages/astro/src/core/errors/dev/runtime.ts b/packages/astro/src/core/errors/dev/runtime.ts
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/astro/src/core/errors/dev/utils/runtime.ts b/packages/astro/src/core/errors/dev/utils/runtime.ts
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/astro/src/core/routing/default.ts b/packages/astro/src/core/routing/default.ts
index 000e4cd1a03b..52a838bd4fc8 100644
--- a/packages/astro/src/core/routing/default.ts
+++ b/packages/astro/src/core/routing/default.ts
@@ -18,7 +18,7 @@ type DefaultRouteParams = {
export const DEFAULT_COMPONENTS = [DEFAULT_404_COMPONENT, SERVER_ISLAND_COMPONENT];
export function createDefaultRoutes(manifest: SSRManifest): DefaultRouteParams[] {
- const root = new URL(manifest.hrefRoot);
+ const root = new URL(manifest.rootDir);
return [
{
instance: default404Instance,
diff --git a/packages/astro/src/manifest/serialized.ts b/packages/astro/src/manifest/serialized.ts
index 013ff1904a37..6e45885bf3cc 100644
--- a/packages/astro/src/manifest/serialized.ts
+++ b/packages/astro/src/manifest/serialized.ts
@@ -46,9 +46,7 @@ export async function serializedManifestPlugin({
};
}
-export async function createSerializedManifest(
- settings: AstroSettings,
-): Promise {
+async function createSerializedManifest(settings: AstroSettings): Promise {
let i18nManifest: SSRManifestI18n | undefined;
let csp: SSRManifestCSP | undefined;
if (settings.config.i18n) {
@@ -78,13 +76,13 @@ export async function createSerializedManifest(
}
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,
+ rootDir: settings.config.root.toString(),
+ srcDir: settings.config.srcDir.toString(),
+ cacheDir: settings.config.cacheDir.toString(),
+ outDir: settings.config.outDir.toString(),
+ buildServerDir: settings.config.build.server.toString(),
+ buildClientDir: settings.config.build.client.toString(),
+ publicDir: settings.config.publicDir.toString(),
trailingSlash: settings.config.trailingSlash,
buildFormat: settings.config.build.format,
compressHTML: settings.config.compressHTML,
diff --git a/packages/astro/src/prerender/routing.ts b/packages/astro/src/prerender/routing.ts
index 459330142cbb..e78ac53777d1 100644
--- a/packages/astro/src/prerender/routing.ts
+++ b/packages/astro/src/prerender/routing.ts
@@ -1,25 +1,23 @@
-import type { DevPipeline } from '../core/app/dev/pipeline.js';
import { routeIsRedirect } from '../core/redirects/index.js';
import { routeComparator } from '../core/routing/priority.js';
-import type { AstroSettings } from '../types/astro.js';
-import type { RouteData } from '../types/public/internal.js';
-import { getPrerenderStatus } from './metadata.js';
+import type { RouteData, SSRManifest } from '../types/public/internal.js';
+import type { AstroServerPipeline } from '../vite-plugin-app/pipeline.js';
type GetSortedPreloadedMatchesParams = {
- pipeline: DevPipeline;
+ pipeline: AstroServerPipeline;
matches: RouteData[];
- settings: AstroSettings;
+ manifest: SSRManifest;
};
export async function getSortedPreloadedMatches({
pipeline,
matches,
- settings,
+ manifest,
}: GetSortedPreloadedMatchesParams) {
return (
await preloadAndSetPrerenderStatus({
pipeline,
matches,
- settings,
+ manifest,
})
)
.sort((a, b) => routeComparator(a.route, b.route))
@@ -27,9 +25,9 @@ export async function getSortedPreloadedMatches({
}
type PreloadAndSetPrerenderStatusParams = {
- pipeline: DevPipeline;
+ pipeline: AstroServerPipeline;
matches: RouteData[];
- settings: AstroSettings;
+ manifest: SSRManifest;
};
type PreloadAndSetPrerenderStatusResult = {
@@ -38,13 +36,12 @@ type PreloadAndSetPrerenderStatusResult = {
};
async function preloadAndSetPrerenderStatus({
- pipeline,
matches,
- settings,
+ manifest,
}: PreloadAndSetPrerenderStatusParams): Promise {
const preloaded = new Array();
for (const route of matches) {
- const filePath = new URL(`./${route.component}`, settings.config.root);
+ const filePath = new URL(`./${route.component}`, manifest.rootDir);
if (routeIsRedirect(route)) {
preloaded.push({
route,
@@ -53,16 +50,6 @@ async function preloadAndSetPrerenderStatus({
continue;
}
- // gets the prerender metadata set by the `astro:scanner` vite plugin
- const prerenderStatus = getPrerenderStatus({
- filePath,
- loader: pipeline.loader,
- });
-
- if (prerenderStatus !== undefined) {
- route.prerender = prerenderStatus;
- }
-
preloaded.push({ route, filePath });
}
return preloaded;
diff --git a/packages/astro/src/types/public/index.ts b/packages/astro/src/types/public/index.ts
index 42c131fc2b68..2b31f34ea595 100644
--- a/packages/astro/src/types/public/index.ts
+++ b/packages/astro/src/types/public/index.ts
@@ -21,7 +21,12 @@ export type {
UnresolvedImageTransform,
} from '../../assets/types.js';
export type { ContainerRenderer } from '../../container/index.js';
-export type { AssetsPrefix, NodeAppHeadersJson, SSRManifest } from '../../core/app/types.js';
+export type {
+ AssetsPrefix,
+ NodeAppHeadersJson,
+ RouteInfo,
+ SSRManifest,
+} from '../../core/app/types.js';
export type {
AstroCookieGetOptions,
AstroCookieSetOptions,
diff --git a/packages/astro/src/vite-plugin-app/app.ts b/packages/astro/src/vite-plugin-app/app.ts
new file mode 100644
index 000000000000..3ac168b6e7dd
--- /dev/null
+++ b/packages/astro/src/vite-plugin-app/app.ts
@@ -0,0 +1,573 @@
+import type http from 'node:http';
+import { prependForwardSlash, removeTrailingForwardSlash } from '@astrojs/internal-helpers/path';
+import { loadActions } from '../actions/loadActions.js';
+import { BaseApp, type RenderErrorOptions } from '../core/app/index.js';
+import { shouldAppendForwardSlash } from '../core/build/util.js';
+import {
+ clientLocalsSymbol,
+ DEFAULT_404_COMPONENT,
+ NOOP_MIDDLEWARE_HEADER,
+ REROUTE_DIRECTIVE_HEADER,
+ REWRITE_DIRECTIVE_HEADER_KEY,
+} from '../core/constants.js';
+import {
+ MiddlewareNoDataOrNextCalled,
+ MiddlewareNotAResponse,
+ NoMatchingStaticPathFound,
+} from '../core/errors/errors-data.js';
+import { type AstroError, createSafeError, isAstroError } from '../core/errors/index.js';
+import type { Logger } from '../core/logger/core.js';
+import { req } from '../core/messages.js';
+import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
+import type { ModuleLoader } from '../core/module-loader/index.js';
+import { routeIsRedirect } from '../core/redirects/index.js';
+import { getProps } from '../core/render/index.js';
+import type { CreateRenderContext, 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 type { AstroSettings, RoutesList } from '../types/astro.js';
+import type { RouteData, SSRManifest } from '../types/public/index.js';
+import type { DevServerController } from '../vite-plugin-astro-server/controller.js';
+import { recordServerError } from '../vite-plugin-astro-server/error.js';
+import { runWithErrorHandling } from '../vite-plugin-astro-server/index.js';
+import {
+ handle500Response,
+ writeSSRResult,
+ writeWebResponse,
+} from '../vite-plugin-astro-server/response.js';
+import { AstroServerPipeline } from './pipeline.js';
+
+export class AstroServerApp extends BaseApp {
+ settings: AstroSettings;
+ logger: Logger;
+ loader: ModuleLoader;
+ manifestData: RoutesList;
+ currentRenderContext: RenderContext | undefined = undefined;
+ constructor(
+ manifest: SSRManifest,
+ streaming = true,
+ logger: Logger,
+ manifestData: RoutesList,
+ loader: ModuleLoader,
+ settings: AstroSettings,
+ ) {
+ super(manifest, streaming, settings, logger, loader, manifestData);
+ this.settings = settings;
+ this.logger = logger;
+ this.loader = loader;
+ this.manifestData = manifestData;
+ }
+
+ static async create(
+ manifest: SSRManifest,
+ routesList: RoutesList,
+ logger: Logger,
+ loader: ModuleLoader,
+ settings: AstroSettings,
+ ): Promise {
+ return new AstroServerApp(manifest, true, logger, routesList, loader, settings);
+ }
+
+ createPipeline(
+ _streaming: boolean,
+ manifest: SSRManifest,
+ settings: AstroSettings,
+ logger: Logger,
+ loader: ModuleLoader,
+ manifestData: RoutesList,
+ ): AstroServerPipeline {
+ return AstroServerPipeline.create(manifestData, {
+ loader,
+ logger,
+ manifest,
+ settings,
+ });
+ }
+
+ async createRenderContext(payload: CreateRenderContext): Promise {
+ this.currentRenderContext = await super.createRenderContext(payload);
+ return this.currentRenderContext;
+ }
+
+ public clearRouteCache() {
+ this.pipeline.clearRouteCache();
+ }
+
+ public async handleRequest({
+ controller,
+ incomingRequest,
+ incomingResponse,
+ isHttps,
+ }: HandleRequest): Promise {
+ const origin = `${isHttps ? 'https' : 'http'}://${
+ incomingRequest.headers[':authority'] ?? incomingRequest.headers.host
+ }`;
+
+ const url = new URL(origin + incomingRequest.url);
+ let pathname: string;
+ if (this.manifest.trailingSlash === 'never' && !incomingRequest.url) {
+ pathname = '';
+ } else {
+ // We already have a middleware that checks if there's an incoming URL that has invalid URI, so it's safe
+ // to not handle the error: packages/astro/src/vite-plugin-astro-server/base.ts
+ pathname = decodeURI(url.pathname);
+ }
+
+ // Add config.base back to url before passing it to SSR
+ url.pathname = removeTrailingForwardSlash(this.manifest.base) + url.pathname;
+ if (
+ url.pathname.endsWith('/') &&
+ !shouldAppendForwardSlash(this.manifest.trailingSlash, this.manifest.buildFormat)
+ ) {
+ url.pathname = url.pathname.slice(0, -1);
+ }
+
+ let body: BodyInit | undefined = undefined;
+ if (!(incomingRequest.method === 'GET' || incomingRequest.method === 'HEAD')) {
+ let bytes: Uint8Array[] = [];
+ await new Promise((resolve) => {
+ incomingRequest.on('data', (part) => {
+ bytes.push(part);
+ });
+ incomingRequest.on('end', resolve);
+ });
+ body = Buffer.concat(bytes);
+ }
+
+ const self = this;
+ await runWithErrorHandling({
+ controller,
+ pathname,
+ async run() {
+ const matchedRoute = await matchRoute(
+ pathname,
+ self.manifestData,
+ self.pipeline,
+ self.manifest,
+ );
+ const resolvedPathname = matchedRoute?.resolvedPathname ?? pathname;
+ return await self.handleRoute({
+ matchedRoute,
+ url,
+ pathname: resolvedPathname,
+ body,
+ incomingRequest: incomingRequest,
+ incomingResponse: incomingResponse,
+ });
+ },
+ onError(_err) {
+ const error = createSafeError(_err);
+ if (self.loader) {
+ const { errorWithMetadata } = recordServerError(
+ self.loader,
+ self.manifest,
+ self.logger,
+ error,
+ );
+ handle500Response(self.loader, incomingResponse, errorWithMetadata);
+ }
+ return error;
+ },
+ });
+ }
+
+ async handleRoute({
+ matchedRoute,
+ incomingRequest,
+ incomingResponse,
+ body,
+ url,
+ pathname,
+ }: HandleRoute): Promise {
+ const timeStart = performance.now();
+ const { loader, logger } = this.pipeline;
+
+ if (!matchedRoute) {
+ // This should never happen, because ensure404Route will add a 404 route if none exists.
+ throw new Error('No route matched, and default 404 route was not found.');
+ }
+
+ let request: Request;
+ let renderContext: RenderContext;
+ let route: RouteData = matchedRoute.route;
+ const componentInstance = await this.pipeline.getComponentByRoute(route);
+ const actions = await loadActions(loader);
+ this.pipeline.setActions(actions);
+ const middleware = (await loadMiddleware(loader)).onRequest;
+ // This is required for adapters to set locals in dev mode. They use a dev server middleware to inject locals to the `http.IncomingRequest` object.
+ const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
+
+ // Allows adapters to pass in locals in dev mode.
+ request = createRequest({
+ url,
+ headers: incomingRequest.headers,
+ method: incomingRequest.method,
+ body,
+ logger,
+ isPrerendered: route.prerender,
+ routePattern: route.component,
+ });
+
+ // Set user specified headers to response object.
+ for (const [name, value] of Object.entries(this.settings.config.server.headers ?? {})) {
+ if (value) incomingResponse.setHeader(name, value);
+ }
+
+ renderContext = await this.createRenderContext({
+ locals,
+ pipeline: this.pipeline,
+ pathname,
+ middleware: isDefaultPrerendered404(matchedRoute.route) ? undefined : middleware,
+ request,
+ routeData: route,
+ clientAddress: incomingRequest.socket.remoteAddress,
+ actions,
+ shouldInjectCspMetaTags: false,
+ });
+
+ let response;
+ let statusCode = 200;
+ let isReroute = false;
+ let isRewrite = false;
+
+ try {
+ response = await renderContext.render(componentInstance);
+ isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
+ isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
+ const statusCodedMatched = getStatusByMatchedRoute(route);
+ statusCode = isRewrite
+ ? // Ignore `matchedRoute` status for rewrites
+ response.status
+ : // Our internal noop middleware sets a particular header. If the header isn't present, it means that the user have
+ // their own middleware, so we need to return what the user returns.
+ !response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute
+ ? response.status
+ : (statusCodedMatched ?? response.status);
+ } catch (err: any) {
+ response = await this.renderError(request, {
+ skipMiddleware: false,
+ locals,
+ status: 500,
+ prerenderedErrorPageFetch: fetch,
+ clientAddress: incomingRequest.socket.remoteAddress,
+ error: err,
+ });
+ statusCode = 500;
+ } finally {
+ this.currentRenderContext?.session?.[PERSIST_SYMBOL]();
+ }
+
+ if (isLoggedRequest(pathname)) {
+ const timeEnd = performance.now();
+ logger.info(
+ null,
+ req({
+ url: pathname,
+ method: incomingRequest.method,
+ statusCode,
+ isRewrite,
+ reqTime: timeEnd - timeStart,
+ }),
+ );
+ }
+
+ 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',
+ this.manifestData,
+ this.pipeline,
+ this.manifest,
+ );
+ if (fourOhFourRoute) {
+ renderContext = await this.createRenderContext({
+ locals,
+ pipeline: this.pipeline,
+ pathname,
+ middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? undefined : middleware,
+ request,
+ routeData: fourOhFourRoute.route,
+ clientAddress: incomingRequest.socket.remoteAddress,
+ status: 404,
+ shouldInjectCspMetaTags: false,
+ });
+ const component = await this.pipeline.preload(
+ fourOhFourRoute.route,
+ fourOhFourRoute.filePath,
+ );
+ response = await renderContext.render(component);
+ }
+ }
+
+ // We remove the internally-used header before we send the response to the user agent.
+ if (isReroute) {
+ response.headers.delete(REROUTE_DIRECTIVE_HEADER);
+ }
+ if (isRewrite) {
+ response.headers.delete(REROUTE_DIRECTIVE_HEADER);
+ }
+
+ if (route.type === 'endpoint') {
+ await writeWebResponse(incomingResponse, response);
+ return;
+ }
+
+ // This check is important in case of rewrites.
+ // A route can start with a 404 code, then the rewrite kicks in and can return a 200 status code
+ if (isRewrite) {
+ await writeSSRResult(request, response, incomingResponse);
+ return;
+ }
+
+ // We are in a recursion, and it's possible that this function is called itself with a status code
+ // By default, the status code passed via parameters is computed by the matched route.
+ //
+ // By default, we should give priority to the status code passed, although it's possible that
+ // the `Response` emitted by the user is a redirect. If so, then return the returned response.
+ if (response.status < 400 && response.status >= 300) {
+ if (
+ response.status >= 300 &&
+ response.status < 400 &&
+ routeIsRedirect(route) &&
+ !this.settings.config.build.redirects &&
+ this.settings.buildOutput === 'static'
+ ) {
+ // If we're here, it means that the calling static redirect that was configured by the user
+ // We try to replicate the same behaviour that we provide during a static build
+ 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;
+ }
+
+ // Apply the `status` override to the response object before responding.
+ // Response.status is read-only, so a clone is required to override.
+ if (response.status !== statusCode) {
+ response = new Response(response.body, {
+ status: statusCode,
+ headers: response.headers,
+ });
+ }
+ await writeSSRResult(request, response, incomingResponse);
+ }
+
+ match(request: Request, _allowPrerenderedRoutes: boolean): RouteData | undefined {
+ const url = new URL(request.url);
+ // ignore requests matching public assets
+ if (this.manifest.assets.has(url.pathname)) return undefined;
+ let pathname = prependForwardSlash(this.removeBase(url.pathname));
+
+ return this.manifestData.routes.find((route) => {
+ return (
+ route.pattern.test(pathname) ||
+ route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname))
+ );
+ });
+ }
+
+ async renderError(
+ request: Request,
+ { locals, skipMiddleware = false, error, clientAddress, status }: RenderErrorOptions,
+ ): Promise {
+ // we always throw when we have Astro errors around the middleware
+ if (
+ isAstroError(error) &&
+ [MiddlewareNoDataOrNextCalled.name, MiddlewareNotAResponse.name].includes(error.name)
+ ) {
+ throw error;
+ }
+
+ const custom500 = getCustom500Route(this.manifestData);
+ // Show dev overlay
+ if (!custom500) {
+ throw error;
+ }
+
+ try {
+ const filePath500 = new URL(`./${custom500.component}`, this.manifest.rootDir);
+ const preloaded500Component = await this.pipeline.preload(custom500, filePath500);
+ const renderContext = await this.createRenderContext({
+ locals,
+ pipeline: this.pipeline,
+ pathname: this.getPathnameFromRequest(request),
+ middleware: skipMiddleware ? undefined : await this.pipeline.getMiddleware(),
+ request,
+ routeData: custom500,
+ clientAddress,
+ actions: await this.pipeline.getActions(),
+ status,
+ shouldInjectCspMetaTags: false,
+ });
+ renderContext.props.error = error;
+ const response = await renderContext.render(preloaded500Component);
+ // Log useful information that the custom 500 page may not display unlike the default error overlay
+ this.logger.error('router', (error as AstroError).stack || (error as AstroError).message);
+ return response;
+ } catch (_err) {
+ if (skipMiddleware === false) {
+ return this.renderError(request, {
+ clientAddress: undefined,
+ prerenderedErrorPageFetch: fetch,
+ status: 500,
+ skipMiddleware: true,
+ error: _err,
+ });
+ }
+ // If even skipping the middleware isn't enough to prevent the error, show the dev overlay
+ throw _err;
+ }
+ }
+}
+
+/** Check for /404 and /500 custom routes to compute status code */
+function getStatusByMatchedRoute(route: RouteData) {
+ if (route.route === '/404') return 404;
+ if (route.route === '/500') return 500;
+ return undefined;
+}
+
+function isDefaultPrerendered404(route: RouteData) {
+ return route.route === '/404' && route.prerender && route.component === DEFAULT_404_COMPONENT;
+}
+
+function isLoggedRequest(url: string) {
+ return url !== '/favicon.ico';
+}
+
+type HandleRequest = {
+ controller: DevServerController;
+ incomingRequest: http.IncomingMessage;
+ incomingResponse: http.ServerResponse;
+ isHttps: boolean;
+};
+
+type AsyncReturnType Promise> = T extends (
+ ...args: any
+) => Promise
+ ? R
+ : any;
+
+type HandleRoute = {
+ matchedRoute: AsyncReturnType;
+ url: URL;
+ pathname: string;
+ body: BodyInit | undefined;
+ incomingRequest: http.IncomingMessage;
+ incomingResponse: http.ServerResponse;
+};
+
+interface MatchedRoute {
+ route: RouteData;
+ filePath: URL;
+ resolvedPathname: string;
+}
+
+async function matchRoute(
+ pathname: string,
+ routesList: RoutesList,
+ pipeline: AstroServerPipeline,
+ manifest: SSRManifest,
+): Promise {
+ const { logger, routeCache, serverLike } = pipeline;
+ const matches = matchAllRoutes(pathname, routesList);
+
+ const preloadedMatches = await getSortedPreloadedMatches({
+ pipeline,
+ matches,
+ manifest,
+ });
+
+ for await (const { route: maybeRoute, filePath } of preloadedMatches) {
+ // attempt to get static paths
+ // if this fails, we have a bad URL match!
+ try {
+ await getProps({
+ mod: await pipeline.preload(maybeRoute, filePath),
+ routeData: maybeRoute,
+ routeCache,
+ pathname: pathname,
+ logger,
+ serverLike,
+ base: manifest.base,
+ trailingSlash: manifest.trailingSlash,
+ });
+ return {
+ route: maybeRoute,
+ filePath,
+ resolvedPathname: pathname,
+ };
+ } catch (e) {
+ // Ignore error for no matching static paths
+ if (isAstroError(e) && e.title === NoMatchingStaticPathFound.title) {
+ continue;
+ }
+ throw e;
+ }
+ }
+
+ // Try without `.html` extensions or `index.html` in request URLs to mimic
+ // routing behavior in production builds. This supports both file and directory
+ // build formats, and is necessary based on how the manifest tracks build targets.
+ const altPathname = pathname.replace(/\/index\.html$/, '/').replace(/\.html$/, '');
+
+ if (altPathname !== pathname) {
+ return await matchRoute(altPathname, routesList, pipeline, manifest);
+ }
+
+ if (matches.length) {
+ const possibleRoutes = matches.flatMap((route) => route.component);
+
+ logger.warn(
+ 'router',
+ `${NoMatchingStaticPathFound.message(
+ pathname,
+ )}\n\n${NoMatchingStaticPathFound.hint(possibleRoutes)}`,
+ );
+ }
+
+ const custom404 = getCustom404Route(routesList);
+
+ if (custom404) {
+ const filePath = new URL(`./${custom404.component}`, manifest.rootDir);
+
+ return {
+ route: custom404,
+ filePath,
+ resolvedPathname: pathname,
+ };
+ }
+
+ return undefined;
+}
+
+function getCustom404Route(manifestData: RoutesList): RouteData | undefined {
+ return manifestData.routes.find((r) => isRoute404(r.route));
+}
+
+function getCustom500Route(manifestData: RoutesList): RouteData | undefined {
+ return manifestData.routes.find((r) => isRoute500(r.route));
+}
diff --git a/packages/astro/src/core/app/dev/index.ts b/packages/astro/src/vite-plugin-app/createExports.ts
similarity index 50%
rename from packages/astro/src/core/app/dev/index.ts
rename to packages/astro/src/vite-plugin-app/createExports.ts
index 6eb424ea3cd5..75679d37c1a2 100644
--- a/packages/astro/src/core/app/dev/index.ts
+++ b/packages/astro/src/vite-plugin-app/createExports.ts
@@ -1,21 +1,20 @@
// @ts-expect-error This is a virtual module
import { routes } from 'astro:routes';
// @ts-expect-error This is a virtual module
-import { manifest } from 'astro:serialized-manifest';
-import type http from 'node:http';
-import type { AstroSettings, RoutesList } from '../../../types/astro.js';
-import type { DevServerController } from '../../../vite-plugin-astro-server/controller.js';
-import { Logger } from '../../logger/core.js';
-import { nodeLogDestination } from '../../logger/node.js';
-import type { ModuleLoader } from '../../module-loader/index.js';
-import type { RouteInfo } from '../types.js';
-import { DevApp } from './app.js';
+import { manifest as serializedManifest } from 'astro:serialized-manifest';
-export { DevApp };
+import type http from 'node:http';
+import type { RouteInfo } from '../core/app/types.js';
+import { Logger } from '../core/logger/core.js';
+import { nodeLogDestination } from '../core/logger/node.js';
+import type { ModuleLoader } from '../core/module-loader/index.js';
+import type { AstroSettings, RoutesList } from '../types/astro.js';
+import type { DevServerController } from '../vite-plugin-astro-server/controller.js';
+import { AstroServerApp } from './app.js';
export default async function createExports(
- settings: AstroSettings,
controller: DevServerController,
+ settings: AstroSettings,
loader: ModuleLoader,
) {
const logger = new Logger({
@@ -23,15 +22,15 @@ export default async function createExports(
level: 'info',
});
const routesList: RoutesList = { routes: routes.map((r: RouteInfo) => r.routeData) };
- const app = await DevApp.create(manifest, routesList, settings, logger, loader);
+ const app = await AstroServerApp.create(serializedManifest, routesList, logger, loader, settings);
return {
handler(incomingRequest: http.IncomingMessage, incomingResponse: http.ServerResponse) {
app.handleRequest({
controller,
incomingRequest,
incomingResponse,
- isHttps: loader.isHttps(),
+ isHttps: loader?.isHttps() ?? false,
});
},
};
diff --git a/packages/astro/src/vite-plugin-app/index.ts b/packages/astro/src/vite-plugin-app/index.ts
new file mode 100644
index 000000000000..10a4a52afd25
--- /dev/null
+++ b/packages/astro/src/vite-plugin-app/index.ts
@@ -0,0 +1,16 @@
+import type * as vite from 'vite';
+
+const VIRTUAL_MODULE_ID = 'astro:app';
+
+export function vitePluginApp(): vite.Plugin {
+ return {
+ name: 'astro:app',
+
+ async resolveId(id) {
+ if (id === VIRTUAL_MODULE_ID) {
+ const url = new URL('./createExports.js', import.meta.url);
+ return await this.resolve(url.toString());
+ }
+ },
+ };
+}
diff --git a/packages/astro/src/vite-plugin-app/pipeline.ts b/packages/astro/src/vite-plugin-app/pipeline.ts
new file mode 100644
index 000000000000..5a494f93d07d
--- /dev/null
+++ b/packages/astro/src/vite-plugin-app/pipeline.ts
@@ -0,0 +1,221 @@
+import { fileURLToPath } from 'node:url';
+import { getInfoOutput } from '../cli/info/index.js';
+import { type HeadElements, Pipeline, type TryRewriteResult } from '../core/base-pipeline.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 type { Logger } from '../core/logger/core.js';
+import type { ModuleLoader } from '../core/module-loader/index.js';
+import { RedirectComponentInstance, routeIsRedirect } from '../core/redirects/index.js';
+import { loadRenderer } from '../core/render/index.js';
+import { createDefaultRoutes } from '../core/routing/default.js';
+import { findRouteToRewrite } from '../core/routing/rewrite.js';
+import { isPage } from '../core/util.js';
+import { resolveIdToUrl } from '../core/viteUtils.js';
+import type { AstroSettings, ComponentInstance, RoutesList } from '../types/astro.js';
+import type {
+ DevToolbarMetadata,
+ RewritePayload,
+ RouteData,
+ SSRElement,
+ SSRLoadedRenderer,
+ SSRManifest,
+} from '../types/public/index.js';
+import { getStylesForURL } from '../vite-plugin-astro-server/css.js';
+import { getComponentMetadata } from '../vite-plugin-astro-server/metadata.js';
+import { createResolve } from '../vite-plugin-astro-server/resolve.js';
+import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
+
+export class AstroServerPipeline extends Pipeline {
+ // renderers are loaded on every request,
+ // so it needs to be mutable here unlike in other environments
+ override renderers = new Array();
+
+ routesList: RoutesList | undefined;
+
+ componentInterner: WeakMap = new WeakMap<
+ RouteData,
+ ComponentInstance
+ >();
+
+ private constructor(
+ readonly loader: ModuleLoader,
+ readonly logger: Logger,
+ readonly manifest: SSRManifest,
+ readonly settings: AstroSettings | undefined,
+ readonly defaultRoutes = createDefaultRoutes(manifest),
+ ) {
+ const resolve = createResolve(loader, manifest.rootDir);
+ const serverLike = settings?.buildOutput === 'server';
+ const streaming = true;
+ super(logger, manifest, 'development', [], resolve, serverLike, streaming);
+ if (settings) {
+ manifest.serverIslandMap = settings.serverIslandMap;
+ manifest.serverIslandNameMap = settings.serverIslandNameMap;
+ }
+ }
+
+ static create(
+ manifestData: RoutesList,
+ {
+ loader,
+ logger,
+ manifest,
+ settings,
+ }: Pick
+
+ ) {
+ const pipeline = new AstroServerPipeline(loader, logger, manifest, settings);
+ pipeline.routesList = manifestData;
+ return pipeline;
+ }
+
+ async headElements(routeData: RouteData): Promise {
+ const { manifest, loader, runtimeMode, settings } = this;
+ const filePath = new URL(`${routeData.component}`, manifest.rootDir);
+ const scripts = new Set();
+
+ // Inject HMR scripts
+ if (settings) {
+ if (isPage(filePath, settings) && runtimeMode === 'development') {
+ scripts.add({
+ props: { type: 'module', src: '/@vite/client' },
+ children: '',
+ });
+
+ if (
+ settings.config.devToolbar.enabled &&
+ (await settings.preferences.get('devToolbar.enabled'))
+ ) {
+ const src = await resolveIdToUrl(
+ loader,
+ 'astro/runtime/client/dev-toolbar/entrypoint.js',
+ );
+ scripts.add({ props: { type: 'module', src }, children: '' });
+
+ const additionalMetadata: DevToolbarMetadata['__astro_dev_toolbar__'] = {
+ root: fileURLToPath(settings.config.root),
+ version: ASTRO_VERSION,
+ latestAstroVersion: settings.latestAstroVersion,
+ debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }),
+ };
+
+ // Additional data for the dev overlay
+ const children = `window.__astro_dev_toolbar__ = ${JSON.stringify(additionalMetadata)}`;
+ scripts.add({ props: {}, children });
+ }
+ }
+
+ // TODO: We should allow adding generic HTML elements to the head, not just scripts
+ for (const script of settings.scripts) {
+ if (script.stage === 'head-inline') {
+ scripts.add({
+ props: {},
+ children: script.content,
+ });
+ } else if (script.stage === 'page' && isPage(filePath, settings)) {
+ scripts.add({
+ props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
+ children: '',
+ });
+ }
+ }
+ }
+
+ // Pass framework CSS in as style tags to be appended to the page.
+ const links = new Set();
+ const { urls, styles: _styles } = await getStylesForURL(filePath, loader);
+ for (const href of urls) {
+ links.add({ props: { rel: 'stylesheet', href }, children: '' });
+ }
+
+ const styles = new Set();
+ for (const { id, url: src, content } of _styles) {
+ // Vite handles HMR for styles injected as scripts
+ scripts.add({ props: { type: 'module', src }, children: '' });
+ // But we still want to inject the styles to avoid FOUC. The style tags
+ // should emulate what Vite injects so further HMR works as expected.
+ styles.add({ props: { 'data-vite-dev-id': id }, children: content });
+ }
+
+ return { scripts, styles, links };
+ }
+
+ componentMetadata(routeData: RouteData) {
+ const filePath = new URL(`${routeData.component}`, this.manifest.rootDir);
+ return getComponentMetadata(filePath, this.loader);
+ }
+
+ async preload(routeData: RouteData, filePath: URL) {
+ if (routeIsRedirect(routeData)) {
+ return RedirectComponentInstance;
+ }
+
+ const { loader } = this;
+
+ // First check built-in routes
+ for (const route of this.defaultRoutes) {
+ if (route.matchesComponent(filePath)) {
+ return route.instance;
+ }
+ }
+
+ // Important: This needs to happen first, in case a renderer provides polyfills.
+ if (this.settings) {
+ const renderers__ = this.settings.renderers.map((r) => loadRenderer(r, loader));
+ const renderers_ = await Promise.all(renderers__);
+ this.renderers = renderers_.filter((r): r is SSRLoadedRenderer => Boolean(r));
+ }
+
+ try {
+ // Load the module from the Vite SSR Runtime.
+ const componentInstance = (await loader.import(filePath.toString())) as ComponentInstance;
+ this.componentInterner.set(routeData, componentInstance);
+ return componentInstance;
+ } catch (error) {
+ // If the error came from Markdown or CSS, we already handled it and there's no need to enhance it
+ if (MarkdownError.is(error) || CSSError.is(error) || AggregateError.is(error)) {
+ throw error;
+ }
+
+ throw enhanceViteSSRError({ error, filePath, loader });
+ }
+ }
+
+ clearRouteCache() {
+ this.routeCache.clearAll();
+ this.componentInterner = new WeakMap();
+ }
+
+ async getComponentByRoute(routeData: RouteData): Promise {
+ const component = this.componentInterner.get(routeData);
+ if (component) {
+ return component;
+ } else {
+ const filePath = new URL(`${routeData.component}`, this.manifest.rootDir);
+ return await this.preload(routeData, filePath);
+ }
+ }
+
+ async tryRewrite(payload: RewritePayload, request: Request): Promise {
+ 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.routesList?.routes,
+ trailingSlash: this.manifest.trailingSlash,
+ buildFormat: this.manifest.buildFormat,
+ base: this.manifest.base,
+ outDir: this.manifest.outDir,
+ });
+
+ const componentInstance = await this.getComponentByRoute(routeData);
+ return { newUrl, pathname, componentInstance, routeData };
+ }
+
+ setManifestData(manifestData: RoutesList) {
+ this.routesList = manifestData;
+ }
+}
diff --git a/packages/astro/src/vite-plugin-astro-server/controller.ts b/packages/astro/src/vite-plugin-astro-server/controller.ts
index 06c8796e7e39..848786a580b3 100644
--- a/packages/astro/src/vite-plugin-astro-server/controller.ts
+++ b/packages/astro/src/vite-plugin-astro-server/controller.ts
@@ -88,7 +88,7 @@ interface RunWithErrorHandlingParams {
controller: DevServerController;
pathname: string;
run: () => Promise;
- onError: (error: unknown) => Error;
+ onError: (error: unknown) => Error | undefined;
}
export async function runWithErrorHandling({
diff --git a/packages/astro/src/vite-plugin-astro-server/error.ts b/packages/astro/src/vite-plugin-astro-server/error.ts
index cd3dedeb4cc0..3cb04e914da4 100644
--- a/packages/astro/src/vite-plugin-astro-server/error.ts
+++ b/packages/astro/src/vite-plugin-astro-server/error.ts
@@ -1,18 +1,15 @@
+import type { SSRManifest } from '../core/app/types.js';
import { collectErrorMetadata } from '../core/errors/dev/index.js';
-import { createSafeError } from '../core/errors/index.js';
import type { Logger } from '../core/logger/core.js';
import { formatErrorMessage } from '../core/messages.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
-import type { AstroConfig } from '../types/public/config.js';
export function recordServerError(
loader: ModuleLoader,
- config: AstroConfig,
+ manifest: SSRManifest,
logger: Logger,
- _err: unknown,
+ err: Error,
) {
- const err = createSafeError(_err);
-
// This could be a runtime error from Vite's SSR module, so try to fix it here
try {
loader.fixStacktrace(err);
@@ -20,12 +17,11 @@ export function recordServerError(
// This is our last line of defense regarding errors where we still might have some information about the request
// Our error should already be complete, but let's try to add a bit more through some guesswork
- const errorWithMetadata = collectErrorMetadata(err, config.root);
+ const errorWithMetadata = collectErrorMetadata(err, manifest.rootDir);
logger.error(null, formatErrorMessage(errorWithMetadata, logger.level() === 'debug'));
return {
- error: err,
errorWithMetadata,
};
}
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index 1b30bc0a3b48..127572cdb065 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -52,11 +52,10 @@ export default function createVitePluginAstroServer({
}
const loader = createViteLoader(viteServer);
- const entrypoint = settings.adapter?.devEntrypoint ?? 'astro/app/dev';
- const createExports = await loader.import(entrypoint.toString());
+ const createExports = await loader.import('astro:app');
const controller = createController({ loader });
- const { handler } = await createExports.default(settings, controller, loader);
-
+ const { handler } = await createExports.default(controller, settings, loader);
+ const { manifest } = await loader.import('astro:serialized-manifest');
const localStorage = new AsyncLocalStorage();
function handleUnhandledRejection(rejection: any) {
@@ -70,7 +69,7 @@ export default function createVitePluginAstroServer({
if (store instanceof IncomingMessage) {
setRouteError(controller.state, store.url!, error);
}
- const { errorWithMetadata } = recordServerError(loader, settings.config, logger, error);
+ const { errorWithMetadata } = recordServerError(loader, manifest, logger, error);
setTimeout(
async () => loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)),
200,
@@ -202,7 +201,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
}
return {
- hrefRoot: settings.config.root.toString(),
+ rootDir: settings.config.root,
srcDir: settings.config.srcDir,
cacheDir: settings.config.cacheDir,
outDir: settings.config.outDir,
diff --git a/packages/astro/src/vite-plugin-astro-server/server-state.ts b/packages/astro/src/vite-plugin-astro-server/server-state.ts
index 5e6c13111dc1..e6cf33071b42 100644
--- a/packages/astro/src/vite-plugin-astro-server/server-state.ts
+++ b/packages/astro/src/vite-plugin-astro-server/server-state.ts
@@ -18,7 +18,11 @@ export function createServerState(): ServerState {
};
}
-export function setRouteError(serverState: ServerState, pathname: string, error: Error) {
+export function setRouteError(
+ serverState: ServerState,
+ pathname: string,
+ error: Error | undefined,
+) {
if (serverState.routes.has(pathname)) {
const routeState = serverState.routes.get(pathname)!;
routeState.state = 'error';
diff --git a/packages/astro/src/vite-plugin-head/index.ts b/packages/astro/src/vite-plugin-head/index.ts
index 8b593f16493d..df9fd6388cd5 100644
--- a/packages/astro/src/vite-plugin-head/index.ts
+++ b/packages/astro/src/vite-plugin-head/index.ts
@@ -63,7 +63,7 @@ export default function configHeadVitePlugin(): vite.Plugin {
if (result) {
let info = this.getModuleInfo(result.id);
const astro = info && getAstroMetadata(info);
- if (astro) {
+ if (astro && isRunnableDevEnvironment(environment)) {
if (astro.propagation === 'self' || astro.propagation === 'in-tree') {
propagateMetadata.call(this, importer, 'propagation', 'in-tree');
}
@@ -77,6 +77,9 @@ export default function configHeadVitePlugin(): vite.Plugin {
}
},
transform(source, id) {
+ if (!isRunnableDevEnvironment(environment)) {
+ return;
+ }
// TODO This could probably be removed now that this is handled in resolveId
let info = this.getModuleInfo(id);
if (info && getAstroMetadata(info)?.containsHead) {
diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/dev.ts b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/dev.ts
index 6318f8155fe4..d58702a9918e 100644
--- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/dev.ts
+++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/dev.ts
@@ -1,4 +1,5 @@
import { manifest } from "astro:serialized-manifest";
+import { routes } from "astro:routes"
import { createExports } from "./worker";
-export default createExports(manifest).default
+export default createExports(manifest, routes).default
diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/worker.ts b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/worker.ts
index 8293f8a7a228..1b869e4a143e 100644
--- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/worker.ts
+++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/worker.ts
@@ -1,11 +1,15 @@
import type { SSRManifest } from 'astro';
-
-import { App } from 'astro/app';
import { handle, type Env } from '@astrojs/cloudflare/handler'
import type { ExportedHandler } from '@cloudflare/workers-types';
+import type { RouteInfo } from 'astro';
+import { DevApp, createConsoleLogger } from 'astro/app';
+import type { RoutesList } from 'astro/dist/types/astro.ts';
+
-export function createExports(manifest: SSRManifest) {
- const app = new App(manifest);
+export function createExports(manifest: SSRManifest, routes: RouteInfo[]) {
+ const routesList: RoutesList = { routes: routes.map((r: RouteInfo) => r.routeData) };
+ const logger = createConsoleLogger('debug');
+ const app = new DevApp(manifest, true, logger, routesList)
return {
default: {
async fetch(request, env, ctx) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3e01b14adf40..e2fd68b3239c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -898,7 +898,7 @@ importers:
version: link:../../..
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
packages/astro/e2e/fixtures/client-idle-timeout:
dependencies:
@@ -935,7 +935,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1038,7 +1038,7 @@ importers:
version: link:../../..
sass:
specifier: ^1.91.0
- version: 1.91.0
+ version: 1.92.1
packages/astro/e2e/fixtures/errors:
dependencies:
@@ -1071,7 +1071,7 @@ importers:
version: 18.3.1(react@18.3.1)
sass:
specifier: ^1.91.0
- version: 1.91.0
+ version: 1.92.1
solid-js:
specifier: ^1.9.9
version: 1.9.9
@@ -1080,7 +1080,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
packages/astro/e2e/fixtures/hmr:
devDependencies:
@@ -1089,7 +1089,7 @@ importers:
version: link:../../..
sass:
specifier: ^1.91.0
- version: 1.91.0
+ version: 1.92.1
packages/astro/e2e/fixtures/hydration-race:
dependencies:
@@ -1137,7 +1137,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1193,7 +1193,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1233,7 +1233,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1273,7 +1273,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1313,7 +1313,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1353,7 +1353,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1393,7 +1393,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
devDependencies:
'@astrojs/preact':
specifier: workspace:*
@@ -1649,7 +1649,7 @@ importers:
version: 5.38.6
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
packages/astro/e2e/fixtures/vite-virtual-modules:
dependencies:
@@ -1670,7 +1670,7 @@ importers:
version: link:../../..
vue:
specifier: ^3.5.20
- version: 3.5.20(typescript@5.9.2)
+ version: 3.5.21(typescript@5.9.2)
packages/astro/performance:
devDependencies:
@@ -9134,12 +9134,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/project-service@8.41.0':
- resolution: {integrity: sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
'@typescript-eslint/project-service@8.42.0':
resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -9150,12 +9144,6 @@ packages:
resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.41.0':
- resolution: {integrity: sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
'@typescript-eslint/tsconfig-utils@8.42.0':
resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -9169,20 +9157,10 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/types@8.41.0':
- resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/types@8.42.0':
resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.41.0':
- resolution: {integrity: sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
'@typescript-eslint/typescript-estree@8.42.0':
resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -9196,10 +9174,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/visitor-keys@8.41.0':
- resolution: {integrity: sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/visitor-keys@8.42.0':
resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -9359,27 +9333,15 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@vue/compiler-core@3.5.20':
- resolution: {integrity: sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==}
-
'@vue/compiler-core@3.5.21':
resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==}
- '@vue/compiler-dom@3.5.20':
- resolution: {integrity: sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==}
-
'@vue/compiler-dom@3.5.21':
resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==}
- '@vue/compiler-sfc@3.5.20':
- resolution: {integrity: sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==}
-
'@vue/compiler-sfc@3.5.21':
resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==}
- '@vue/compiler-ssr@3.5.20':
- resolution: {integrity: sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==}
-
'@vue/compiler-ssr@3.5.21':
resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==}
@@ -9397,29 +9359,15 @@ packages:
'@vue/reactivity@3.1.5':
resolution: {integrity: sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==}
- '@vue/reactivity@3.5.20':
- resolution: {integrity: sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==}
-
'@vue/reactivity@3.5.21':
resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==}
- '@vue/runtime-core@3.5.20':
- resolution: {integrity: sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==}
-
'@vue/runtime-core@3.5.21':
resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==}
- '@vue/runtime-dom@3.5.20':
- resolution: {integrity: sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==}
-
'@vue/runtime-dom@3.5.21':
resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==}
- '@vue/server-renderer@3.5.20':
- resolution: {integrity: sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==}
- peerDependencies:
- vue: 3.5.20
-
'@vue/server-renderer@3.5.21':
resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==}
peerDependencies:
@@ -9428,9 +9376,6 @@ packages:
'@vue/shared@3.1.5':
resolution: {integrity: sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==}
- '@vue/shared@3.5.20':
- resolution: {integrity: sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==}
-
'@vue/shared@3.5.21':
resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==}
@@ -12940,11 +12885,6 @@ packages:
sass-formatter@0.7.9:
resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==}
- sass@1.91.0:
- resolution: {integrity: sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==}
- engines: {node: '>=14.0.0'}
- hasBin: true
-
sass@1.92.1:
resolution: {integrity: sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ==}
engines: {node: '>=14.0.0'}
@@ -13987,14 +13927,6 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
- vue@3.5.20:
- resolution: {integrity: sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==}
- peerDependencies:
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
-
vue@3.5.21:
resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==}
peerDependencies:
@@ -16901,15 +16833,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.41.0(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2)
- '@typescript-eslint/types': 8.41.0
- debug: 4.4.1
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/project-service@8.42.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2)
@@ -16924,10 +16847,6 @@ snapshots:
'@typescript-eslint/types': 8.42.0
'@typescript-eslint/visitor-keys': 8.42.0
- '@typescript-eslint/tsconfig-utils@8.41.0(typescript@5.9.2)':
- dependencies:
- typescript: 5.9.2
-
'@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)':
dependencies:
typescript: 5.9.2
@@ -16944,26 +16863,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.41.0': {}
-
'@typescript-eslint/types@8.42.0': {}
- '@typescript-eslint/typescript-estree@8.41.0(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/project-service': 8.41.0(typescript@5.9.2)
- '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2)
- '@typescript-eslint/types': 8.41.0
- '@typescript-eslint/visitor-keys': 8.41.0
- debug: 4.4.1
- fast-glob: 3.3.3
- is-glob: 4.0.3
- minimatch: 9.0.5
- semver: 7.7.2
- ts-api-utils: 2.1.0(typescript@5.9.2)
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/project-service': 8.42.0(typescript@5.9.2)
@@ -16991,11 +16892,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.41.0':
- dependencies:
- '@typescript-eslint/types': 8.41.0
- eslint-visitor-keys: 4.2.1
-
'@typescript-eslint/visitor-keys@8.42.0':
dependencies:
'@typescript-eslint/types': 8.42.0
@@ -17215,7 +17111,7 @@ snapshots:
'@babel/types': 7.28.2
'@vue/babel-helper-vue-transform-on': 1.5.0
'@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.3)
- '@vue/shared': 3.5.20
+ '@vue/shared': 3.5.21
optionalDependencies:
'@babel/core': 7.28.3
transitivePeerDependencies:
@@ -17232,14 +17128,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vue/compiler-core@3.5.20':
- dependencies:
- '@babel/parser': 7.28.3
- '@vue/shared': 3.5.20
- entities: 4.5.0
- estree-walker: 2.0.2
- source-map-js: 1.2.1
-
'@vue/compiler-core@3.5.21':
dependencies:
'@babel/parser': 7.28.3
@@ -17248,28 +17136,11 @@ snapshots:
estree-walker: 2.0.2
source-map-js: 1.2.1
- '@vue/compiler-dom@3.5.20':
- dependencies:
- '@vue/compiler-core': 3.5.20
- '@vue/shared': 3.5.20
-
'@vue/compiler-dom@3.5.21':
dependencies:
'@vue/compiler-core': 3.5.21
'@vue/shared': 3.5.21
- '@vue/compiler-sfc@3.5.20':
- dependencies:
- '@babel/parser': 7.28.3
- '@vue/compiler-core': 3.5.20
- '@vue/compiler-dom': 3.5.20
- '@vue/compiler-ssr': 3.5.20
- '@vue/shared': 3.5.20
- estree-walker: 2.0.2
- magic-string: 0.30.18
- postcss: 8.5.6
- source-map-js: 1.2.1
-
'@vue/compiler-sfc@3.5.21':
dependencies:
'@babel/parser': 7.28.3
@@ -17282,11 +17153,6 @@ snapshots:
postcss: 8.5.6
source-map-js: 1.2.1
- '@vue/compiler-ssr@3.5.20':
- dependencies:
- '@vue/compiler-dom': 3.5.20
- '@vue/shared': 3.5.20
-
'@vue/compiler-ssr@3.5.21':
dependencies:
'@vue/compiler-dom': 3.5.21
@@ -17322,31 +17188,15 @@ snapshots:
dependencies:
'@vue/shared': 3.1.5
- '@vue/reactivity@3.5.20':
- dependencies:
- '@vue/shared': 3.5.20
-
'@vue/reactivity@3.5.21':
dependencies:
'@vue/shared': 3.5.21
- '@vue/runtime-core@3.5.20':
- dependencies:
- '@vue/reactivity': 3.5.20
- '@vue/shared': 3.5.20
-
'@vue/runtime-core@3.5.21':
dependencies:
'@vue/reactivity': 3.5.21
'@vue/shared': 3.5.21
- '@vue/runtime-dom@3.5.20':
- dependencies:
- '@vue/reactivity': 3.5.20
- '@vue/runtime-core': 3.5.20
- '@vue/shared': 3.5.20
- csstype: 3.1.3
-
'@vue/runtime-dom@3.5.21':
dependencies:
'@vue/reactivity': 3.5.21
@@ -17354,12 +17204,6 @@ snapshots:
'@vue/shared': 3.5.21
csstype: 3.1.3
- '@vue/server-renderer@3.5.20(vue@3.5.20(typescript@5.9.2))':
- dependencies:
- '@vue/compiler-ssr': 3.5.20
- '@vue/shared': 3.5.20
- vue: 3.5.20(typescript@5.9.2)
-
'@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.9.2))':
dependencies:
'@vue/compiler-ssr': 3.5.21
@@ -17368,8 +17212,6 @@ snapshots:
'@vue/shared@3.1.5': {}
- '@vue/shared@3.5.20': {}
-
'@vue/shared@3.5.21': {}
'@webcomponents/template-shadowroot@0.2.1': {}
@@ -18165,7 +18007,7 @@ snapshots:
detective-typescript@14.0.0(typescript@5.9.2):
dependencies:
- '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2)
+ '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2)
ast-module-types: 6.0.1
node-source-walk: 7.0.1
typescript: 5.9.2
@@ -21487,14 +21329,6 @@ snapshots:
dependencies:
suf-log: 2.5.3
- sass@1.91.0:
- dependencies:
- chokidar: 4.0.3
- immutable: 5.1.3
- source-map-js: 1.2.1
- optionalDependencies:
- '@parcel/watcher': 2.5.1
-
sass@1.92.1:
dependencies:
chokidar: 4.0.3
@@ -22426,7 +22260,7 @@ snapshots:
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3)
'@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3)
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3)
- '@vue/compiler-dom': 3.5.20
+ '@vue/compiler-dom': 3.5.21
kolorist: 1.8.0
magic-string: 0.30.18
vite: 6.3.5(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.92.1)(yaml@2.8.1)
@@ -22624,16 +22458,6 @@ snapshots:
vscode-uri@3.1.0: {}
- vue@3.5.20(typescript@5.9.2):
- dependencies:
- '@vue/compiler-dom': 3.5.20
- '@vue/compiler-sfc': 3.5.20
- '@vue/runtime-dom': 3.5.20
- '@vue/server-renderer': 3.5.20(vue@3.5.20(typescript@5.9.2))
- '@vue/shared': 3.5.20
- optionalDependencies:
- typescript: 5.9.2
-
vue@3.5.21(typescript@5.9.2):
dependencies:
'@vue/compiler-dom': 3.5.21