5月27日 16:58

Rspack Loader 系统的工作原理是什么?

Rspack 的 Loader 系统是其处理各类文件的核心机制。虽然 Rspack 基于 Rust 开发,但设计上充分考虑了与 Webpack Loader 生态的兼容性——大部分社区 Loader 可以直接使用,同时通过内置 Loader 提供了显著的性能优势。理解 Loader 系统的工作方式,是用好 Rspack 的关键前提。

Loader 的角色与定位

Loader 是一种模块转换器,负责将源文件转换为 Rspack 能够处理的模块格式。它的核心能力包括:

  • 语言编译:将 TypeScript、JSX 等编译为 JavaScript
  • 样式处理:将 Less、Sass 编译为 CSS
  • 资源管理:处理图片、字体等静态资源
  • 代码校验:集成 ESLint 等代码检查工具

Rspack 中 Loader 的定位与 Webpack 一致:先通过 Loader 对模块进行预处理,转换为 Rspack 支持的模块类型,再根据 rules[].type 进行后置处理。这两步构成了完整的模块处理流水线。

配置方式

基本规则

module.rules 中通过 test 匹配文件扩展名,用 use 指定 Loader:

javascript
module.exports = { module: { rules: [ { test: /\.js$/, use: 'babel-loader' } ] } }

多 Loader 链式调用

多个 Loader 组成链式处理,例如处理 CSS 文件:

javascript
module.exports = { module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] } ] } }

带选项的 Loader

通过 options 传递配置参数:

javascript
module.exports = { module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }

执行顺序:从右到左与 Pitch 机制

Loader 链的执行顺序是理解整个系统的关键。

基本执行顺序

Loader 按从右到左、从下到上的顺序执行。对于 use: ['style-loader', 'css-loader', 'sass-loader']

  1. sass-loader 先执行,将 SCSS 编译为 CSS
  2. css-loader 解析 CSS 中的 @importurl()
  3. style-loader 将 CSS 注入 DOM

Pitch 阶段

每个 Loader 除了正常的转换函数外,还可以定义一个 pitch 方法。Pitch 阶段从左到右执行,在所有正常阶段之前运行。当某个 Loader 的 pitch 方法返回了值,后续 Loader 的 pitch 和正常阶段都会被跳过,执行流程直接回溯到前一个 Loader。

对于 use: ['a-loader', 'b-loader', 'c-loader'],完整执行流程为:

shell
a-loader pitch → b-loader pitch → c-loader pitch → c-loader normal → b-loader normal → a-loader normal

如果 b-loader 的 pitch 返回了值,则跳过 c-loaderb-loader 的 normal 阶段,直接进入 a-loader 的 normal 阶段。这个机制在 style-loader 的实现中被使用——它在 pitch 阶段拦截请求,直接返回一段将 CSS 注入页面的代码。

内置 Loader:Rspack 的性能利器

Rspack 提供了多个 Rust 实现的内置 Loader,在保持与 JavaScript Loader 相同可组合性的同时,提供了远超 JS Loader 的性能。

builtin:swc-loader

基于 SWC 的超快 JavaScript/TypeScript 编译器,替代 babel-loaderts-loader

javascript
module.exports = { module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, use: { loader: 'builtin:swc-loader', options: { jsc: { parser: { syntax: 'typescript', tsx: true }, transform: { react: { runtime: 'automatic' } } } } } } ] } }

builtin:swc-loader 的核心优势在于 SWC 本身就是 Rust 编写的,因此这个 Loader 运行在 Rspack 的 Rust 进程内部,无需跨语言通信开销。

builtin:lightningcss-loader

基于 Lightning CSS 的内置 CSS 处理器,可替代 postcss-loader + autoprefixer 的组合:

javascript
import { rspack } from '@rspack/core'; module.exports = { module: { rules: [ { test: /\.css$/, use: [ { loader: 'builtin:lightningcss-loader', options: { targets: ['chrome 60', 'firefox 60'] } } ] } ] } }

主要配置项:

  • targets:指定目标浏览器,支持 browserslist 查询字符串或版本号对象
  • errorRecovery:默认 true,遇到无效 CSS 语法时跳过并发出警告而非中断编译

需要注意 Lightning CSS 严格遵循 CSS 规范,处理非标准 CSS(如 CSS Modules 的局部作用域语法)时可能出现兼容问题。

builtin:css-loader

Rspack 还提供了 builtin:css-loader 作为 css-loader 的内置替代:

javascript
module.exports = { module: { rules: [ { test: /\.css$/, use: ['style-loader', 'builtin:css-loader'] } ] } }

原生 CSS 支持

