Jetzt abonnieren, um Benachrichtigungen über neue Beiträge zu erhalten:

Verbesserung des TypeScript-Supports von Workers: Präzision, Ergonomie und Interoperabilität

2022-11-18

Lesezeit: 4 Min.
Dieser Beitrag ist auch auf English, 繁體中文, Français, 日本語, Português, Español, und 简体中文 verfügbar.

TypeScript vereinfacht Entwicklern das Schreiben von Code, der nicht abstürzt, indem es Typfehler noch vor der Ausführung des Programms abfängt. Wir möchten, dass Entwickler die Vorteile dieses Tools nutzen und haben deshalb vor einem Jahr ein System zur automatischen Generierung von TypeScript-Typen für die Cloudflare Workers Runtime entwickelt. Dadurch konnten Entwickler in ihren IDEs Code-Vervollständigungen für Workers-APIs sehen und den Code vor der Bereitstellung auf seinen Typ prüfen. Jede Woche erschien eine neue Version der Typen, die die neuesten Änderungen.

Improving Workers TypeScript support: accuracy, ergonomics and interoperability

Im Laufe des letzten Jahres gaben uns unseren Kunden und internen Teams viel Feedback zur Verbesserung unserer Typen. Mit der Umstellung auf das Bazel-Build-System in Vorbereitung auf das Opensourcing der Runtime sahen wir die Gelegenheit, unsere Typen so umzubauen, dass sie genauer, einfacher zu verwenden und einfacher zu generieren sind. Heute kündigen wir die nächste große Version von @cloudflare/workers-types an – mit einer Reihe neuer Features und dem Opensourcing der vollständig neu geschriebenen automatischen Generierungsskripte.

So verwenden Sie TypeScript mit Workers

Die Einrichtung von TypeScript in Workers ist ganz einfach! Wenn Sie zum ersten Mal mit Workers arbeiten, installieren Sie Node.js und führen Sie dann npx wrangler init in Ihrem Terminal aus, um ein neues Projekt zu generieren. Wenn Sie ein bestehendes Workers-Projekt haben und unsere verbesserten Typisierungen nutzen möchten, installieren Sie die neuesten Versionen von TypeScript und @cloudflare/workers-types mit npm install --save-dev typescript @cloudflare/workers-types@latest und erstellen Sie dann eine tsconfig.json-Datei mit folgendem Inhalt:

Ihr Editor wird jetzt Probleme hervorheben und Ihnen während der Eingabe Code-Vervollständigungen vorschlagen. So machen Sie weniger Fehler und die Arbeit wird angenehmer.

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": ["esnext"],
    "types": ["@cloudflare/workers-types"]
  }
}

Editor hebt die falsche Verwendung von set anstelle von put hervor und bietet Codevervollständigungen

Verbesserte Interoperabilität mit Standardtypen

Cloudflare Workers implementieren viele der gleichen Laufzeit-APIs wie Browser, und wir verbessern ständig unsere Standardkonformität mit dem WinterCG. So können Browser beispielsweise Audiodateien abspielen, während Worker direkten Zugriff auf das Netzwerk von Cloudflare haben, um global verteilte Daten zu speichern. Diese Diskrepanz bedeutet: Die Runtime-APIs und Typen, die von jeder Plattform bereitgestellt werden, sind unterschiedlich. Es ist daher schwierig, Workers-Typen mit Frameworks wie Remix zu verwenden, die die gleichen Dateien im Cloudflare-Netzwerk und im Browser ausführen. Diese Dateien müssen gegen lib.dom.d.ts typgeprüft werden, was mit unseren Typen nicht kompatibel ist.

Als Lösung für dieses Problem generieren wir jetzt eine separate Version unserer Typen. Sie kann selektiv importiert werden, ohne dass Sie @cloudflare/workers-types in das Feld types in Ihrer tsconfig.json aufnehmen müssen. So könnte das aussehen:

Außerdem erzeugen wir automatisch ein diff unserer Typen gegen die lib.webworker.d.ts von TypeScript. In Zukunft werden wir anhand dieser Informationen Bereiche identifizieren, in denen wir die Spec-compliance weiter verbessern können.

