背景
webpack 太慢了!!!
如果你做过中大型前端项目,一定体会过那种痛苦:项目启动要等好几分钟,改一行代码,还得等几秒才能看到热更新结果。
我曾经曾负责过一个中大型项目,包含几千个模块,结果就是开发环境冷启动常常耗时几分钟,热更新也要好几秒,那种等待的过程,简直让人痛苦。
于是我开始想方设法去优化 webpack 的编译性能。我尝试了各种社区方案:增量编译、缓存优化、多进程打包...但效果都不理想。一开始以为是自己对 webpack 原理理解不够深入,于是花了大量时间啃源码,把整整 571 个 JavaScript 文件、接近一万行代码看完之后,问题依然没有得到解决。
后来,我把目光投向了一个新方向:Rspack。它的官网介绍非常直白:
基于 Rust 的高性能 Web 打包工具:使用兼容 API 无缝替换 webpack。
我抱着试试看的心态接入了一下,结果令人惊喜,同样的项目(我的项目是基于 React + UmiJS + webpack),webpack 编译耗时 2 分钟左右,而 Rspack 只需 40~50 秒,启动速度提升了 2~3 倍。
看到这里你肯定会好奇:
- Rspack 为什么能实现数倍的编译效率提升?
- 仅仅因为用了 Rust,性能就能这么高吗?
- 它又是如何兼容 webpack API,从而做到无缝替换的?
要理解这些问题,我们不能只停留在听别人说快的层面,而要透过现象看本质。接下来,我会带你拆解 Rspack 的核心构建流程,并与 webpack 做对比。我不会展示太多源码细节(那样会增加你的理解负担),而是结合流程图,帮你从整体上理清它的运行机制。
看完本篇文章,你就会明白:Rspack 为什么快、快在哪里、又是如何做到兼容与性能并存的。
核心流程解析
Rspack 的核心理念和 webpack 一样,都是通过模块化依赖分析,把各种类型的资源,无论是 JavaScript、CSS,还是图片等,打包生成最终能在浏览器中运行的 bundle 文件。
整个过程,其实可以拆解为 四个主要阶段:初始化阶段、桥接阶段、构建阶段和生成阶段:

如果你看完这四个阶段的流程图,可能第一反应就是:这不就是 webpack 吗?确实,Rspack 的整体核心流程与 webpack 十分接近。
不过,Rspack 多了一个独特的阶段:桥接阶段。这个名字并不是官方定义,而是为了让你更直观地理解它的作用,它相当于一座桥梁,用来连接 JavaScript 层和 Rust 底层引擎之间的通信。我暂且这样称呼它,如果你有更贴切的表达,也欢迎提出。
而正是这个桥接阶段 ,让 Rspack 能够通过 JavaScript 调用 Rust 封装的 JsCompiler 方法来完成真正的核心编译,这也是 Rspack 能实现高性能打包的关键所在。
需要注意的是,在这四个阶段中,Loader 和 Plugin 机制始终贯穿始终,它们共同构成了 Rspack 框架的核心支撑体系,让整个构建过程既灵活又高效。
初始化阶段
初始化阶段的流程如下:

Rspack 的初始化阶段的代码和 webpack 初始化的代码非常相似,甚至类、函数的命名都是一样。Rspack 很多 TypeScript 文件头部都有以下图中的这么一段注释,说明这些代码是直接基于 webpack 源码改写的。如果你看过 webpack 的源码,那么理解 Rspack 的初始化过程会非常轻松。

