Saltearse al contenido

API de adaptadores de Astro

Astro está diseñado para que sea fácil de desplegar a cualquier proveedor de la nube que soporte SSR (server side rendering). Esta capacidad la proporcionan los adaptadores, que son integraciones. Lee la guía de SSR para aprender cómo añadir un adaptador.

Un adaptador es un tipo especial de integración que proporciona un punto de entrada para el renderizado en el servidor. Un adaptador hace dos cosas:

  • Implementa API específicas del host para manejar solicitudes.
  • Configura la compilación de acuerdo con las convenciones del host.

Un adaptador es una integración y puede hacer todo lo que puede hacer una integración.

Un adaptador debe llamar a la API setAdapter en el hook astro:config:done así:

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'
}
});
},
},
};
}

El objeto pasado a setAdapter se define como:

interface AstroAdapter {
name: string;
serverEntrypoint?: string;
previewEntrypoint?: string;
exports?: string[];
args?: any;
adapterFeatures?: AstroAdapterFeatures;
supportedAstroFeatures?: AstroFeatureMap;
}
export interface AstroAdapterFeatures {
/**
* Crea una función edge que se comunicará con el middleware de Astro
*/
edgeMiddleware: boolean;
/**
* Solo SSR. Cada ruta se convierte en su propia función/archivo
*/
functionPerRoute: boolean;
}
export type SupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated';
export type AstroFeatureMap = {
/**
* El adaptador puede servir páginas estáticas
*/
staticOutput?: SupportsKind;
/**
* El adaptador es capaz de servir páginas que son estáticas o renderizadas a través del servidor
*/
hybridOutput?: SupportsKind;
/**
* El adaptador es capaz de servir páginas SSR
*/
serverOutput?: SupportsKind;
/**
* El adaptador puede emitir assets estáticos
*/
assets?: AstroAssetsFeature;
};
export interface AstroAssetsFeature {
supportKind?: SupportsKind;
/**
* Si este adaptador implementa archivos en un entorno compatible con la biblioteca `sharp`
*/
isSharpCompatible?: boolean;
/**
* Si este adaptador implementa archivos en un entorno compatible con la biblioteca `squoosh`
*/
isSquooshCompatible?: boolean;
}

Las propiedades son:

  • name: Un nombre único para tu adaptador, usado para iniciar sesión.
  • serverEntrypoint: el punto de entrada para el renderizado en el servidor.
  • exports: un array de exportaciones con nombre cuando se usa junto con createExports (explicado a continuación).
  • adapterFeatures: Un objeto que habilita características específicas que deben ser compatibles con el adaptador. Estas características cambiarán la salida generada, y el adaptador debe implementar la lógica adecuada para manejar las diferentes salidas.
  • supportedAstroFeatures: Un mapa de las características integradas en Astro. Esto permite que Astro determine qué características un adaptador no puede o no está dispuesto a admitir, para que se puedan proporcionar mensajes de error adecuados.

La API del adaptador de Astro intenta funcionar con cualquier tipo de host y ofrece una forma flexible de ajustarse a las API del host.

Algunos hosts serverless esperan que exportes una función, como handler:

export function handler(event, context) {
// ...
}

Con la API del adaptador, logras esto implementando createExports en tu serverEntrypoint:

import { App } from 'astro/app';
export function createExports(manifest) {
const app = new App(manifest);
const handler = (event, context) => {
// ...
};
return { handler };
}

Y luego en la integración, donde llamas a setAdapter, proporciona este nombre en exports:

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'],
});
},
},
};
}

Algunos hosts esperan que ustedes mismos inicien el servidor, por ejemplo, escuchando un puerto. Para estos tipos de hosts, la API del adaptador te permite exportar una función start que se llamará cuando se ejecute el script del paquete.

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

Este módulo se utiliza para renderizar páginas que se han compilado previamente a través de astro build. Astro usa los objetos estándar Request y Response. Los hosts que tienen una API diferente para request/response deben convertirse a estos tipos en su adaptador.

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

Se proporcionan los siguientes métodos:

app.render(request: Request, options?: RenderOptions)
Sección titulada app.render(request: Request, options?: RenderOptions)

Este método llama a la página de Astro que coincide con la solicitud, la renderiza y devuelve una Promise a un objeto Response. Esto también funciona para las rutas API que no procesan páginas.

const response = await app.render(request);

El método app.render() acepta un argumento obligatorio request, y un objeto RenderOptions opcional para addCookieHeader, clientAddress, locals, y routeData.

Cuando es usado, locals debe ser el tercer argumento pasado. Puedes pasar undefined para routeData si no estás apuntando a una ruta específica.

Si se agregan o no automáticamente todas las cookies escritas por Astro.cookie.set() a los encabezados de respuesta.

Cuando se establece en true, se agregarán al encabezado Set-Cookie de la respuesta como pares clave-valor separados por comas. Puedes usar la API estándar response.headers.getSetCookie() para leerlos individualmente. Cuando se establece en false (predeterminado), las cookies solo estarán disponibles desde App.getSetCookieFromResponse(response).

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

La dirección IP del cliente que estará disponible como Astro.clientAddress en las páginas y como ctx.clientAddress en las rutas API y el middleware.

