webpack 不同 loader 的执行顺序

webpack 中 loader 常用的有配置(普通和 enforce)和内联两种形式。在编译的过程中混用的情况比较常见,本文理一下执行顺序。

默认的从右往左(从下到上)是编程风格的问题,可以参考这篇文章的说法:why-webpack-loader-read-from-right-to-left

有三种使用 loader 的方式:

  • 配置方式:在 webpack.config.js 文件中指定 loader。
  • 内联方式:在每个 import 语句中显式指定 loader。
    - CLI 方式:在 shell 命令中指定它们。不在执行顺序考虑范围里

结论

loader 的执行分为两个阶段 Pitching phase 和 Normal phase,有点类似捕获和冒泡,这里先贴一下结论,下面再举例

Pitching phase:post -> inline -> normal -> pre
Normal phase:pre -> normal -> inline -> post

其中只要在配置中没有配置 enforce 属性(pre 或 post)的就是 normal loader,inline 指的就是内联方式写的 loader。

下面以 Normal phase 为例说明执行顺序

默认配置方式的执行顺序

先看一个配置示例,详细的配置说明推荐查看官方文档

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.xjs$/,
        use: [
          { loader: "a-loader.js" },
          { loader: "b-loader.js" },
          { loader: "c-loader.js" },
        ],
      },
    ],
  },
};

loader 从右到左(或从下到上)执行,此处的顺序是 c-loader -> b-loader -> a-loader

配置添加 enforce

添加 enforce 属性,pre 优先处理,post 最后处理

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.xjs$/,
        use: [
          {
            loader: "a-loader.js",
            enforce: "pre",
          },
          { loader: "b-loader.js" },
          {
            loader: "c-loader.js",
            enforce: "post",
          },
        ],
      },
    ],
  },
};

执行顺序就是 a-loader -> b-loader -> c-loader

内联 loader

若匹配的文件开头来了一句

// xxx.xjs
import { a } from "./e-loader.js!./d-loader.js!./file1.js";

内联 loader 的执行顺序也符合从右到左的规则 d-loader.js -> e-loader.js

搭配刚才 enforce 的配置就是 a-loader -> b-loader -> d-loader -> e-loader -> c-loader

覆盖配置中的 loader

在编译的过程中(例如解析.vue 文件),loader 的选用往往是动态决定的,也就是 inline loader 是动态生成的,此时可能要选用特定的 loader 去处理已经在配置里配置了 loader 的文件。

而这个文件一般已经用配置里的 loader 解析过了,没有必要重复使用这些 loader(需要自行将结果缓存),要忽略这些 loader 可以使用 ! 前缀进行处理。

还是搭配刚才 enfoece 的配置

  • 使用 ! 前缀禁用 normal loaders
// xxx.xjs
import { a } from "!./e-loader.js!./d-loader.js!./file1.js";
// a-loader -> d-loader -> e-loader -> c-loader
  • 使用 !! 前缀禁用全部配置的 loaders
// xxx.xjs
import { a } from "!!./e-loader.js!./d-loader.js!./file1.js";
// d-loader -> e-loader
  • 使用 -! 禁用全部配置的 preLoaders 和 normal loaders
// xxx.xjs
import { a } from "-!./e-loader.js!./d-loader.js!./file1.js";
// d-loader -> e-loader -> c-loader

其他

当 rules 中有多个匹配的 loaders 时,从下向上执行。像下面这个例子,执行顺序就是 js-loader2.js -> js-loader.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "./js-loader.js",
      },
      {
        test: /\.js$/,
        loader: "./js-loader2.js",
      },
    ],
  },
};

最后贴一个官方对配置顺序的说明:Rule.enforce