webpack loaders是webpack实现对非js文件打包的重要工具,本文章主要记录自身对loaders工作流程的学习。
- webpack.config.js中使用loaders
针对特定结尾文件,使用loaders有两种方法,第一种是use
属性配置为数组,包含所需要的loader,第二种方法是use
属性配置为对象,可以进行options
配置。
如果不进行特殊配置,loader知会在node_modules里面查找是否有安装,如果需要执行其他loader,需要在webpack.conf.js
配置resolveLoader
属性:
// <dir_root>/loader/loader1.js
function loader(params) {
console.log("loader1~~~~~");
return params;
}
module.exports = loader;
// <dir_root>/loader/loader2.js
function loader(params) {
console.log("loader1~~~~~");
return params;
}
module.exports = loader;
// <dir_root>/loader/loader3.js
function loader(params) {
console.log("loader1~~~~~");
return params;
}
module.exports = loader;
// <dir_root>/webpack.config.js
module.exports = {
mode: "development",
// JavaScript 执行入口文件
entry: "./src/main.js",
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: "bundle.js",
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, "./dist"),
},
// 先到node_modules里面找,找不到就去loader目录下找
resolveLoader: {
modules: ["node_modules", path.resolve(__dirname, "loader")],
},
devtool: "source-map",
// watch: true,
module: {
rules: [
// 第一种
{
test: /\.js$/,
use: ["loader3", "css-loader2", "less-loader1"],
},
// 第二种
{
test: /\.js$/,
use: {
loader: "loader3",
},
},
{
test: /\.js$/,
use: {
loader: "loader2",
},
},
{
test: /\.js$/,
use: {
loader: "loader1",
},
},
],
},
};
- loader的执行顺序
loader的执行顺序总结为从右到左,从下到上,上述例子中两种配置方式输出都是loader1、loader2、loader3。
如果需要调整顺序,需要loader的enforce
属性。enforce
如果被配置为pre
,则会最新执行,post
则会最后执行,上述例子中第二种方式如果改为:
{
test: /\.js$/,
use: {
loader: "loader3",
},
enforce: 'pre'
},
{
test: /\.js$/,
use: {
loader: "loader2",
},
},
{
test: /\.js$/,
use: {
loader: "loader1",
},
enforce: 'post'
},
输出会变为loader3,loader2,loader1。
根据配置,loader被分为4类: pre,normal,post,iniline。下面介绍inline
- inline-loader
除了在webpack.config.js中配置loader,也可以使用行内loader,在引用一个文件时即指定该文件处理的loader
// <dir_root>/loader/inline-loader.js
function loader(params) {
console.log("inline-loader~~~~~");
return params;
}
module.exports = loader;
//入口文件 <dir_root>/src/main.js
let b = require("inline-loader!./b");
console.log("b");
// <dir_root>/src/b.js
module.exports = "b";
上述配置加上enforce
后输出为
// 处理main.js
loader3
loader2
loader1
// 处理b.js
loader3
loader2
inline-loader
loader1
** 执行顺序:pre normal inline post **
如果不希望执行inline-loader前面的loader,则配置为 let b = require("-!inline-loader!./b");
,具体有关文档参考官方,官方文档中configured loaders
意为通过webpack.config.js配置的文档。
pitch
如果loader有被设置pitch,那么先执行pitch,再执行loader本体。
//loader1
loader.pitch = function(remainingRequest, precedingRequest, data) {
console.log('loader1')
};
//loader2 loader3 类上
按照上述修改loader,去掉对b文件的引用,输出为:
// pitch 中的输出
loader1
loader2
loader3
// loader函数的输出
loader3
loader2
loader1
执行顺序为:loader3.pitch -> loader2.pitch -> loader1.pitch -> 获取资源 ->loader1 -> loader2 ->loader3
如果 pitch 中 loader2.pitch 有返回值 则直接跳到 normal 中的 loader3 中间全部不执行
如果 pitch 中 loader3.pitch 有返回值 则只执行 loader3-pitch
** 注意最后一个执行的 loader 或者说排在第一个的 loader 必须返回 eval 函数可执行的 string or buffer **
- 尝试手写loader
手写loader前首先了解一下什么是loaderContext,对于loader函数,this均被设置为loaderContext,拥有很多自带属性和自带方法。
手写file-loader用于加载文件或图片
loader的基本格式如下:
let loaderUtils = require("loader-utils");
function loader(source) {
}
module.exports = loader;
loaderUtils库是非常常用的处理库,可以不引用,但一般都会用到。
file-loader做到的功能就是将传入的文件重命名为哈希值加上文件扩展名,发送到输出文件夹中,由于处理文件需要输入为二进制,因此需要设置为loader.raw = true
,实现如下:
let loaderUtils = require("loader-utils");
function loader(source) {
// interpolateName获取文件的hash值,并插入值,生成唯一的文件名
let filename = loaderUtils.interpolateName(this, "[hash].[ext]", {
content: source,
});
// this.emitFile为loaderContext中自带的函数,发送文件到输出文件夹
this.emitFile(filename, source);
return `module.exports = "${filename}"`;
}
loader.raw = true; //这样拿到的源码就成了二进制
module.exports = loader;
编写url-loader,该loader可以判断图片文件大小,如果文件太大,则使用file-loader发送,否则将图片转为base64编码
//webpack.config.js
{
test: /\.jpg$/,
use: {
loader: "url-loader",
options: {
limit: 200 * 1024,
},
},
},
// url-loader
let loaderUtils = require("loader-utils");
let fileLoader = require("./file-loader");
let fs = require("fs");
let mime = require("mime");
let path = require("path");
function loader(source) {
let { limit } = loaderUtils.getOptions(this);
// 判断大小
if (limit && source.length < limit) {
// mime 用于获取文件类型
return `module.exports="data:${mime.getType(
source
)};base64,${source.toString("base64")}"`;
} else {
// 返回file-loader处理
return fileLoader.call(this, source);
}
}
loader.raw = true;
module.exports = loader;