Skip to content

Commit

Permalink
community onboarding: codebase documentation around runtime (#10612)
Browse files Browse the repository at this point in the history
* document runtime

* apply suggestions from code review
  • Loading branch information
lilnasy authored Apr 1, 2024
1 parent a544f4c commit e9afd67
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 39 deletions.
72 changes: 45 additions & 27 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2352,7 +2352,9 @@ interface AstroSharedContext<
RouteParams extends Record<string, string | undefined> = Record<string, string | undefined>,
> {
/**
* 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;
/**
Expand Down Expand Up @@ -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<string, any> = Record<string, any>,
APIParams extends Record<string, string | undefined> = Record<string, string | undefined>,
> extends AstroSharedContext<Props, Params> {
/**
* 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'];
/**
Expand All @@ -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' } },
Expand All @@ -2428,21 +2450,21 @@ 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<Props, APIParams>['params'];
/**
* List of props passed from `getStaticPaths`. Only available to static builds.
*
* Example usage:
* ```ts
* import type { APIContext } from "astro"
*
* export function getStaticPaths() {
* return [
* { params: { id: '0' }, props: { name: 'Sarah' } },
Expand All @@ -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, APIParams>['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
Expand All @@ -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!";
Expand All @@ -2498,6 +2520,8 @@ export interface APIContext<
* ---
* <h1>{greeting}</h1>
* ```
*
* [Reference](https://docs.astro.build/en/reference/api-reference/#contextlocals)
*/
locals: App.Locals;

Expand Down Expand Up @@ -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<string, any> = Record<string, any>,
APIParams extends Record<string, string | undefined> = Record<string, string | undefined>,
Expand Down
51 changes: 41 additions & 10 deletions packages/astro/src/core/README.md
Original file line number Diff line number Diff line change
@@ -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`.
43 changes: 43 additions & 0 deletions packages/astro/src/core/constants.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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 `<head>` 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
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/src/core/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/core/polyfill.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/core/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e9afd67

Please sign in to comment.