Rust bindings for NodeJs
在前端的工程领域,Rust 语言编写的工具大放异彩,为前端开发体验带来了质的飞跃
- swc-project/swc,babel 的取代者
- web-infra-dev/rspack,webpack 的替代者
- oxc-project/oxc,eslint 领域的新星
- rolldown/rolldown,rollup 和 esbuild 的统一增强
下面,我将解释为什么我们能在 js 中调用 rust 语言编写的程序
.node 模块
在 NodeJs 中,除了可以原生加载 js 模块,还可以加载 node 原生模块,这种模块以 .node 扩展名结尾
在 c++ to node 一段中,阐述了如何通过 node-gyp,将 C++ 代码编译为 node 模块,以便于在 NodeJs 中使用
简而言之,在安装了 NodeJs 之后,我们可以在 C++ 代码中,直接引入 NodeJs 提供的头文件 #include <node.h>,以此来编写相关的代码
#include <node.h>
namespace demo {
//...
}经过构建后,我们就可以得到 node 模块,在 js 中使用:
import addon from './build/Release/binding.node'
console.log(
addon.hello() //=> world
)C/C++ 代码被允许访问、创建和操作 JavaScript 对象,就像它们是由 JavaScript 代码创建的一样。C++ 通常比 Js 执行速度更快,所以性能是 node 模块的天然优势之一
NAPI
Node-API 是 Node 8.0.0 中引入的工具包,以便开发人员可以编写一次性的 C++ 代码,在不同版本的 NodeJs 和平台上运行
#include <node.h>
#include <napi.h>因此,当我们想用 C++ 来构建一个 node 模块,首选 NAPI
napi-rs
所谓 napi-rs,顾名思义,就是用 rust 语言编写的 node-api 的实现,参考 napi-rs/napi-rs。这个项目,使得我们可以快速使用 rust 语言分发 node 模块,提供给 js 使用
我们知道,NodeJs 帮我们抹去了平台之间差异,比如 arm64 和 x86。使得我们可以一套 JS 代码,同时跑在 MacOS、Windows 和 Linux 上
而 node 模块,作为一个二进制模块,不具有跨平台的特性。
假设我们在 MacOS 平台上构建原生模块,得到了 node 模块。当把源码拷贝到 Linux 上的时候,很可能程序就不能运行了,因为这个 node 模块是在另一个平台架构下构建出来了
因此,为了适配不同的系统架构,我们通常需要在不同的机器下构建,得到适配不同平台的 node 模块。当我们的 js 代码运行的时候,根据当前的平台去加载不同的 node 模块
在 .node 模块和 .js 模块直接的“桥梁” js 文件通常长这样:
let nativeBinding = null
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'utils-rs.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./utils-rs.android-arm64.node')
} else {
nativeBinding = require('@peterroe/utils-rs-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
nativeBinding = require('./utils-rs-android-arm-eabi.node')
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
case 'win32':
switch (arch) {
case 'x64':
case 'ia32':
case 'arm64':
case 'darwin':
switch (arch) {
case 'x64':
case 'arm64':
case 'freebsd':
case 'linux':
case 'arm64':
case 'arm':
case 'riscv64':
case 's390x':
const { sum } = nativeBinding
module.exports.sum = sum因此,当我们想发布一个平台兼容性很好的 rust 编写的工具,需要发大量的 NPM 模块,每个 NPM 模块包含特定平台的编译出来的 .node 模块。例如 rspack: