Rspack 源码解析 (1) —— 架构总览:从 Node.js 到 Rust 的跨界之旅

Rspack 源码解析 (1) —— 架构总览:从 Node.js 到 Rust 的跨界之旅 写在前面本系列文章旨在通过阅读 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// 简化代码exportfunctionrspack(options:RspackOptions,callback?:Callback):Compiler{// 1. 标准化用户配置constcreateCompiler(userOptions:RspackOptions){constoptionsgetNormalizedRspackOptions(userOptions);// 2. 创建 JS 侧的 Compiler 实例constcompilernewCompiler(options.context,options);// 3. 注册用户配置的插件if(Array.isArray(options.plugins)){for(constpluginofoptions.plugins){plugin.apply(compiler);}}returncompiler;};// ...returncompiler;}这部分非常容易理解和 Webpack 几乎一模一样。第二站JS Compiler 与 惰性初始化Rspack 的一个巧妙设计是Lazy Initialization (惰性初始化)。当你new Compiler()时Rust 核心其实还没启动直到你真正调用.run()或.watch()时。文件packages/rspack/src/Compiler.tsexportclassCompiler{// 持有 Rust 实例的引用#instance?:binding.JsCompiler;constructor(context:string,options:RspackOptionsNormalized){this.hooks{...};// 初始化 Tapable 钩子// 注意构造函数里并没有初始化 Rust 实例}// 私有方法获取或创建 Rust 实例#getInstance(callback){// 1. 加载 Native 绑定constinstanceBindingrequire(rspack/binding);// 2. 调用 Rust 的构造函数this.#instancenewinstanceBinding.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.rsRspack 使用了napi-rs这个库通过#[napi]宏可以轻松地把 Rust 结构体变成 JS 类。// 这里的 #[napi] 宏表示这个结构体会被导出给 JS 使用#[napi(custom_finalize)]structJsCompiler{// 内部持有一个真正的 Rust Compilercompiler:ManuallyDropCompiler,}#[napi]implJsCompiler{// 这个构造函数对应 JS 里的 new instanceBinding.JsCompiler(...)#[napi(constructor)]pubfnnew(env:Env,// NAPI 环境上下文mutoptions:RawOptions,// 从 JS 传来的配置对象// ... 其他参数)-ResultSelf{// 1. 将 JS 的 RawOptions 转换为 Rust 的 CompilerOptionsletcompiler_options:rspack_core::CompilerOptionsoptions.try_into()?;// 2. 创建真正的核心编译器letrspackrspack_core::Compiler::new(compiler_options,// ...);// 3. 返回包装后的 JS 对象Ok(Self{compiler:ManuallyDrop::new(Compiler::from(rspack)),// ...})}// 对应 JS 里的 instance.build()#[napi]pubfnbuild(mutself,reference:ReferenceJsCompiler,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(核心逻辑)pubstructCompiler{puboptions:ArcCompilerOptions,// 编译配置pubcompilation:Compilation,// 编译状态管理pubplugin_driver:SharedPluginDriver,// 插件驱动器publoader_resolver:ArcResolver,// Loader 解析器// ...}implCompiler{pubfnnew(...)-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。