写在前面:本系列文章旨在通过阅读 Rspack 源码,学习rust相关使用场景,了解Rust生态中比较优秀的项目是如何管理Rust代码的,也为自己之后学习并应用Rust指明方向,也愿您能有所得。
Rspack源码结构概览
Rspack的源码是一个标准的 Monorepo(单体仓库-将多个相关项目、模块的源码都放在同一个代码仓库中统一管理,而不是每个项目一个独立仓库),Rspack的源码目录下有:
crates:所有Rust子模块(核心、插件、绑定层等等)packages:所有的JS/TS子包(API、CLI等)tests:自测代码examples:相关示例website:相关文档等scripts:相关构建脚本
整个项目的相对核心的目录我们已经列出来了,当然还会有一些相关配置文件没有一一列举,在后面的源码解析的整个流程中,我们会慢慢说明。
宏观架构:三层世界 Node + NAPI + Rust
基于我们上面的目录结构,可以看出来 Rspack 的整个架构分为三层,分别是:Node.js层、Binding层(Node-API)、Rust Core层
Node.js层:用户接口与生态相融
-
职责
- 负责与用户交互(配置、插件、Loader、Cli)
- 保持与Webpack生态的兼容性
- 提供JS/TS API和命令行工具
-
代表目录/文件
rspack核心 JS SDK,也就是我们安装的@rspack/corerspack-cli命令行工具,处理rspack build命令rspack.config.js用户配置
Binding层:NAPI跨语言桥梁
-
职责
- 通过 napi-rs 将 Rust 能力暴露给 Node.js
- 负责类型转换、内存管理、回调注册
- 让JS插件、Loader能与Rust编译器协作
-
代表目录/文件
rspack_binding_api胶水层,定义了 Rust 如何暴露给 Node.js。struct jsCompiler 、 #[napi]宏
Rust Core层:高性能编译引擎
-
职责
- 实现所有核心编译流程(模块解析、依赖图、代码生成、优化、产物输出)
- 插件系统、Loader 调度、缓存、HMR、增量构建等
- 充分利用 Rust 的并发和类型安全
-
代表目录/文件
rspack_core核心编译器,实现了 Compiler, Compilation, Plugin System 等。crates/rspack_plugin_*内置插件crates/rspack_loader_*内置Loader
源码追踪:一次构建的完整旅程
让我们随着代码的执行顺序,看看 Rspack 是如何启动的。
第一站:用户入口 (Node.js)
当你运行 rspack 时,代码最终会进入 @rspack/core 的入口。
文件:packages/rspack/src/rspack.ts
// 简化代码
export function rspack(options: RspackOptions, callback?: Callback): Compiler {
// 1. 标准化用户配置
const createCompiler = (userOptions: RspackOptions) => {
const options = getNormalizedRspackOptions(userOptions);
// 2. 创建 JS 侧的 Compiler 实例
const compiler = new Compiler(options.context, options);
// 3. 注册用户配置的插件
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
}
return compiler;
};
// ...
return compiler;
}
这部分非常容易理解,和 Webpack 几乎一模一样。
第二站:JS Compiler 与 惰性初始化
Rspack 的一个巧妙设计是 Lazy Initialization (惰性初始化)。当你 new Compiler() 时,Rust 核心其实还没启动,直到你真正调用 .run() 或 .watch() 时。
文件:packages/rspack/src/Compiler.ts
export class Compiler {
// 持有 Rust 实例的引用
#instance?: binding.JsCompiler;
constructor(context: string, options: RspackOptionsNormalized) {
this.hooks = { ... }; // 初始化 Tapable 钩子
// 注意:构造函数里并没有初始化 Rust 实例
}
// 私有方法:获取或创建 Rust 实例
#getInstance(callback) {
// 1. 加载 Native 绑定
const instanceBinding = require('@rspack/binding');
// 2. 调用 Rust 的构造函数
this.#instance = new instanceBinding.JsCompiler(
this.compilerPath,
rawOptions, // 传入处理好的配置
this.#builtinPlugins, // 传入内置插件
this.#registers, // 传入 JS 回调函数的注册表(用于跨语言 Hook)
// ... 传入文件系统
);
}
run(callback) {
// 真正编译时,才初始化 Rust 实例
this.#getInstance((err, instance) => {
instance.build(callback); // 调用 Rust 的 build
});
}
}
初学者提示:这里 require('@rspack/binding') 加载的是一个 .node 文件(二进制动态链接库),它是由 Rust 编译出来的。
第三站:穿越 NAPI 桥梁 (The Bridge)
现在我们进入了 crates/rspack_binding_api。这是连接 JS 和 Rust 的桥梁。
文件:crates/rspack_binding_api/src/lib.rs
Rspack 使用了 napi-rs 这个库,通过 #[napi] 宏,可以轻松地把 Rust 结构体变成 JS 类。
// 这里的 #[napi] 宏表示这个结构体会被导出给 JS 使用
#[napi(custom_finalize)]
struct JsCompiler {
// 内部持有一个真正的 Rust Compiler
compiler: ManuallyDrop<Compiler>,
}
#[napi]
impl JsCompiler {
// 这个构造函数对应 JS 里的 new instanceBinding.JsCompiler(...)
#[napi(constructor)]
pub fn new(
env: Env, // NAPI 环境上下文
mut options: RawOptions, // 从 JS 传来的配置对象
// ... 其他参数
) -> Result<Self> {
// 1. 将 JS 的 RawOptions 转换为 Rust 的 CompilerOptions
let compiler_options: rspack_core::CompilerOptions = options.try_into()?;
// 2. 创建真正的核心编译器
let rspack = rspack_core::Compiler::new(
compiler_options,
// ...
);
// 3. 返回包装后的 JS 对象
Ok(Self {
compiler: ManuallyDrop::new(Compiler::from(rspack)),
// ...
})
}
// 对应 JS 里的 instance.build()
#[napi]
pub fn build(&mut self, reference: Reference<JsCompiler>, f: Function) -> Result<()> {
// 在 Rust 的异步运行时中执行构建
self.run(...)
}
}
初学者提示:
struct类似于面向对象里的 class 属性定义。impl类似于 class 的方法定义。#[napi]是“魔法”,自动生成胶水代码,让 JS 能调用这些 Rust 代码。
第四站:核心引擎 (Rust Core)
最后,我们来到了真正干活的地方:crates/rspack_core。
文件:crates/rspack_core/src/compiler/mod.rs (核心逻辑)
pub struct Compiler {
pub options: Arc<CompilerOptions>, // 编译配置
pub compilation: Compilation, // 编译状态管理
pub plugin_driver: SharedPluginDriver, // 插件驱动器
pub loader_resolver: Arc<Resolver>, // Loader 解析器
// ...
}
impl Compiler {
pub fn new(...) -> Self {
// 初始化各种核心组件
}
}
在 Rust 侧,Compiler 是一个长期存在的对象(单例模式),它负责创建 Compilation。每次构建(Build)都会产生一个新的 Compilation,它包含了模块图(Module Graph)和 Chunk 图。
总结
通过第一篇的架构概览,我们理清了 Rspack 的启动流程:
- 用户在 CLI 或脚本中调用
rspack()。 - JS 层 (
packages/rspack) 处理配置,初始化Compiler.ts。 - Binding 层 (
crates/rspack_binding_api) 利用 NAPI 接收配置,创建 Rust 实例。 - Core 层 (
crates/rspack_core) 启动,随时准备进行编译。
给 Rust 初学者的建议:
在阅读 Rspack 源码时,不必纠结于通过 Arc, Mutex, RwLock 这种复杂的并发控制细节(虽然它们在 Rspack 中无处不在)。先关注 struct 的数据结构设计和 impl 的方法流程,把 Rust 当作带类型的 Python 或 C++ 来看,会更容易上手。
下一篇预告: 我们将深入 Compilation(编译过程),看看 Rspack 是如何从一个入口文件开始,构建出整个项目的依赖图谱的(Make Phase)。