Loader 的执行顺序是怎样的?为什么设计成从右到左(或从下到上)执行?
在 Webpack 中,Loader 的执行顺序和设计原理可以拆分为以下几个核心要点:
一、 Loader 的执行顺序
简而言之:从右到左,从下到上(从后往前)。
1. 基础示例
如果在 webpack.config.js 中配置如下:
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader', // 第 3 个执行
'css-loader', // 第 2 个执行
'sass-loader' // 第 1 个执行
]
}
]
}
};
处理 .scss 文件时的实际执行流程是:
sass-loader:将 SCSS 语法编译为 CSS。css-loader:解析 CSS 中的@import和url(),将其转换为 CommonJS 模块。style-loader:创建<style>标签,将 CSS 注入到 HTML 的<head>中。
2. 进阶:完整的 Loader 执行生命周期(Pitch 阶段与 Normal 阶段)
Webpack 在处理 Loader 时,实际上分为两个阶段:Pitching 阶段 和 Normal (执行) 阶段。
|- compiler
| |- style-loader `pitch`
| |- css-loader `pitch`
| |- sass-loader `pitch`
| |- 读取文件内容 (Resource)
| |- sass-loader (normal execution)
| |- css-loader (normal execution)
| |- style-loader (normal execution)
- Pitch 阶段(从左到右 / 从上到下): Webpack 会先依次调用 Loader 上的
pitch方法(如果有)。 - Normal 阶段(从右到左 / 从下到上): 真正接收文件内容并进行转换的阶段。
- 短路机制: 如果某个 Loader 的
pitch方法返回了值,Webpack 会跳过后续的 Pitch 和文件读取,直接倒退回执行前一个 Loader 的 Normal 阶段。
3. 优先级配置(enforce)
除了数组中的顺序,还可以通过 enforce 属性人为改变 Loader 的类型和优先级:
preLoader(前置)normalLoader(普通,默认)inlineLoader(行内,写在import语句里的)postLoader(后置)
Pitch 阶段执行顺序: post -> inline -> normal -> pre
Normal 阶段执行顺序: pre -> normal -> inline -> post
二、 为什么设计成“从右到左”?
这种看似“反直觉”的设计,其实有着深刻的数学和编程语言学依据:
1. 函数组合(Functional Composition)
这是最核心的原因。在函数式编程(Functional Programming)中,有一个经典的原理叫函数组合(compose)。
假设我们有三个函数:
如果我们想先用 处理数据,再用 ,最后用 ,数学上写作:
在 JS 代码中表达这种嵌套关系就是:
styleLoader(cssLoader(sassLoader(sourceCode)));
可以看到,最先作用于原始代码(sourceCode)的 sassLoader 被包裹在最里层(最右侧),而最后执行的 styleLoader 在最外层(最左侧)。
Webpack 的 Loader 链就是这种函数组合的体现:每个 Loader 都是一个纯函数,上一个 Loader 的输出作为下一个 Loader 的输入。
2. 符合 compose 函数的实现惯例
在许多前端函数库(如 Redux、Lodash、Ramda)中,compose 函数的实现逻辑就是从右向左执行的:
// Redux 中的 compose 实现简写
const compose = (...funcs) => {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
};
// 执行 compose(f, g, h)(x) 相当于 f(g(h(x)))
注:如果想从左往右执行,在函数式编程中通常叫 pipe(管道)。 Webpack 选择了 compose 的思想,而不是 pipe。
3. 语义化:靠近文件的 Loader 最先处理文件
从直观配置的角度来看:[style-loader, css-loader, sass-loader]
sass-loader紧贴着原始的.scss文件,所以它最先处理文件。- 处理完后向上/向左抛出,交给
css-loader。 - 最后抛给
style-loader产出最终结果。
这种栈(Stack)结构的“后进先出”机制,保证了最底层的抽象(最基础的文件解析)最先被执行。
总结
- 顺序规则:Loader 在 Normal 阶段 从右往左(从下往上)执行;在 Pitch 阶段 从左往右执行。
- 设计原因:遵循函数式编程中的 函数组合(Compose) 理念,数学表达为
Style(Css(Sass(code))),使得前一个 Loader 的输出自然地成为下一个 Loader 的输入。