为了帮助你更系统地理解,我把整个初始化阶段拆分为两部分:配置解析阶段、Compiler 的创建与插件机制 。
配置解析阶段
Rspack 的构建是高度可配置的,无论你是通过 CLI、Node API,还是插件传入配置,这些配置最终都会被统一解析为一致的内部结构。
关键过程如下:
- loadConfig:负责加载并合并配置来源(CLI 参数、rspack.config.js、Node API 传参);
- validate(optionsSchema):校验配置格式,提前发现潜在错误;
- getNormalizedRspackOptions 和 applyWebpackOptionsBaseDefaults:对配置进行标准化,补齐内部依赖的结构,合并出最终配置。
Compiler 的创建与插件机制
Rspack 的核心是 Compiler 对象,它就像整个构建系统的大脑,负责调度插件、触发钩子、控制编译的每一个步骤。
关键过程如下:
- 创建 compiler 实例,代表一次完整的构建会话;
- 应用 NodeEnvironmentPlugin,设置运行环境、日志系统以及文件系统抽象层;
- 遍历用户配置的插件数组 plugins,依次调用 apply 方法,将插件注册到 compiler 实例上;
- 执行 RspackOptionsApply ,应用所有内置插件(例如 EntryOptionPlugin 、RuntimePlugin 等),这些插件并不需要我们手动配置,RspackOptionsApply 会根据配置内容动态注入对应的插件;
- 最后,调用 compiler.compile() 进入正式构建阶段。
EntryOptionPlugin 会根据 entry 配置创建构建入口,告诉 Rspack 从哪些文件开始生成依赖图。
从整个流程来看,初始化阶段不仅是配置的准备工作,更是在构建一个具备可扩展性和可调度能力的编译运行环境,同时确保 Rspack 能够无缝兼容 webpack 的插件生态。
桥接阶段
前面我们介绍到,桥接阶段的作用是让 Rspack 能够通过 JavaScript 调用 Rust 封装的 JsCompiler 方法来完成真正的核心编译。那么 Rspack 是如何实现语言层面的跨界通信?
实现这一目标的核心技术是 FFI(Foreign Function Interface,外部函数接口),FFI 是一种通用机制,允许一种编程语言调用另一种语言的函数,或者与其他语言的库进行数据交换。
具体实现通过以下技术栈实现:
- N-API:Node.js 提供的一套稳定的 C 语言接口,用于构建与 JavaScript 运行时安全交互的原生插件;
- napi-rs:一个基于 Rust 框架,利用 Rust 的 FFI 能力,将 Rust 代码编译为符合 N-API 规范的原生模块。
通过 N-API 与 napi-rs 的结合,Rspack 成功将 Rust 的高性能优势引入构建流程,同时保留了 JavaScript 层面的灵活性和生态兼容性,实现了高效、稳定的 JavaScript 与 Rust 跨语言协作。桥接阶段的核心流程:

getInstance 方法是 JavaScript 层与 Rust 层之间通信的核心桥梁,它实现了 懒加载式初始化,即仅在真正需要执行构建时,才创建 Rust 端的 JsCompiler 实例,并将 JavaScript 层的配置安全、高效地传递至底层编译核心。
这一过程正是 Rspack 实现跨语言协同的关键所在。整个流程可分为以下几个关键步骤:
配置转换
首先,通过 getRawOptions 方法,将 JavaScript 层的配置对象转换为 Rust 可识别的 RawOptions。 转换完成后,会在 rawOptions 上设置两个重要字段:
- __references:记录内置模块的映射关系,确保 Rust 编译器能正确识别这些非文件系统的依赖;
- __virtual_files:保存虚拟文件路径与其内容的映射,用于处理虚拟文件路径到源码的映射。
__references 和 __virtual_files 这两个字段可能不太好理解,我们姑且可以这样理解它们的核心作用: 用于在 JavaScript 层向 Rust 编译器(JsCompiler)传递非真实文件系统的内容或内置依赖关系,让 Rust 层也能正确识别并参与构建过程。
js
rawOptions.__references = Object.fromEntries(
this.#ruleSet.builtinReferences.entries()
);
rawOptions.__virtual_files =
VirtualModulesPlugin.__internal__take_virtual_files(this);
加载原生模块
Rspack 通过 N-API 引入一个 Node 原生模块 @rspack/binding,这是一个预编译的 Rust 二进制模块。该模块导出了多个核心对象,例如:
js
{
JsCompiler, // Rspack 的底层核心编译器类
JsPlugin, // 插件系统的绑定
JsResolver, // 模块解析器绑定
...
}
通过这个模块,JavaScript 层可以像调用普通函数一样操作 Rust 端对象,完成真正的核心编译。
Hook 和文件系统绑定
- createHooksRegisters 的作用是建立 JS 与 Rust 之间的 Hook 映射桥梁。它让 Rust 层的编译事件能回调到 JavaScript 层,从而让 JavaScript 插件系统在构建阶段得以监听并参与执行。没有这一步,Rust 的高性能内核就无法与 JavaScript 的灵活插件生态协同工作;
- inputFileSystem 提供了文件系统的桥接绑定。它不仅负责源文件的读取、构建产物的写入和虚拟文件管理,还为多线程的 Rust 编译过程提供统一的文件访问接,这样可以确保无论多少并行任务同时读取或写入文件,数据状态始终保持一致,避免出现读写冲突或缓存不一致的问题。
创建 Rust 编译器实例并触发构建
最后,通过 new JsCompiler() 创建 Rust 核心编译器实例,随后调用 JsCompiler.build() 方法,此时 Rust 的高性能编译逻辑才真正开始执行。
构建阶段
构建阶段是 Rspack 整个编译流程中最核心、最复杂,也是最能体现性能优势的部分。我们先看下核心流程:

