From e9afd67139cede23e512db712c36fb2814d50cd8 Mon Sep 17 00:00:00 2001 From: Arsh <69170106+lilnasy@users.noreply.github.com> Date: Tue, 2 Apr 2024 03:39:48 +0530 Subject: [PATCH] community onboarding: codebase documentation around runtime (#10612) * document runtime * apply suggestions from code review --- packages/astro/src/@types/astro.ts | 72 ++++++++++++++--------- packages/astro/src/core/README.md | 51 ++++++++++++---- packages/astro/src/core/constants.ts | 43 ++++++++++++++ packages/astro/src/core/create-vite.ts | 2 +- packages/astro/src/core/messages.ts | 6 +- packages/astro/src/core/polyfill.ts | 4 ++ packages/astro/src/core/render-context.ts | 4 ++ packages/astro/src/core/request.ts | 7 +++ 8 files changed, 150 insertions(+), 39 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index df45fa4107..01925212fc 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2352,7 +2352,9 @@ interface AstroSharedContext< RouteParams extends Record = Record, > { /** - * The address (usually IP address) of the user. Used with SSR only. + * The address (usually IP address) of the user. + * + * Throws an error if used within a static site, or within a prerendered page. */ clientAddress: string; /** @@ -2402,15 +2404,33 @@ interface AstroSharedContext< currentLocale: string | undefined; } +/** + * The `APIContext` is the object made available to endpoints and middleware. + * It is a subset of the `Astro` global object available in pages. + * + * [Reference](https://docs.astro.build/en/reference/api-reference/#endpoint-context) + */ export interface APIContext< Props extends Record = Record, APIParams extends Record = Record, > extends AstroSharedContext { + /** + * The site provided in the astro config, parsed as an instance of `URL`, without base. + * `undefined` if the site is not provided in the config. + */ site: URL | undefined; + /** + * A human-readable string representing the Astro version used to create the project. + * For example, `"Astro v1.1.1"`. + */ generator: string; /** - * A full URL object of the request URL. - * Equivalent to: `new URL(request.url)` + * The url of the current request, parsed as an instance of `URL`. + * + * Equivalent to: + * ```ts + * new URL(context.request.url) + * ``` */ url: AstroSharedContext['url']; /** @@ -2420,6 +2440,8 @@ export interface APIContext< * * Example usage: * ```ts + * import type { APIContext } from "astro" + * * export function getStaticPaths() { * return [ * { params: { id: '0' }, props: { name: 'Sarah' } }, @@ -2428,14 +2450,12 @@ export interface APIContext< * ]; * } * - * export async function GET({ params }) { - * return { - * body: `Hello user ${params.id}!`, - * } + * export async function GET({ params }: APIContext) { + * return new Response(`Hello user ${params.id}!`) * } * ``` * - * [context reference](https://docs.astro.build/en/reference/api-reference/#contextparams) + * [Reference](https://docs.astro.build/en/reference/api-reference/#contextparams) */ params: AstroSharedContext['params']; /** @@ -2443,6 +2463,8 @@ export interface APIContext< * * Example usage: * ```ts + * import type { APIContext } from "astro" + * * export function getStaticPaths() { * return [ * { params: { id: '0' }, props: { name: 'Sarah' } }, @@ -2451,19 +2473,17 @@ export interface APIContext< * ]; * } * - * export function GET({ props }) { - * return { - * body: `Hello ${props.name}!`, - * } + * export function GET({ props }: APIContext): Response { + * return new Response(`Hello ${props.name}!`); * } * ``` - * - * [context reference](https://docs.astro.build/en/guides/api-reference/#contextprops) + * + * [Reference](https://docs.astro.build/en/guides/api-reference/#contextprops) */ props: AstroSharedContext['props']; /** - * Redirect to another page. Only available in SSR builds. - * + * Create a response that redirects to another page. + * * Example usage: * ```ts * // src/pages/secret.ts @@ -2472,18 +2492,20 @@ export interface APIContext< * } * ``` * - * [context reference](https://docs.astro.build/en/guides/api-reference/#contextredirect) + * [Reference](https://docs.astro.build/en/guides/api-reference/#contextredirect) */ redirect: AstroSharedContext['redirect']; /** - * Object accessed via Astro middleware. - * + * An object that middlewares can use to store extra information related to the request. + * + * It will be made available to pages as `Astro.locals`, and to endpoints as `context.locals`. + * * Example usage: - * + * * ```ts * // src/middleware.ts - * import {defineMiddleware} from "astro:middleware"; + * import { defineMiddleware } from "astro:middleware"; * * export const onRequest = defineMiddleware((context, next) => { * context.locals.greeting = "Hello!"; @@ -2498,6 +2520,8 @@ export interface APIContext< * --- *

{greeting}

* ``` + * + * [Reference](https://docs.astro.build/en/reference/api-reference/#contextlocals) */ locals: App.Locals; @@ -2535,12 +2559,6 @@ export interface APIContext< currentLocale: string | undefined; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type Routing = { - prefixDefaultLocale: boolean; - strategy: 'pathname'; -}; - export type APIRoute< Props extends Record = Record, APIParams extends Record = Record, diff --git a/packages/astro/src/core/README.md b/packages/astro/src/core/README.md index ef4dd8750b..94eff9e05b 100644 --- a/packages/astro/src/core/README.md +++ b/packages/astro/src/core/README.md @@ -1,20 +1,51 @@ # `core/` -Code that executes within the top-level Node context. Contains the main Astro logic for the `build`, `dev`, `preview`, and `sync` commands, and also manages the Vite server and SSR. +Code that executes directly on Node (not processed by vite). Contains the main Astro logic for the `build`, `dev`, `preview`, and `sync` commands, and also manages the lifecycle of the Vite server. -The `core/index.ts` file is the main entry point for the `astro` package. +The `core/index.ts` module exports the CLI commands as functions and is the main entrypoint of the `astro` package. +```ts +import { dev, build, preview, sync } from 'astro'; +``` [See CONTRIBUTING.md](../../../../CONTRIBUTING.md) for a code overview. -## Pipeline +``` + Pages + used by / + / + creates / + App --------- AppPipeline AstroGlobal + \ implements / + \ creates / + creates impl.\ provided to / +vite-plugin-astro-server --------- DevPipeline ------ Pipeline ------------- RenderContext Middleware + / \ used by / + / creates \ / + creates / implements \ / + AstroBuilder --------- BuildPipeline APIContext + \ + \ + used by \ + Endpoints +``` -The pipeline is an internal concept that describes how Astro pages are eventually created and rendered to the user. +## `App` -Each pipeline has different requirements, criteria and quirks. Although, each pipeline must use the same underline functions, because -the core of the pipeline is the same. +## `vite-plugin-astro-server` (see `../vite-plugin-astro-server/`) -The core of the pipeline is rendering a generic route (page, endpoint or redirect) and returning a `Response`. -When rendering a route, a pipeline must pass a `RenderContext` and `ComponentInstance`. The way these two information are -computed doesn't concern the core of a pipeline. In fact, these types will be computed in different manner based on the type of pipeline. +## `AstroBuilder` -Each consumer will decide how to handle a `Response`. +## `Pipeline` + +The pipeline is an interface representing data that stays unchanged throughout the duration of the server or build. For example: the user configuration, the list of pages and endpoints in the project, and environment-specific way of gathering scripts and styles. + +There are 3 implementations of the pipeline: +- `DevPipeline`: in-use during the `astro dev` CLI command. Created and used by `vite-plugin-astro-server`, and then forwarded to other internals. +- `BuildPipeline`: in-use during the `astro build` command in `"static"` mode, and for prerendering in `"server"` and `"hybrid"` output modes. See `core/build/`. +- `AppPipeline`: in-use during production server(less) deployments. Created and used by `App` (see `core/app/`), and then forwarded to other internals. + +All 3 expose a common, environment-agnostic interface which is used by the rest of the internals, most notably by `RenderContext`. + +## `RenderContext` + +Each request is rendered using a `RenderContext`. It manages data unique to each request. For example: the parsed `URL`, internationalization data, the `locals` object, and the route that matched the request. It is responsible for executing middleware, calling endpoints, and rendering pages by gathering necessary data from a `Pipeline`. diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts index aabdcbcab2..c7431aa965 100644 --- a/packages/astro/src/core/constants.ts +++ b/packages/astro/src/core/constants.ts @@ -1,9 +1,35 @@ // process.env.PACKAGE_VERSION is injected when we build and publish the astro package. export const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development'; +/** + * The name for the header used to help rerouting behavior. + * When set to "no", astro will NOT try to reroute an error response to the corresponding error page, which is the default behavior that can sometimes lead to loops. + * + * ```ts + * const response = new Response("keep this content as-is", { + * status: 404, + * headers: { + * // note that using a variable name as the key of an object needs to be wrapped in square brackets in javascript + * // without them, the header name will be interpreted as "REROUTE_DIRECTIVE_HEADER" instead of "X-Astro-Reroute" + * [REROUTE_DIRECTIVE_HEADER]: 'no', + * } + * }) + * ``` + * Alternatively... + * ```ts + * response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no'); + * ``` + */ export const REROUTE_DIRECTIVE_HEADER = 'X-Astro-Reroute'; + +/** + * The name for the header used to help i18n middleware, which only needs to act on "page" and "fallback" route types. + */ export const ROUTE_TYPE_HEADER = 'X-Astro-Route-Type'; +/** + * The value of the `component` field of the default 404 page, which is used when there is no user-provided 404.astro page. + */ export const DEFAULT_404_COMPONENT = 'astro-default-404'; /** @@ -12,8 +38,25 @@ export const DEFAULT_404_COMPONENT = 'astro-default-404'; */ export const REROUTABLE_STATUS_CODES = [404, 500]; +/** + * The symbol which is used as a field on the request object to store the client address. + * The clientAddresss provided by the adapter (or the dev server) is stored on this field. + */ export const clientAddressSymbol = Symbol.for('astro.clientAddress'); + +/** + * The symbol used as a field on the request object to store the object to be made available to Astro APIs as `locals`. + * Use judiciously, as locals are now stored within `RenderContext` by default. Tacking it onto request is no longer necessary. + */ export const clientLocalsSymbol = Symbol.for('astro.locals'); + +/** + * The symbol used as a field on the response object to keep track of streaming. + * + * It is set when the `` element has been completely generated, rendered, and the response object has been passed onto the adapter. + * + * Used to provide helpful errors and warnings when headers or cookies are added during streaming, after the response has already been sent. + */ export const responseSentSymbol = Symbol.for('astro.responseSent'); // possible extensions for markdown files diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 5a4046915b..56092bd323 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -69,7 +69,7 @@ const ONLY_DEV_EXTERNAL = [ 'string-width', ]; -/** Return a common starting point for all Vite actions */ +/** Return a base vite config as a common starting point for all Vite commands. */ export async function createVite( commandConfig: vite.InlineConfig, { settings, logger, mode, command, fs = nodeFs }: CreateViteOptions diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index f69b846974..bd2cfab04c 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -25,7 +25,11 @@ import { } from './errors/index.js'; import { padMultilineString } from './util.js'; -/** Display */ +/** + * Prestyled messages for the CLI. Used by astro CLI commands. + */ + +/** Display each request being served with the path and the status code. */ export function req({ url, method, diff --git a/packages/astro/src/core/polyfill.ts b/packages/astro/src/core/polyfill.ts index c183d23f66..7b0280fb7f 100644 --- a/packages/astro/src/core/polyfill.ts +++ b/packages/astro/src/core/polyfill.ts @@ -1,6 +1,10 @@ import buffer from 'node:buffer'; import crypto from 'node:crypto'; +/** + * Astro aims to compatible with web standards as much as possible. + * This function adds two objects that are globally-available on most javascript runtimes but not on node 18. + */ export function apply() { // Remove when Node 18 is dropped for Node 20 if (!globalThis.crypto) { diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 36a27c6e81..4155de863d 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -29,6 +29,10 @@ import { sequence } from './middleware/index.js'; import { renderRedirect } from './redirects/render.js'; import { type Pipeline, Slots, getParams, getProps } from './render/index.js'; +/** + * Each request is rendered using a `RenderContext`. + * It contains data unique to each request. It is responsible for executing middleware, calling endpoints, and rendering the page by gathering necessary data from a `Pipeline`. + */ export class RenderContext { private constructor( readonly pipeline: Pipeline, diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts index 2e77a89c14..8445792af7 100644 --- a/packages/astro/src/core/request.ts +++ b/packages/astro/src/core/request.ts @@ -27,6 +27,13 @@ export interface CreateRequestOptions { const clientAddressSymbol = Symbol.for('astro.clientAddress'); const clientLocalsSymbol = Symbol.for('astro.locals'); +/** + * Used by astro internals to create a web standard request object. + * + * The user of this function may provide the data in a runtime-agnostic way. + * + * This is used by the static build to create fake requests for prerendering, and by the dev server to convert node requests into the standard request object. + */ export function createRequest({ base, url,