跳转到内容

Astro 适配器 API

Astro 可以轻松部署到任何云托管平台,以实现服务端渲染(SSR)。该能力由适配器集成提供,请参阅 SSR 指南 (EN) 了解如何使用现有的适配器。

适配器是一种特殊类型的集成,它为服务器端渲染提供了入口。适配器包含两项主要功能:

  • 实现托管平台的 API,以处理请求。
  • 根据托管平台的约定配置构建过程。

由于适配器是一种集成,因此它拥有集成提供的全部能力。

必须 通过在 astro:config:done 钩子中调用 setAdapter API 来使用适配器,例如:

my-adapter.mjs
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?: AstroAdapterFeatures;
supportedAstroFeatures?: AstroFeatureMap;
}
export interface AstroAdapterFeatures {
/**
* 创建一个与 Astro 中间件通信的边缘函数
*/
edgeMiddleware: boolean;
/**
* 仅 SSR 可用。每个路由都会成为自己的函数/文件
*/
functionPerRoute: boolean;
}
export type SupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated';
export type AstroFeatureMap = {
/**
* 该适配器是否能够为静态页面提供服务
*/
staticOutput?: SupportsKind;
/**
* 该适配器是否能够为静态页面或通过服务器渲染的页面提供服务。
*/
hybridOutput?: SupportsKind;
/**
* 此适配器是否能够为 SSR 页面提供服务
*/
serverOutput?: SupportsKind;
/**
* 此适配器对 assets 的支持程度
*/
assets?: AstroAssetsFeature;
};
export interface AstroAssetsFeature {
supportKind?: SupportsKind;
/**
* 此适配器是否在与库 `sharp` 兼容的环境中部署文件
*/
isSharpCompatible?: boolean;
/**
* 此适配器是否在与库 `squoosh` 兼容的环境中部署文件
*/
isSquooshCompatible?: boolean;
}

这些属性分别是:

  • name:适配器的唯一名称,用于日志记录。
  • serverEntrypoint:服务端渲染的入口。
  • exports:导出数组,与 createExports 配套使用(在下文中说明)。
  • adapterFeatures:一个对象,用于启用适配器必须支持的特定功能。这些功能将改变构建输出,适配器必须实现适当的逻辑来处理不同的输出。
  • supportedAstroFeatures:Astro 内置功能的映射。这允许 Astro 确定适配器无法或不愿意支持的功能,以便提供适当的错误消息。

Astro 的适配器 API 尝试适配多种类型的托管方,并提供了灵活的配置方式。

一些无服务架构的托管方会希望你导出一个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 };
}

在此之后,你需要在 setAdapterexports 属性中配置该 handler

my-adapter.mjs
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 函数,该函数会在绑定脚本执行时被调用。

import { App } from 'astro/app';
export function start(manifest) {
const app = new App(manifest);
addEventListener('fetch', event => {
// ...
});
}

该模块用于渲染已通过 astro build 命令预构建的页面。Astro 使用标准的 RequestResponse 对象。如果托管方使用不同格式的请求/响应 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(request: Request, options?: RenderOptions)
段落标题 app.render(request: Request, options?: RenderOptions)

此方法用于匹配符合请求的 Astro 页面,并返回一个 Promise 对象给 Response 。该方法对于不渲染页面的 API 路由同样适用。

const response = await app.render(request);

app.render() 方法接受一个必填的 request 参数,以及一个可选的 RenderOptions 对象,用于 addCookieHeaderclientAddresslocalsrouteData

是否自动将 Astro.cookie.set() 写入的所有 cookie 添加到响应头中。

当设置为 true 时,它们将作为逗号分隔的键值对添加到响应的 Set-Cookie 头中。你可以使用标准的 response.headers.getSetCookie() API 来单独读取它们。 当设置为 false(默认值)时,这些 cookie 只能从 App.getSetCookieFromResponse(response) 中获取。

const response = await app.render(request, { addCookieHeader: true });