import type { KVNamespace } from "@cloudflare/workers-types";

declare const USERS_NAMESPACE: KVNamespace;

Verbesserte Kompatibilität mit Kompatibilitätsdaten

Cloudflare gibt starke Zusagen zur Abwärtskompatibilität für alle von uns bereitgestellten APIs. Wir verwenden Kompatibilitätsflags und Datumsangaben, um wichtige Änderungen auf abwärtskompatible Weise vorzunehmen. Manchmal ändern diese Kompatibilitätsflags die Typen. Zum Beispiel fügt das global_navigator-Flag einen neuen globalen navigator hinzu, und das url_standard-Flag ändert die URLSearchParams-Konstruktorsignatur.

Sie können jetzt die Version der Typen auswählen, die Ihrem Kompatibilitätsdatum entspricht. So können Sie sicher sein, dass Sie keine Features verwenden, die nicht in Runtime unterstützt werden.

Verbesserte Integration mit Wrangler

{
  "compilerOptions": {
    ...
    "types": ["@cloudflare/workers-types/2022-08-04"]
  }
}

Neben den Kompatibilitätsdaten wirkt sich die Konfiguration Ihrer Worker-Umgebung auch auf die Runtime- und Type-API-Oberfläche aus. Wenn Sie in Ihrer wrangler.toml Bindings wie KV-Namespaces oder R2-Buckets konfiguriert haben, müssen sich diese in TypeScript-Typen widerspiegeln. Ebenso müssen benutzerdefinierte Text-, Daten- und WebAssembly-Modulregeln deklariert werden, damit TypeScript die Exporttypen kennt. Bisher konnten Sie eine separate TypeScript-Umgebungsdatei mit diesen Deklarationen erstellen.

Um wrangler.toml als Single Source of Truth zu erhalten, können Sie jetzt npx wrangler types ausführen, um diese Datei automatisch zu erzeugen.

Zum Beispiel generiert die folgende wrangler.toml ...

... diese Ambiet-Typen:

kv_namespaces = [{ binding = "MY_NAMESPACE", id = "..." }]
rules = [{ type = "Text", globs = ["**/*.txt"] }]

Verbesserte integrierte Dokumentation und Änderungsprotokolle

interface Env {
  MY_NAMESPACE: KVNamespace;
}
declare module "*.txt" {
  const value: string;
  export default value;
}

Codevervollständigungen bieten Entwicklern, die neu auf der Workers-Plattform sind, eine hervorragende Möglichkeit, die API-Oberfläche zu erkunden. Wir nehmen jetzt die Dokumentation für Standard-APIs aus den offiziellen TypeScript-Typen in unsere Typen auf. Wir beginnen auch die Integration von Dokumentationen für cloudflare-spezifische APIs in diese Typen.

Entwickler, die bereits mit der Workers-Plattform arbeiten, können möglicherweise nur schwer erkennen, wie sich die Typen mit jedem Release von @cloudflare/workers-types ändern. Um Typfehler zu vermeiden und neue Features hervorzuheben, generieren wir jetzt mit jeder Version ein detailliertes Änderungsprotokoll, in dem wir die neuen, geänderten und entfernten Definitionen aufschlüsseln.

docs in types shown with code completions

Wie funktioniert die Typgenerierung hinter den Kulissen?

Wie bereits erwähnt, haben wir die Skripte zur automatischen Typgenerierung komplett überarbeitet. Jetzt sind sie noch zuverlässiger, erweiterungsfähiger und wartungsfreundlicher. Das bedeutet, dass Entwickler verbesserte Typen erhalten, sobald neue Versionen der Runtime veröffentlicht werden. Unser System verwendet jetzt das neue Runtime-Typ-Informationssystem (Runtime-type-information oder RTTI) von  workerd, um die Typen von Workers Runtime-APIs abzufragen, anstatt zu versuchen, diese Informationen aus geparsten C++ ASTs zu extrahieren.

