diff --git a/blog/webpack/img/html-webpack-plugin-flow.png b/blog/webpack/img/html-webpack-plugin-flow.png
new file mode 100644
index 0000000000000000000000000000000000000000..8420d7c67d4c9fb59aca1370dd8c91de541e2bdc
Binary files /dev/null and b/blog/webpack/img/html-webpack-plugin-flow.png differ
diff --git a/blog/webpack/img/plugin.jpg b/blog/webpack/img/plugin.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..262e1a43649eacaa4c6f63ccf967cf285414a144
Binary files /dev/null and b/blog/webpack/img/plugin.jpg differ
diff --git a/blog/webpack/webpack-loader.md b/blog/webpack/webpack-loader.md
index 6cf3d0e80b435b76d1ac0ddb4c8c3a43a26db16b..a4feb1d864997aa6342e86a273c6e883839523cf 100644
--- a/blog/webpack/webpack-loader.md
+++ b/blog/webpack/webpack-loader.md
@@ -1,5 +1,7 @@
# Webpack loader
+文档:[https://webpack.docschina.org/api/loaders](https://webpack.docschina.org/api/loaders)
+
loader 将不同类型的文件转换为 webpack 可识别的模块
## loader 使用方式
@@ -624,4 +626,3 @@ module.exports = {
};
```
-https://www.bilibili.com/video/BV14T4y1z7sw?p=77&spm_id_from=pageDriver
\ No newline at end of file
diff --git a/blog/webpack/webpack-plugin.md b/blog/webpack/webpack-plugin.md
new file mode 100644
index 0000000000000000000000000000000000000000..f2d01d1248415b7672101b4f6bbfb6255accd1e3
--- /dev/null
+++ b/blog/webpack/webpack-plugin.md
@@ -0,0 +1,579 @@
+# webpack-plugin
+
+插件作用: 扩展 webpack
+
+文档:[https://webpack.docschina.org/api/plugins](https://webpack.docschina.org/api/plugins)
+
+### 钩子
+
+钩子的本质就是:事件
+
+### Tapable
+
+Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义
+
+统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
+
+- tap:可以注册同步钩子和异步钩子。
+- tapAsync:回调方式注册异步钩子。
+- tapPromise:Promise 方式注册异步钩子。
+
+
+### compiler
+
+compiler 对象中保存着完整的 Webpack 环境配置,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。
+
+主要属性:
+
+- compiler.options 可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。
+- compiler.inputFileSystem 和 compiler.outputFileSystem 可以进行文件操作,相当于 Nodejs 中 fs。
+- compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。
+
+### Compilation
+
+compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。
+
+主要属性:
+
+- compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块。
+- compilation.chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。
+- compilation.assets 可以访问本次打包生成所有文件的结果。
+- compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。
+
+### 生命周期简图
+
+
+
+## 第一个插件
+
+执行流程:
+
+1. webpack 加载 webpack.config.js 中的所有配置,此时就会`new TestPlugin()`,执行插件的`constructor`
+2. webpack 创建 compiler 对象
+3. 遍历所有 plugins 中的插件,调用插件的 apply 方法
+4. 执行剩下的编译流程,触发各个 hooks 时间
+
+```js
+// plugins/test-plugin.js
+class TestPlugin {
+ constructor() {
+ console.log("TestPlugin constructor");
+ }
+
+ apply(compiler) {
+ console.log("TestPlugin apply");
+ }
+}
+
+module.exports = TestPlugin;
+```
+
+使用插件
+
+```js
+// webpack.config.js
+const path = require("path");
+const TestPlugin = require("./plugins/test-plugin.js");
+
+module.exports = {
+ entry: "./src/index.js",
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "bundle.js",
+ clean: true,
+ },
+
+ module: {},
+
+ plugins: [new TestPlugin()],
+
+ mode: "development",
+};
+```
+
+compiler 钩子: [https://webpack.docschina.org/api/compiler-hooks/](https://webpack.docschina.org/api/compiler-hooks/)
+
+注册钩子
+
+```js
+// plugins/test-plugin.js
+class TestPlugin {
+ constructor() {
+ console.log("TestPlugin constructor");
+ }
+
+ apply(compiler) {
+ console.log("TestPlugin apply");
+
+ // 从文档可知, environment hook 是 SyncHook,
+ // 也就是同步钩子, 只能用tap注册
+ compiler.hooks.environment.tap("TestPlugin", (compilationParams) => {
+ console.log("compiler.environment()");
+ });
+
+ // 从文档可知, emit 是 AsyncSeriesHook,
+ // 也就是异步串行钩子,特点就是异步任务顺序执行
+ compiler.hooks.emit.tap("TestPlugin", (compilation) => {
+ console.log("compiler.emit() 111");
+ });
+
+ compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
+ setTimeout(() => {
+ console.log("compiler.emit() 222");
+ callback();
+ }, 2000);
+ });
+
+ compiler.hooks.emit.tapPromise("TestPlugin", (compilation) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ console.log("compiler.emit() 333");
+ resolve();
+ }, 1000);
+ });
+ });
+
+ // 从文档可知, make 是 AsyncParallelHook,
+ // 也就是异步并行钩子, 特点就是异步任务同时执行
+ // 可以使用 tap、tapAsync、tapPromise 注册。
+ // 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。
+ compiler.hooks.make.tap("TestPlugin", (compilation) => {
+ setTimeout(() => {
+ console.log("compiler.make() 111");
+ }, 2000);
+ });
+
+ // 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行
+ compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {
+ setTimeout(() => {
+ console.log("compiler.make() 222");
+ // 必须调用
+ callback();
+ }, 1000);
+ });
+
+ compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {
+ // 必须返回promise
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ console.log("compiler.make() 333");
+ resolve();
+ }, 1000);
+ });
+ });
+ }
+}
+
+module.exports = TestPlugin;
+```
+
+## Node.js 调试 debugger
+
+package.json
+
+```json
+{
+ "scripts": {
+ "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
+ }
+}
+```
+
+参数说明
+
+```
+--inspect-brk 启动调试,首行打断点
+```
+
+运行指令
+
+```bash
+npm run debug
+```
+
+打开 Chrome 浏览器任意页面,打开调试面板,找到 Node 的绿色图标
+
+可以在代码任意位置打断点
+
+```js
+debugger;
+```
+
+## BannerWebpackPlugin
+
+给打包输出文件添加注释
+
+```js
+// plugins/banner-webpack-plugin.js
+class BannerWebpackPlugin {
+ constructor(options = {}) {
+ this.options = options;
+ }
+
+ apply(compiler) {
+ // 需要处理文件
+ const extensions = ["js", "css"];
+
+ // 前缀注释
+ const prefix = `/*
+* Author: ${this.options.author}
+*/\n`;
+
+ // emit是异步串行钩子
+ compiler.hooks.emit.tapAsync(
+ "BannerWebpackPlugin",
+ (compilation, callback) => {
+ // compilation.assets包含所有即将输出的资源
+ // 通过过滤只保留需要处理的文件
+ const assetPaths = Object.keys(compilation.assets).filter(
+ (assetPath) => {
+ const splitted = assetPath.split(".");
+ return extensions.includes(splitted[splitted.length - 1]);
+ }
+ );
+
+ // 遍历需要处理的资源,添加注释
+ assetPaths.forEach((assetPath) => {
+ // 获取文件内容
+ const source = compilation.assets[assetPath].source();
+
+ // 添加注释
+ const content = prefix + source;
+
+ // 覆盖资源
+ compilation.assets[assetPath] = {
+ // 资源内容
+ source() {
+ return content;
+ },
+ // 资源大小
+ size() {
+ return content.length;
+ },
+ };
+ });
+
+ callback();
+ }
+ );
+ }
+}
+
+module.exports = BannerWebpackPlugin;
+```
+
+使用
+
+```js
+// webpack.config.js
+const path = require("path");
+const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin.js");
+
+module.exports = {
+ entry: "./src/index.js",
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "bundle.js",
+ clean: true,
+ },
+
+ module: {},
+
+ plugins: [
+ new BannerWebpackPlugin({
+ author: "老王",
+ }),
+ ],
+
+ // mode: "development",
+ mode: "production",
+};
+```
+
+## CleanWebpackPlugin
+
+在 webpack 打包输出前将上次打包内容清空。
+
+```js
+// plugins/clean-webpack-plugin.js
+class CleanWebpackPlugin {
+ apply(compiler) {
+ // 获取操作文件的对象
+ const fs = compiler.outputFileSystem;
+ // emit是异步串行钩子
+ compiler.hooks.emit.tapAsync(
+ "CleanWebpackPlugin",
+ (compilation, callback) => {
+ // 获取输出文件目录
+ const outputPath = compiler.options.output.path;
+ // 删除目录所有文件
+ const err = this.removeFiles(fs, outputPath);
+ // 执行成功err为undefined,执行失败err就是错误原因
+ callback(err);
+ }
+ );
+ }
+
+ removeFiles(fs, path) {
+ try {
+ // 读取当前目录下所有文件
+ const files = fs.readdirSync(path);
+
+ // 遍历文件,删除
+ files.forEach((file) => {
+ // 获取文件完整路径
+ const filePath = `${path}/${file}`;
+ // 分析文件
+ const fileStat = fs.statSync(filePath);
+ // 判断是否是文件夹
+ if (fileStat.isDirectory()) {
+ // 是文件夹需要递归遍历删除下面所有文件
+ this.removeFiles(fs, filePath);
+ } else {
+ // 不是文件夹就是文件,直接删除
+ fs.unlinkSync(filePath);
+ }
+ });
+
+ // 最后删除当前目录
+ fs.rmdirSync(path);
+ } catch (e) {
+ // 将产生的错误返回出去
+ return e;
+ }
+ }
+}
+
+module.exports = CleanWebpackPlugin;
+```
+
+使用
+
+```js
+// webpack.config.js
+const path = require("path");
+
+const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin.js");
+
+module.exports = {
+ entry: "./src/index.js",
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "bundle.js",
+ clean: true,
+ },
+
+ module: {},
+
+ plugins: [new CleanWebpackPlugin()],
+
+ // mode: "development",
+ mode: "production",
+};
+```
+
+## AnalyzeWebpackPlugin
+
+分析 webpack 打包资源大小,并输出分析文件。
+
+```js
+// plugins/analyze-webpack-plugin.js
+
+class AnalyzeWebpackPlugin {
+ apply(compiler) {
+ // emit是异步串行钩子
+ compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {
+ // Object.entries将对象变成二维数组。二维数组中第一项值是key,第二项值是value
+
+ let list = ["# 分析打包资源大小", "", "| 名称 | 大小 |", "| --- | --- |"];
+
+ for (let [filename, file] of Object.entries(compilation.assets)) {
+ list.push(`| ${filename} | ${Math.ceil(file.size() / 1024)}KB |`);
+ }
+
+ let source = list.join("\n");
+
+ // 生成一个md文件
+ compilation.assets["analyze.md"] = {
+ source() {
+ return source;
+ },
+ size() {
+ return source.length;
+ },
+ };
+ });
+ }
+}
+
+module.exports = AnalyzeWebpackPlugin;
+```
+
+使用
+
+```js
+// webpack.config.js
+const path = require("path");
+
+const AnalyzeWebpackPlugin = require("./plugins/analyze-webpack-plugin.js");
+
+module.exports = {
+ entry: "./src/index.js",
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "bundle.js",
+ clean: true,
+ },
+
+ module: {},
+
+ plugins: [new AnalyzeWebpackPlugin()],
+
+ // mode: "development",
+ mode: "production",
+};
+```
+
+## InlineChunkWebpackPlugin
+
+webpack 打包生成的 runtime 文件太小了,额外发送请求性能不好,所以需要将其内联到 js 中,从而减少请求数量。
+
+html-webpack-plugin 执行流程
+
+
+
+```
+pnpm i safe-require -D
+```
+
+```js
+// plugins/inline-chunk-webpack-plugin.js
+const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");
+
+class InlineChunkWebpackPlugin {
+ constructor(tests) {
+ this.tests = tests;
+ }
+
+ apply(compiler) {
+ compiler.hooks.compilation.tap(
+ "InlineChunkWebpackPlugin",
+ (compilation) => {
+ // 获取html-webpack-plugin插件实例
+ const hooks = HtmlWebpackPlugin.getHooks(compilation);
+
+ // 注册一个钩子,在html-webpack-plugin插件生成html文件时调用
+ hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {
+ assets.headTags = this.getInlineTag(
+ assets.headTags,
+ compilation.assets
+ );
+ assets.bodyTags = this.getInlineTag(
+ assets.bodyTags,
+ compilation.assets
+ );
+ });
+
+ // 删除runtime文件
+ hooks.afterEmit.tap("InlineChunkHtmlPlugin", () => {
+ Object.keys(compilation.assets).forEach((assetName) => {
+ if (this.tests.some((test) => assetName.match(test))) {
+ delete compilation.assets[assetName];
+ }
+ });
+ });
+ }
+ );
+ }
+
+ // 将html-webpack-plugin生成的html文件中的link和script标签提取出来
+ getInlineTag(tags, assets) {
+ /**
+ *
+ * 输入
+ * [
+ {
+ tagName: 'script',
+ voidTag: false,
+ meta: { plugin: 'html-webpack-plugin' },
+ attributes: { defer: true, type: undefined, src: 'runtime-main.js' }
+ }
+ ]
+
+ 输出:
+ [
+ {
+ tagName: 'script',
+ innerHTML: 'runtime的文件内容',
+ closeTag: true,
+ }
+ ]
+ */
+ return tags.map((tag) => {
+ if (tag.tagName !== "script") return tag;
+
+ // 文件路径
+ const scriptName = tag.attributes.src;
+
+ if (!this.tests.some((test) => scriptName.match(test))) return tag;
+
+ return {
+ tagName: "script",
+ innerHTML: assets[scriptName].source(),
+ closeTag: true,
+ };
+ });
+ }
+}
+
+module.exports = InlineChunkWebpackPlugin;
+
+```
+
+使用
+```js
+// webpack.config.js
+const path = require("path");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+
+const InlineChunkWebpackPlugin = require("./plugins/inline-chunk-webpack-plugin.js");
+
+module.exports = {
+ entry: "./src/index.js",
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "[name].js",
+ clean: true,
+ },
+
+ module: {
+
+ },
+
+ optimization: {
+ splitChunks: {
+ chunks: "all",
+ },
+ runtimeChunk: {
+ name: (entrypoint) => `runtime-${entrypoint.name}`,
+ },
+ },
+
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: "./src/index.html",
+ }),
+ new InlineChunkWebpackPlugin([/runtime(.*)\.js$/g]),
+ ],
+
+ // mode: "development",
+ mode: "production",
+};
+
+```
+https://www.bilibili.com/video/BV14T4y1z7sw?p=84&spm_id_from=pageDriver
diff --git a/doc/webpack.md b/doc/webpack.md
index 8d5a53e3544dbaf529d0d008986e63ca26d75bc8..3bfa4db4490a82665e0265b4aa8fc5d22443b6bf 100644
--- a/doc/webpack.md
+++ b/doc/webpack.md
@@ -8,6 +8,8 @@
- [webpack-loader](blog/webpack/webpack-loader.md)
+- [webpack-plugin](blog/webpack/webpack-plugin.md)
+
中文文档:
- [https://webpack.docschina.org](https://webpack.docschina.org)