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

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

相关推荐
Mintopia10 分钟前
Next.js 全栈开发基础:在 pages/api/*.ts 中创建接口的艺术
前端·javascript·next.js
小妖66613 分钟前
react-router 怎么设置 basepath 设置网站基础路径
前端·react.js·前端框架
xvmingjiang19 分钟前
Element Plus 中 el-input 限制为数值输入的方法
前端·javascript·vue.js
XboxYan36 分钟前
借助CSS实现自适应屏幕边缘的tooltip
前端·css
极客小俊37 分钟前
iconfont 阿里巴巴免费矢量图标库超级好用!
前端
小杨 想拼44 分钟前
使用js完成抽奖项目 效果和内容自定义,可以模仿游戏抽奖页面
前端·游戏
yvvvy1 小时前
🐙 Git 从入门到面试能吹的那些事
前端·trae
EmmaGuo20151 小时前
flutter3.7.12版本设置TextField的contextMenuBuilder的文字颜色
前端·flutter
pepedd8642 小时前
全面解析this-理解this指向的原理
前端·javascript·trae
渔夫正在掘金2 小时前
神奇魔法类:使用 createMagicClass 增强你的 JavaScript/Typescript 类
前端·javascript