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

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

相关推荐
牧羊狼的狼5 小时前
React 中的 HOC 和 Hooks
前端·javascript·react.js·hooks·高阶组件·hoc
知识分享小能手7 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
魔云连洲7 小时前
深入解析:Vue与React的异步批处理更新机制
前端·vue.js·react.js
mCell7 小时前
JavaScript 的多线程能力:Worker
前端·javascript·浏览器
超级无敌攻城狮9 小时前
3 分钟学会!波浪文字动画超详细教程,从 0 到 1 实现「思考中 / 加载中」高级效果
前端
excel9 小时前
用 TensorFlow.js Node 实现猫图像识别(教学版逐步分解)
前端
gnip10 小时前
JavaScript事件流
前端·javascript
赵得C10 小时前
【前端技巧】Element Table 列标题如何优雅添加 Tooltip 提示?
前端·elementui·vue·table组件
wow_DG10 小时前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(一):响应式原理
前端·javascript·vue.js
weixin_4569042710 小时前
UserManagement.vue和Profile.vue详细解释
前端·javascript·vue.js