Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

233
node_modules/astro/dist/container/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,233 @@
import './polyfill.js';
import type { AstroUserConfig, NamedSSRLoadedRendererValue, Props, RouteType, SSRLoadedRenderer, SSRLoadedRendererValue, SSRManifest, SSRResult } from '../@types/astro.js';
import type { AstroComponentFactory } from '../runtime/server/index.js';
/**
* Options to be passed when rendering a route
*/
export type ContainerRenderOptions = {
/**
* If your component renders slots, that's where you want to fill the slots.
* A single slot should have the `default` field:
*
* ## Examples
*
* **Default slot**
*
* ```js
* container.renderToString(Component, { slots: { default: "Some value"}});
* ```
*
* **Named slots**
*
* ```js
* container.renderToString(Component, { slots: { "foo": "Some value", "bar": "Lorem Ipsum" }});
* ```
*/
slots?: Record<string, any>;
/**
* The request is used to understand which path/URL the component is about to render.
*
* Use this option in case your component or middleware needs to read information like `Astro.url` or `Astro.request`.
*/
request?: Request;
/**
* Useful for dynamic routes. If your component is something like `src/pages/blog/[id]/[...slug]`, you'll want to provide:
* ```js
* container.renderToString(Component, { params: ["id", "...slug"] });
* ```
*/
params?: Record<string, string | undefined>;
/**
* Useful if your component needs to access some locals without the use a middleware.
* ```js
* container.renderToString(Component, { locals: { getSomeValue() {} } });
* ```
*/
locals?: App.Locals;
/**
* Useful in case you're attempting to render an endpoint:
* ```js
* container.renderToString(Endpoint, { routeType: "endpoint" });
* ```
*/
routeType?: RouteType;
/**
* Allows to pass `Astro.props` to an Astro component:
*
* ```js
* container.renderToString(Endpoint, { props: { "lorem": "ipsum" } });
* ```
*/
props?: Props;
/**
* When `false`, it forces to render the component as it was a full-fledged page.
*
* By default, the container API render components as [partials](https://docs.astro.build/en/basics/astro-pages/#page-partials).
*
*/
partial?: boolean;
};
export type AddServerRenderer = {
renderer: NamedSSRLoadedRendererValue;
name: never;
} | {
renderer: SSRLoadedRendererValue;
name: string;
};
export type AddClientRenderer = {
name: string;
entrypoint: string;
};
export type AstroContainerUserConfig = Omit<AstroUserConfig, 'integrations' | 'adapter'>;
/**
* Options that are used for the entire lifecycle of the current instance of the container.
*/
export type AstroContainerOptions = {
/**
* @default false
*
* @description
*
* Enables streaming during rendering
*
* ## Example
*
* ```js
* const container = await AstroContainer.create({
* streaming: true
* });
* ```
*/
streaming?: boolean;
/**
* @default []
* @description
*
* List or renderers to use when rendering components. Usually, you want to pass these in an SSR context.
*/
renderers?: SSRLoadedRenderer[];
/**
* @default {}
* @description
*
* A subset of the astro configuration object.
*
* ## Example
*
* ```js
* const container = await AstroContainer.create({
* astroConfig: {
* trailingSlash: "never"
* }
* });
* ```
*/
astroConfig?: AstroContainerUserConfig;
resolve?: SSRResult['resolve'];
/**
* @default {}
* @description
*
* The raw manifest from the build output.
*/
manifest?: SSRManifest;
};
export declare class experimental_AstroContainer {
#private;
private constructor();
/**
* Creates a new instance of a container.
*
* @param {AstroContainerOptions=} containerOptions
*/
static create(containerOptions?: AstroContainerOptions): Promise<experimental_AstroContainer>;
/**
* Use this function to manually add a **server** renderer to the container.
*
* This function is preferred when you require to use the container with a renderer in environments such as on-demand pages.
*
* ## Example
*
* ```js
* import reactRenderer from "@astrojs/react/server.js";
* import vueRenderer from "@astrojs/vue/server.js";
* import customRenderer from "../renderer/customRenderer.js";
* import { experimental_AstroContainer as AstroContainer } from "astro/container"
*
* const container = await AstroContainer.create();
* container.addServerRenderer(reactRenderer);
* container.addServerRenderer(vueRenderer);
* container.addServerRenderer("customRenderer", customRenderer);
* ```
*
* @param options {object}
* @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package.
* @param options.renderer The server renderer exported by integration.
*/
addServerRenderer(options: AddServerRenderer): void;
/**
* Use this function to manually add a **client** renderer to the container.
*
* When rendering components that use the `client:*` directives, you need to use this function.
*
* ## Example
*
* ```js
* import reactRenderer from "@astrojs/react/server.js";
* import { experimental_AstroContainer as AstroContainer } from "astro/container"
*
* const container = await AstroContainer.create();
* container.addServerRenderer(reactRenderer);
* container.addClientRenderer({
* name: "@astrojs/react",
* entrypoint: "@astrojs/react/client.js"
* });
* ```
*
* @param options {object}
* @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package.
* @param options.entrypoint The entrypoint of the client renderer.
*/
addClientRenderer(options: AddClientRenderer): void;
private static createFromManifest;
/**
* @description
* It renders a component and returns the result as a string.
*
* ## Example
*
* ```js
* import Card from "../src/components/Card.astro";
*
* const container = await AstroContainer.create();
* const result = await container.renderToString(Card);
*
* console.log(result); // it's a string
* ```
*
*
* @param {AstroComponentFactory} component The instance of the component.
* @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
*/
renderToString(component: AstroComponentFactory, options?: ContainerRenderOptions): Promise<string>;
/**
* @description
* It renders a component and returns the `Response` as result of the rendering phase.
*
* ## Example
*
* ```js
* import Card from "../src/components/Card.astro";
*
* const container = await AstroContainer.create();
* const response = await container.renderToResponse(Card);
*
* console.log(response.status); // it's a number
* ```
*
*
* @param {AstroComponentFactory} component The instance of the component.
* @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
*/
renderToResponse(component: AstroComponentFactory, options?: ContainerRenderOptions): Promise<Response>;
}

