订阅以接收新文章的通知:

自动生成 Cloudflare Workers 的类型

2021-11-16

2 分钟阅读时间
这篇博文也有 English日本語版本。

根据过往情况来看,让 Rust 和 TypeScript 类型的存储库保持最新十分困难。它们是手动生成,这意味着面临不准确和过时的风险。直到最近,每当类型发生变化时,都需要手动更新 workers-types 存储库。我们还习惯于为大多数完整的浏览器 API 添加类型信息。这会导致混淆,当人们尝试使用不受 Workers 运行时支持的浏览器 API 时,它们会编译,但会抛出错误。

而在这个夏天,一切都发生了变化。我们的实习生 Brendan Coll 构建了一个可生成类型的自动化管道。每当我们构建 Workers 运行时时,它就会运行一次,为我们的 TypeScript 和 Rust 存储库生成类型。现在,所有内容都准确且为最新。

快速概览

每次构建 Workers 运行时代码时,都会有一个脚本在公共 API 上运行,生成 Rust 和 TypeScript 类型,以及一个包含静态类型中间表示的 JSON 文件。这些类型将被发送到适当的存储库,JSON 文件也会被上传,以防人们想要创建他们自己的类型包。这一点将在后面进行详述。

这意味着静态类型将始终准确且为最新。它还允许项目以其他静态类型语言运行 Workers,以从我们的中间表示生成他们自己的类型。下面是来自我们 Cloudflare 机器人的示例 PR。它在运行时类型中检测到一个变化,并正在更新 TypeScript 文件和中间表示。

使用自动生成的类型

首先,使用 wrangler 生成一个新的 TypeScript 项目:

如果您已经拥有 TypeScript 项目,则可以使用以下命令安装最新版本的 workers-types

$ wrangler generate my-typescript-worker https://github.com/cloudflare/worker-typescript-template

然后将 @cloudflare/workers-types 添加到项目的 tsconfig.json 文件。

$ npm install --save-dev @cloudflare/workers-types

之后,应当会在您选择的 IDE 中自动完成类型。

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

了解详情

下面是来自 Workers 运行时代码库的一些示例代码。

每次构建期间,都会有一个 Python 脚本在该代码上运行,并生成一个 Abstract Syntax Tree,其中包含有关函数的信息,包括标识符、任何参数类型和任何返回类型。

class Blob: public js::Object {
public:
  typedef kj::Array<kj::OneOf<kj::Array<const byte>, kj::String, js::Ref<Blob>>> Bits;
  struct Options {
    js::Optional<kj::String> type;
    JS_STRUCT(type);
  };

  static js::Ref<Blob> constructor(js::Optional<Bits> bits, js::Optional<Options> options);
  
  int getSize();
  js::Ref<Blob> slice(js::Optional<int> start, js::Optional<int> end);

  JS_RESOURCE_TYPE(Blob) {
    JS_READONLY_PROPERTY(size, getSize);
    JS_METHOD(slice);
  }
};

最后,会向 TypeScript 类型存储库自动传送带有已更新类型的 PR。

{
  "name": "Blob",
  "kind": "class",
  "members": [
    {
      "name": "size",
      "type": {
        "name": "integer"
      },
      "readonly": true
    },
    {
      "name": "slice",
      "type": {
        "params": [
          {
            "name": "start",
            "type": {
              "name": "integer",
              "optional": true
            }
          },
          {
            "name": "end",
            "type": {
              "name": "integer",
              "optional": true
            }
          }
        ],
        "returns": {
          "name": "Blob"
        }
      }
    }
  ]
}

覆盖

declare type BlobBits = (ArrayBuffer | string | Blob)[];

interface BlobOptions {
  type?: string;
}

declare class Blob {
  constructor(bits?: BlobBits, options?: BlobOptions);
  readonly size: number;
  slice(start?: number, end?: number, type?: string): Blob;
}

在某些情况下,TypeScript 支持一些 C++ 运行时不支持的概念,也就是泛型和函数重载。在这些情况下,我们会使用分部声明覆盖生成的类型。例如,DurableObjectStorage 为其 getter 和 setter 函数使用大量泛型。

您还可以使用 Markdown 编写类型覆盖。这里是覆盖 KVNamespace 类型的一个示例。

declare abstract class DurableObjectStorage {
	 get<T = unknown>(key: string, options?: DurableObjectStorageOperationsGetOptions): Promise<T | undefined>;
	 get<T = unknown>(keys: string[], options?: DurableObjectStorageOperationsGetOptions): Promise<Map<string, T>>;
	 
	 list<T = unknown>(options?: DurableObjectStorageOperationsListOptions): Promise<Map<string, T>>;
	 
	 put<T>(key: string, value: T, options?: DurableObjectStorageOperationsPutOptions): Promise<void>;
	 put<T>(entries: Record<string, T>, options?: DurableObjectStorageOperationsPutOptions): Promise<void>;
	 
	 delete(key: string, options?: DurableObjectStorageOperationsPutOptions): Promise<boolean>;
	 delete(keys: string[], options?: DurableObjectStorageOperationsPutOptions): Promise<number>;
	 
	 transaction<T>(closure: (txn: DurableObjectTransaction) => Promise<T>): Promise<T>;
	}

创建您自己的类型

JSON IR(中间表示)已经和 TypeScript 类型一起开放源码,可在本 GitHub 存储库中找到。我们还开放了类型架构本身的源码,该类型架构描述了 IR 的格式。如果您有兴趣为您自己的语言生成 Workers 类型,则可以使用 IR,它采用“规范化的”数据结构描述声明,并从中生成类型。

“workers.json”内的声明包含用于派生函数签名的元素和代码生成所需的其他元素,例如标识符、参数类型、返回类型和错误管理。一个具体用例是为编译到 WebAssembly 的语言生成外部函数声明,以精确地从 Workers 运行时导入可用的函数调用集。

总结

Cloudflare 深切关注支持 TypeScript 和 Rust 生态系统。Brendan 制作了一个工具,可确保两种语言的类型信息都始终为最新且准确。我们还以 JSON 格式开源了类型信息本身,这样,感兴趣的任何人都可以为他们想要的任何语言创建类型数据!

我们保护整个企业网络,帮助客户高效构建互联网规模的应用程序,加速任何网站或互联网应用程序抵御 DDoS 攻击,防止黑客入侵,并能协助您实现 Zero Trust 的过程

从任何设备访问 1.1.1.1,以开始使用我们的免费应用程序,帮助您更快、更安全地访问互联网。要进一步了解我们帮助构建更美好互联网的使命,请从这里开始。如果您正在寻找新的职业方向,请查看我们的空缺职位
Full Stack WeekCloudflare Workers开发人员ServerlessDeveloper Platform

在 X 上关注

Brendan Coll|@_mrbbot
Jonathan Kuperman|@jkup
Cloudflare|@cloudflare

相关帖子

2024年10月31日 13:00

Moving Baselime from AWS to Cloudflare: simpler architecture, improved performance, over 80% lower cloud costs

Post-acquisition, we migrated Baselime from AWS to the Cloudflare Developer Platform and in the process, we improved query times, simplified data ingestion, and now handle far more events, all while cutting costs. Here’s how we built a modern, high-performing observability platform on Cloudflare’s network. ...