开篇
loader不难,loader不难,loader不难。默念三遍,然后开始。
loader 简介
loader 这个东西配置 webpack 的时候经常用到,刚开始会让人觉得有一种神秘感。
看了文档之后才发现,loader 只是一个导出为函数的 JavaScript 模块。对,不要怕它,它只是个函数模块。
webpack内部用了loader-runner 这个开源库,在webpack项目下lib\NormalModule.js中的doBuild方法中调用。loader-runner具体的内部逻辑还不是很清晰,这里先不说了。
用法
我们经常用 loader 来对模块的源代码进行转换。例如ES6+转ES5,sass转css等等。
loader 的常见用法如下:
rules: [{
test: 'xxx',
use: ['a-loader', 'b-loader', 'c-loader']
}]
rules: [{
test: 'xxx',
use: 'xx-loader'
}]
rules: [{
test: 'xxx',
user: [
{ loader: 'a-loader' },
{
loader: 'b-loader',
options: {}
}
]
}]
loader的运行顺序是从右到左,后面的先执行,是反着来的。
下一个执行的loader会接收上一个执行的loader后的返回值作为参数继续处理。类似gulp中的pipe管道流。
到这里应该能理解为什么css相关的loader,都是style-loader放在最前面吧。
loader 前置函数
每个 loader 都支持加一个 Pitch 函数,我这里把它理解成前置函数。
loader本身是按相反顺序执行,但在(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。
可以参考官网给出的例子:
如果是这样的配置的loader
rules: [{
test: 'xxx',
use: ['a-loader', 'b-loader', 'c-loader']
}]
内部的执行逻辑是这样的:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
注意:如果某个loader 的 pitch 方法有返回值,那么webpack会忽略 当前loader 和 剩下的loader,直接执行之前已经调用过 pitch 方法的loader。
关于 pitch 的详细解析,可以参考webpack中文文档;
写个简单的loader
我们已经知道了 loader 只是一个 javascript函数模块而已,而且有固定的参数格式(官网有介绍):
module.exports = function(
content, // 资源文件的内容,可能是 String 或 Buffer
map, // sourceMap,可以不写
meta // 可以是任何东西(例如元数据),可以不写
){
// do something
}
接下来我们就可以自己写个简单的loader玩一下,
这里我写一个my-img-loader,用于将图片转为base64编码。
先安装mime工具包,用来获取资源类型
npm i -D mime
然后在项目根目录下新建一个 loaders 文件夹,然后新建一个 my-img-loader.js,编写代码:
// 用来获取资源的类型
const mime = require('mime');
module.exports = function (content, map, meta) {
// 将资源内容转换为 Buffer
if (typeof content === 'string') {
content = Buffer.from(content);
}
// this.resourcePath 表示当前资源的绝对路径
let mimetype = mime.getType(this.resourcePath);
// 生成 base64 码
let base64 = `data:${mimetype || ''};base64,${content.toString('base64')}`;
// 如果是处理顺序排在最后一个的 loader,那么它的返回值将最终交给 webpack 的 require
// 也就是说这里要返回一串 CommonJs 规范的 JS 代码
// 因为我这里只用到一个loader,所以需要这样设置,不然的话,css里面会出现 background: url([object Ojbect])
// 如果不是最后一个 loader,就不需要 `module.exports` 了
return `module.exports = ${JSON.stringify(base64)}`;
}
// 设置用 Buffer 格式 来传递处理结果,也就是二进制数据
// 我不清楚这里为什么要用 Buffer 格式来传递,不设置的话,显示不了base64图片
// 有知道的读者请告诉下我
module.exports.raw = true;
然后设置 webpack.config.js 的rules,
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: [],
use: ['./loaders/my-img-loader']
// use: {
// loader: 'url-loader',
// options: {
// limit: 10000,
// name: 'img/[name].[hash:7].[ext]'
// }
// }
}
为了方便起见,读者可以下载我之前提交的spa-webpack-demo来更改。
亦或者,直接把代码复制到已有的项目尝试。
然后 npm run dev 看效果。
代码结构如下:
如果想要给 loader 添加 options 参数,可以用 loader-utils工具库来处理。
最后说下 loader 的类型
同步 loader
module.exports = function(content, map, meta) {
// 做一些同步的操作
let result = someSyncOperation(content);
// 将资源返回
return result;
};
异步 loader
异步loader需要先调用this.async(),执行这个方法后,loader-runner内部会将loader识别为异步的,并返回一个callback。
module.exports = function(content, map, meta) {
// 执行 this.async(),告诉 webpack 这是个异步的 loader
let callback = this.async();
// 做一些异步的操作
someAsyncOperation(content, function(err, result) {
if (err) return callback(err);
// 告诉 webpack,loader执行完毕,并把 result 传入,供下一个loader 使用
callback(null, result, map, meta);
});
};
写在最后
希望本文能对读者有帮助。
如果有错误的地方,还请指出。
谢谢阅读。