该客户端 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 });

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 提供一个值。这样做将绕过内部调用 app.match 来确定要渲染的路由。

const routeData = app.match(request);
if (routeData) {
return app.render(request, { routeData });
} else {
/* 特定于适配器的 404 响应 */
return new Response(..., { status: 404 });
}

该方法用于判断请求是否匹配 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@3.0.0

Astro features 是适配器告诉 Astro 它们是否能够支持某个特性的一种方式,也是适配器支持程度的一种方式。

当使用这些属性时,Astro 将:

  • 运行特定的验证;
  • 抛出(emit)上下文日志;

这些操作是基于支持或不支持的特性、支持程度以及用户使用的配置来运行的。

以下配置告诉 Astro,该适配器对 assets 有实验性支持,但该适配器与内置服务 Sharp 和 Squoosh 不兼容:

my-adapter.mjs
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: {
assets: {
supportKind: "experimental",
isSharpCompatible: false,
isSquooshCompatible: false
}
}
});
},
},
};
}

Astro 将在终端中记录警告

/* 该功能是实验性的,可能会出现问题或更改。*/
[@matthewp/my-adapter] The feature is experimental and subject to issues or changes.

或者,如果用于 assets 的服务与适配器不兼容,则会抛出错误:

/* 当前选择的适配器 `@matthewp/my-adapter` 与 "Sharp" 服务不兼容。你的项目将无法构建。*/
[@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.

一组可以改变产出文件输出的特性。当适配器选择这些特性时,它们将在特定的钩子中获得额外的信息。

这是一个仅在使用 SSR 时启用的功能。默认情况下,Astro 会产出一个 entry.mjs 文件,该文件负责在每个请求上产出渲染的页面。

functionPerRoutetrue 时,Astro 会为项目中定义的每个路由创建一个单独的文件。

每个文件都只会渲染一个页面。页面将在 dist/pages/ 目录下(或者在 outDir 所指定目录中的 /pages/ 目录下)产出,产出的文件将保持与 src/pages/ 目录相同的文件路径。

构建出的 pages/ 目录下的文件,将会与 src/pages/ 目录下的页面文件的目录结构保持一致,例如:

  • 文件夹dist/
    • 文件夹pages/
      • 文件夹blog/
        • entry._slug_.astro.mjs
        • entry.about.astro.mjs
      • entry.index.astro.mjs

通过将 true 传递给适配器来启用此功能。

my-adapter.mjs
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: {
functionPerRoute: true
}
});
},
},
};
}

然后,使用 astro:build:ssr 钩子,它将为你提供一个 entryPoints 对象,该对象将页面路由映射到构建后的物理文件。

my-adapter.mjs
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: {
functionPerRoute: true
}
});
},
'astro:build:ssr': ({ entryPoints }) => {
for (const [route, entryFile] of entryPoints) {
// 对路由和条目文档执行某些操作
}
}
},
};
}

无服务器(Serverless)环境

段落标题 无服务器(Serverless)环境

在无服务器(Serverless)环境中设置 functionPerRoute: true 会为每个路由创建一个 JavaScript 文件(handler)。根据你的托管平台,处理程序可能会有不同的名称:lambda、function、page 等。

每个路由都会在处理程序(handler)运行时受到 冷启动 的影响,这可能会导致一些延迟。这种延迟受到不同因素的影响。

当设置 functionPerRoute: false 时,只有一个单一的处理程序(handler)负责渲染所有路由。当此处理程序(handler)首次触发时,你将受到冷启动的影响。然后,所有其他路由都应该没有延迟。但是,你将无法享受由 functionPerRoute: true 提供的代码拆分(code splitting)所带来的好处。

定义在构建时是否会打包任何 SSR 中间件代码。

启用此功能时,会阻止在构建期间将中间件代码打包并导入到所有页面中:

my-adapter.mjs
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

my-adapter.mjs
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) {
// 通过你的打包工具生成一个新的物理文件
}
贡献

你有什么想法?

社区
京ICP备15031610号-99