Loader 执行顺序详解

1. 面试题

Q: Loader 的执行顺序是什么?为什么是这个顺序?

2. 核心解答

Loader 是一组串行执行的过程。

(1) 从右到左 / 从下到上

这是 Webpack (及其前身) 对 Loader 的默认调用顺序。因为它是按照函数式编程 (Functional Programming) 的组合 (Compose) 思想设计的。

// 假设有 loader1, loader2, loader3
// 最终执行效果相当于:
loader1(loader2(loader3(source_code)));

配置示例

module.rules: [
  {
    test: /\.less$/,
    use: [
      'style-loader', // 3. 将 JS 字符串生成 style 标签插入 DOM
      'css-loader',   // 2. 将 CSS 转化为 CommonJS 模块
      'less-loader'   // 1. 将 Less 编译为 CSS
    ]
  }
]

(2) Pitching Phase

如果你看过 Loader 的源码,你会发现 Loader 还有一个 pitch 属性 (也是个函数)。 Loader 的完整执行过程其实分为两个阶段: PitchingNormal

  1. Pitching Phase: 从 左到右 执行。 style-loader.pitch -> css-loader.pitch -> less-loader.pitch
  2. Normal Phase: 从 右到左 执行。 less-loader -> css-loader -> style-loader

特殊机制:如果在 Pitching 阶段某个 Loader 的 pitch 方法直接返回了非 undefined 的值,那么后续的 Pitching Loaders 和 Normal Loaders 都会被跳过,直接回头执行前一个 Loader 的 Normal 阶段。这也叫 熔断机制

(3) loader-runner

Webpack 内部使用 loader-runner 这个独立的库来运行 Loader。它负责处理 Pitching 和 Normal 阶段的转换,以及异步 Loader (this.async()) 的等待。

3. 面试加分项

Q: 为什么要设计 Pitching 阶段?

为了让 Loader 有机会跳过后续的处理过程,或者在处理之前获取 metadata。 最典型的例子是 style-loader。当你 require('./style.css') 时,style-loader 的 pitch 方法会被执行。它会生成一段 JS 代码 (包含 require('./style.css') 的 Request 字符串),通过 !! (忽略其他 loader) 再次请求自身 (Normal 阶段),从而解决循环引用问题,并将生成的 CSS 内容注入到 DOM 中。这涉及到了 Loader 之间的数据传递和请求拦截

4. 总结

  • Order: Right to Left (Compose).
  • Phase: Pitching (L->R) -> Normal (R->L).
  • Pitch Return: Short-circuit (Skip subsequent).