Aller au contenu

Adaptateur l'API de Astro

Astro est conçu pour faciliter le déploiement vers n’importe quel fournisseur de cloud pour le SSR (rendu côté serveur). Cette capacité est fournie par les adaptateurs, qui sont des intégrations. Consultez le guide SSR pour apprendre à utiliser un adaptateur existant.

Un adaptateur est un type particulier d’intégration qui fournit un point d’entrée pour le rendu côté serveur. Un adaptateur a deux fonctions :

  • Implémente les API spécifiques à l’hôte pour gérer les requêtes.
  • Configure la compilation en fonction des conventions de l’hôte.

Un adaptateur est une intégration et peut faire tout ce qu’une intégration peut faire.

Un adaptateur doit appeler l’API setAdapter dans le hook astro:config:done comme suit :

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

L’objet passé dans setAdapter est défini comme ceci :

interface AstroAdapter {
name: string;
serverEntrypoint?: string;
previewEntrypoint?: string;
exports?: string[];
args?: any;
adapterFeatures?: AstroAdapterFeatures;
supportedAstroFeatures?: AstroFeatureMap;
}
export interface AstroAdapterFeatures {
/**
* Crée une fonction edge qui communiquera avec le middleware Astro.
*/
edgeMiddleware: boolean;
/**
* SSR uniquement. Chaque route devient sa propre fonction/fichier.
*/
functionPerRoute: boolean;
}
export type SupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated';
export type AstroFeatureMap = {
/**
* L'adaptateur est capable de servir des pages statiques
*/
staticOutput?: SupportsKind;
/**
* L'adaptateur est capable de servir des pages statiques ou rendues par le serveur.
*/
hybridOutput?: SupportsKind;
/**
* L'adaptateur est capable de servir des pages SSR
*/
serverOutput?: SupportsKind;
/**
* L'adaptateur peut émettre des actifs statiques
*/
assets?: AstroAssetsFeature;
};
export interface AstroAssetsFeature {
supportKind?: SupportsKind;
/**
* Si cet adaptateur déploie les fichiers dans un environnement compatible avec la bibliothèque `sharp`.
*/
isSharpCompatible?: boolean;
/**
* Si cet adaptateur déploie des fichiers dans un environnement compatible avec la bibliothèque `squoosh`.
*/
isSquooshCompatible?: boolean;
}

Les propriétés sont les suivantes :

  • name : Un nom unique pour votre adaptateur, utilisé pour la journalisation.
  • serverEntrypoint : Le point d’entrée pour l’affichage côté serveur.
  • exports : Un tableau d’exportations nommées lorsqu’il est utilisé en conjonction avec createExports (expliqué ci-dessous).
  • adapterFeatures : Un objet qui active des fonctionnalités spécifiques qui doivent être supportées par l’adaptateur. Ces fonctionnalités vont changer la sortie construite, et l’adaptateur doit implémenter la logique appropriée pour gérer la sortie différente.
  • supportedAstroFeatures : Une liste des caractéristiques intégrées d’Astro. Cela permet à Astro de déterminer quelles fonctionnalités un adaptateur ne peut pas ou ne veut pas supporter afin que les messages d’erreur appropriés puissent être fournis.

L’API de l’adaptateur Astro tente de fonctionner avec n’importe quel type d’hôte, et offre un moyen flexible de se conformer aux API de l’hôte.

Certains hôtes sans serveur attendent de vous que vous exportiez une fonction, comme handler :

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

Avec l’API de l’adaptateur, vous y parvenez en implémentant createExports dans votre serverEntrypoint :

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

Ensuite, dans votre intégration, lorsque vous appelez setAdapter, fournissez ce nom dans 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'],
});
},
},
};
}

Certains hôtes attendent que vous démarriez le serveur vous-mêmes, par exemple en écoutant un port. Pour ces types d’hôtes, l’API de l’adaptateur vous permet d’exporter une fonction start qui sera appelée lors de l’exécution du script bundle.

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

Ce module est utilisé pour afficher les pages qui ont été pré-construites par astro build. Astro utilise les objets standards Request et Response. Les hôtes qui ont une API différente pour les requêtes/réponses doivent convertir ces types dans leur adaptateur.

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

Les méthodes suivantes sont proposées :

app.render(request: Request, options?: RenderOptions)
Titre de la section app.render(request: Request, options?: RenderOptions)

Cette méthode appelle la page Astro qui correspond à la demande, l’affiche et renvoie une promesse à un objet Response. Cette méthode fonctionne également pour les routes API qui n’affichent pas de pages.

const response = await app.render(request);

La méthode app.render() accepte un argument obligatoire request, et un objet optionnel RenderOptions pour addCookieHeader, clientAddress, locals, et routeData.

Ajouter ou non automatiquement tous les cookies écrits par Astro.cookie.set() aux en-têtes de la réponse.

Lorsqu’ils sont définis à true, ils seront ajoutés à l’en-tête Set-Cookie de la réponse sous forme de paires clé-valeur séparées par des virgules. Vous pouvez utiliser l’API standard response.headers.getSetCookie() pour les lire individuellement. Si la valeur est false (par défaut), les cookies ne seront disponibles qu’à partir de App.getSetCookieFromResponse(response).

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

L’adresse IP du client qui sera disponible en tant que Astro.clientAddress dans les pages, et en tant que ctx.clientAddress dans les routes de l’API et le middleware.

L’exemple ci-dessous lit l’en-tête x-forwarded-for et le transmet comme clientAddress. Cette valeur devient disponible pour l’utilisateur en tant que Astro.clientAddress.

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

