新規投稿のお知らせを受信されたい方は、サブスクリプションをご登録ください:

Workers TypeScriptのサポートに関する改良:精度、人間工学、相互運用性

2022-11-18

4分で読了
この投稿はEnglish繁體中文FrançaisDeutschPortuguêsEspañol简体中文でも表示されます。

TypeScriptは、プログラム実行前に型エラーを検出することで、開発者がクラッシュが起こらないコードを簡単に書けるようにするものです。私たちは開発者にこのツールを活用してもらいたいと考え、1年前に  Cloudflare Workersランタイム用のTypeScript型を自動生成するシステムを構築しました。これにより開発者はWorkers API のコード補完をIDEで確認し、デプロイ前にコードをタイプチェックすることができるようになりました。毎週、 最新の変更を反映した新しいバージョンの型が公開されます  。

Improving Workers TypeScript support: accuracy, ergonomics and interoperability

この1年間、お客様や社内のチームから、どのようにすれば型を改善できるかについてさまざまなフィードバックをいただきました。ランタイムのオープンソース化に向けてBazelビルドシステムに切り替えたことをきっかけに、さらに正確で使いやすく、生成もシンプルな型に作り直すことができたのです。本日、多数の新機能を備えた@cloudflare/workers-typesの次のメジャーリリースと、完全リライトの自動生成スクリプトのオープンソースを発表できることを嬉しく思います。

WorkerでのTypeScriptの使用方法

WorkersでTypeScriptをセットアップするのは簡単です。Workersを使い始めたばかりなら、Node.jsをインストールし、ターミナルでnpx wrangler initを実行して、新しいプロジェクトを生成することができます。既存のWorkersプロジェクトがあり、改良された型付けを利用したい場合は、npm install --save-dev typescript @cloudflare/workers-types@latestで最新バージョンのTypeScriptと@cloudflare/workers-typesをインストールし、以下の内容の tsconfig.jsonファイルを作成してください。

エディターが問題箇所を強調表示し、入力中にコード補完を行うことで、ミスを減り、開発環境が改善されます。

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

putコマンドの代わりにsetコマンドの誤った使用を強調表示し、コード補完を行うエディタ

標準型による相互運用性向上

Cloudflare Workersはブラウザと同じランタイムAPIを多く実装しており、当社では WinterCGで標準準拠をさらなる改善を進めています。しかし、ブラウザと Workerでできることの間には、基本的な相違がかならず生じます。例えば、ブラウザはオーディオファイルを再生することができますが、Workersは世界各地に分散しているデータ保存のためにCloudflareのネットワークへ直接アクセスすることができるのです。このミスマッチは、各プラットフォームが提供するランタイム API と型が異なることを意味し、ひいてはCloudflareネットワークとブラウザで同じファイルを実行するRemixのようなフレームワークでWorkersの型を使用することを困難にしているのです。これらのファイルはlib.dom.d.tsに対する型チェックが必要であり、当社の型と互換性がありません。

この問題を解決するために、tsconfig.jsontypesをフィールドに@cloudflare/workers-typesを含めなくても、選択的にインポートできる別バージョンのtypesを生成するようになりました。以下がその例です。

さらに、TypeScriptのlib.webworker.d.tsに対する型のdiff を自動的に生成しています。今後、これを利用して、仕様準拠をさらに改善できる部分を特定していきます。

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

declare const USERS_NAMESPACE: KVNamespace;

互換性のある日付での互換性を改善

Cloudflareでは、当社が提供しているすべてのAPIについて、強力な後方互換性のPromiseを維持しています。 互換性フラグと日付 を使用し、後方互換性のある方法でいくつかの変更を行います。これらの互換性フラグによって型が変更されることもあります。たとえば、global_navigator フラグは新しいnavigator globalを追加し、url_standardフラグは URLSearchParamsコンストラクタのシグネチャを変更します。

互換性のある日付に合わせた型のバージョンを選択できるようになったので、実行時にサポートされない機能を使用していないことが確認できます。

Wranglerとの統合を改善

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

互換性の日付に加えて、Worker環境の設定もランタイムと型のAPIサーフェイスに影響を与えます。wrangler.tomlKV namespacesR2 bucketなどのバインディングが設定されている場合、それらをTypeScriptの型に反映させる必要があります。同様に、カスタムのテキスト、データ、WebAssemblyモジュールのルールも、TypeScriptがエクスポートのタイプを認識できるように宣言します。以前は、これらの宣言を含む別のアンビエントTypeScriptファイルを作成しなければなりませんでした。

