NAPI-RS v3:优化 Rust 与 前端 Node.js 跨平台支持

欢迎关注 猩猩程序员 公众号

NAPI-RS 是一个 Rust 库,它旨在简化和优化 Rust 与 Node.js 的交互。具体来说,它通过提供 Rust 对 Node.js 原生接口(Node-API)的封装,使得 Rust 可以作为 Node.js 插件(native addon)进行开发。

本文来自 napi.rs/blog/announ...

自 NAPI-RS V2 发布已过去 4 年。期间,NAPI-RS 社区快速发展。我们在社区中发现了许多 API 设计的问题。例如,ThreadsafeFunction 一直难以使用,主要原因是 Node-API 的 ThreadsafeFunction 设计过于复杂,导致 Rust 封装暴露了太多底层的复杂性。然而,通过与 Rolldown 和 Rspack 团队的合作,我们最终找到了一个既能平衡 API 复杂性又能确保正确性的设计。

WebAssembly 是这次的最大更新。在 V3 中,您几乎不需要修改代码即可将项目编译为 WebAssembly。如果编译目标是 wasm32-wasip1-threads 或更高版本,您可以直接在浏览器中运行使用 Rust 特性如 std::threadtokio 的代码,而无需做任何额外修改。

跨编译也是一个大更新。在之前的版本中,您需要使用 nodejs-rust:lts-debiannodejs-rust:lts-debian-aarch64 的 Docker 镜像来构建项目。这些镜像非常大,导致 CI 构建时间变慢,而且很难与社区同步工具和基础设施。

现在让我们深入了解 V3 的新功能。

WebAssembly

支持 WebAssembly 对 NAPI-RS 社区来说意义重大。有几个场景只有 WebAssembly 能处理:

  • 提供浏览器中的游乐场和可重现的环境,比如 Rolldown repl 和 Oxc playground。
  • 为没有预构建二进制文件的平台提供后备包。对于一些项目,很难为所有可能的平台维护预构建二进制文件。
  • 使项目可以在 StackBlitz 中使用。

为什么不使用 wasm-bindgen?

其中一个主要原因是,您不需要为同一个项目编写两种不同的绑定。

例如,Oxc 项目之前维护了一个 wasm-bindgen 绑定。但是随着项目规模的扩大,API 渐渐增加,维护成本也越来越高。通常需要将相同的逻辑从 Node.js 绑定移植到 wasm-bindgen 绑定。

此外,使用 wasm-bindgen 存在许多限制,比如无法使用 std::thread 和依赖于 std::thread 的第三方库。例如,您可能需要编写如下代码:

rust 复制代码
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;

...

#[cfg(not(target_arch = "wasm32"))]
const hash = entries.par_iter().map(|chunk| chunk.compute_hash()).collect::<Vec<_>>();

#[cfg(target_arch = "wasm32")]
const hash = entries.iter().map(|chunk| chunk.compute_hash()).collect::<Vec<_>>();

使用 NAPI-RS,您可以直接编译这些代码并运行,无需麻烦。

另一个痛点是,如果您正在使用 C 或 C++ 编写的 crate,设置 wasm-bindgen 构建过程非常复杂。请参见:

使用 NAPI-RS 的 WebAssembly 示例应用

这是一个使用 NAPI-RS WebAssembly 的示例应用。您可以将图像转换为 webp、jpeg 或 avif 格式,并指定不同的质量。

webp 特性来自 libwebp-sys crate,它在底层使用了 libwebplibwebp 是一个 C 库,但您可以在 NAPI-RS 项目中自由使用它,并将其构建为 WebAssembly,无需做额外修改。

avif 特性来自 libavif,它是一个 C/C++ 混合库。您也可以在 NAPI-RS 项目中自由使用它。

API 改进

API 设计有很多改进,既提升了可用性也加强了安全性。

生命周期

生命周期被引入到 NAPI-RS V3 中,详细信息请参见 Understanding Lifetime.

在 V2 中,由于代码生成和 API 设计的复杂性,我们没有时间将生命周期添加到 API 中,导致一些问题。

例如:

scss 复制代码
#[napi]
pub fn promise_finally_callback(mut promise: PromiseRaw<()>, config: Object) -> Result<()> {
  // ❌ 编译错误
  // 借用的数据逃逸到函数外部
  // `config` 在这里逃逸了
  promise.finally(|env| {
    let on_finally = config.get_named_property::<Function<(), ()>>("on_finally")?;
    Ok(())
  })?;
  Ok(())
}

在 V3 中,生命周期的引入解决了这个问题,您可以通过 Rust 的生命周期来限制此类行为。

ThreadsafeFunction

ThreadsafeFunction 在 V3 中进行了重新设计。在以前的版本中,ThreadsafeFunction 的 API 太底层,根本不安全。在新 API 中,我们隐藏了 Node-API 中的 ref、unref、acquire、release 等概念,通过所有权封装了这些 API,并禁止使用底层引用计数模型进行生命周期管理。

如果您想将 ThreadsafeFunction 传递给不同的线程,我们现在允许使用 std::sync::Arc 来实现这一点。

跨编译

跨编译对 Rust 社区来说一直很痛苦。虽然有 rust-cross 项目,但它使用起来不方便:

  • GNU Linux 发行版的 GLIBC 版本太新,比如 arm-unknown-linux-gnueabihf 只支持 GLIBC 2.31。
  • 配置为 GLIBC 2.17(许多企业需要支持的版本)非常复杂。
  • 在 QEMU 和 Docker 上运行很慢,且有局限性。

NAPI-RS V3 引入了新的跨编译功能,您可以使用 napi build --use-napi-cross 标志进行编译,支持以下目标和主机:

Target/Host x86_64 arm64
x86_64-unknown-linux-gnu
aarch64-unknown-linux-gnu
armv7-unknown-linux-gnueabihf
powerpc64le-unknown-linux-gnu
s390x-unknown-linux-gnu

该工具链的源代码可以在 napi-rs/cross-toolchain 上找到。

新的 pnpm 包模板

我们已经支持 yarn 作为 package-template 项目的包管理器,自 V2 起,yarn 的 supportedArchitectures 特性非常适合跨平台编译和测试,且对 Docker 支持良好。

由于 pnpm 的流行,我们在 V3 中也支持了 package-template-pnpm

结语

NAPI-RS 将继续改进开发体验,并且需要更多的时间和精力来维护该项目。请考虑赞助该项目,它将帮助我们改进项目并使其更好。

欢迎关注 猩猩程序员 公众号

相关推荐
红辣椒...4 分钟前
codex+第三方模型
java·服务器·前端
木子雨廷6 分钟前
Flutter 使用 flutter_flavorizr 多渠道打包
前端·flutter
环境工程笔记8 分钟前
浏览器自动化跑成功了,为什么结果还是不对?
前端
东风破_10 分钟前
一文搞懂 JavaScript 变量声明:var、let、const 到底有什么区别?
前端·javascript
问心无愧051313 分钟前
ctf show web入门261
android·前端·笔记
触底反弹15 分钟前
你真的理解 JavaScript 变量提升(Hoisting)吗?从 V8 引擎编译原理深入剖析
前端·面试
蜡台27 分钟前
Vue2 使用 typescript 教程
前端·vue.js·typescript
光影少年40 分钟前
Redux Toolkit 用法、解决原生Redux 冗余问题
开发语言·前端·javascript·react.js·中间件·前端框架·ecmascript
云水一下1 小时前
JavaScript 从零基础到精通系列:DOM 操作与事件驱动编程
前端·javascript