前面在加载样式文件的时候介绍了很多 loader,每个 loader 都有不同的配置方法。实际上,Webpack 提供了多种方式来设置 loader 参数。本文就来详细说明如何配置一个 loader。

Webpack 默认只支持 CommonJS 规范。其他规范的模块,Webpack 需要借助于 loader 来解析。下面的样例展示了利用 babel 来加载 JavaScript 文件的 loader 配置。

const config = {
  module: {
    rules: [
      {
        // 文件匹配条件,支持正则表达式或者函数
        test: /\.js$/,

        // 文件目录匹配
        include: path.join(__dirname, "app"),
        exclude: (path) => path.match(/node_modules/);

        // 针对匹配到的文件需要执行的动作
        use: "babel-loader",
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Loader 的顺序

在 Webpack 中,loader 是按从右向左,从下向上的顺序依次评估的。可以通过函数调用的方式加以理解。use: ["style-loader", "css-loader"] 等同于 style(css(input)) 。参考样例:

const config = {
  test: /\.css$/,
  use: ['style-loader', 'css-loader'],
};
1
2
3
4

基于从右向左的原则,配置还可以改成这样:

const config = [
  { test: /\.css$/, use: 'style-loader' },
  { test: /\.css$/, use: 'css-loader' },
];
1
2
3
4

正常情况下,通过上面的规则,我们可以实现各种处理顺序的 loader 定义。但是,有时候如果可以动态调整 loader 定义的顺序会给开发带来极大的遍历。这时候我们可以通过 enforce 这个字段来实现,enforce 取值为 pre 则 loader 会在其他 loader 之前执行,取值为 post 则 loader 会在其他 loader 之后执行。

代码规范静态检查配置可以很好的说明 enforce 的使用,通常我们需要在加载源代码的时候就检查代码规范,因此需要配置 enforce: "pre"enforce:"post" 不太常用,通常会被用在对构建输出做校验的场景下。

const config = {
  test: /\.js$/,
  enforce: 'pre', // "post" too
  use: 'eslint-loader',
};
1
2
3
4
5

通常来说,我们也可以通过纯代码的形式控制 loader 顺序,但是 enforce 提供了更多的遍历,同时也允许 loader 的配置分散在多个文件中,灵活性更好。

loader 的参数

Webpack 支持通过 query 参数的形式给 loader 设置参数。

const config = { test: /\.js$/, use: 'babel-loader?presets[]=env' };
1

这种传参方式同样可以被应用在源代码中。缺点是可读性差一些。

通常情况下,我们使用 use 来给 loader 传参:

const config = {
  test: /\.js$/,
  use: { loader: 'babel-loader', options: { presets: ['env'] } },
};
1
2
3
4

内联 loader 定义

通常情况下,我们通过 webpack 的配置文件来定义 loader,但是,Webpack 同时支持在源代码中通过内联的形式定义 loader。

import 'url-loader!./foo.png';
import '!!url-loader!./bar.png';
1
2

一般来说,我们应该避免这么做,因为这样使得我们的源代码跟 webpack 产生了耦合。

我们也可以在 webpack 的 entry 配置中定义 loader。

const config = { entry: { app: 'babel-loader!./app' } };
1

通过 info 对象来加载资源

use 支持传递一个函数,返回 loader 配置。在这个函数里,我们可以根据环境等条件返回不同的配置。use 函数中必须返回一个值,可以为 falsy, object,或者字符串。

const config = {
  rules: [
    {
      test: /\.js$/,
      use: [
        (info) => ({
          loader: 'babel-loader',
          options: { presets: ['env'] },
        }),
      ],
    },
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13

info 对象包含如下内容:

{
  resource: '/webpack-demo/src/main.css', // 表示匹配到的资源路径
  realResource: '/webpack-demo/src/main.css',
  resourceQuery: '', // 表示匹配到的资源的查询参数
  issuer: '', // 表示引用这个资源的模块路径
  compiler: 'mini-css-extract-plugin /webpack-demo/node_modules/css-loader/dist/cjs.js!/webpack-demo/node_modules/postcss-loader/src/index.js??ref--4-2!/webpack-demo/node_modules/postcss-loader/src/index.js??ref--4-3!/webpack-demo/src/main.css'
}
1
2
3
4
5
6
7

通过 resourceQuery 加载资源

可以通过 oneOf 字段,我们可以配置让 Webpack 根据不同的资源加载不同的 loader。

const config = {
  test: /\.png$/,
  oneOf: [
    { resourceQuery: /inline/, use: 'url-loader' },
    { resourceQuery: /external/, use: 'file-loader' },
  ],
};
1
2
3
4
5
6
7

除了 resourceQuery,也可以使用 resourcePath.

通过 issuer 加载资源

issuer 可以用来根据引用者来加载不同的 loader。下面的例子,表示当一个 css 是被 JavaScript 文件引用的时候,需要加载 style-loader

const config = {
  test: /\.css$/,
  rules: [{ issuer: /\.js$/, use: 'style-loader' }, { use: 'css-loader' }],
};
1
2
3
4

issuer 也可以与 not 混用,

const config = {
  test: /\.css$/,
  rules: [
    // 将被其他非 css 模块引入的 css 写入到 dom 中
    { issuer: { not: /\.css$/ }, use: 'style-loader' },
    { use: 'css-loader' }, // 处理 css 导入
  ],
};
1
2
3
4
5
6
7
8

调整 loader 匹配条件的各种方法

  • test, include, exclude 是最常用的匹配条件,可以设置为正则表达式、字符串、函数、对象,或者一个数组。
  • resource: /inline/ 匹配资源路径,包括查询参数,比如: /path/foo.inline.js, /path/bar.png?inline.
  • issuer: /bar.js/ 当一个资源的引用方满足条件的时候,这个资源被匹配到。比如:/path/foo.png 如果被 /path/bar.js 引用了,则它就会被匹配。
  • resourcePath: /inline/ 仅匹配资源路径,比如: /path/foo.inline.png
  • resourceQuery: /inline/ 仅匹配资源的查询参数,比如: /path/foo.png?inline.

同时还可以与下面的布尔型匹配条件组合使用。

  • not 不匹配某一个条件。
  • and 同时满足一组条件。
  • or 满足一组条件中的一个。

总结

loader-runner 这个包可以不借助 Webpack 直接运行 loader, 可以帮助我们更好的理解 loader 的工作机制。通过将 inspect-loader 这个工具与 Webpack 配置结合起来,我们可以了解 loader 之间数据是如何传递的。

我们通过 loader 来配置 Webpack 根据不同的模块使用不同的处理机制。

一个 loader 定义包块匹配模块的条件和处理模块的动作。

Webpack 提供了多种定义 loader 的方式,我们可以根据不同的需求使用不同的配置。

关注微信公众号,获取最新推送~