訂閱以接收新文章的通知:

改善 Workers TypeScript 支援:正確性、人體工學和互通性

2022-11-18

閱讀時間:4 分鐘
本貼文還提供以下語言版本:EnglishFrançaisDeutsch日本語PortuguêsEspañol简体中文

TypeScript 能在程式執行之前擷取類型錯誤,讓開發人員能夠輕鬆寫入不會崩潰的程式碼。我們希望開發人員能夠充分利用這種工具,這就是我們在一年前構建一個系統來自動產生 Cloudflare Workers 執行階段之 TypeScript 類型的原因所在。這樣一來,開發人員能在其 Workers API 中的 IDE 中查看程式碼完成情況,並在部署之前鍵入校驗碼。每週皆會發布這些類型的新版本,以體現最新的變更。

Improving Workers TypeScript support: accuracy, ergonomics and interoperability

在過去一年裡,我們收到了客戶及內部團隊針對我們如何改進相關類型提出的大量回饋意見。隨著我們轉換至 Bazel 構建系統來為開源執行階段做準備,我們看到了重新構建類型來提高產生準確性、易用性及簡單性的機會。我們今天很高興宣布推出 @cloudflare/workers-types 的下一個主要版本,新版本包含一系列新功能,以及全面重寫的自動產生指令碼之開源資源。

如何搭配使用 TypeScript 與 Workers

在 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"]
  }
}

編輯器反白顯示未正確使用 set 取代 put 的情況,並提供代碼完成情況

標準類型的互通性有所提升

Cloudflare Workers 實施了與瀏覽器相同的多個相同執行階段 API,我們正努力透過 WinterCG 進一步提高我們的標準合規性。不過,瀏覽器與 Workers 可以執行的操作之間始終具有根本性的差異。例如,瀏覽器能夠播放音訊檔案,而 Workers 可以直接存取 Cloudflare 的網路來儲存全球分散式資料。這種不相符意味著每個平台提供的執行階段 API 及類型均有所不同,這反過來又使其很難將 Workers 類型與在 Cloudflare 網路及瀏覽器中執行相同檔案的框架(如 Remix)搭配使用。這些檔案需要針對 lib.dom.d.ts 執行類型檢查,這與我們的類型並不相容。

為了解決這個問題,我們現在產生了一個可供有選擇性匯入的單獨類型版本,而不必在 tsconfig.jsontypes 欄位中加入 @cloudflare/workers-types 。這裡有一個相應的範例:

此外,我們會自動產生針對 TypeScript 之 lib.webworker.d.ts 的類型差異。放眼未來,我們將用其確定可進一步改善規範合規性的領域。

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

declare const USERS_NAMESPACE: KVNamespace;

與相容日期之相容性有所提高

Cloudflare 為我們提供的所有 API 信守強大的向後相容承諾。我們使用相容性旗標及日期以向後相容的方式作出重大變更。有時,這些相容性旗標會變更類型。例如,global_navigator 旗標會加入新的的全域 navigator,而 url_standard 旗標會變更 URLSearchParams 建構函式簽章。

現在,我們允許您選擇與相容日期相符的類型版本,確保您沒有使用執行階段不予支援的功能。

與 Wrangler 的整合有所改進

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

除了相容性日期,您的 Worker 環境設定還會影響執行階段及類型 API 圖面。如果您在 wrangler.toml 中設定了 KV 命名空間R2 貯體等繫結,則需要將這些繫結體現在 TypeScript 類型中。同樣,需要聲明自訂文字、資料及 WebAssembly 模組規則,這樣 TypeScript 才能獲悉匯出類型。以往,建立包含這些聲明的單獨環境 TypeScript 檔案完全取決於您。

為將 wrangler.toml 保存為單一事實來源,您現在可以執行 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

類型產生在後台如何運作?

如前所述,我們已經全面重新構建自動類型產生指令碼,來提高可靠性、可擴展性及可維護性。這就表示,開發人員將在發布執行階段的新版本後立即獲得改進的類型。我們的系統目前使用 workerd 的新執行階段類型資訊 (RTTI) 系統,來查詢 Workers 執行階段 API 的類型,而不是嘗試從解析的 C++ AST 中提取這些資訊。

隨後,我們會將此 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 等類型,避免使用 any 類型化值。

  • 使用方法多載指定輸入與輸出類型間的對應關係。例如,當 type 參數為 text 時,KVNamespace#get() 應返回一個 string ,但當參數為  arrayBuffer 時,應返回一個 ArrayBuffer

  • 重新命名類型來比對 TypeScript 標準,並減少詳細資訊。

  • 完全取代一種類型,獲得更準確的聲明。例如,我們會以 const 聲明取代  WebSocketPair,以獲取包含 Object.values() 的更好類型。

  • 為內部產生的非類型化值(如 Request#cf 對象)提供類型。

  • 隱藏 Workers 中不可使用的內部類型。

以往這些覆寫係在單獨的 TypeScript 檔案中定義,以便重寫 C++ 聲明。這就表示,它們往往會與原始聲明不同步。在新系統中,覆寫隨原始專案一併定義,包含 C++ 巨集,這就是說,這些可隨執行階段實作變更一起查看。請參閱 workerd JavaScript 黏附程式碼的讀我檔案,了解更多詳細資料及範例。

立即嘗試使用 workers-types 鍵入內容!

我們鼓勵您使用 npm install --save-dev @cloudflare/workers-types@latest 升級至 @cloudflare/workers-types 的最新版本,並嘗試新的 wrangler types 指令。我們將在每個 workerd 發布版本中發布這些類型的新版本。如果您對 Cloudflare Developers Discord 有任何看法,或發現任何可以改進的類型,請開出一個 GitHub 問題告知我們。

我們保護整個企業網路,協助客戶有效地建置網際網路規模的應用程式,加速任何網站或網際網路應用程式抵禦 DDoS 攻擊,阻止駭客入侵,並且可以協助您實現 Zero Trust

從任何裝置造訪 1.1.1.1,即可開始使用我們的免費應用程式,讓您的網際網路更快速、更安全。

若要進一步瞭解我們協助打造更好的網際網路的使命,請從這裡開始。如果您正在尋找新的職業方向,請查看我們的職缺
Developer WeekDevelopersWrangler

在 X 上進行關注

Brendan Coll|@_mrbbot
Cloudflare|@cloudflare

相關貼文