Anschließend übergeben wir diese RTTI an ein TypeScript-Programm, mit Hilfe der TypeScript-Compiler-API erzeugt es Deklarationen und führt AST-Transformationen durch, um sie zu bereinigen. Dies ist in das Bazel-Build-System von workerd integriert, d. h. die Erzeugung von Typen ist jetzt ein einziger bazel build //types:types-Befehl. Wir nutzen den Cache von Bazel, um so wenig wie möglich während der Generierung neu zu erstellen.

// 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 = [ ... ], ...)

Während die automatisch generierten Typen die JavaScript-Schnittstelle von Workers Runtime-APIs korrekt beschreiben, bietet TypeScript zusätzliche Features, mit denen wir Typen mit höherer Zuverlässigkeit bereitstellen und Entwickelung ergonomischer gestalten können. Unser System ermöglicht es uns, partielle TypeScript-„Überschreibungen“ von Hand zu schreiben, die mit den automatisch generierten Typen zusammengeführt werden. Dadurch können wir:

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>; }
new automatic type generation architecture
  • Typparameter (Generika) zu Typen wie ReadableStream hinzufügen und any (jegliche) typisierten Werte vermeiden.

  • die Korrespondenz zwischen Eingabe- und Ausgabetypen mit Methodenüberladungen festlegen. KVNamespace#get() sollte zum Beispiel einen string zurückgeben, wenn das type-Argument text lautet, aber ArrayBuffer, wenn es arrayBuffer ist.

  • Typen umbenennen, um den TypeScript-Standards zu entsprechen und die Ausführlichkeit zu reduzieren.

  • einen Typ vollständig ersetzen, um genauere Deklarationen zu erhalten. z. B. ersetzen wir WebSocketPair durch eine const-Deklaration für bessere Typen mit Object.values().

  • Typen für Werte bereitstellen, die intern nicht typisiert sind, wie z. B. das Request#cf-Objekt.

  • interne Typen ausblenden, die in Ihren Workern nicht verwendbar sind.

Zuvor wurden diese Überschreibungen in separaten TypeScript-Dateien zu den „C++“-Deklarationen definiert, die sie überschrieben. Somit stimmten sie oft nicht mehr mit den ursprünglichen Deklarationen überein. Im neuen System werden die Überschreibungen zusammen mit den Ursprüngen mit „C++“-Makros definiert. Also können sie jetzt zusammen mit Änderungen an der Runtime-Implementierung überprüft werden. In der README für den JavaScript-Glue-Code von workerd finden Sie viele weitere Details und Beispiele.

Probieren Sie Workers-Types noch heute!

Wir empfehlen Ihnen ein Upgrade auf die neueste Version von @cloudflare/workers-types mit npm install --save-dev @cloudflare/workers-types@latest, und probieren Sie den neuen Befehl wrangler types aus. Wir werden mit jedem workerd-Release eine neue Version der Typen veröffentlichen. Berichten Sie uns im Cloudflare Developers Discord, was Sie darüber denken. Und wenn Sie verbesserungswürdige Typen finden, öffnen Sie bitte ein GitHub-Issue.

Wir schützen komplette Firmennetzwerke, helfen Kunden dabei, Internetanwendungen effizient zu erstellen, jede Website oder Internetanwendung zu beschleunigen, DDoS-Angriffe abzuwehren, Hacker in Schach zu halten, und unterstützen Sie bei Ihrer Umstellung auf Zero Trust.

Greifen Sie von einem beliebigen Gerät auf 1.1.1.1 zu und nutzen Sie unsere kostenlose App, die Ihr Internet schneller und sicherer macht.

Wenn Sie mehr über unsere Mission, das Internet besser zu machen, erfahren möchten, beginnen Sie hier. Sie möchten sich beruflich neu orientieren? Dann werfen Sie doch einen Blick auf unsere offenen Stellen.
Developer Week (DE)EntwicklerWrangler

Folgen auf X

Brendan Coll|@_mrbbot
Cloudflare|@cloudflare

Verwandte Beiträge

24. Oktober 2024 um 13:00

Durable Objects aren't just durable, they're fast: a 10x speedup for Cloudflare Queues

Learn how we built Cloudflare Queues using our own Developer Platform and how it evolved to a geographically-distributed, horizontally-scalable architecture built on Durable Objects. Our new architecture supports over 10x more throughput and over 3x lower latency compared to the previous version....