自定义 Loader

Loader 本质上是一个导出为函数的 JavaScript 模块,webpack 内部会调用这个函数,然后将上一个 loader 产生的结果传递进去。

这个导出的函数中需要用到 this,所以说导出的不能是一个箭头函数。

  1. // loader/customer-loader.js
  2. module.exports = function(content) {
  3. // ...
  4. }

加载 Loader 的路径

一般在使用一个 loader 时方式如下:

{
    use: [
    // 直接指定 loader 的名称
      'style-loader',
    'css-loader'
  ],
}
使用上面这种第三方 loader 时,直接指定的就是 loader 的名称,webpack 会去 node_modules 中查找这个 loader,而我们自定义的 loader 就不能直接这样写了,解决这个问题的方法有两种。

方法一:配置相对路径
可以直接配置一个相对路径,但是这个相对路径是基于 webpack.context 这个属性的,所以说一定要确认好 content 的路径。

// webpack.config.js
module.exports = {
  context: __dirname,
  module: {
      rules: [
      {
          test: /\.js$/i,
        use: [
          {
              loader: './loaders/customer-loader', // .js 后缀可加可不加
          },
        ],
      },
    ],
  },
}

方法二:配置 resolveLoader 属性:
第三方 loader 可以去 node_module 中查找就是因为 resolveLoader.module 这个配置,这个配置的默认值就是 node_modules,我们也可以配置其他路径。

// webpack.config.js
module.exports = {
  resolveLoader: {
    modules: ['node_modules', './loaders'],
  },
}

Loader 的执行循序

在自定义 loader 的模块中,还可以导出一个 pitch 的函数,也成为 pitch-loader(默认导出的为 normal- loader),如下:

// loader/customer-loader.js
module.exports = function(content) {
    // ...
}

// 在上面的函数中挂载了一个 pitch 函数
module.exports.pitch = function() {
    // ...
}
在执行 loader 时会优先执行顺序 pitchLoader,然后再倒序指定 normalLoader,一般只会用到 normalLoader,所以这也是为什么说 loader 是从后往前执行的原因。

enforce

enforce 可以控制 loader 的执行顺序,一共有两个值:

  - pre
  - post

默认所有 loader 为 normal,如果是在行内指定的 loader 为 inline。

PitchLoader 的执行顺序是:
post, inline, normal, pre。

NormalLoader 的执行顺序是:
pre, normal, inline, post。

如果要控制一个 loader 的执行顺序需要写多个 rule:

module.exports = {
    module: {
      rules: [
      {
          test: /\.js$/i,
        use: 'babel-loader',
      },
      {
        test: /\.js$/i,
        use: 'customer-loader',
        enforce: 'pre', // 优先执行
      },
    ],
  },
}

异步 Loader

    自定义 Loader 导出的函数默认是同步的,且必须把 content 再返回,但是如果在这个函数中出现异步情况时,这时需要先调用 `this.async` 获取一个 callback,这个 callback 第一个参数为错误对象,没有则传递 null,第二个是处理的结果,使用示例如下:
module.exports = function(content) {
    const callback = this.async();

  setTimeout(() => {
      callback(null, content);
  }, 2000);
}

获取传递配置

在使用 loader 时可能会传递一些配置项,如果想要获取这些配置项,我们可以借助一个库 loader-utils

安装依赖:

npm i loader-utils -D
获取配置:
const { getOptions } = require('loader-utils');

module.exports = function(context) {
    const options = getOptions(this);
  // options 就是传递的配置了
}

配置参数校验

schema-utils 这个库可以帮助我们完成传递的配置参数校验,查看是否正确的传递了对应的配置。

安装依赖:

npm i schema-utils -D
创建 schema,schema 是一个 json 文件:
{
  "type": "object",
  "properties": {
    "preset": {
      "type": "array",
      "description": "请传递 preset"
    },
    "test": {
      "type": "boolean",
      "description": "请传递 test"
    }
  }
}
修改 loader:
const { validate } = require('schema-utils');
const { getOptions } = require('loader-utils');
const schema = require('../schema/babel-loader-schema.json');

module.exports = function(content) {
  const options = getOptions(this);
  // 校验
  validate(schema, options);
  return content;
}
如果传递的 test 属性不是一个 boolean 时,就会报错了:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1561260/1618911024356-f8b62ac0-8c12-4dc2-9488-35d89c0414de.png#clientId=u3ff4fca4-5359-4&from=paste&height=204&id=u774e32a9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=204&originWidth=1120&originalType=binary&size=41983&status=done&style=stroke&taskId=ub9538fb8-54b2-49f7-886e-be133ee7a4f&width=1120)

自定义一个简单的 Babel-loader

首先要安装 babel 依赖:

npm i @babel/core @babel/preset-env
创建 babel-loader:
// loaders/babel-loader.js
const babel = require('@babel/core');
const { getOptions } = require('loader-utils');

module.exports = function(content) {
  const callback = this.async();
  const options = getOptions(this);
  babel.transform(content, options, (err, result) => {
    if (err) {
      callback(err);
    } else {
      callback(null, result.code);
    }
  });
}
使用:
// webpack.config.js
module.exports = {
    module: {
      rules: [
      {
          test: /\.js$/i,
        use: [
          {
              loader: 'babel-loader',
            options: {
                presets: [
                ['@babel/preset-env'],
              ],
            },
          },
        ],
      },
    ],
  },
  resolveLoader: ['node_modules', './loaders'],
}