架构
mini-rspack 遵循类似于 webpack 的模块化架构,组件设计为协同工作,将源代码转换为打包输出。本页面解释了高级架构以及不同组件如何交互。
概述
mini-rspack 的架构由以下主要组件组成:
- Compiler:管理编译过程的主入口点
- Compilation:表示应用程序的单次构建
- Module:表示依赖图中的一个模块
- Loader:转换模块内容
- Plugin:扩展打包工具的功能
- Hook System:为插件提供挂钩到编译不同阶段的方式
- Resolver:解析模块路径并处理模块解析算法
组件交互
以下是这些组件在打包过程中如何交互:
- 使用配置对象创建 Compiler
- 当调用
compiler.run()
时,它创建一个新的 Compilation 实例 - Compilation 读取入口点并开始构建依赖图
- 对于每个模块,它:
- 使用 Resolver 解析模块路径
- 读取模块内容
- 应用 Loaders 转换内容
- 解析转换后的内容以提取依赖项
- 将模块添加到依赖图
- 一旦处理完所有模块,Compilation 创建块并生成资源
- 在此过程中,调用 Hooks,允许 Plugins 修改行为
Rust 和 JavaScript 交互
mini-rspack 使用 Rust 实现核心功能并提供 JavaScript API。以下是两种语言如何交互:
- JavaScript API 在
index.js
中定义,提供类似 webpack 的接口 - Rust 代码使用 napi-rs 编译为原生 Node.js 模块
- JavaScript API 调用 Rust 代码执行实际打包
- 回调和钩子允许 JavaScript 代码(如插件和加载器)与 Rust 核心交互
代码结构
代码库组织如下:
mini-rspack/
├── src/ # Rust 源代码
│ ├── lib.rs # 主库入口点
│ ├── compiler.rs # 编译器实现
│ ├── compilation.rs # 编译实现
│ ├── module.rs # 模块实现
│ ├── loader.rs # 加载器系统
│ ├── loader_runner.rs # 加载器运行器实现
│ ├── plugin.rs # 插件系统
│ ├── hook.rs # 钩子系统
│ └── utils.rs # 实用函数
├── index.js # JavaScript API
├── loaders/ # 示例加载器
├── plugins/ # 示例插件
└── tests/ # 测试文件
├── js/ # JavaScript 测试
└── rust/ # Rust 测试
编译过程
mini-rspack 中的编译过程遵循以下步骤:
- 初始化:使用提供的配置创建编译器实例
- 入口解析:解析配置中指定的入口点
- 模块处理:对于每个模块:
- 解析模块路径
- 读取模块内容
- 应用加载器转换内容
- 解析转换后的内容以提取依赖项
- 将模块添加到依赖图
- 依赖解析:解析每个模块的依赖项并重复模块处理步骤
- 块创建:根据入口点和动态导入将模块分组为块
- 资源生成:从块生成资源(JavaScript 文件)
- 插件处理:允许插件修改生成的资源
- 输出:将资源写入输出目录
技术实现细节
模块解析
mini-rspack 使用正则表达式模式从 JavaScript 模块中提取依赖项:
rust
// 提取 CommonJS requires
let re_require = Regex::new(r#"require\(['"](.*?)['"]"#).unwrap();
for cap in re_require.captures_iter(source) {
let dep = cap.get(1).unwrap().as_str();
dependencies.push(dep.to_string());
}
// 提取 ES 模块导入
let re_import = Regex::new(r#"import\s+.*?from\s+['"]([^'"]+)['"]"#).unwrap();
for cap in re_import.captures_iter(source) {
let dep = cap.get(1).unwrap().as_str();
dependencies.push(dep.to_string());
}
// 提取动态导入
let re_dynamic_import = Regex::new(r#"import\(['"]([^'"]+)['"]\)"#).unwrap();
for cap in re_dynamic_import.captures_iter(source) {
let dep = cap.get(1).unwrap().as_str();
dependencies.push(dep.to_string());
}
加载器运行器
加载器是转换模块内容的 JavaScript 函数。加载器运行器按顺序执行这些函数:
rust
pub fn apply_loaders(source: &str, loaders: &Vec<Loader>, module_path: &Path) -> Result<String> {
// 如果没有加载器,按原样返回源
if loaders.is_empty() {
return Ok(source.to_string());
}
// 为加载器运行器创建临时文件
let mut temp_file = tempfile::NamedTempFile::new()?;
let loader_runner_path = temp_file.path();
// 生成加载器运行器代码
let loader_runner_code = generate_loader_runner(source, loaders, module_path)?;
// 将加载器运行器代码写入临时文件
std::fs::write(loader_runner_path, loader_runner_code)?;
// 执行加载器运行器
let output = std::process::Command::new("node")
.arg(loader_runner_path)
.output()?;
// 检查执行是否成功
if !output.status.success() {
let error_message = String::from_utf8_lossy(&output.stderr);
return Err(anyhow::anyhow!("Loader execution failed: {}", error_message));
}
// 从输出获取转换后的源
let transformed_source = String::from_utf8_lossy(&output.stdout).to_string();
Ok(transformed_source)
}
钩子系统
钩子系统允许插件挂钩到编译过程的不同阶段:
rust
pub struct SyncHook {
pub name: String,
pub taps: Vec<String>,
}
impl SyncHook {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
taps: Vec::new(),
}
}
pub fn tap(&mut self, name: &str) {
self.taps.push(name.to_string());
}
pub fn call(&self, args: Option<&mut HashMap<String, String>>) {
// 调用挂钩的函数
println!("Hook '{}' called with {} taps", self.name, self.taps.len());
// 现在,我们只打印挂钩
for tap in &self.taps {
println!(" - Tap: {}", tap);
}
}
}