• 作者:老汪软件技巧
  • 发表时间:2024-08-31 10:02
  • 浏览量:

前言

作为一个前端,一直在听说前端工具链都在用rust重构,可我一直很纳闷,Rust语言跟JavaScript语言之间怎么互通呢?

比如说现在比较火的打包工具Rspack,它就是用Rust写的。

先来说说为啥用Rust。

为什么是Rust

众所周知,作为一个前端的打包工具,快一直是个没有解决的问题,试想一下,当你运行一个大型的React项目的时候,一个run start就花了十几分钟甚至更长的时间,你的内心直接了。

而作为Rust,它有以下几个非常优秀的特点:

性能:Rust 是一种系统编程语言,具有与 C/C++ 类似的低级别控制,同时提供了内存安全性和并发性。使用 Rust 开发的工具通常比用 JavaScript 或其他动态语言开发的工具更快。Rspack 通过使用 Rust 可以显著提高打包速度,特别是在处理大规模代码库时,能够减少构建时间。内存安全性:Rust 的内存管理模型可以在编译时避免很多常见的内存管理错误(如空指针、缓冲区溢出等),从而提高工具的稳定性和安全性。这对于开发一个稳定的打包工具非常重要,因为打包工具需要处理复杂的依赖关系和文件系统操作。并发性:Rust 提供了强大的并发编程支持,使得 Rspack 能够更好地利用多核 CPU 的性能。通过在多个线程上并行处理任务,Rspack 可以进一步加快构建过程。生态系统和互操作性:Rust 的生态系统正在快速发展,拥有丰富的库和工具支持。Rust 还可以通过 FFI(外部函数接口)与 C 和其他语言的库进行互操作,进一步增强了工具的扩展性和灵活性。未来发展和维护:Rust 的设计使得代码更容易维护和扩展,尤其是在大型项目中。这有助于确保 Rspack 在未来能够适应更多需求和技术演进。

由此总结来看,通过使用 Rust,Rspack 能够在性能、稳定性和并发性方面优于传统的 JavaScript 打包工具,为开发者提供更高效的开发体验,这也就是为什么它如此之快的原因。

接下来我们再了解一下Rust在Rspack中具体做了哪些事情,毕竟不可能全部用Rust去实现,有的东西还是需要Js自己去做的。

Rust 做了啥

在 Rspack 中,Rust 主要负责核心的构建任务,包括代码解析、依赖分析、模块打包、优化和生成最终的打包文件等。Rspack 利用 Rust 的性能优势来处理前端打包过程中涉及的大量计算和复杂操作。总结有一下几点:

代码解析和依赖分析

Rspack 需要解析不同类型的文件(如 JavaScript、TypeScript、CSS 等),并提取其中的依赖关系。包括两部分:

模块打包

在前端打包过程中,模块打包是核心步骤之一。Rspack 使用 Rust 来处理模块打包的各个方面:

性能优化

Rspack 中的性能优化部分由 Rust 实现,以确保打包过程高效且生成的文件性能良好:

代码生成

Rust 负责将处理后的模块和资源生成最终可以在浏览器中运行的文件,包括:

插件系统

Rspack 可能会使用 Rust 来实现一些核心插件,这些插件扩展了 Rspack 的功能:

错误处理和调试

Rust 在 Rspack 中还负责处理错误和调试信息的输出:

跨平台支持

由于 Rust 的跨平台能力,Rspack 能够在不同操作系统上高效运行,生成适用于各种环境的打包文件。

Rust怎么做的

那既然做了这么多事情,到底怎么做的?开始我们的正题,Rust语言到底是怎么跟JavaScript语言打交道的?

其实,在 Rspack 的实现中,Rust 与 JavaScript 的交互是通过 FFI(Foreign Function Interface,外部函数接口)实现的。FFI 允许不同编程语言之间进行函数调用和数据交换。在 Rust 和 JavaScript 之间,FFI 通常通过 Node.js 的原生模块来实现。Rust 编写的代码会被编译成共享库(如 .dll、.so 或 .dylib 文件),然后通过 Node.js 的原生模块接口调用这些库。

也就是说,我可以用Rust写几个方法,然后编译成不同操作系统(跨平台能力)的共享库,JavaScript就可以直接调用这些方法了。这不是export和import吗?好像就是这么回事。

基本流程编写 Rust 代码:创建 Node.js 原生模块:通过 Node.js 调用 Rust 代码:

下面我们来实操一下。

基本示例

需要提前安装 Rust 运行环境,博主是 Mac 下运行的。

Step 1: 编写 Rust 代码

首先,编写一个简单的 Rust 函数,将其暴露给 JavaScript:

# Cargo.toml
[package]
name = "my_rust_lib"
version = "0.1.0"
edition = "2021"
[dependencies]
# `libc` is a commonly used dependency when interfacing with C or other languages
libc = "0.2"
[lib]
crate-type = ["cdylib"]

// src/lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn hello_from_rust() -> *const c_char {
    let greeting = CString::new("Hello from Rust!").unwrap();
    greeting.into_raw() // 转换为指针返回
}
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
    if s.is_null() { return }
    unsafe {
        CString::from_raw(s); // 释放内存
    }
}

Step 2: 编译 Rust 代码

编译这个 Rust 代码为共享库(动态链接库,类似npm link):

cargo build --release

在 target/release 目录下,你会得到一个编译后的共享库文件。例如,在 macOS 上它可能是 libmy_rust_lib.dylib,在 Linux 上是 libmy_rust_lib.so,在 Windows 上是 my_rust_lib.dll。

具体文件截图如下:

Step 3: 创建 Node.js 原生模块

接下来,在 Node.js 中加载这个共享库并调用 Rust 函数。

安装必要的 npm 包:

npm install ffi-napi ref-napi

编写 Node.js 代码 (index.js):

const ffi = require('ffi-napi');
const ref = require('ref-napi');
// 定义 C 的 `char *` 类型
const charPtr = ref.refType(ref.types.CString);
// 加载 Rust 编译后的共享库
const lib = ffi.Library('./target/release/libmy_rust_lib', {
  'hello_from_rust': [charPtr, []],
  'free_string': ['void', [charPtr]]
});
// 调用 Rust 函数
const messagePtr = lib.hello_from_rust();
const message = ref.readCString(messagePtr, 0);
console.log(message); // 输出 "Hello from Rust!"
// 释放在 Rust 中分配的内存
lib.free_string(messagePtr);

在这个代码中:

Step 4: 运行 Node.js 代码

运行 Node.js 代码:

node index.js

你将看到输出:

Hello from Rust!

总结

在实际发布的过程中,开发者会将编译好的二进制文件与其他 JavaScript 代码一起打包,并发布到 npm 这样的包管理平台。最终的 npm 包中已经包含了这些二进制文件,用户只需要安装该 npm 包即可,无需额外安装 Rust。

当用户通过 npm install rspack 安装 Rspack 时,npm 会下载已经预编译好的二进制文件(或在本地编译),并将其集成到用户的 Node.js 环境中。用户不需要关心底层的 Rust 代码,因为所有的细节都已经封装好。

用户通过 Node.js 来调用 Rspack 的功能,实际上是在调用已经编译好的二进制文件。这些二进制文件以 Node.js 原生模块的形式存在,并且可以直接在 JavaScript 中调用。

讲了这么多,你可能对Rust语言不了解,但是作为一个前端,至少大概要明白我们已经站在了巨人的肩膀上了。


上一条查看详情 +星辰中的维纳斯
下一条 查看详情 +没有了