loader不难,loader不难,loader不难。默念三遍,而后开始。javascript
loader
这个东西配置 webpack
的时候常常用到,刚开始会让人以为有一种神秘感。css
看了文档以后才发现,loader 只是一个导出为函数的 JavaScript 模块
。对,不要怕它,它只是个函数模块。java
webpack
内部用了loader-runner 这个开源库,在webpack
项目下lib\NormalModule.js
中的doBuild
方法中调用。loader-runner
具体的内部逻辑还不是很清晰,这里先不说了。webpack
咱们常常用 loader
来对模块的源代码进行转换。例如ES6+
转ES5
,sass
转css
等等。git
loader
的常见用法以下:github
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
的运行顺序是从右到左,后面的先执行,是反着来的。web
下一个执行的loader
会接收上一个执行的loader
后的返回值做为参数继续处理。相似gulp中的pipe管道流
。npm
到这里应该能理解为何css
相关的loader
,都是style-loader
放在最前面吧。gulp
每一个 loader
都支持加一个 Pitch
函数,我这里把它理解成前置函数。api
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
只是一个 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工具库来处理。
module.exports = function(content, map, meta) {
// 作一些同步的操做
let result = someSyncOperation(content);
// 将资源返回
return result;
};
复制代码
异步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);
});
};
复制代码
但愿本文能对读者有帮助。
若是有错误的地方,还请指出。
谢谢阅读。