如何简单创建一个webpack loader

常言道授人以鱼不如授人以渔,当开发中某些loader满足不了我们特有的需求时,与其去网上搜罗,不如自己动手写个loader。

假如我们需实现这一个需求,对于html中的每个img标签,假设img标签中都没有alt属性(为了简单),统一给他们加上alt属性,并赋予相同的值,值由option配置。

为了实现该需求,首先创建index.jsindex.htmlwebpack.config.js

1.index.js比较简单,只需import引入index.html即可:

1
import './index.html';

2.index.html中,只需添加多个img 标签即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>This is a test page</title>
</head>
<body>
<div class="wrapper">
<img src="./123.png" />
</div>
<img src="./123.png" />
</body>
</html>

3.webpack配置文件如下,webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: './src/index.js',
devtool: 'false',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Myself loader'
})
],
output: {
chunkFilename: '[name].js',
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.html$/,
use: [
{
loader: 'html-loader',// 这边为何需要html-loader,后面解释
options: {
minimize: false
}
},
{
loader: path.resolve(__dirname, './img-alt-loader/index.js'),
options: {
title: 'this is img tag'
}
}
]
},
{
test: /\.(png|jpg|gif|jpeg|svg)$/,
use: {
loader: 'url-loader',
options: {
limit: 1
}
}
},
]
},
}

下面开始创建loader,从上面webpack配置文件可以看出,我们在当前根目录下新建一个img-alt-loader文件夹,并在该文件夹下创建index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//index.js
// loader-utils 和 schema-utils 是在loader中常用的工具组件
const loaderUtils = require('loader-utils');
const validateOptions = require('schema-utils');

const attrParse = require('./attributesParser');
const schema = require('./option.json');

module.exports = function loader(content) {
this.cacheable && this.cacheable();

const options = loaderUtils.getOptions(this) || {}; //获取option
validateOptions(schema, options, 'img alt loader'); // 验证option类型

const altTitle = options.title || 'default img';
const attributes = ['img:src'];

const links = attrParse(content, function(tag, attr) { //解析html tag
return attributes.find(function(ar) {
return (tag + ":" + attr) === ar;
});
});

links.reverse();
content = [content];

links.forEach(function(link) {
var c = content.pop();
content.push(c.substr(link.start + link.length + 1));
content.push(` alt="${altTitle}"`);
content.push(c.substr(0, link.start + link.length + 1));
});

content.reverse();
content = content.join('');

return content;
//直接return了content,原因是我们把img-alt-loader并不作为最终的loader,即产生已经转换好的代码,所以直接返回content,
//如果我们把该loader作为最终的loader,那么这边的返回值,需得写成: return `module.exports=...`;
}

options.json 中只需check title类型是string

1
2
3
4
5
6
7
8
9
//option.json
{
"type": "object",
"properties": {
"title": {
"type": "string"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//attributesParser.js
var Parser = require("fastparse");
var processMatch = function(match, strUntilValue, name, value, index) {
if(!this.isRelevantTagAttr(this.currentTag, name)) return;
this.results.push({
start: index + strUntilValue.length,
length: value.length,
value: value
});
};

var parser = new Parser({
outside: {
"<!--.*?-->": true,
"<![CDATA[.*?]]>": true,
"<[!\\?].*?>": true,
"<\/[^>]+>": true,
"<([a-zA-Z\\-:]+)\\s*": function(match, tagName) {
this.currentTag = tagName;
return "inside";
}
},
inside: {
"\\s+": true, // eat up whitespace
">": "outside", // end of attributes
"(([0-9a-zA-Z\\-:]+)\\s*=\\s*\")([^\"]*)\"": processMatch,
"(([0-9a-zA-Z\\-:]+)\\s*=\\s*\')([^\']*)\'": processMatch,
"(([0-9a-zA-Z\\-:]+)\\s*=\\s*)([^\\s>]+)": processMatch
}
});

module.exports = function parse(html, isRelevantTagAttr) {
return parser.parse("outside", html, {
currentTag: null,
results: [],
isRelevantTagAttr: isRelevantTagAttr
}).results;
};

目录结构如下:

1
2
3
4
5
6
7
8
9
|-- project
|-- src
|-- index.js
|-- index.html
|-- img-alt-loader
|-- index.js
|-- option.json
|-- attributesParser.js
|-- webpack.config.js

使用webpack 开发模式打包--mode development,可以看出如下结果,img标签上都添加了alt="this is img tag" 属性。

由于nodejs是单线程操作的,所以对于某些异步操作,尽量把loader设为async loader,这样就不影响后续的操作并加快了打包速度。
img-alt-loader可能没有什么实际意义,但对于如何自己写loader提供了入门。O(∩_∩)O


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!