wrangler.tomlをSSOTとして維持するために、npx wrangler typesを実行してこのファイルを自動的に生成することができるようになりました。

例えば、次のように wrangler.toml...

...これらのアンビエント型を生成します。

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

統合されたドキュメントと変更履歴の改善

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

コード補完により、Workersプラットフォームを初めて使う開発者はAPIサーフェスを便利に知ることができるようになりました。現在、当社はTypeScriptの公式型から、標準APIのドキュメントを型に含めるようにしました。  Cloudflare固有のAPIのドキュメントを型に含める作業も始めています。

すでにWorkersプラットフォームを使用している開発者にとっては、 @cloudflare/workers-typesの各リリースで型がどのように変化しているかを確認するのが難しいと感じることもあるでしょう。型エラーを回避し、新機能を強調するために、各リリースで新規、変更、削除された定義を分割した詳細な変更履歴を生成するようになりました

docs in types shown with code completions

型生成の仕組みについて

先に述べたように、当社では 自動型生成スクリプト をすべて再構築し、信頼性、拡張性、保守性をこれまで以上に高いものにしました。つまり、開発者はランタイムの新バージョン公開直後から、改良版の型を手に入れることができることになります。当社のシステムは現在、解析済みのC++ ASTからこうした情報を抽出するのではなく、workerdの新しい実行時型情報(RTTI)システム を使用してWorkersランタイムAPIの型を照会しています。

このRTTIをTypeScriptプログラムに渡すと、TypeScript Compiler APIを使って宣言文を生成し、AST変換を行って整頓してくれます。これはworkerdのBazelビルドシステムに組み込まれており、型の生成はbazel build //types:typesコマンド一つで済むようになりました。Bazelのキャッシュを利用して、生成時にできるだけ再構築しないようにしています。

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

自動生成された型はWorkersのランタイムAPIの JavaScriptインターフェイスを正しく記述していますが、TypeScriptは_より忠実な型_を提供し、開発者の人間工学の改善に利用できる追加機能が使えます。当社のシステムでは、自動生成された型にマージされるTypeScriptの「オーバーライド」を部分的に手書きで記述することができます。これにより、…

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
  • ReadableStreamなどの型に型パラメータ(ジェネリック)を追加し、型付けされた値を一切使わないようにします。

  • メソッドのオーバーロードで入出力型の対応を指定します。たとえば、KVNamespace#get()は、型引数がテキストの場合は文字列を返しますが、arrayBufferの場合はArrayBuffer を返します。

  • TypeScriptの標準に合わせ、型の名前を変更し、冗長性を低減します。

  • より正確な宣言のために、型を完全に置き換えます。例えば、  WebSocketPairを  constと置き換えて、Object.values()でより良い型が得られるようにします。

  • Request#cfオブジェクトのような内部的に型付けされていない値に対して型を提供します。

  • Workersで使えない内部型を非表示にします。

これらのオーバーライドはこれまで、オーバーライドするC++の宣言とは別のTypeScriptファイルで定義されていました。そのため、オリジナルの宣言と同期がとれない状況が頻繁に発生していました。新システムでは、オーバーライドはC++マクロでオリジナルと一緒に定義されるため、ランタイムの実装変更と一緒にレビューすることができます。README for  workerd's JavaScript glue code でさらに詳細と事例を見ることができます。

workersの型を使って実際にタイピングしてみましょう。

npm install --save-dev @cloudflare/workers-types@latest@cloudflare/workers-typesの最新バージョンにアップグレードし、新しいwrangler typesコマンドを試すことをお勧めします。workerdのリリースごとに新バージョンの型を公開する予定です。Cloudflare Developers Discordでご意見をお聞かせください。また、改善すべきタイプを見つけた場合はGitHub issueを作成してください。

Cloudflareは企業ネットワーク全体を保護し、お客様がインターネット規模のアプリケーションを効率的に構築し、あらゆるWebサイトやインターネットアプリケーションを高速化し、DDoS攻撃を退けハッカーの侵入を防ぎゼロトラスト導入を推進できるようお手伝いしています。

ご使用のデバイスから1.1.1.1 にアクセスし、インターネットを高速化し安全性を高めるCloudflareの無料アプリをご利用ください。

より良いインターネットの構築支援という当社の使命について、詳しくはこちらをご覧ください。新たなキャリアの方向性を模索中の方は、当社の求人情報をご覧ください。
Developer Week開発者Wrangler

Xでフォロー

Brendan Coll|@_mrbbot
Cloudflare|@cloudflare

関連ブログ投稿