除了内置 Loader,Rspack 还通过 experiments.css 提供原生 CSS 支持,CSS 作为一等公民被直接处理:

javascript
module.exports = { experiments: { css: true } }

启用后可通过 type: 'css'type: 'css/auto'type: 'css/global'type: 'css/module' 等模块类型控制 CSS 处理方式。此时不需要 css-loader,也不能与 style-loader 搭配使用,需使用 CssExtractRspackPlugin 或 Rspack 内置的 CSS 提取方案。

匹配规则与条件

test / include / exclude

  • test:正则匹配文件路径
  • include:限定只处理指定目录
  • exclude:排除指定目录
javascript
{ test: /\.js$/, include: path.resolve(__dirname, 'src'), exclude: /node_modules/, use: 'babel-loader' }

oneOf

当同一类文件只需匹配一个规则时,使用 oneOf 避免重复处理:

javascript
{ test: /\.css$/, oneOf: [ { resourceQuery: /module/, use: 'css-loader?modules' }, { use: 'css-loader' } ] }

resource 与 issuer

  • resource:匹配被导入的资源路径
  • issuer:匹配发起导入的模块路径
javascript
{ test: /\.css$/, issuer: /\.js$/, // 只处理从 JS 文件中导入的 CSS use: 'style-loader' }

自定义 Loader

同步 Loader

最简单的 Loader 形式,接收源码字符串,返回转换结果:

javascript
module.exports = function(content) { return content.toUpperCase(); };

异步 Loader

需要执行异步操作时,调用 this.async() 获取回调函数:

javascript
module.exports = function(content) { const callback = this.async(); someAsyncOperation(content).then(result => { callback(null, result); }).catch(err => { callback(err); }); };

Raw Loader

默认情况下 Loader 接收 UTF-8 字符串。当需要处理二进制文件时,导出 raw: true 以接收 Buffer:

javascript
module.exports = function(content) { // content 是 Buffer const size = content.length; return `export default ${size}`; }; module.exports.raw = true;

获取选项

通过 this.getOptions() 获取配置参数:

javascript
module.exports = function(content, map, meta) { const options = this.getOptions(); const result = content.replace(options.search, options.replace); return result; };

Rspack Loader 架构:与 Webpack 的关键区别

Rspack 的 Loader 系统在设计上与 Webpack 保持了 API 兼容,但底层架构有本质区别。

旧架构:Rust 端的 Loader Runner

在早期版本中,Rspack 将 JS Loader 转换为可以从 Rust 端调用的原生函数,Loader Runner 完全运行在 Rust 端。这种方式的局限在于对复杂 JS Loader 的兼容性不够理想。

新架构:标识符驱动的调度机制

当前架构中,Rspack 从 Rust 核心将 Loader 请求委托给 JS 端的调度器,使用修改版的 webpack loader-runner 执行。每个 JS Loader 通过标识符(identifier)唯一识别,包含无法序列化字段的选项会复用 Loader 标识来避免重复解析。

Rust-JS 通信优化

由于 Rspack 的核心构建逻辑在 Rust 端,而社区 Loader 运行在 JS 端,两者之间的通信是性能瓶颈。Rspack 的优化策略是将连续的 JS Loader 组合在一起执行,减少 Rust-JS 之间的通信轮次。当模块处理链中包含多个 JS Loader 时,Rspack 会把它们合并为一次 JS 调用,而非每个 Loader 都进行一次跨语言通信。

增量构建优势

与 esbuild 的 onLoad 回调不同,Rspack 在 rebuild 时只触发变动模块的 Loader 重复执行,未变化的模块直接使用缓存。在大项目中这是一个关键的 O(n) 复杂度优势。

性能优化建议

  1. 优先使用内置 Loaderbuiltin:swc-loader 替代 babel-loaderbuiltin:lightningcss-loader 替代 postcss-loader,性能提升通常在数倍到数十倍
  2. 缩小 Loader 作用范围:使用 includeexclude 避免对 node_modules 等目录执行不必要的转换
  3. 启用缓存:对 babel-loader 等仍需使用的 JS Loader 开启 cacheDirectory
  4. 使用原生 CSS 方案:如果项目不依赖 css-loader 的特定功能,启用 experiments.css 可以获得更好的 CSS 处理性能
  5. 避免不必要的 Loader:Rspack 原生支持 Asset Modules,不需要 file-loaderurl-loader,通过 type: 'asset/resource'type: 'asset' 替代

兼容性说明

Rspack 支持绝大多数社区 Loader,只有少数依赖 Webpack 内部实现细节的 Loader(如 cache-loader)尚未兼容。如果遇到不兼容的情况,可以通过 Rspack GitHub Issues 反馈。

标签:Rspack