El ejemplo a continuación lee el encabezado x-forwarded-for y lo pasa como clientAddress. Este valor está disponible para el usuario como Astro.clientAddress.

const clientAddress = request.headers.get("x-forwarded-for");
const response = await app.render(request, { clientAddress });

El objeto context.locals se usa para almacenar y acceder a la información durante el ciclo de vida de una solicitud.

El ejemplo a continuación lee un encabezado llamado x-private-header, intenta analizarlo como un objeto y pasarlo a locals, que luego se puede pasar a cualquier función middleware.

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

Proporciona un valor para routeData si ya conoces la ruta a renderizar. Al hacerlo, se omitirá la llamada interna a app.match para determinar la ruta a renderizar.

const routeData = app.match(request);
if (routeData) {
return app.render(request, { routeData });
} else {
/* respuesta 404 específica del adaptador */
return new Response(..., { status: 404 });
}

Este método se usa para determinar si una solicitud coincide con las reglas de enrutamiento de la aplicación Astro.

if(app.match(request)) {
const response = await app.render(request);
}

Por lo general, puedes llamar a app.render(request) sin usar .match porque Astro maneja 404 si se proporciona un archivo 404.astro. Usa app.match(request) si quieres manejar los 404 de una manera diferente.

Permitir la instalación vía astro add

Sección titulada Permitir la instalación vía astro add

El comando astro add permite a los usuarios agregar integraciones y adaptadores a su proyecto fácilmente. Si quieres que tu adaptador sea instalable mediante esta herramienta, agrega astro-adapter al campo keywords en tu package.json:

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

Una vez que publiques tu adaptador a npm, correr astro add example instalará tu paquete con cualquier otra dependencia peer especificada en tu package.json. También indicaremos a los usuarios a actualizar la configuración de su proyecto manualmente.

Agregado en: astro@3.0.0

Las características de Astro son una forma en que un adaptador le indica a Astro si puede admitir una característica, así como el nivel de soporte del adaptador para esa característica.

Cuando se utilizan estas propiedades, Astro realizará lo siguiente:

  • ejecutará validaciones específicas;
  • emitirá información contextual en los registros;

Estas operaciones se ejecutan en función de las características admitidas o no admitidas, su nivel de soporte y la configuración que utiliza el usuario.

La siguiente configuración le indica a Astro que este adaptador tiene soporte experimental para assets, pero el adaptador no es compatible con los servicios integrados Sharp y 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 registrará una advertencia en la terminal:

[@matthewp/my-adapter] The feature is experimental and subject to issues or changes.

y un error si el servicio utilizado para activos no es compatible con el adaptador:

[@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.

Un conjunto de características que cambian la salida de los archivos emitidos. Cuando un adaptador elige habilitar estas características, recibirá información adicional dentro de hooks específicos.

Esta es una característica que se habilita al usar solo SSR. Por defecto, Astro emite un único archivo entry.mjs, que se encarga de emitir la página renderizada en cada solicitud.

Cuando functionPerRoute está en true, Astro en su lugar creará un archivo separado para cada ruta definida en el proyecto.

Cada archivo emitido solo renderizará una página. Las páginas se emitirán dentro de un directorio dist/pages/, y los archivos emitidos mantendrán las mismas rutas de archivo del directorio src/pages/.

Los archivos dentro del directorio pages/ de la compilación reflejarán la estructura de directorios de los archivos de página en src/pages/, por ejemplo:

  • Directorydist/
    • Directorypages/
      • Directoryblog/
        • entry._slug_.astro.mjs
        • entry.about.astro.mjs
      • entry.index.astro.mjs

Habilita la característica pasando true al adaptador.

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

Luego, utiliza el hook astro:build:ssr, que te proporcionará un objeto entryPoints que mapea una ruta de página al archivo físico emitido después de la compilación.

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) {
// haz algo con route y entryFile
}
}
},
};
}

Establecer functionPerRoute: true en un entorno serverless crea un archivo JavaScript (controlador) para cada ruta. El nombre de un controlador puede variar según la plataforma de alojamiento que estés utilizando: lambda, función, página, etc.

Cada una de estas rutas está sujeta a un arranque en frío cuando se ejecuta el controlador, lo que puede causar cierto retraso. Este retraso está influenciado por diferentes factores.

Con functionPerRoute: false, solo hay un controlador único encargado de renderizar todas tus rutas. Cuando se activa este controlador por primera vez, estarás sujeto a un arranque en frío. Después de eso, todas las demás rutas deberían funcionar sin retraso. Sin embargo, perderás el beneficio de la división de código que proporciona functionPerRoute: true.

Define si se incluirá el código de middleware SSR al realizar la compilación.

Cuando está habilitado, esto evita que el código de middleware se empaquete e importe en todas las páginas durante la compilación:

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

Luego, utiliza el hook astro:build:ssr, que te proporcionará un middlewareEntryPoint, que es una URL que apunta al archivo físico en el sistema de archivos.

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 }) => {
// recuerda verificar si esta propiedad existe, será `undefined` isi el adaptador no opta por habilitar la característica.
if (middlewareEntryPoint) {
createEdgeMiddleware(middlewareEntryPoint)
}
}
},
};
}
function createEdgeMiddleware(middlewareEntryPoint) {
// emite un nuevo archivo físico utilizando tu sistema de empaquetado.
}