L’objet context.locals utilisé pour stocker et accéder aux informations pendant le cycle de vie d’une requête.

L’exemple ci-dessous lit un en-tête nommé x-private-header, tente de l’analyser comme un objet et de le passer à locals, qui peut alors être passé à n’importe quelle fonction 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 });
}

Fournissez une valeur pour routeData si vous connaissez déjà la route à afficher. Vous éviterez ainsi l’appel interne à app.match pour déterminer la route à afficher.

const routeData = app.match(request);
if (routeData) {
return app.render(request, { routeData });
} else {
/* adapter-specific 404 response */
return new Response(..., { status: 404 });
}

Cette méthode est utilisée pour déterminer si une demande est conforme aux règles de routage de l’application Astro.

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

Vous pouvez généralement appeler app.render(request) sans utiliser .match car Astro gère les 404 si vous fournissez un fichier 404.astro. Utilisez app.match(request) si vous voulez gérer les 404 d’une manière différente.

Autoriser l’installation via astro add

Titre de la section Autoriser l’installation via astro add

La commande astro add permet aux utilisateurs d’ajouter facilement des intégrations et des adaptateurs à leur projet. Si vous voulez que votre adaptateur soit installable avec cet outil, ajoutez astro-adapter au champ keywords dans votre package.json :

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

Une fois que vous avez publié votre adaptateur sur npm, lancer astro add example installera votre paquet avec toutes les dépendances spécifiées dans votre package.json. Nous demanderons également aux utilisateurs de mettre à jour manuellement la configuration de leur projet.

Ajouté à la version : astro@3.0.0

Les fonctionnalités Astro permettent à un adaptateur d’indiquer à Astro s’il est en mesure de prendre en charge une fonctionnalité, ainsi que le niveau de prise en charge de l’adaptateur.

Lors de l’utilisation de ces propriétés, Astro

  • exécute une validation spécifique ;
  • émet des informations contextuelles dans les journaux ;

Ces opérations sont exécutées en fonction des fonctionnalités prises en charge ou non, de leur niveau de prise en charge et de la configuration utilisée par l’utilisateur.

La configuration suivante indique à Astro que cet adaptateur dispose d’un support expérimental pour les ressources, mais qu’il n’est pas compatible avec les services intégrés Sharp et 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 enregistre un avertissement dans le terminal :

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

est une erreur si le service utilisé pour les ressources n’est pas compatible avec l’adaptateur :

[@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 ensemble de fonctionnalités qui modifient la sortie des fichiers émis. Lorsqu’un adaptateur opte pour ces fonctionnalités, il obtiendra des informations supplémentaires à l’intérieur de hooks spécifiques.

Il s’agit d’une fonctionnalité qui n’est activée qu’en cas d’utilisation de SSR. Par défaut, Astro émet un seul fichier entry.mjs, qui est responsable de l’affichage de la page à chaque requête.

Lorsque functionPerRoute est true, Astro va créer un fichier séparé pour chaque route définie dans le projet.

Chaque fichier émis n’affichera qu’une seule page. Les pages seront émises dans un répertoire dist/pages/ (ou sous un répertoire /pages/ dans le répertoire spécifié pour outDir), et les fichiers émis garderont le même chemin que le dossier src/pages/.

Les fichiers contenus dans le répertoire pages/ de la compilation reflèteront la structure de répertoire de vos fichiers de pages dans src/pages/, par exemple :

  • Répertoiredist/
  • Répertoirepages/
  • Répertoireblog/
  • entry._slug_.astro.mjs
  • entry.about.astro.mjs
  • entry.index.astro.mjs

Activez la fonctionnalité en passant true à l’adaptateur.

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

Ensuite, utilisez le hook astro:build:ssr, qui vous donnera un objet entryPoints qui fait correspondre une route de page au fichier physique créé après la construction.

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) {
// faire quelque chose avec route et entryFile
}
}
},
};
}

Définir functionPerRoute : true dans un environnement sans serveur crée un fichier JavaScript (handler) pour chaque route. Un handler peut avoir différents noms en fonction de votre plateforme d’hébergement : lambda, fonction, page, etc.

Chacunes de ces routes sont soumises à un démarrage à froid lorsque le gestionnaire s’exécute, ce qui peut entraîner un certain retard. Ce retard est influencé par différents facteurs.

Avec functionPerRoute : false, il n’y a qu’un seul gestionnaire chargé d’afficher toutes vos routes. Lorsque ce gestionnaire est déclenché pour la première fois, vous êtes soumis à un démarrage à froid. Ensuite, tous les autres itinéraires devraient fonctionner sans délai. Cependant, vous perdrez le bénéfice de la division du code que functionPerRoute : true fournit.

Définit si le code du middleware SSR sera regroupé lors de la construction.

Lorsque cette option est activée, elle empêche le code du middleware d’être regroupé et importé par toutes les pages au cours de la construction :

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

Ensuite, utilisez le hook astro:build:ssr, qui vous donnera un middlewareEntryPoint, une URL vers le fichier physique sur le système de fichiers.

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 }) => {
// n'oubliez pas de vérifier si cette propriété existe, elle sera `undefined` si l'adaptateur n'a pas opté pour la fonctionnalité
if (middlewareEntryPoint) {
createEdgeMiddleware(middlewareEntryPoint)
}
}
},
};
}
function createEdgeMiddleware(middlewareEntryPoint) {
// créer un nouveau fichier physique à l'aide de votre bundler
}