宣布 Jco 1.0

内容

我们很高兴地宣布Jco 1.0 版本的发布:这是一个专为 WebAssembly 组件和WASI 0.2构建的本地 JavaScript WebAssembly 工具链和运行时1。Jco 可以在 Node.js 中本地运行 Wasm 组件,轻松地使用 Node.js 运行时执行用不同编程语言编写的库。通过实现整个 WASI 0.2 API 表面,这些组件可以访问网络、文件系统和 Node.js 运行时中可用的其他系统 API。

我们的目标是让 Jco 成为 JavaScript 所有与组件相关操作的综合工具。通过 Jco 1.0,我们正在稳定一个用于 Wasm 组件的 Node.js 运行时,以及一个工具链,用于将其他语言编写的 Wasm 组件导入 JavaScript。从现在开始,我们希望继续为 Jco 稳定更多功能。一些功能已经可以作为实验性功能使用;这包括对浏览器的原生支持,以及将 JavaScript 代码编译为 WebAssembly 的原生支持。其他功能,比如对 WebAssembly 注册表的支持,尚未启动,但我们预计稍后会添加。

Jco 是 Bytecode Alliance 的第三个 JS 工具链项目。为了帮助人们更好地了解,以下是三个项目的简要概述以及它们在撰写时的目的:

  • Javy:使用QuickJS JavaScript引擎构建的JavaScript → WASI 0.1编译器工具链。这项工作早于Wasm组件,但未来可能会添加支持。
  • ComponentizeJS:使用SpiderMonkey JavaScript引擎构建的JavaScript → Wasm组件工具链,完全支持WASI 0.2。这个项目较新,尚未被视为稳定。
  • Jco:用于JavaScript的全面WebAssembly组件工具链和运行时环境。这包括一个Wasm组件 → JS工具链,完全支持WASI 0.2。

示例

为了让你体验使用 Jco 的感觉,让我们编译一个小的 Rust 程序为 WASI 0.2.0 组件,安装 Jco 用于 Node.js,然后将新构建的组件嵌入到运行时中。如果你想直接查看最终结果,可以在这里查看

依赖和存储库

因为我们将编写 Rust 代码,并从 Node.js 中使用它,所以我们希望您已经安装了一个正常工作的 Node.js 环境Rust 工具链。一旦您安装好了这些,我们就可以安装 cargo-component 来构建我们的 Rust 代码,并安装 Jco 来为 Node.js 构建它。

$ npm install -g @bytecodealliance/jco        # 安装 jco cli 工具 
$ npm install @bytecodealliance/preview2-shim # 将 jco WASI 运行时作为本地依赖项安装 
$ cargo install cargo-component               # 安装 `cargo component` 子命令

我们随后可以继续创建一个新的项目存储库,其中包含 Rust 和 JS 代码:

这应该让你得到一个大致如下的结构:

src/lib.rs # 我们的 Rust 库入口点 wit/world.wit # 包含我们的 WIT IDL 定义 Cargo.toml # 配置我们的 Rust 项目 index.js # 我们的 JS 二进制入口点 package.json # 配置我们的 JS 项目

现在,一切准备就绪,我们可以开始编写一些代码了!

定义 WIT 接口

这个项目有三个步骤:定义 WIT 接口、编写要导出的 Rust 代码以及编写要导入的 JS 代码。我们将从 WIT 接口开始。对于那些对 Wasm 组件不熟悉的人:WIT 是一种接口描述语言(IDL),允许您以声明性、与语言无关的方式描述编程接口。让我们定义一个接口,它接受一个普通字符串,并将其转换为 大声喊叫的情况!!1!

组织 my-org:wit-playground

world playground { export scream: func(input: string) -> string; }

我们可以将此保存到生成的 wit/world.wit 文件 2,现在我们已经定义了一个名为 scream 的函数,该函数接受一个字符串,并返回一个字符串。如果这是您第一次编辑 WIT 文件并且您正在使用 VS Code,请考虑安装 WIT IDL 包,以获得对 WIT 文件的编辑器支持。有了这个,现在是时候编写我们的 Rust 代码了!

