Webpack自定义Loader

1,597 阅读2分钟

本笔记来自Coderwhy老师Webpack5教程

  • Loader本质上是一个导出为函数的JavaScript模块;

  • loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去;

  • Loader函数会接收三个参数

    • content:资源文件的内容
    • map:sourcemap相关的数据
    • meta:一些元数据
module.exports = function(content,map,meta){
        //函数处理代码....
    return content
}

使用自己创建的Loader

module.exports = {
    module: {
        rules: [
            {
                test: /.js$/,
                //直接写loader函数的文件地址
                use: ['./loader/myLoader.js']
            }
        ]
    }

resolveLoader属性

  • 配置resolveLoader属性,可以告诉webpack,我们可以直接去哪个文件夹内寻找loader文件,这样我们就可以像导入node_modules文件夹下的Loader一样,采用十分简洁的形式。
module.exports = {
    module: {
        rules: [
            {
                test: /.js$/,
                //直接写loader函数的文件名称
                use: ['myLoader']
            }
        ]
    }
    resolveLoader: {
        //此处配置从哪里找loader
        modules: ['./loaders','node_modules']
    }
}

Loader的执行顺序

  • NormalLoader的定义
module.exports = function(content,map,meta){
        //函数处理代码....
    return content
}
  • PitchLoader的定义
module.exports.pitch = function(content,map,meta){
        //函数处理代码....
    return content
}
  • 从代码上我们可以看到,Webpack在使用loader时
  • 首先执行PitchLoader,并执行loaderContext.loaderIndex++操作
  • 然后执行NormalLoader并执行loaderContext.loaderIndex--操作
  • 其实这也是为什么loader的执行顺序是相反的

Webpack执行原理.png

改变Loader的执行顺序

  • 拆分成多个Rule对象,通过enforce改变执行顺序

  • enforce一共有四种方式

    • 默认所有的loader都是normal
    • 在行内设置的loader是inline(在前面将css加载时讲过,import 'loader1!loader2!./test.js')
    • 也可以通过enforce设置 pre 和 post;
  • PitchingLoader的执行顺序:post, inline, normal, pre;

  • NormalLoader的执行顺序:pre, normal, inline, post;

同步Loader与异步Loader

同步的loader

  • 默认创建的Loader就是同步的Loader;

  • 这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理;

    • 通常在有错误的情况下,我们会使用 this.callback;
  • this.callback的用法:

    • 第一个参数必须是 Error 或者 null
    • 第二个参数是一个 string或者Buffer
module.exports.pitch = function(content,map,meta){
        //函数处理代码....
    this.callback(null,content)
    //return content
}

异步的loader

  • 在loader内使用this.async(),来把loader变成异步loader
module.exports.pitch = function(content,map,meta){
    const callback = this.async();
        //函数处理代码....
    setTimeout(() => {
        callback(null,content)
    })
}

loader参数

  • 获取传入的参数
  • 使用库 loader-utils
//webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /.js$/,
                //直接写loader函数的文件名称
                use: [
                    {
                        loader: 'myloader',
                        options: {
                            test: '测试获取option'
                        }
                    }
                ]
            }
        ]
    }
    resolveLoader: {
        //此处配置从哪里找loader
        modules: ['./loaders','node_modules']
    }
}
​
//loader.js
const { getOptions } = require('loader-utils')
​
modules.exports = function(content){
    
    const options = getOptions(this)
    //处理函数...
    
    return content
}

参数校验

//loader-schema.json
{
  "type": "object",
  "properties": {
    "test": {
      "type": "string",
      "description": "这是一个测试字段"
    },
  "additionalProperties": true
}
//loader.js
const { getOptions } = require('loader-utils')
const { validate } = require('schema-utils')
​
const schema = require(./loader-schema.json)
​
modules.exports = function(content){
    
    const options = getOptions(this)
    //处理函数...
    validate(option,schema)
    
    return content
}

Babel-loader案例

//babel-schema.json
{
  "type": "object",
  "properties": {
    "presets": {
      "type": "array"
    },
  "additionalProperties": true
}
const babel = require("@babel/core");
const { getOptions } = require("loader-utils");
const { validate } = require('schema-utils')
​
const schema = require(./babel-schema.json)
​
module.exports = function(content) {
  // 0.设置为异步的loader
  const callback = this.async();
​
  // 1.获取传入的参数
  const options = getOptions(this);
  validate(option,schema)
​
  // 2.对源代码进行转换
  babel.transform(content, options, (err, result) => {
    if (err) {
      callback(err);
    } else {
      callback(null, result.code)
    }
  })
}