返回介绍

Astro 适配器 API

发布于 2024-06-05 21:19:57 字数 15019 浏览 0 评论 0 收藏 0

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

什么是适配器

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

  • 实现托管平台的 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 尝试适配多种类型的托管方,并提供了灵活的配置方式。

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 };
}

在此之后,你需要在 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

有些托管方希望你自行管理服务的启动,例如通过监听一个端口的方式。对于这类托管方,可以导出一个 start 函数,该函数会在绑定脚本执行时被调用。

import { App } from 'astro/app';


export function start(manifest) {
  const app = new App(manifest);


  addEventListener('fetch', event => {
    // ...
  });
}

astro/app

该模块用于渲染已通过 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)

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

const response = await app.render(request);
RenderOptions

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

addCookieHeader

是否自动将 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

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

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

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

该方法用于判断请求是否匹配 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 命令 轻松地在他们的项目中添加集成和适配器。如果希望其他用户可以使用该命令安装 你的 适配器,请在 package.json 文件的 keywords 项中添加 astro-adapter 属性

{
  "name": "example",
  "keywords": ["astro-adapter"],
}

将适配器发布到 npm 后,执行 astro add example 命令,即可安装适配器以及在 package.json 文件中指定的对等依赖。我们将指导用户手动更新他们的项目配置。

Astro features

添加于: 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.

Adapter features

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

functionPerRoute

这是一个仅在使用 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)环境中设置 functionPerRoute: true 会为每个路由创建一个 JavaScript 文件(handler)。根据你的托管平台,处理程序可能会有不同的名称:lambda、function、page 等。

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

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

edgeMiddleware

定义在构建时是否会打包任何 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) {
    // 通过你的打包工具生成一个新的物理文件
}
Reference

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文