327
node_modules/astro/dist/container/index.js generated vendored Normal file
View File

@@ -0,0 +1,327 @@
import "./polyfill.js";
import { posix } from "node:path";
import { getDefaultClientDirectives } from "../core/client-directive/index.js";
import { ASTRO_CONFIG_DEFAULTS } from "../core/config/schema.js";
import { validateConfig } from "../core/config/validate.js";
import { createKey } from "../core/encryption.js";
import { Logger } from "../core/logger/core.js";
import { nodeLogDestination } from "../core/logger/node.js";
import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
import { removeLeadingForwardSlash } from "../core/path.js";
import { RenderContext } from "../core/render-context.js";
import { getParts, validateSegment } from "../core/routing/manifest/create.js";
import { getPattern } from "../core/routing/manifest/pattern.js";
import { ContainerPipeline } from "./pipeline.js";
function createManifest(manifest, renderers, middleware) {
function middlewareInstance() {
return {
onRequest: middleware ?? NOOP_MIDDLEWARE_FN
};
}
return {
hrefRoot: import.meta.url,
trailingSlash: manifest?.trailingSlash ?? ASTRO_CONFIG_DEFAULTS.trailingSlash,
buildFormat: manifest?.buildFormat ?? ASTRO_CONFIG_DEFAULTS.build.format,
compressHTML: manifest?.compressHTML ?? ASTRO_CONFIG_DEFAULTS.compressHTML,
assets: manifest?.assets ?? /* @__PURE__ */ new Set(),
assetsPrefix: manifest?.assetsPrefix ?? void 0,
entryModules: manifest?.entryModules ?? {},
routes: manifest?.routes ?? [],
adapterName: "",
clientDirectives: manifest?.clientDirectives ?? getDefaultClientDirectives(),
renderers: renderers ?? manifest?.renderers ?? [],
base: manifest?.base ?? ASTRO_CONFIG_DEFAULTS.base,
componentMetadata: manifest?.componentMetadata ?? /* @__PURE__ */ new Map(),
inlinedScripts: manifest?.inlinedScripts ?? /* @__PURE__ */ new Map(),
i18n: manifest?.i18n,
checkOrigin: false,
middleware: manifest?.middleware ?? middlewareInstance,
experimentalEnvGetSecretEnabled: false,
key: createKey()
};
}
class experimental_AstroContainer {
#pipeline;
/**
* Internally used to check if the container was created with a manifest.
* @private
*/
#withManifest = false;
/**
* Internal function responsible for importing a renderer
* @private
*/
#getRenderer;
constructor({
streaming = false,
manifest,
renderers,
resolve,
astroConfig
}) {
this.#pipeline = ContainerPipeline.create({
logger: new Logger({
level: "info",
dest: nodeLogDestination
}),
manifest: createManifest(manifest, renderers),
streaming,
serverLike: true,
renderers: renderers ?? manifest?.renderers ?? [],
resolve: async (specifier) => {
if (this.#withManifest) {
return this.#containerResolve(specifier, astroConfig);
} else if (resolve) {
return resolve(specifier);
}
return specifier;
}
});
}
async #containerResolve(specifier, astroConfig) {
const found = this.#pipeline.manifest.entryModules[specifier];
if (found) {
return new URL(found, astroConfig?.build.client).toString();
}
return found;
}
/**
* Creates a new instance of a container.
*
* @param {AstroContainerOptions=} containerOptions
*/
static async create(containerOptions = {}) {
const { streaming = false, manifest, renderers = [], resolve } = containerOptions;
const astroConfig = await validateConfig(ASTRO_CONFIG_DEFAULTS, process.cwd(), "container");
return new experimental_AstroContainer({
streaming,
manifest,
renderers,
astroConfig,
resolve
});
}
/**
* Use this function to manually add a **server** renderer to the container.
*
* This function is preferred when you require to use the container with a renderer in environments such as on-demand pages.
*
* ## Example
*
* ```js
* import reactRenderer from "@astrojs/react/server.js";
* import vueRenderer from "@astrojs/vue/server.js";
* import customRenderer from "../renderer/customRenderer.js";
* import { experimental_AstroContainer as AstroContainer } from "astro/container"
*
* const container = await AstroContainer.create();
* container.addServerRenderer(reactRenderer);
* container.addServerRenderer(vueRenderer);
* container.addServerRenderer("customRenderer", customRenderer);
* ```
*
* @param options {object}
* @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package.
* @param options.renderer The server renderer exported by integration.
*/
addServerRenderer(options) {
const { renderer, name } = options;
if (!renderer.check || !renderer.renderToStaticMarkup) {
throw new Error(
"The renderer you passed isn't valid. A renderer is usually an object that exposes the `check` and `renderToStaticMarkup` functions.\nUsually, the renderer is exported by a /server.js entrypoint e.g. `import renderer from '@astrojs/react/server.js'`"
);
}
if (isNamedRenderer(renderer)) {
this.#pipeline.manifest.renderers.push({
name: renderer.name,
ssr: renderer
});
} else {
this.#pipeline.manifest.renderers.push({
name,
ssr: renderer
});
}
}
/**
* Use this function to manually add a **client** renderer to the container.
*
* When rendering components that use the `client:*` directives, you need to use this function.
*
* ## Example
*
* ```js
* import reactRenderer from "@astrojs/react/server.js";
* import { experimental_AstroContainer as AstroContainer } from "astro/container"
*
* const container = await AstroContainer.create();
* container.addServerRenderer(reactRenderer);
* container.addClientRenderer({
* name: "@astrojs/react",
* entrypoint: "@astrojs/react/client.js"
* });
* ```
*
* @param options {object}
* @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package.
* @param options.entrypoint The entrypoint of the client renderer.
*/
addClientRenderer(options) {
const { entrypoint, name } = options;
const rendererIndex = this.#pipeline.manifest.renderers.findIndex((r) => r.name === name);
if (rendererIndex === -1) {
throw new Error(
"You tried to add the " + name + " client renderer, but its server renderer wasn't added. You must add the server renderer first. Use the `addServerRenderer` function."
);
}
const renderer = this.#pipeline.manifest.renderers[rendererIndex];
renderer.clientEntrypoint = entrypoint;
this.#pipeline.manifest.renderers[rendererIndex] = renderer;
}
// NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
// @ematipico: I plan to use it for a possible integration that could help people
static async createFromManifest(manifest) {
const astroConfig = await validateConfig(ASTRO_CONFIG_DEFAULTS, process.cwd(), "container");
const container = new experimental_AstroContainer({
manifest,
astroConfig
});
container.#withManifest = true;
return container;
}
#insertRoute({
path,
componentInstance,
params = {},
type = "page"
}) {
const pathUrl = new URL(path, "https://example.com");
const routeData = this.#createRoute(pathUrl, params, type);
this.#pipeline.manifest.routes.push({
routeData,
file: "",
links: [],
styles: [],
scripts: []
});
this.#pipeline.insertRoute(routeData, componentInstance);
return routeData;
}
/**
* @description
* It renders a component and returns the result as a string.
*
* ## Example
*
* ```js
* import Card from "../src/components/Card.astro";
*
* const container = await AstroContainer.create();
* const result = await container.renderToString(Card);
*
* console.log(result); // it's a string
* ```
*
*
* @param {AstroComponentFactory} component The instance of the component.
* @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
*/
async renderToString(component, options = {}) {
const response = await this.renderToResponse(component, options);
return await response.text();
}
/**
* @description
* It renders a component and returns the `Response` as result of the rendering phase.
*
* ## Example
*
* ```js
* import Card from "../src/components/Card.astro";
*
* const container = await AstroContainer.create();
* const response = await container.renderToResponse(Card);
*
* console.log(response.status); // it's a number
* ```
*
*
* @param {AstroComponentFactory} component The instance of the component.
* @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
*/
async renderToResponse(component, options = {}) {
const { routeType = "page", slots } = options;
const request = options?.request ?? new Request("https://example.com/");
const url = new URL(request.url);
const componentInstance = routeType === "endpoint" ? component : this.#wrapComponent(component, options.params);
const routeData = this.#insertRoute({
path: request.url,
componentInstance,
params: options.params,
type: routeType
});
const renderContext = await RenderContext.create({
pipeline: this.#pipeline,
routeData,
status: 200,
request,
pathname: url.pathname,
locals: options?.locals ?? {},
partial: options?.partial ?? true
});
if (options.params) {
renderContext.params = options.params;
}
if (options.props) {
renderContext.props = options.props;
}
return renderContext.render(componentInstance, slots);
}
#createRoute(url, params, type) {
const segments = removeLeadingForwardSlash(url.pathname).split(posix.sep).filter(Boolean).map((s) => {
validateSegment(s);
return getParts(s, url.pathname);
});
return {
route: url.pathname,
component: "",
generate(_data) {
return "";
},
params: Object.keys(params),
pattern: getPattern(
segments,
ASTRO_CONFIG_DEFAULTS.base,
ASTRO_CONFIG_DEFAULTS.trailingSlash
),
prerender: false,
segments,
type,
fallbackRoutes: [],
isIndex: false
};
}
/**
* If the provided component isn't a default export, the function wraps it in an object `{default: Component }` to mimic the default export.
* @param componentFactory
* @param params
* @private
*/
#wrapComponent(componentFactory, params) {
if (params) {
return {
default: componentFactory,
getStaticPaths() {
return [{ params }];
}
};
}
return { default: componentFactory };
}
}
function isNamedRenderer(renderer) {
return !!renderer?.name;
}
export {
experimental_AstroContainer
};

