上一篇文章从分析style-loader源码入手让大家了解了webpack loader 工作原理,这一篇咱们手写一个简单的webpack loader 。
url-loader这个loader想必大家都用过,小图片转base64。今天咱们就写一个简单版的url-loader。
loader编写原则
我们先来回顾下loader编写原则
单一原则: 每个
Loader只做一件事;
链式调用:Webpack会按顺序链式调用每个Loader;
统一原则: 遵循Webpack制定的设计规则和结构,输入与输出均为字符串,各个Loader完全独立,即插即用;
基础点
1、缓存:提高执行效率上,如何处理利用缓存是极其重要的。 Webpack,Hot-Replace 以及 React Hot Loader 也充分地利用缓存来提高编译效率。 Webpack Loader 同样可以利用缓存来提高效率,并且只需在一个可缓存的 Loader 上加一句 this.cacheable(); 很多 Loader 都是可以缓存的,但也有例外。可以缓存的 Loader 需要具备可预见性,不变性等等。
2、异步:异步并不陌生,当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步。在一个异步的模块中,回传时需要调用 Loader API 提供的回调方法 this.async(),使用起来也很简单
module.exports = function(source) {
var callback = this.async();
// 做异步的事
doSomeAsyncOperation(content, function(err, result) {
if(err) return callback(err);
callback(null, result);
});
};
3、pitching Loader : 一般 Loader 从右到左链式执行。这种说法实际说的是 Loader 中 module.exports 出来的执行方法顺序。在一些场景下,Loader 并不依赖上一个 Loader 的结果,而只关心原输入内容。这时候,从左到右执行并没有什么问题。在 Loader 的 module 中,可使用 module.exports.pitch = function(); pitch 方法在 Loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间传递。具体的实践可以查看 style-loader,里面就有使用到 pitch。
module.exports.pitch = function(remaining, preceding, data) {
if(somothingFlag()) {
return "module.exports = require(" + JSON.stringify("-!" + remaining) + ");";
}
data.value = 1;
};
4、默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader。module也可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw = true; 这样内容将会以 raw Buffer 的形式传入到 loader 中了。
5、Loader 中的 this
- data pitch loader 中可以通过 data 让 pitch 和 normal module 进行数据共享。
- query 则能获取到 Loader 上附有的参数。 如 require("./somg-loader?ls"); 通过 query 就可以得到 "ls" 了。
- emitFile emitFile 能够让开发者更方便的输出一个 file 文件,这是 webpack 特有的方法,使用的方法也很直接
好啦,了解了上面你的基础点,我们就能来手写个简单的webpack loader了;
loader 配置
其实配置适合url-loader一样的, 匹配图片:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: ['./my-loader/img-loader?limit=10240 '],
include: [srcDir],
}
编写自己的loader
这边只是给大家做个示范,就不上传到npm了。我们先在根目录创建img-loader.js文件,定义loader方法,然后导出,因为我们匹配的是图片,记住要用上面说过的 module.exports.raw = true; Buffer 的形式传入到 loader 中。
function loader(source) {
return xxx
}
loader.raw = true; //把文件转成二进制流
module.exports = loader;
然后就开始填充我们loader的功能。首先载入工具库,为了后续使用。第一步先验证options是否符合类型,第二步获取参数,然后替换传入的资源文件字符串。按照这个思路,请看我们的代码
//获取参数的工具箱
const _loaderUtils = require("loader-utils");
const _schemaUtils = require("schema-utils");
//获取文件标识
const mime = require("mime");
const schema = {
type: "object",
properties: {
limit: {
type: ["number", "string"]
},
}
};
function getFilePath(buffer) {
//生成一个新的图片, 添加到最终资源中, 并返回路径
//interpolateName获取文件的hash值,并插入值,生成唯一的文件名
var filename = _loaderUtils.interpolateName(this, "[contenthash:5].[ext]", {
content: buffer
})
// emitFile 能够让开发者更方便的输出一个 file 文件,这是 webpack 特有的方法,使用的方法也很直接
//发射文件,会在dist目录下面生成一个文件
this.emitFile(filename, buffer)
return filename;
}
function loader(source) {
//获取参数
let options = (0, _loaderUtils.getOptions)(this);
(0, _schemaUtils.validate)(schema, options, 'img-loader');
const { limit = 0 } = options
//获取图片的类型
const mimetype = mime.getType(this.resourcePath);
//如果文件大小小于limit,则使用base64
if (source.length < limit) {
//组装base64
let base64 = `data:${mimetype};base64,${source.toString('base64')}`;
//最后必须修改文件的路径
return `module.exports = ${JSON.stringify(base64)}`;
} else {
//使用file-loader
src = getFilePath.call(this, source);
return `module.exports=\`${src}\``;
}
}
loader.raw = true; //把文件转成二进制流
module.exports = loader;
如果获取的图片文件的大小小于limit参数,则把传入的文件流转为base64然后返回,如果不大于就生成一个新的图片, 添加到最终资源中并返回。
最后运行起来结果也是正确的,大家也可自行实践一下。
到这里基本就结束了,觉得有帮助,不妨点个赞,不足之处,还望斧正~