编写 Rust 库

正如前面提到的,我们在这个示例中使用cargo-component工具来将我们的Rust代码编译为Wasm组件。它还没有达到1.0版本,但已经足够满足我们的需求。因此,我们将向您展示如何在今天使用它。如果您来自未来(例如不是2024年2月),请查看cargo-component文档,以查看这些说明是否已更改,如果是-请遵循新的说明。我们将采取的步骤是:定义WIT API定义,生成与之对应的Rust绑定(例如Rust特性),然后实现这些特性以实现这些定义。即使将来执行此操作的确切方式可能会发生变化,但在概念上仍将有类似的方法可供使用。

首先要做的事情是:我们希望 cargo component 为我们的 WIT 文件生成绑定。这是通过 Rust 特性完成的,当我们编译时它会自动执行。这将产生一个编译器错误,但没关系 - 我们将在下一步更新代码:

这将生成 Rust 绑定,当前版本的 cargo-component 将其放在 lib/bindings.rs 下。现在不用担心阅读那些代码 - 它做了很多繁重的工作,所以我们不必自己做。重要的是它为我们提供了可以实现的特性。让我们在我们的代码中实现这些特性,并编写一个小写字母转大写的函数

mod bindings; // 教会我们的程序有关 src/bindings.rs 的内容

use bindings::Guest; // 导入我们的 WIT 世界的 trait repr use wit_bindgen::rt::string::String as WitString; // 导入 WIT 字符串类型

struct Component; // 定义要导出的类型 impl Guest for Component { // 导入 WIT 世界 + 方法 fn scream(input: WitString) -> WitString { // 实现我们的 scream 方法 let mut s = input.to_uppercase(); s.push_str("!!1!"); s.into() } }

现在,如果我们重新运行 cargo component check,它应该可以编译而不会出现任何警告。这意味着我们已经准备好运行:

这将会将生成的 Wasm 代码写入target/wasm32-wasi/debug/playground_jco.wasm。一旦 Rust 支持本机 WASI 0.2 目标,预计这将更改为target/wasm32-wasi-p2/debug/playground_jco.wasm或类似的形式,因为当前的一系列变通方法将不再需要(如果您在 2024 年 3 月之后阅读此内容,则此信息很重要)。

编写 JavaScript 二进制

好的,现在是时候使用 Jco 了。首先,我们要拿出 Rust 代码,并从中生成 JS 绑定。我们可以使用 jco transpile command 来实现这一点。让我们将其指向我们新生成的 Wasm 库,就像这样:

$ jco transpile \                                   # 调用 `jco transpile`
    target/wasm32-wasi/debug/playground_jco.wasm \  # 指向我们的 Rust 库
    -o target/jco                                   # 将结果写入 `target/jco`'

成功时,它会告诉您它已经生成了一些JS文件作为其CLI输出的一部分:

转译后的JS组件文件:
  • 目标/jco/interfaces/wasi-cli-environment.d.ts 0.09 KiB
  • 目标/jco/interfaces/wasi-cli-exit.d.ts 0.16 KiB
  • 目标/jco/interfaces/wasi-cli-stderr.d.ts 0.17 KiB
  • 目标/jco/interfaces/wasi-cli-stdin.d.ts 0.17 KiB
  • 目标/jco/interfaces/wasi-cli-stdout.d.ts 0.17 KiB
  • 目标/jco/interfaces/wasi-clocks-wall-clock.d.ts 0.11 KiB
  • 目标/jco/interfaces/wasi-filesystem-preopens.d.ts 0.2 KiB
  • 目标/jco/interfaces/wasi-filesystem-types.d.ts 2.71 KiB
  • 目标/jco/interfaces/wasi-io-error.d.ts 0.08 KiB
  • 目标/jco/interfaces/wasi-io-streams.d.ts 0.58 KiB
  • 目标/jco/playground_jco.core.wasm 1.83 MiB
  • 目标/jco/playground_jco.core2.wasm 16.5 KiB
  • 目标/jco/playground_jco.d.ts 0.72 KiB
  • 目标/jco/playground_jco.js 40 KiB

下一步是将其导入到我们的Node.js文件中。为了做到这一点,我们首先必须更新我们的package.json,声明我们正在编写一个ES模块。在我们的package.json中,将字段"type"设置为"module"

{
  "name": "playground-jco",
  "version": "1.0.0",
  "type": "module",          // ← 添加此行
  // ...
}'

最后,我们准备编写一些JS代码。我们将从我们的Rust库中导入scream函数,将一个字符串传递给它,然后打印字符串的输出。

让 msg = scream('chashu would like some tuna'); console.log(msg);

现在,如果我们使用 node index.js 运行这个命令,我们应该会得到以下输出:

CHASHU 想要一些金枪鱼!!1!

通过这样,我们成功地为Node.js编写了我们的第一个Rust组件。你可以为自己鼓掌一下,因为这真的是_非常酷_!通过利用WIT和Wasm,我们能够定义一个与语言无关的接口,从而为两种编程语言提供强类型代码。通过利用代码生成,所有繁琐的FFI和数据编组部分都已经为我们处理好了,使我们能够专注于真正重要的事情(尖叫我们想要金枪鱼)。

在这个例子中,我们碰巧使用了JavaScript和Rust来演示Jco运行时。但重要的是要理解的是,这同样可以使用JavaScript和Go。或者Rust和Python。Wasm组件和WASI的要点在于,我们可以通过强类型ABI将任意数量的语言连接在一起。

我们故意保持事情相当简单:一个接受字符串并返回字符串的方法。然而,最新版本的WASI(0.2)提供了访问异步套接字、定时器和http等功能。使用我们在本文中展示的相同广泛技术,例如,可以编写网络应用程序,使用不同的语言,所有这些语言都通过强类型的WIT接口相互链接。

结论

在这篇文章中,我们介绍了 Jco 1.0.0:一个原生 JavaScript 运行时,可以运行 WASI 0.2 组件。我们讨论了已经稳定的内容,目前正在进行的功能,以及未来的发展方向。最后,我们展示了如何使用 Jco 将 Rust 库嵌入到 Node.js 项目中的示例。

我们很高兴地报告,野外的几个项目已经成功地使用 Jco 构建了他们的项目。其中一个特别令人印象深刻的项目是由无与伦比的Catherine“whitequark”完成的,她已经将 YoWASP FPGA 工具链移植到浏览器并使用 Jco。这个项目使用户能够通过 WebUSB 直接从他们的浏览器刷写 FPGA 硬件,甚至可以在移动设备上运行。

Jco 是 Bytecode Alliance 项目,由 Guy Bedford(Fastly)设计和领导。Jco 1.0 版本的发布得益于 Pat Hickey(Fastly)、Wassim Chegham(Microsoft)、Dirk Bäumer(Microsoft)和 Yosh Wuyts(Microsoft)的协助。代表 Bytecode Alliance,我们要感谢所有参与 Jco 构思、设计和开发的人员。我们对人们能够开始在他们的项目中使用它感到非常兴奋!

总结
Jco是一个本地JavaScript WebAssembly工具链和运行时,专为WebAssembly组件和WASI 0.2构建。它可以在Node.js中本地运行Wasm组件,使得可以轻松地使用不同编程语言编写的库并在Node.js运行时中执行。通过实现完整的WASI 0.2 API表面,这些组件可以访问Node.js运行时中可用的网络、文件系统和其他系统API。Jco旨在成为JavaScript所有组件相关操作的综合工具。Jco 1.0稳定了Wasm组件的Node.js运行时,并提供了一个工具链,用于将其他语言编写的Wasm组件导入JavaScript。未来,Jco希望继续稳定更多功能,如对浏览器的本地支持和将JavaScript代码编译为WebAssembly。Jco是Bytecode Alliance的第三个JS工具链项目,旨在为JavaScript提供全面的WebAssembly组件工具链和运行时。