Webpack Loaders插件编写
本文主要目的是让阅读者能简单的实现一个loader插件,并且了解loader基本运行机制。
1. 了解什么是loader
官方解释:loader 用于对模块的源代码进行转换。loader 可以使你在 import
或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import
CSS文件!
而我对此定义就是,loader用于处理webpack加载的每个文件
。
2. 如何加载一个loader
在为webpack做配置的webpack.config.js
文件中,loader
存放于导出的module.rules
中。
module.exports = {
module:{
rules:[
{...loader配置项},
{...loader配置项},
{...loader配置项}
]
}
}
webpack支持配置
、内联
、CLI
方式。个人推荐使用配置
方式,来为webpack配置一个loader(具体三者区别,请查看文末使用loader)。
现在,来为webpack
配置一个CSS
解析器用于代码运行时候将打包后CSS
代码通过<style>
添加到页面头部。
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' }
]
}
]
}
这个配置中,test 表示如果通过了这个正则表达式文件,则使用use数组中的loader
对文件进行处理。在这里有个规则需要说明一下,loader加载解析器的先后顺序是从右往左
或从下往上
。
执行webpack
则默认开始执行编译,如果项目中包含了CSS
文件,会调用style-loader
对他进行处理。
另外注意的是loader
是运行在node
环境下的,也就是说在我们编写的loader
是可以调用node
上下文与node
包来进行各种处理的,而loader
与插件
的区别在于,loader
用于解析模块,而插件
则是介入了整个webpack
生命周期。
3. 编写第一个loader
最近在处理个业务需求,就是将less文件中的图片放置到阿里的CDN上,优化打包速度,减小包体积,并且利用浏览器缓存机制缓存图片。
创建一个loader.js
文件,用node
导出方式,编写一个空的loader
。
module.exports = function () {
}
// 其实也支持 方式,看喜欢用那种方式吧。
export default function () {
}
现在遇到了第一个问题,就是,如何将这个loader
文件加入到webpack编译过程中。
官方文件告诉了我们这个结果:
loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是
npm install
,node_modules
)解析。
修改配置文件为下面格式:
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: path.resolve(__dirname,'./loader.js') }
]
}
]
}
注意这一行{ loader: path.resolve(__dirname,'./loader.js') }
,我们将整个loader的路径传入给webpack。webpack会使用此路径载入这个loader
。就如同const loader = require(path.resolve(__dirname,'./loader.js'))
一样。
OK,现在我们知道了webpack运行loader的小机制之后,开始着手编写这个loader
。
const postcss = require('postcss');
const fs = require('fs');
const file = [];
const errlist = [];
module.exports = function (source) {
postcss([])
.process(source)
.then(value => {
...
});
return source;
}
由于我们要识别整个CSS中包含的规则,所以使用Postcss
来处理css
取得css
文件的AST
, source
为文件内容,传入内容可能是正常的文本内容,也有可能是二进制流(默认均为utf-8编码传入,需要buffer请参考API文档设置 raw)
。
我们根据需求,从AST
中得到自己想要的信息,再把source
原模原样传出去,交给下一个loader
。
现在,我们来测试一下这个解析器是否能正常工作。
运行webpack
之后,你会看到类似promise reject
之类的异常信息,loader
并没有正常执行。这是因为Postcss
是异步执行逻辑,而我们的return source
则是同步模式所使用的规则。
现在修改loader
为异步模式。
const postcss = require('postcss');
const fs = require('fs');
// 保存图片资源地址
const file = []
module.exports = function (source) {
const callback = this.async();
// 加载一个空的 postcss 配置文件
postcss([])
// 将source(css text)解析成css AST
.process(source)
.then(value => {
// 遍历所有css name,如: .classname #idname 等等
value.root.walkRules(rule => {
// 遍历当前css name下包含的所有的规则
rule.walkDecls(decl => {
// {prop,value}
// prop:规则名
// value: 规则值
// 如 backaground:red; 则内容格式如下
// {prop:'background',value:'red'}
if (decl.prop === 'background-image' || decl.prop === 'background') {
// 取得图片地址,并且清洗干净
// 接着push如存放图片地址的数组
let url = /url\(.*?\)/.exec(decl.value);
if (url && url.length > 0) {
// eslint-disable-next-line prefer-destructuring
url = url[0];
url = url.replace('url(', '').replace(')', '');
url = url.replace(/"/gi, '').replace(/\'/gi, '');
if (!file.includes(url)) {
file.push(url);
}
} else if (decl.value.indexOf('url') > -1) {
// 容错机制
file.push(decl.value);
}
}
});
});
// 保存输出内容
fs.writeFileSync(
'./cssImageFile.txt',
file.join('\n')
);
return callback(source);
});
}
这样,我们就完成了一个异步处理的loader
。
在this
上,webpack
挂接了很多相关的信息,如sourcepath
、async
。编写loader
时,请及时查阅loader API
文档。
参考
官方文档:使用loader
https://www.webpackjs.com/concepts/loaders/
官方文档:编写一个loader
https://www.webpackjs.com/contribute/writing-a-loader/
官方文档:loader API