webpack自定义loader

125 阅读1分钟

前言

webpack本身只能处理js和json文件,loader的作用是对特定类型的模块进行转换,我们在开发过程中会使用很多loader,比如css-loader、style-loader、babel-loader等等。这里我们来学习一下如何自定义一个自己的loader。

loader的机制

loader本质上是导出为函数的javascript模块。loader runner会调用此函数,然后将上一个loader产生的结果或者资源文件传入进去。函数中的this作为上下文会被webpack填充。

/**
*
* @param {string/Buffer} content源文件的内容
* @param {object}[map] sourcemap相关的数据
* @param {any} [meta] 元数据,可以是任何内容
*/
module.exports = function (content, map, meta) {
  // 你的 webpack loader 代码
}
​

loader的执行顺序

loader总是从右到左被调用。

文档结构如下:

1679473805602.png

// webpack.config.js
// ......
resolveLoader:{
    modules: ["./src/loaders","node_modules"]    // 规定从哪些路径下解析loader包
},
 module: {
    rules: [
      {
        test: /.js$/,
        use: ["a-loader", "b-loader", "c-loader"],
      },
    ],
  },
​
// ......
      
// 打印结果
// c-loader执行
// b-loader执行
// a-loader执行

pitching loader

其实loader上还有pitch方法,执行顺序与loader相反。

1679475199372.png

// a-loader.js
const loader = function (content, map, meta) {
  console.log("a-loader执行");
  return content;
};
​
loader.pitch = function () {
  console.log("a-loader pitch执行");
};
​
module.exports = loader;
​
// b-loader.jsconst loader = function (content, map, meta) {
  console.log("b-loader执行");
  return content;
};
​
loader.pitch = function () {
  console.log("b-loader pitch执行");
};
​
module.exports = loader;
​
// c-loader.jsconst loader = function (content, map, meta) {
  console.log("c-loader执行");
  return content;
};
​
loader.pitch = function () {
  console.log("c-loader pitch执行");
};
​
module.exports = loader;
​
// 打印结果
// a-loader pitch执行
// b-loader pitch执行
// c-loader pitch执行
// c-loader执行      
// b-loader执行
// a-loader执行

如果loader在pitch方法中返回一个结果,那么这个执行过程会回过身来,并跳过剩下的loader。实际应用中,可以好好运用这个特点。

同步的loader

这个loader必须通过return或者this.callback来返回结果,交给下一个loader来处理。this.callback方法则更加灵活,因为它允许传递多个参数,不仅仅是content。

module.exports = function (content,map,meta){
    return content;
}
​
module.exports = function (content,map,meta){
    this.callback(null,content,map,meta); 
    // 第一个参数必须是error或者null
    // 第二个参数是一个string 或者 Buffer
    // 第三个和第四个参数是可选的
}

异步的loader

有时候我们使用Loader时会进行一些异步的操作;我们希望在异步操作完成后,再返回这个loader处理的结果,这个时候就要使用异步的loader。

对于异步loader,使用this.async来获取callback函数:

module.exports = function(content,map,meta) {
    const callback = this.async();
    
    setTimeOut(function(){
        console.log("loader exec~",content);
        callback(null,content);
    },2000)
}

案例

自定义babel-loader

// babel-loader.jsconst babel = require("@babel/core");
​
module.exports = function (content) {
  // 1.使用异步loader
  const callback = this.async()
​
  // 2.获取options
  let options = this.getOptions()
  // 如果webpack没有配置,则使用babel配置文件
  if (!Object.keys(options).length) {
    options = require('../babel.config')
  }
​
  // 使用Babel转换代码
  babel.transform(content, options, (err, result) => {
    if (err) {
      callback(err)
    } else {
      callback(null, result.code)
    }
  })
};
​
​
// webpack.config.js
// ......
module: {
    rules: [
      {
        test: /.js$/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env"],
            },
          },
        ],
      },
    ],
  },
​