如何编写一个loader

loader其实就是个函数,但是不能使用箭头函数

  1. module.exports = function (source) {
  2. return source.replace("LC", "supercll");
  3. };
  1. module: {
  2. rules: [
  3. {
  4. test: /\.js/,
  5. use: [path.resolve(__dirname, "./loaders/replaceLoader.js")],
  6. },
  7. ],
  8. },

image.png
这样其实就完成了一个最简单的loader

配置选项

loader函数中的this.query接受了从use对象中传递的options参数

  1. module.exports = function (source) {
  2. console.log(this.query);
  3. return source.replace("LC", this.query.name);
  4. };
  1. module: {
  2. rules: [
  3. {
  4. test: /\.js/,
  5. use: [
  6. {
  7. loader: path.resolve(__dirname, "./loaders/replaceLoader.js"),
  8. options: {
  9. name: "lc",
  10. },
  11. },
  12. ],
  13. },
  14. ],
  15. },

这样就实现了选项参数传递

解析参数

有时候config传入的参数比较复杂,这个时候就可以使用loader-utils模块来帮助我们解析这些参数

const loaderUtils = require("loader-utils");
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    return source.replace("LC", options.name);
};

常用方法

this.callback

可以额外把源代码,sourceMap或者其他额外的信息返回出去
基本配置

this.callback(
    err: Error | null,
    content: string | Buffer,
    sourceMap?: SourceMap,
    meta?: any
);
const loaderUtils = require("loader-utils");
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const res = source.replace("LC", options.name);
    this.callback(null, res);
};

this.async编写异步loader

如果loader函数中有异步代码时,

const loaderUtils = require("loader-utils");
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const callback = this.async();
    let timer = setTimeout(() => {
        clearTimeout(timer);
        const res = source.replace("LC", options.name);
        callback(null, res);
    }, 2000);
};

多loader打包

    module: {
        rules: [
            {
                test: /\.js/,
                use: [
                    { loader: path.resolve(__dirname, "./loaders/replaceLoader.js") },
                    {
                        loader: path.resolve(__dirname, "./loaders/replaceLoaderAsync.js"),
                        options: {
                            name: "lc",
                        },
                    },
                ],
            },
        ],
    },

resolveLoader配置自动寻找路径

    resolveLoader: {
        modules: ["node_modules", "./loaders"],
    },
    module: {
        rules: [
            {
                test: /\.js/,
                use: [
                    { loader: "replaceLoader" },
                    {
                        loader: "replaceLoaderAsync",
                        options: {
                            name: "lc",
                        },
                    },
                ],
            },
        ],
    },
这样就便于引入自定义loader

实际应用

如果使用一个库的时候,想给这个库添加格外的功能或者异常检测的代码,就可以写一个loader,通过抽象语法树分析所有的代码

  • 比如遇到一个function,就可以给这个function添加trycatch,即时捕获异常
  • 再比如我想要页面有多种语言,这个时候就可以编写loader,把文字放入到占位符中,通过判断全局变量,把所有的文字进行一个全局替换
  • 非常方便批量的一个修改

    如何编写一个plugin

    plugin可以处理模块,
    是一个钩子函数,在指定的周期阶段生效
    plugin不同于loader,他是一个
    类中有个apply方法,有一个参数compiler,里面传入的是webpack 的实例

基本格式

class CopyRightWebpackPlugin {
    constructor() {}

    apply(compiler) {}
}
module.exports = CopyRightWebpackPlugin;

compiler里包含了hooks
hooks属性里包含了webpack各种周期钩子函数

compiler钩子

emit

AsyncSeriesHook
生成资源到 output 目录之前。
参数:compilation

这是个异步钩子,需要异步包装一下
tapAsync包装为异步,第一个参数是plugin类的名字

class CopyRightWebpackPlugin {
    apply(compiler) {
        // hooks属性里包含了webpack各种周期函数
        compiler.hooks.emit.tapAsync("CopyRightWebpackPlugin", (compilation, cb) => {
            // compilation存放了这次的配置内容,cb
            console.log(123);
            cb();
        });
    }
}
module.exports = CopyRightWebpackPlugin;

钩子函数的参数

  • compilation,不同于compiler,complication存放了这次打包的所有配置
    • assets
  • cb回调函数
class CopyrightWebpackPlugin {
    apply(compiler) {
        // hooks属性里包含了webpack各种周期函数
        compiler.hooks.emit.tapAsync("CopyRightWebpackPlugin", (compilation, cb) => {
            // compilation存放了这次的配置内容,cb
            // console.log(compilation.assets)
            compilation.assets["copyright.txt"] = {
                source: function () {
                    return "copyright by all lc";
                },
                size: function () {
                    // 文件大小
                    return 20;
                },
            };
            cb();
        });
    }
}
module.exports = CopyrightWebpackPlugin;

compile

这是一个同步的周期钩子
SyncHook
一个新的编译(compilation)创建之后,钩入(hook into) compiler。
参数:compilationParams
同步就使用tap函数包装,并且只需要传入一个参数complication

class CopyrightWebpackPlugin {
  apply(compiler) {
        // hooks属性里包含了webpack各种周期函数

        compiler.hooks.compile.tap("CopyrightWebpackPlugin", complication => {
            console.log("compile");
        });
    }
}

如何快速查看plugin属性

  • 开启node调试工具—inspect
  • —inspect-brk,自动在第一行打一个断点
    {
    "name": "plugin",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js",
      "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "webpack": "^5.1.3",
      "webpack-cli": "^4.1.0"
    }
    }
    
    运行脚本debug,打开webpack官网

image.png

image.png
这样就可以得到各种的属性

总结

  • 编写loader
    • loader其实是一个函数,创建一个loader文件通过module。exports的方式导出,但是不能使用箭头函数
    • webpack传入loader函数的所有信息都保存在this中
      • this.query存放了use中的options属性
      • 可以使用loader-utils来帮助解析这些属性
      • 当我希望loader函数返回异常源码,sourceMap或者其他额外的信息的时候可以使用this.callback
      • 还有编写异步loader的时候不是原生支持的,可以使用this.async来包装为异步函数
    • loader实际应用在于
      • 比如我想给源码中的function 函数都添加上一个异常捕获的功能,tyrcatch
      • 或者说我的页面希望有多种语言展示,那么就可以编写loader实现一个全局替换
      • 非常便于一个批量修改把,达到一个自动化的目的
    • 编写plugin
      • 与loader不同,plugin是一个类,平常使用的时候就能体现出来
      • plugin类中有一个apply方法,方法中有一个参数compiler,compiler中存放的是webpack的一个实例
      • 而compiler中包含了hooks这个属性,hooks中又存放了各种webpack的周期钩子函数,可以调用这些钩子函数来达到在webpack各个周期增加功能的功能
      • 需要注意的是webpack的周期钩子有两种状态,一个是同步的一个是异步的
      • 在使用同步钩子时需要在后边加上tap方法来包装钩子函数,tap的第一个参数是这个插件的名字,第二个参数就是回调函数了
      • 如果是异步钩子的话,则需要使用tabAsync来包装,tabAsync第二个参数回调函数多了一个参数callback,使用的时候需要在最后调用一次这个cb函数达到异步回调的目的