NAPI-RS v3 发布:WebAssembly 支持、跨编译改进与 API 设计优化

本文首发公众号 猩猩程序员 欢迎关注

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

自 NAPI-RS V2 发布已有四年。在这段时间里,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 项目中使用它。

原始图像:

  • 原始大小:13.3 MB

WebP 转换:

  • 质量:75
  • 转换后的 WebP 图像大小:0 B

API 改进

此次更新在 API 设计上做了许多改进,增强了可用性和安全性。

生命周期

NAPI-RS V3 引入了生命周期(lifetime)的概念,详情请参见 理解生命周期

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

一些类型存在安全问题,例如之前的 JsObject 类型,它可能在 #[napi] fn 调用之外被使用,实际其底层的 napi_value 已经变得无效。现在,我们可以通过 Rust 的生命周期来约束这种行为。

例如:

rust 复制代码
use napi::bindgen_prelude::*;
use napi_derive::napi;
 
#[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 中,提供了相关的参考 API,更多信息请参见 JavaScript 值引用

ThreadsafeFunction

ThreadsafeFunction 在 V3 中重新设计。在之前的版本中,ThreadsafeFunction 的 API 过于底层,且不安全。在新的 API 中,我们隐藏了 Node-API 中的 refunrefacquirerelease 等概念,使用所有权来封装这些 API,并禁止使用底层的引用计数模型进行生命周期管理。

如果您需要将 ThreadsafeFunction 传递到不同的线程,现在我们允许使用 std::sync::Arc 来实现。

rust 复制代码
use std::sync::Arc;

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn pass_threadsafe_function(tsf: Arc<ThreadsafeFunction<u32, u32>>) -> Result<()> {
  for i in 0..100 {
    let tsf = tsf.clone();
    std::thread::spawn(move || {
      tsf.call(Ok(i), ThreadsafeFunctionCallMode::NonBlocking);
    });
  }
  Ok(())
}

TypeScript 类型生成也有所改进。在 V2 中,您只能为 ThreadsafeFunction 生成 (...args: any[]) => any 类型,但在 V3 中,由于在泛型中定义了 FnArgsReturn 类型,您现在可以为 ThreadsafeFunction 生成正确的类型。

跨编译

跨编译一直是 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

所有这些目标都支持 GLIBC 2.17。

这是跨工具链的 GitHub 仓库。我们基本上从 manylinux-cross 项目中提取了必要的工具和文件,然后上传到 npm 注册表中。@napi-rs/cli 将选择正确的工具链并将环境变量注入构建过程中。

我们还将 cargo-zigbuildcargo-xwin 集成到 @napi-rs/cli 中,因此您可以在一台机器上构建多个不同的目标。详情请见 跨编译

全新的 pnpm 包模板

自 V2 起,我们已支持在 package-template 项目中使用 yarn 作为包管理器,因为 yarn 的 supportedArchitectures 特性非常适合跨平台编译和测试,并且有很好的 Docker 支持。

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

为什么没有 npm 包模板?

因为存在以下问题:

BUG\] 在重新安装 时,平台特定的可选依赖未包含在 `package-lock.json` 中 @JustinChristensen 详细信息见 npm/issues。 ### NAPI-RS API 您现在可以轻松地将 NAPI-RS 工具集成到您的 JavaScript 基础设施中: ```php // 程序化调用 import { NapiCli } from '@napi-rs/cli' const cli = new NapiCli() const { task, abort } = await cli.build({ release: true, features: ['allocator-api'], esm: true, platform: true, }) const outputs = await task ``` 所有 NAPI 命令都有相应的 API,您可以访问 [cli 文档](https://link.juejin.cn?target=https%3A%2F%2Fchatgpt.com%2Fc%2F68801f74-52ac-8002-b9e7-d092a1f97e9c%23 "https://chatgpt.com/c/68801f74-52ac-8002-b9e7-d092a1f97e9c#") 了解更多信息。 ### 社区发展迅速! 当 V2 发布时,只有 Next.js、Parcel 和 SWC 在使用 NAPI-RS。 如今,NAPI-RS 已广泛应用于各种类型应用程序的开发中。 本文首发公众号 **猩猩程序员** 欢迎关注

相关推荐
啃火龙果的兔子1 小时前
修改 Lucide-React 图标样式的方法
前端·react.js·前端框架
前端 贾公子1 小时前
为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?
前端·javascript·vue.js
潘多拉的面1 小时前
Vue的ubus emit/on使用
前端·javascript·vue.js
遗憾随她而去.1 小时前
js面试题 高频(1-11题)
开发语言·前端·javascript
hqxstudying4 小时前
J2EE模式---前端控制器模式
java·前端·设计模式·java-ee·状态模式·代码规范·前端控制器模式
开开心心就好5 小时前
Excel数据合并工具:零门槛快速整理
运维·服务器·前端·智能手机·pdf·bash·excel
im_AMBER6 小时前
Web开发 05
前端·javascript·react.js
Au_ust6 小时前
HTML整理
前端·javascript·html
安心不心安6 小时前
npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
前端·npm·node.js
迷曳8 小时前
28、鸿蒙Harmony Next开发:不依赖UI组件的全局气泡提示 (openPopup)和不依赖UI组件的全局菜单 (openMenu)、Toast
前端·ui·harmonyos·鸿蒙