11
node_modules/astro/dist/container/pipeline.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import type { ComponentInstance, RewritePayload, RouteData, SSRResult } from '../@types/astro.js';
import { type HeadElements, Pipeline, type TryRewriteResult } from '../core/base-pipeline.js';
export declare class ContainerPipeline extends Pipeline {
#private;
static create({ logger, manifest, renderers, resolve, serverLike, streaming, }: Pick<ContainerPipeline, 'logger' | 'manifest' | 'renderers' | 'resolve' | 'serverLike' | 'streaming'>): ContainerPipeline;
componentMetadata(_routeData: RouteData): Promise<SSRResult['componentMetadata']> | void;
headElements(routeData: RouteData): Promise<HeadElements> | HeadElements;
tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
insertRoute(route: RouteData, componentInstance: ComponentInstance): void;
getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance>;
}

80
node_modules/astro/dist/container/pipeline.js generated vendored Normal file
View File

@@ -0,0 +1,80 @@
import { Pipeline } from "../core/base-pipeline.js";
import {
createModuleScriptElement,
createStylesheetElementSet
} from "../core/render/ssr-element.js";
import { findRouteToRewrite } from "../core/routing/rewrite.js";
class ContainerPipeline extends Pipeline {
/**
* Internal cache to store components instances by `RouteData`.
* @private
*/
#componentsInterner = /* @__PURE__ */ new WeakMap();
static create({
logger,
manifest,
renderers,
resolve,
serverLike,
streaming
}) {
return new ContainerPipeline(
logger,
manifest,
"development",
renderers,
resolve,
serverLike,
streaming
);
}
componentMetadata(_routeData) {
}
headElements(routeData) {
const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
const links = /* @__PURE__ */ new Set();
const scripts = /* @__PURE__ */ new Set();
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));
}
}
return { links, styles, scripts };
}
async tryRewrite(payload, request) {
const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,
routes: this.manifest?.routes.map((r) => r.routeData),
trailingSlash: this.manifest.trailingSlash,
buildFormat: this.manifest.buildFormat,
base: this.manifest.base
});
const componentInstance = await this.getComponentByRoute(routeData);
return { componentInstance, routeData, newUrl, pathname };
}
insertRoute(route, componentInstance) {
this.#componentsInterner.set(route, {
page() {
return Promise.resolve(componentInstance);
},
renderers: this.manifest.renderers,
onRequest: this.resolvedMiddleware
});
}
// At the moment it's not used by the container via any public API
// @ts-expect-error It needs to be implemented.
async getComponentByRoute(_routeData) {
}
}
export {
ContainerPipeline
};

1
node_modules/astro/dist/container/polyfill.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

2
node_modules/astro/dist/container/polyfill.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import { applyPolyfills } from "../core/app/node.js";
applyPolyfills();

View File

@@ -0,0 +1,2 @@
import type * as vite from 'vite';
export default function astroContainer(): vite.Plugin;

View File

@@ -0,0 +1,15 @@
const virtualModuleId = "astro:container";
function astroContainer() {
return {
name: "astro:container",
enforce: "pre",
resolveId(id) {
if (id === virtualModuleId) {
return this.resolve("astro/virtual-modules/container.js");
}
}
};
}
export {
astroContainer as default
};