整个流程可分为以下几个关键步骤:
创建 Compilation
当 Rspack 开始构建时,首先会创建本次编译的核心实例 Compilation。它负责管理模块依赖图、代码块(Chunk)结构、资源生成等关键数据。
与 webpack 每次重新创建新的 Compilation 不同,Rspack 采用了持久化 Compilation 的设计。这种设计的好处非常明显:
- 避免了频繁创建对象带来的性能开销;
- 提高内存访问效率,更利于 CPU 缓存命中;
- 为后续的增量编译和热更新打下坚实基础。
Rspack 甚至在底层实现了一个 fast_set 函数,用于高效替换编译状态:
js
pub fn fast_set<T>(dest: &mut T, src: T) {
let old = mem::replace(dest, src); // 原子性替换
spawn_blocking(|| {
mem::drop(old); // 异步释放,避免阻塞主线程
});
}
这些小函数体现了 Rspack 对性能的极致追求:利用 Rust 的所有权机制和异步资源管理,在保证线程安全的同时,避免主线程卡顿。这说明 Rspack 并非简单地用 Rust 重写 webpack,而是从架构层面重新思考了如何高效构建。
构建参数与模块工厂
接下来,通过 new_compilation_params 方法创建本次编译的参数结构体 CompilationParams。其中包含两个核心工厂:
- NormalModuleFactory:负责处理常规模块,比如 .js、.ts 文件;
- ContextModuleFactory :处理动态上下文模块(如 require.context),按目录或模式批量加载模块。
这两个工厂告诉编译器遇到某种类型的模块时,该用什么规则去解析。
触发钩子
当编译参数准备好之后,Rspack 会进入一系列钩子触发流程,为正式构建模块图做准备,例如 compiler_hooks.make 钩子,此时 EntryPlugin 会被调用,它会执行compilation.add_entry(),完成以下动作:
- 为每个入口文件生成唯一 ID;
- 创建对应的 EntryDependency 对象;
- 将依赖注入模块图,建立入口与模块的关联;
- 最后触发 CompilationAddEntry 钩子,通知插件系统入口点已准备就绪。
模块构建
接下来,compilation.make() 正式启动。Rspack 的模块构建是基于异步任务循环的,每个任务都会生成新的子任务,形成一条协作的处理链:
js
ProcessDependenciesTask → FactorizeTask → FactorizeResultTask
→ AddTask → BuildTask → ProcessDependenciesTask
在这个循环中:
- 主任务负责维护模块图状态;
- 后台任务则并行执行模块解析与依赖分析。
具体流程如下:
BuildTask 会调用 run_loaders() 转译模块内容(例如将各类资源转为 JS),再用 SWC 解析代码为 AST,然后分析 AST 提取依赖关系,如果有依赖关系的话则重新调用 ProcessDependenciesTask,如此循环,直到所有依赖都被处理完毕。
最终,Rspack 得到一份完整的构建产物描述:MakeArtifact,这张依赖图就是后续生成阶段的基础。
这种主任务 + 后台任务协作的架构,是 Rspack 能实现高性能编译的关键,Rust 在这里真正发挥了作用:并行、安全、高效。
总结来看,构建阶段是 Rspack 的性能核心。从持久化 Compilation 到异步任务架构,每一处设计都在为性能服务,它不只是 Rust 的快,而是架构层面的重新思考,这也正是 Rspack 能在同样的构建逻辑下,实现数倍性能提升的根本原因。
生成阶段
经过前面的构建阶段,Rspack 已经完成了模块的解析与依赖分析。接下来,我们进入整个编译流程的最后一个阶段:生成阶段(seal 阶段)。
这是将模块图封装为最终输出文件的关键环节,也是一系列优化与代码生成的集中体现。
seal 函数的执行链比较长,负责将已构建的模块图转换为最终的输出资源。我们可以把它分为多个阶段,包括依赖优化、Chunk 构建、代码生成和资源输出:

