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/core
    • rspack-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

typescript 复制代码
// 简化代码
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

typescript 复制代码
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 类。

rust 复制代码
// 这里的 #[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 (核心逻辑)

rust 复制代码
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 的启动流程:

  1. 用户在 CLI 或脚本中调用 rspack()
  2. JS 层 (packages/rspack) 处理配置,初始化 Compiler.ts
  3. Binding 层 (crates/rspack_binding_api) 利用 NAPI 接收配置,创建 Rust 实例。
  4. Core 层 (crates/rspack_core) 启动,随时准备进行编译。

给 Rust 初学者的建议 : 在阅读 Rspack 源码时,不必纠结于通过 Arc, Mutex, RwLock 这种复杂的并发控制细节(虽然它们在 Rspack 中无处不在)。先关注 struct数据结构设计impl方法流程,把 Rust 当作带类型的 Python 或 C++ 来看,会更容易上手。

下一篇预告 : 我们将深入 Compilation(编译过程),看看 Rspack 是如何从一个入口文件开始,构建出整个项目的依赖图谱的(Make Phase)。

相关推荐
gogoing8 小时前
ESLint 配置字段说明
前端·javascript
gogoing8 小时前
CSS 属性值计算过程(Computed Value)
前端·css
gogoing8 小时前
webpack 的性能优化
前端·javascript
gogoing8 小时前
Node.js 模块查找策略(require 完整流程)
javascript·node.js
桃花键神8 小时前
Bright Data Web Scraping指南 2026: 使用 MCP + Dify 自动采集海外社交媒体数据
大数据·前端·人工智能
gogoing8 小时前
await fetch() 的两阶段设计
前端·javascript
gogoing8 小时前
前端首屏加载优化
前端·javascript
gogoing9 小时前
重排与重绘
前端·javascript
打小就很皮...9 小时前
基于Python + LangChain + 通义千问的聊天机器人实战
前端·langchain·机器人·千问
REDcker9 小时前
个人博客网站建设指南 Markdown资产化与静态站选型部署
前端·后端·博客·markdown·网站·资产·建站