loader 本质上是导出为函数的 JavaScript 模块。loader runner 会调用此函数,然后将上一个 loader 产生的结果或者资源文件传入进去。函数中的 this 作为上下文会被 webpack 填充,正因为此,所有loader函数不能是箭头函数。
如果是单个处理结果,可以在 同步模式 中直接返回。如果有多个处理结果,则必须调用 this.callback()。在 异步模式 中,必须调用 this.async() 来告知 loader runner 等待异步结果,它会返回 this.callback() 回调函数。随后 loader 必须返回 undefined 并且调用该回调函数。这里先简单介绍下this.callback函数:
# this.callback
可以同步或者异步调用的并返回多个结果的函数。预期的参数是:
/*
第一个参数必须是 Error 或者 null
第二个参数是一个 string 或者 Buffer。
可选的:第三个参数必须是一个可以被 this module 解析的 source map。
可选的:第四个参数,会被 webpack 忽略,可以是任何东西(例如一些元数据)。
*/
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
一个loader方法可以接收三个参数。起始 loader 只有一个入参:资源文件的内容。compiler 预期得到最后一个 loader 产生的处理结果。这个处理结果应该为 String 或者 Buffer(能够被转换为 string)类型,代表了模块的 JavaScript 源码。
/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function webpackLoader(content, map, meta) {
// 你的 webpack loader 代码
}
# 编写同步loader
有了上面的基础知识,下面我们开始来编写一个简单的loader来实现字符串大写转小写。
loaders/lower-case-loader.js
module.exports = function (source) {
return source.toLowerCase()
}
src/index.js
'FANGFEIYUE'
下面我们来使用我们编写的loader,resolveLoader仅用于配置解析 webpack 的 loader 包,可以为 loader 设置独立的解析规则。
webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules:[
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'lower-case-loader'
}
}
]
},
resolveLoader: {
// 配置别名,可以通过配置别名的方式来替换初始模块路径
// alias: {
// "lower-case-loader": path.resolve(__dirname,'./loaders/lower-case-loader.js')
// }
// 告诉 webpack 解析模块时应该搜索的目录, 前面的目录优先于 node_modules
modules: [path.resovle(__dirname, './loaders'), 'node_modules']
}
};
查看打包后的结果:
dist/main.js
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ (() => {
eval("'fangfeiyue'\n\n//# sourceURL=webpack://loader/./src/index.js?");
/***/ })
/******/ });
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./src/index.js"]();
/******/
/******/ })()
;
# 编写异步loader
在 异步模式 中,必须调用 this.async() 来告知 loader runner 等待异步结果,它会返回 this.callback() 回调函数。随后 loader 必须返回 undefined 并且调用该回调函数。
loaders/lower-case-loader.js
module.exports = function (source) {
const callback = this.async()
setTimeout(() => {
callback(null, source.toLowerCase())
}, 1000)
}
# 向loader中传入参数
在平常的开发中,我们可以使用options来给loader配置参数,那我在我们自己实现的loader中如何给loader传递配置参数呢?
webpack.config.js
//...
module: {
rules:[
{
test: /\.m?js$/,
use: {
loader: 'lower-case-loader',
options: {
content: '123'
}
}
}
]
}
//...
webpack5之前可以使用loader-utils
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const opt = loaderUtils.getOptions(this) || {};
console.log(opt) // { content: '123' }
return source.toLowerCase()
}
从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法
module.exports = function (source) {
const opt = this.getOptions() || {}
console.log(opt) // { content: '123' }
return source.toLowerCase()
}
参考: