# 2.自定义 webpack-loader
# 目的
- 访问到 JS 配置文件中相对路径的图片
# 实现方案
- 因为在编译阶段,目录 img 下的图片,并不会被依赖,webpack 不会进行打包。所以在初始化的,将 img 强行进行依赖,打入包中。
- webpack-loader 对 JS 文件进行处理,将相对路径的图片,替换为绝对路径带有 MD5 后缀的图片。
# 自定义 webpack-loader 注意事项
# 先知
- webpack 只认识 JS 和 JSON,不认识 HTML、CSS、Ts、Less、图片,所以需要 loader 进行翻译
- loader 的处理顺序,是和书写顺序是相反的,即:less-loader->css-loader->style-loader,链式调用
- loader 的标准格式是,暴露一个 Node.js 模块,导出一个函数,对源内容进行处理后,返回处理后的内容。
module.exports = {
module: {
rules: [
{
// 增加对 less 文件的支持
test: /\.less/,
// less 文件的处理顺序为先 less-loader 再 css-loader 再 style-loader
use: [
"style-loader",
{
loader: "css-loader",
// 给 css-loader 传入配置项
options: {
minimize: true,
},
},
"less-loader",
],
},
],
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# loader 的两种模版
# return source(返回转化后的内容)
module.exports = (source) => {
const content = source.replace("hello", "哈哈哈");
return content;
};
this.callback();
module.exports = (source) => {
// 处理 source
const content = source.replace("hello", "哈哈");
// 使用 this.callback 返回内容
this.callback(null, content);
// 使用 this.callback 返回内容时,该 loader 必须返回 undefined,
// 以让 Webpack 知道该 loader 返回的结果在 this.callback 中,而不是 return 中
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# callback 详细参数
this.callback(
// 当无法转换源内容时,给 Webpack 返回一个 Error
err: Error | null,
// 源内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
// 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# loader 的同步和异步
同步:上面两种方式,没有加 async 就是同步代码 异步:适用于需要依赖网络请求的 loader
module.exports = function (source) {
// 调用 this.async() API,告诉 webpack本次转换是异步的,loader 会在 callback 中返回结果
const callback = this.async();
// 使用 setTimeout 模拟异步过程
setTimeout(() => {
const content = source.replace("hello", "哈哈");
// 通过 callback 返回执行异步后的结果
callback(null, content);
}, 3000);
};
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 实战
- 将所以 img 目录下文件通过 require,引入(如果没有这一步,虽然图片路径对了,但是 webpack 打包里面会不存在该图片)
const exports = {};
const jsonReq = require.context(
// 其组件目录的相对路径
".",
// 是否查询其子目录
true,
// 匹配基础组件文件名的正则表达式
/\.(json|js)$/
);
const regExp = /(10\d{3}).*\.(json|js)$/;
jsonReq.keys().forEach((fileName) => {
const matches = fileName.match(regExp);
if (matches) {
const name = `config_${matches[1]}`;
const ext = matches[2];
// 同名情况下 JS 优先级更高
if (ext === "js" || !exports[name]) {
exports[name] = jsonReq(fileName);
}
}
});
// 引入所有的图片
const imgReq = require.context(".", true, /\.(png|jpe?g|svga)$/);
imgReq.keys().forEach((fileName) => {
imgReq(fileName);
});
export default exports;
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
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
- 正则匹配所有 images,进行 loader 转化
// 1. 通过正则,匹配文件,svga普通loder,如果是
{
test: /\.svga$/,
use: "file-loader"
},
{
test: /nuwa\/schemas\/\d{5}.*\.js$/,
use: "./src/banners/nuwa/build/loader.js"
}
// 2. 将所有图片进行转化
config.module
.rule("images") // -> Default configuration
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1,
fallback: {
loader: "file-loader",
options: { name: "img/[name].[hash:8].[ext]" }
}
})
.end();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 如果文件是 svga,转为[hash].svga 格式,如果是其他格式,则在文件后面加入[hash.slice(0, 8)],这么做是因为文件通过 file-loader,转为上述格式的文件
const md5File = require("md5-file");
const path = require("path");
const { extractImageFromJSON, replaceJSON } = require("./utils");
const nodeEval = require("node-eval");
module.exports = function loader(content, map, meta) {
let callback = this.async();
const filePath = this.resourcePath;
const imageList = extractImageFromJSON(filePath);
const changedImageList = imageList.map((image) => {
// 文件 md5
const hash = md5File.sync(image);
const baseName = path.basename(image);
// 文件后缀和文件名
const ext = baseName.split(".").pop();
const name = baseName.replace(`.${ext}`, "");
// 具体的格式,参考 build/vue.common.config.js 中 file-loader 的配置
// svga 的处理逻辑 build/vue.common.config.js 中 file-loader
const url =
ext === "svga"
? `${hash}.${ext}`
: `./img/${name}.${hash.slice(0, 8)}.${ext}`;
return {
url,
file: image,
};
});
// require 的方式会导致 source 内容缓存,需要用 vm.runInContext 执行 content 的内容
// const source = require(filePath);
const source = nodeEval(content);
const parsedContent = replaceJSON(changedImageList, {
content: JSON.stringify(source),
dirname: path.dirname(filePath),
});
return callback(null, `module.exports = ${parsedContent}`, map, meta);
};
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
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
← 1.项目设计