Astro 适配器 API
Astro 可以轻松部署到任何云托管平台,以实现按需渲染,也叫做服务端渲染(SSR)。该能力由适配器集成提供,请参阅 按需渲染指南 了解如何使用现有的适配器。
什么是适配器?
段落标题 什么是适配器?适配器是一种特殊类型的集成,它为请求时的服务器渲染提供了入口。适配器包含两项主要功能:
- 实现托管平台的 API,以处理请求。
- 根据托管平台的约定配置构建过程。
构建适配器
段落标题 构建适配器由于适配器是一种集成,因此它拥有集成提供的全部能力。
必须 通过在 astro:config:done
钩子中调用 setAdapter
API 来使用适配器,例如:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', supportedAstroFeatures: { staticOutput: 'stable' } }); }, }, };}
setAdapter
的传入参数定义如下:
interface AstroAdapter { name: string; serverEntrypoint?: string; previewEntrypoint?: string; exports?: string[]; args?: any; adapterFeatures: AstroAdapterFeaturesMap; supportedAstroFeatures?: AstroFeatureMap;}
export interface AstroAdapterFeatures { /** * 创建一个与 Astro 中间件通信的边缘函数 */ edgeMiddleware: boolean; /** * 确定适配器的构建输出类型。默认为 `server`。 */ buildOutput?: 'static' | 'server';}
export type AdapterSupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated' | 'limited';
export type AdapterSupportWithMessage = { support: Exclude<AdapterSupportsKind, 'stable'>; message: string;};
export type AdapterSupport = AdapterSupportsKind | AdapterSupportWithMessage;
export type AstroAdapterFeatureMap = { /** * 适配器对静态页面的支持 */ staticOutput?: AdapterSupport; /** * 适配器对静态页面或通过服务器渲染的页面的支持 */ hybridOutput?: AdapterSupport; /** * 适配器对按需渲染的支持 */ serverOutput?: AdapterSupport; /** * 适配器对 i18n 域名的支持 */ i18nDomains?: AdapterSupport; /** * 适配器对 `astro:env/server` 导出的 `getSecret` 的支持 */ envGetSecret?: AdapterSupport; /** * 适配器对 Sharp 图像服务的支持 */ sharpImageService?: AdapterSupport;};
这些属性分别是:
- name:适配器的唯一名称,用于日志记录。
- serverEntrypoint:按需服务器渲染的入口。
- exports:导出数组,与
createExports
配套使用(在下文中说明)。 - adapterFeatures:一个对象,用于启用适配器必须支持的特定功能。这些功能将改变构建输出,适配器必须实现适当的逻辑来处理不同的输出。
- supportedAstroFeatures:Astro 内置功能的映射。这允许 Astro 确定适配器无法或不愿意支持的功能,以便提供适当的错误消息。
服务端入口
段落标题 服务端入口Astro 的适配器 API 尝试适配多种类型的托管方,并提供了灵活的配置方式。
Exports
段落标题 Exports一些无服务架构的托管方会希望你导出一个handler
函数:
export function handler(event, context) { // ...}
在适配器 API 中,你可以在 serverEntrypoint
中实现 createExports
方法:
import { App } from 'astro/app';
export function createExports(manifest) { const app = new App(manifest);
const handler = (event, context) => { // ... };
return { handler };}
在此之后,你需要在 setAdapter
的 exports
属性中配置该 handler
:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', exports: ['handler'], }); }, }, };}
Start
段落标题 Start有些托管方希望你自行管理服务的启动,例如通过监听一个端口的方式。对于这类托管方,可以导出一个 start
函数,该函数会在绑定脚本执行时被调用。
import { App } from 'astro/app';
export function start(manifest) { const app = new App(manifest);
addEventListener('fetch', event => { // ... });}
astro/app
段落标题 astro/app该模块用于渲染已通过 astro build
命令预构建的页面。Astro 使用标准的 Request 和 Response 对象。如果托管方使用不同格式的请求/响应 API,需要在适配器中进行转换处理。
import { App } from 'astro/app';import http from 'http';
export function start(manifest) { const app = new App(manifest);
addEventListener('fetch', event => { event.respondWith( app.render(event.request) ); });}
该模块提供以下几个方法:
app.render()
段落标题 app.render()类型: (request: Request, options?: RenderOptions) => Promise<Response>
此方法用于匹配符合请求的 Astro 页面,并返回一个 Promise 对象给 Response 。该方法对于不渲染页面的 API 路由同样适用。
const response = await app.render(request);
RenderOptions
段落标题 RenderOptions类型: {addCookieHeader?: boolean; clientAddress?: string; locals?: object; routeData?: RouteData;}
app.render()
方法接受一个必填的 request
参数,以及一个可选的 RenderOptions
对象,用于 addCookieHeader
、clientAddress
、locals
和 routeData
。
addCookieHeader
段落标题 addCookieHeader类型: boolean
默认值: false
是否自动将 Astro.cookie.set()
写入的所有 cookie 添加到响应头中。
当设置为 true
时,它们将作为逗号分隔的键值对添加到响应的 Set-Cookie
头中。你可以使用标准的 response.headers.getSetCookie()
API 来单独读取它们。
当设置为 false
(默认值)时,这些 cookie 只能从 App.getSetCookieFromResponse(response)
中获取。
const response = await app.render(request, { addCookieHeader: true });
clientAddress
段落标题 clientAddress类型: string
默认值: request[Symbol.for("astro.clientAddress")]
该客户端 IP 地址将作为 Astro.clientAddress
在页面中可用,并作为 API 路由和中间件中的 ctx.clientAddress
。
下面的示例读取 x-forwarded-for
头,并将其作为 clientAddress
传递。该值将作为 Astro.clientAddress
提供给用户。
const clientAddress = request.headers.get("x-forwarded-for");const response = await app.render(request, { clientAddress });
locals
段落标题 locals类型: object
context.locals
对象 用于在请求的生命周期中存储和访问信息。
下面的示例读取名为 x-private-header
的头,并尝试将其解析为对象并将其传递给 locals
,然后可以将其传递给任何 中间件函数。
const privateHeader = request.headers.get("x-private-header");let locals = {};try { if (privateHeader) { locals = JSON.parse(privateHeader); }} finally { const response = await app.render(request, { locals });}
routeData
段落标题 routeData类型: RouteData
默认值: app.match(request)
如果你已经知道要渲染的路由,请为 integrationRouteData
提供一个值。这样做将绕过内部调用 app.match
来确定要渲染的路由。
const routeData = app.match(request);if (routeData) { return app.render(request, { routeData });} else { /* 特定于适配器的 404 响应 */ return new Response(..., { status: 404 });}
app.match()
段落标题 app.match()类型: (request: Request) => RouteData | undefined
该方法用于判断请求是否匹配 Astro 应用的路由规则。
if(app.match(request)) { const response = await app.render(request);}
通常可以在不使用 .match
的情况下调用 app.render(request)
。因为当配置了 404.astro
文件后,Astro 就会自动处理 404 的情况。如果想要自定义处理规则,请使用 app.match(request)
。
使用 astro add
安装适配器
段落标题 使用 astro add 安装适配器用户可以使用 astro add
命令 轻松地在他们的项目中添加集成和适配器。如果希望其他用户可以使用该命令安装 你的 适配器,请在 package.json
文件的 keywords
项中添加 astro-adapter
属性:
{ "name": "example", "keywords": ["astro-adapter"],}
当将适配器发布到 npm 后,执行 astro add example
命令,即可安装适配器以及在 package.json
文件中指定的对等依赖。我们将指导用户手动更新他们的项目配置。
Astro features
段落标题 Astro features
添加于:
astro@3.0.0
Astro features 是适配器告诉 Astro 它们是否能够支持某个特性的一种方式,也是适配器支持程度的一种方式。
当使用这些属性时,Astro 将:
- 运行特定的验证;
- 抛出(emit)上下文日志;
这些操作是基于支持或不支持的特性、支持程度以及用户使用的配置来运行的。
以下配置告诉 Astro,该适配器对 Sharp 提供的内置图像服务有实验性支持:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', supportedAstroFeatures: { sharpImageService: 'experimental' } }); }, }, };}
如果使用 Sharp 图像服务,Astro 将根据适配器的支持程度向终端输出警告和错误:
[@matthewp/my-adapter] The feature is experimental and subject to issues or changes.
[@matthewp/my-adapter] The currently selected adapter `@matthewp/my-adapter` is not compatible with the service "Sharp". Your project will NOT be able to build.
还可以提供一条消息,以便为用户提供更多上下文:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', supportedAstroFeatures: { sharpImageService: { support: 'limited', message: 'This adapter has limited support for Sharp, certain features may not work as expected.' } } }); }, }, };}
Adapter features
段落标题 Adapter features一组可以改变产出文件输出的特性。当适配器选择这些特性时,它们将在特定的钩子中获得额外的信息。
edgeMiddleware
段落标题 edgeMiddleware类型: boolean
定义在构建时是否会打包任何按需渲染的中间件代码。
启用此功能时,会阻止在构建期间将中间件代码打包并导入到所有页面中:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', adapterFeatures: { edgeMiddleware: true } }); }, }, };}
然后,使用 astro:build:ssr
钩子,它将为你提供一个 middlewareEntryPoint
,一个指向文件系统上物理文件的 URL
。
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', adapterFeatures: { edgeMiddleware: true } }); },
'astro:build:ssr': ({ middlewareEntryPoint }) => { // 请记住检查此属性是否退出,如果适配器未选择加入该功能,则它将是 `undefined` if (middlewareEntryPoint) { createEdgeMiddleware(middlewareEntryPoint) } } }, };}
function createEdgeMiddleware(middlewareEntryPoint) { // 通过你的打包工具生成一个新的物理文件}
envGetSecret
段落标题 envGetSecret类型: AdapterSupportsKind
此功能允许你的适配器获取用户在 env.schema
中配置的密钥。
通过任何有效的 AdapterSupportsKind
值传递给适配器来启用此功能:
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', adapterFeatures: { envGetSecret: 'stable' } }); }, }, };}
astro/env/setup
模块允许你为 getSecret()
提供一个实现。在你的服务器入口中,尽早调用 setGetEnv()
:
import { App } from 'astro/app';import { setGetEnv } from "astro/env/setup"
setGetEnv((key) => process.env[key])
export function createExports(manifest) { const app = new App(manifest);
const handler = (event, context) => { // ... };
return { handler };}
如果你支持密钥,请确保在请求时将 setGetEnv()
调用在 getSecret()
之前:
import type { SSRManifest } from 'astro';import { App } from 'astro/app';import { setGetEnv } from 'astro/env/setup';import { createGetEnv } from '../utils/env.js';
type Env = { [key: string]: unknown;};
export function createExports(manifest: SSRManifest) { const app = new App(manifest);
const fetch = async (request: Request, env: Env) => { setGetEnv(createGetEnv(env));
const response = await app.render(request);
return response; };
return { default: { fetch } };}
buildOutput
段落标题 buildOutput类型: 'static' | 'server'
astro@5.0.0
此属性允许你强制指定构建的特定输出形态。这对于只能使用特定输出类型的适配器非常有用,例如,你的适配器可能期望一个静态网站,但使用适配器来创建特定于主机的文件。如果未指定,则默认为 server
。
export default function createIntegration() { return { name: '@matthewp/my-adapter', hooks: { 'astro:config:done': ({ setAdapter }) => { setAdapter({ name: '@matthewp/my-adapter', serverEntrypoint: '@matthewp/my-adapter/server.js', adapterFeatures: { buildOutput: 'static' } }); }, }, };}