开始之前,先介绍一下loader,下面是webpack中文网
对loader
的描述
所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的
this
上下文将由 webpack 填充,并且 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。
loader的参数以及返回值(同样摘抄自webpack中文网
)
第一个 loader 的传入参数只有一个:资源文件(resource file)的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string) ,代表了模块的 JavaScript 源码。另外还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。
loader在webpack中执行的时机
本图片源于网络
loader的分类
loader
分为同步loader
和异步loader
。loader
本质是一个函数
。所以这两种loader
的区别,可以理解为只是返回值不同(只是为了方便理解的类比)
。实际操作并不是
简单的两种返回值。
同步loader
同步loader
比较简单,无论是 return
还是 调用this.callback
都可以同步地返回转换后的 content 内容。
- 使用
return
这种比较直观,就一个普通函数,执行完成返回一个值。例如:
function loader(source) {
// do anything
return source;
}
- 使用
this.callback
this.callback
的函数参数类型是
// 后两个参数是非必传的
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
这种比return
方式更灵活,可以返回多个值。例如:
function loader(source) {
// do anything
this.callback(null, source, map, meta);
}
注意: 函数中调用callback
后,return
的返回值将被忽略。
异步loader
异步loader
只有一种处理方式。对于异步 loader,使用 this.async
来获取 callback
函数。这里的callback
函数,和同步loader
里面的参数是一样的。
function loader (source) {
console.log('--- loader begin ----');
console.log(source);
console.log('--- loader end ----\n');
const callback = this.async();
// do anything
callback(null, source);
}
module.exports = loader;
注意几种特殊情况:
- 函数中调用
this.async
后,return
的返回值将被忽略。 - 函数中调用
this.async
后,再调用this.callback
,则会将当前loader
当做同步loader
来处理。 - 函数中调用
this.async
后,再调用this.callback
且立即调用this.async返回的callback
。程序会报错。
至此,我们就能开始着手写一个简单的loader
了。下面便开始,着手写几个loader
banner-loader
这个loader
的功能是给每一个文件头部,添加一行段注释,标明作者。例如:
/**
** @author laibao101
** @time 2019-06-04 19:57:20
**/
// source code
功能分析:这个loader
,并没有任何的异步操作,所以我们使用同步loader
即可。注释方面,暂且先写死,通过参数配置的,后续再做。
function loader (source) {
const time = new Date().toLocaleString()
return `
/**
** @author laibao101
** @time ${time}
**/
${source}
`
}
使用模板字符串,将注释代码写进去,返回即可。这样一个简单的loader
就完成了。
异步的banner-loader
这个loader
是为了写一个异步loader
而写的。功能是从一个文件中读取模板,给文件添加banner
// banner-loader
const path = require('path');
const fs = require('fs');
const baseUrl = process.cwd();
const configTextPath = "bannerConfig.txt";
// 拼接完整的配置文件路径
const fullConfigPath = path.resolve(baseUrl, configTextPath);
// 根据模板中的占位符,来获取数据
const configItemMap = {
author: "laibao101",
time: new Date().toLocaleString()
};
// 匹配占位符
const reg = /{(\w+)}/gi;
function loader (source) {
const callback = this.async();
fs.readFile(fullConfigPath, (err, data) => {
if (err) {
// 如果读取文件失败,返回错误
callback(err);
}
// 获取文件内容
const template = data.toString();
// 根据模板,修改占位符数据,完成banner
const banner = template.replace(reg, (match, key) => {
return configItemMap[key] || key;
});
// 拼接返回值
const ret = `
${banner}
${source}
`
callback(null, ret);
});
}
module.exports = loader;
// bannerConfig.txt
/**
** @author {author}
** @time {time}
**/
可配置参数的同步banner-loader
前面写到的banner-loader
,参数都是loader
内部定的。如果需要从webpack.config.js
中获取参数,则可以使用webpack
提供的this.query
参数来获取loader配置中的options
参数。这在loader
中使用的很频繁。
例如:
babel-loader
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
url-loader
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
}
}
所以,我们将上面的banner-loader,修改一下,将参数改为从loader配置中获取
// banner-loader
function loader (source) {
const time = new Date().toLocaleTimeString();
const { author = '' } = this.query || {}
return `
/**
** @author ${author}
** @time ${time}
**/
${source}
`
}
module.exports = loader;
// webpack.config.js
{
...
options: {
author: "laibao101"
}
}
这样,我们就可以实现参数从配置文件中获取的loader了。
完整代码
下一篇
介绍 raw-loader
、pitch-loader
。并各自编写一个demo