前言
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总是从右到左被调用。
文档结构如下:
// 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相反。
// 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.js
const loader = function (content, map, meta) {
console.log("b-loader执行");
return content;
};
loader.pitch = function () {
console.log("b-loader pitch执行");
};
module.exports = loader;
// c-loader.js
const 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.js
const 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"],
},
},
],
},
],
},