TypeScript permet aux développeurs d'écrire facilement du code qui ne provoque pas de défaillances en détectant les erreurs de type avant l'exécution de votre programme. Nous voulons permettre aux développeurs de profiter de cet outil ; c'est pourquoi, il y a un an de cela, nous avons construit un système permettant de générer automatiquement des types TypeScript pour le runtime de Cloudflare Workers. Celui-ci permettait aux développeurs d'afficher des suggestions de code dans leurs IDE pour les API Workers et de vérifier l'intégrité du code avant son déploiement. Chaque semaine, une nouvelle version des types était publiée, reflétant les modifications les plus récentes.
Au cours de l'année passée, nous avons reçu de nombreux commentaires de la part de clients et de nos équipes internes concernant les améliorations que nous pourrions apporter à nos types. Avec l'adoption du système de version Bazel en préparation de la mise en disponibilité open source du runtime, nous avons vu une opportunité de reconstruire nos types afin qu'ils soient plus précis, plus faciles à utiliser et plus simples à générer. Aujourd'hui, nous sommes heureux d'annoncer la prochaine version majeure de @cloudflare/workers-types
avec une pléthore de nouvelles fonctionnalités, ainsi que la mise en disponibilité open source de scripts de génération automatique entièrement réécrits.
Comment utiliser TypeScript avec Workers
Configurer TypeScript dans Workers, c'est facile ! Si vous faites vos premiers pas avec Workers, installez Node.js, puis exécutez la commande npx wrangler init
sur votre terminal pour générer un nouveau projet. Si vous disposez d'un projet Workers existant et vous souhaitez profiter de nos types améliorés, installez les toutes dernières versions de TypeScript et de @cloudflare/workers-types
avec la commande npm install --save-dev typescript @cloudflare/workers-types@latest
, puis créez un fichier tsconfig.json
avec le contenu suivant :
Votre éditeur met maintenant en évidence les problèmes et vous propose des suggestions de code au fur et à mesure de la saisie, rendant votre expérience de développeur moins sujette aux erreurs et plus agréable.
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["esnext"],
"types": ["@cloudflare/workers-types"]
}
}
Éditeur mettant en évidence l'utilisation incorrecte de set
au lieu de put
et fournissant des suggestions de code
Amélioration de l'interopérabilité avec les types standard
Cloudflare Workers met en œuvre, dans une large mesure, les mêmes API de runtime que les navigateurs, et nous nous employons à améliorer encore notre conformité aux normes avec WinterCG. Cependant, il subsistera toujours des différences fondamentales entre ce que peuvent faire les navigateurs et Workers. Par exemple, les navigateurs peuvent lire des fichiers audio, tandis que les instances Workers disposent d'un accès direct au réseau de Cloudflare pour stocker des données distribuées dans le monde entier. Ce décalage signifie que les API de runtime et les types fournis par chaque plateforme sont différents, rendant difficile l'utilisation des types Workers avec des frameworks (tels que Remix) qui exécutent les mêmes fichiers sur le réseau Cloudflare et dans le navigateur. Ces fichiers doivent faire l'objet d'une vérification contre lib.dom.d.ts
, qui est incompatible avec nos types.
Pour résoudre ce problème, nous générons maintenant une version distincte de nos types, qui peut être importée de manière sélective, sans devoir inclure @cloudflare/workers-types
dans le champ types
de votre fichier tsconfig.json
. Voici un exemple de ce que cela donne :
En outre, nous générons automatiquement un diff de nos types en fonction u fichier lib.webworker.d.ts
de TypeScript. À l'avenir, nous utiliserons ces données pour identifier les domaines dans lesquels nous pouvons encore améliorer notre conformité spec.
import type { KVNamespace } from "@cloudflare/workers-types";
declare const USERS_NAMESPACE: KVNamespace;
Amélioration de la compatibilité avec les dates de compatibilité
Cloudflare tient de solides promesses de rétrocompatibilité pour toutes les API fournies. Nous utilisons des indicateurs et des dates de compatibilité pour introduire d'importants changements de manière rétrocompatible. Parfois, ces indicateurs de compatibilité modifient les types. Par exemple, l'indicateur global_navigator
ajoute un nouvel objet global navigator
, et l'indicateur url_standard
modifie la signature de constructeur URLSearchParams
.
Nous vous permettons désormais de sélectionner la version des types qui correspond à votre date de compatibilité, afin que vous ayez l'assurance de ne pas utiliser de fonctionnalités qui ne seront pas prises en charge lors de l'exécution.
Amélioration de l'intégration avec Wrangler
{
"compilerOptions": {
...
"types": ["@cloudflare/workers-types/2022-08-04"]
}
}
En plus des dates de compatibilité, la configuration de l'environnement de votre instance Workers affecte également le runtime et la surface de l'API de type. Si vous avez configuré des liaisons telles que les espaces de noms KV ou les buckets R2 dans votre fichier wrangler.toml
, celles-ci doivent être reflétées dans les types TypeScript. De même, les règles relatives aux textes personnalisés, aux données et au module WebAssembly doivent être déclarées pour que TypeScript ait connaissance des types d'exportations. Auparavant, c'est à vous qu'il incombait de créer un fichier ambient TypeScript distinct contenant ces déclarations.
Pour vous assurer que le fichier wrangler.toml
reste la source unique de données exactes, vous pouvez maintenant exécuter la commande npx wrangler types
afin de générer ce fichier automatiquement.
Par exemple, le fichier wrangler.toml
suivant…
…génère les types ambient suivants :
kv_namespaces = [{ binding = "MY_NAMESPACE", id = "..." }]
rules = [{ type = "Text", globs = ["**/*.txt"] }]
Amélioration de la documentation intégrée et des journaux de modifications
interface Env {
MY_NAMESPACE: KVNamespace;
}
declare module "*.txt" {
const value: string;
export default value;
}
Les suggestions de code offrent aux développeurs effectuant leurs premiers pas sur la plateforme Workers un excellent moyen d'explorer la surface des API. Nous incluons maintenant dans nos types la documentation des API standard issue des types officiels de TypeScript. Nous commençons également à y intégrer des documentations relatives aux API spécifiques de Cloudflare.
Pour les développeurs utilisant déjà la plateforme Workers, il peut être difficile de suivre les modifications des types à chaque nouvelle version de @cloudflare/workers-types
. Afin d'éviter les erreurs de type et de mettre en évidence les nouvelles fonctionnalités, nous générons maintenant avec chaque version un journal de modifications détaillé qui différencie clairement les définitions nouvelles, modifiées et supprimées.
Comment fonctionnent les rouages de la génération de types ?
Comme nous l'avons indiqué plus haut, nous avons entièrement reconstruit les scripts de génération automatique de types afin de les rendre plus fiables et extensibles et d'en faciliter la maintenance. Cela signifie que les développeurs recevront des types améliorés dès la publication de nouvelles versions du runtime. Notre système utilise désormais le nouveau système runtime-type-information (RTTI) de workerd
pour interroger les types d'API du runtime Workers, plutôt que d'essayer d'extraire ces informations depuis les AST C++ analysés.
Nous transmettons ensuite ce RTTI à un programme TypeScript qui utilise l'API TypeScript Compiler pour générer des déclarations et exécuter des transformations AST afin de les mettre en ordre. Celui-ci est intégré au système de version Bazel de workerd
, ce qui signifie que la génération de types relève maintenant d'une commande bazel build //types:types
unique. Nous utilisons le cache de Bazel pour limiter autant que possible la reconstruction pendant la génération.
// Encode the KV namespace type without any compatibility flags enabled
CompatibilityFlags::Reader flags = {};
auto builder = rtti::Builder(flags);
auto type = builder.structure<KvNamespace>();
capnp::TextCodec codec;
auto encoded = codec.encode(type);
KJ_DBG(encoded); // (name = "KvNamespace", members = [ ... ], ...)
Si que les types générés automatiquement décrivent correctement l'interface JavaScript des API d'exécution de Workers, TypeScript fournit des fonctionnalités supplémentaires que nous pouvons utiliser pour accroître la fidélité des types et améliorer l'ergonomie pour les développeurs. Notre système nous permet d'écrire à la main des « substitutions » TypeScript partielles, qui sont fusionnées avec les types générés automatiquement. Ceci nous offre les possibilités suivantes :
import ts, { factory as f } from "typescript";
const keyParameter = f.createParameterDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
"key",
/* questionToken */ undefined,
f.createTypeReferenceNode("string")
);
const returnType = f.createTypeReferenceNode("Promise", [
f.createUnionTypeNode([
f.createTypeReferenceNode("string"),
f.createLiteralTypeNode(f.createNull()),
]),
]);
const getMethod = f.createMethodSignature(
/* modifiers */ undefined,
"get",
/* questionToken */ undefined,
/* typeParameters */ undefined,
[keyParameter],
returnType
);
const kvNamespace = f.createInterfaceDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
"KVNamespace",
/* typeParameters */ undefined,
/* heritageClauses */ undefined,
[getMethod]
);
const file = ts.createSourceFile("file.ts", "", ts.ScriptTarget.ESNext);
const printer = ts.createPrinter();
const output = printer.printNode(ts.EmitHint.Unspecified, kvNamespace, file);
console.log(output); // interface KVNamespace { get(key: string): Promise<string | null>; }
Ajouter des paramètres de type (génériques) aux types tels que
ReadableStream
et éviter les valeurs de type any.Spécifier la correspondance entre les types d'entrée et de sortie avec les surcharges de fonction. Par exemple,
KVNamespace#get()
devrait renvoyer une chaîne lorsque l'argumenttype
esttext
, mais renvoyerArrayBuffer
lorsque l'argument type estarrayBuffer
.Renommer les types en fonction des normes TypeScript et réduire la verbosité.
Remplacer entièrement un type pour des déclarations plus précises. Par exemple, nous remplaçons
WebSocketPair
par une déclarationconst
pour améliorer les types avecObject.values()
.Fournir des types pour les valeurs qui ne comportent pas de type en interne, à l'image de l'objet
Request#cf
.Masquer les types internes non utilisables dans votre instance Workers.
Auparavant, ces remplacements étaient définis dans des fichiers TypeScript distincts des déclarations C++ qu'ils remplaçaient. Cela signifiait qu'ils étaient souvent désynchronisés par rapport aux déclarations d'origine. Dans le nouveau système, les remplacements sont définis parallèlement aux déclarations originales avec des macros C++ ; ils peuvent donc être examinés en même temps que les modifications de mise en œuvre du runtime. Reportez-vous au fichier README du code JavaScript glue de workerd
pour beaucoup plus d'informations détaillées et d'exemples.
Essayez de coder avec workers-types dès aujourd'hui !
Nous vous encourageons à installer la version la plus récente de @cloudflare/workers-types
avec la commande npm install --save-dev @cloudflare/workers-types@latest
et d'essayer la nouvelle commande wrangler types
. Nous publierons une nouvelle version des types avec chaque version de workerd
. Faites-nous part de vos commentaires sur le canal Discord Cloudflare Developers et ouvrez un incident sur GitHub si vous découvrez des types que nous pourrions améliorer.