依赖优化
生成阶段一开始,Rspack 会触发 compilation_hooks.seal,这也是插件最后一次修改模块图的机会。
随后进入 optimize_dependencies 流程,对模块依赖关系进行多轮优化,包括死代码消除、循环依赖检测、赖结构优化等。这个过程是迭代进行的,Rspack 会反复调用插件,直到所有插件都表示无需再优化。
Chunk 图构建
优化完成后,调用 build_chunk_graph,把模块图转换为 Chunk 图。在这个阶段,Rspack 会进行代码分割(Code Splitting),将不同模块根据依赖、动态加载关系等分配到合适的 Chunk 中。
Rspack 还提供了 build_chunk_graph_new 方法,用于支持并行代码分割,在大规模项目中能显著提升性能。
模块与 Chunk 优化
生成 Chunk 图后,进入一系列优化钩子流程,然后通过 module_ids 和 chunk_ids 分配模块和 Chunk 的唯一标识符,这一整套优化链条,决定了最终生成文件的体积。
代码生成与资源输出
最后进入最关键的 code_generation 阶段,Rspack 会为每个模块生成最终的 JavaScript 或 CSS 代码。
紧接着:
- create_module_assets :收集模块构建中生成的静态资源(如 CSS、图片、字体),通过 emit_asset 写入 compilation.assets;
- create_chunk_assets :为每个 Chunk 生成最终的输出文件(如 main.js, main.css, main.js.map),同样写入资源集合 compilation.assets;
- 最后触发 after_seal 钩子,把控制权交还给 compiler,准备写入磁盘。
最终,Rspack 根据配置的输出路径与命名规则,把生成的所有资源文件写入文件系统,完成整个构建流程。
总结
看完本篇关于 Rspcak 的核心流程解析,相信你能回答了开篇我们提出的问题。
对比 webpack,Rspack 的优势不止是语言层面的性能提升,它并不是简单地使用 Rust 来重写 webpack,而是重新思考了构建这件事本身,从持久化 Compilation 到异步任务流,从 SWC 驱动的极速解析到多线程并发的全面落地,每一处细节都体现了对高性能构建系统的系统性设计。
不止于此,Rspack 真正令人惊叹之处,在于在极致性能与生态兼容之间找到了完美的平衡,深度兼容 webpack 的配置、插件和 Loader 生态,让已有项目无需大改动即可平滑迁移,这也是 Rspack 区别于其他高性能构建工具(如 Vite、esbuild)的核心竞争力之一。
Rspack 的迭代速度非常快,而且编译效率还在不断优化,从 1.0 版本发布到现在的 1.5 版本,不到一年的时间,Rspack 每个版本的更新日志里都体现了对编译性能和产物体积的优化。相信在不远的时间里,Rspack 将会持续给我们带来更多的惊喜。