Webpack 常见 Plugin 面试题

一、Webpack Plugin 是什么?

面试题

什么是 Webpack Plugin?Plugin 和 Loader 有什么区别?

核心思路(一句话)

Loader 是"转换模块内容",Plugin 是"通过生命周期 Hook 介入整个构建流程"。


1. 结构化理解

text 复制代码
Webpack 构建
│
├── Loader
│   └── 处理某一个模块
│       ├── TypeScript → JavaScript
│       ├── SCSS → CSS
│       └── Markdown → JavaScript/HTML
│
└── Plugin
    └── 介入整个构建生命周期
        ├── 创建 HTML
        ├── 清理 dist
        ├── 修改/生成资源
        ├── 压缩代码
        ├── 分析构建结果
        ├── 注入编译期常量
        └── 实现热模块替换等

Webpack 官方的核心描述也是:Loader 对模块源码进行转换,而 Plugin 可以直接参与 compilation 生命周期。(webpack)


二、Plugin 的底层实现原理

面试题

Webpack Plugin 为什么可以扩展 Webpack?

核心思路(一句话)

Webpack 内部使用 Tapable 提供生命周期 Hook,Plugin 在 apply() 中注册 Hook 回调,Webpack 执行到对应阶段时触发回调。


解决方案流程图

text 复制代码
webpack.config.js
       │
       ▼
plugins: [new MyPlugin()]
       │
       ▼
Webpack 创建 Compiler
       │
       ▼
调用 Plugin.apply(compiler)
       │
       ▼
Plugin 注册 Hook
compiler.hooks.xxx.tap(...)
       │
       ▼
Webpack 开始构建
       │
       ├── 初始化
       ├── 创建 Compilation
       ├── 构建 Module
       ├── 创建 Chunk
       ├── 优化
       ├── 生成 Asset
       └── 输出文件
              │
              ▼
      到达对应生命周期 Hook
              │
              ▼
       执行 Plugin 回调
              │
              ▼
       修改 / 分析 / 生成资源

Webpack 的 Plugin API 本质上建立在 Tapable Hook 系统之上,不同 Hook 有不同的同步/异步执行方式。(webpack)


三、一个 Plugin 最基本的结构是什么?

js 复制代码
class MyWebpackPlugin {
  apply(compiler) {
    compiler.hooks.done.tap("MyWebpackPlugin", (stats) => {
      console.log("Webpack 构建完成!");
    });
  }
}

module.exports = {
  plugins: [
    new MyWebpackPlugin(),
  ],
};

底层发生了什么?

text 复制代码
new MyWebpackPlugin()
        ↓
加入 webpack.plugins
        ↓
Webpack 初始化 Compiler
        ↓
调用 plugin.apply(compiler)
        ↓
plugin 注册 compiler.hooks.done
        ↓
Webpack 构建完成
        ↓
触发 done
        ↓
执行回调

所以 Plugin 最核心的三个要素就是:

text 复制代码
Plugin
├── apply(compiler)
├── 选择合适的 Hook
└── 在 Hook 回调中执行自己的逻辑

Webpack 官方要求 Plugin 提供 apply 方法,并通过 Hook 注册行为。(webpack)


四、Compiler 和 Compilation 到底有什么区别?

这是 Plugin 面试非常重要的追问点。

核心思路(一句话)

Compiler 管整个 Webpack 生命周期;Compilation 管一次具体构建产生的模块、Chunk 和 Asset。


架构图

text 复制代码
Webpack
  │
  ▼
Compiler
│
│  整个 Webpack 进程
│
├── environment
├── entryOption
├── beforeRun
├── run
├── watchRun
├── beforeCompile
├── compile
├── thisCompilation
├── compilation
├── make
├── emit
├── done
└── afterDone
       │
       │ 创建一次构建
       ▼
   Compilation
   │
   ├── modules
   ├── dependencies
   ├── chunks
   ├── chunk graph
   ├── assets
   ├── optimize
   └── processAssets

Webpack 官方明确区分:

  • Compiler:控制整个构建过程。
  • Compilation:表示一次具体的编译结果,包含 modules、chunks、assets 等。(webpack)

一个非常好记的比喻

text 复制代码
Compiler
= 工厂

Compilation
= 工厂某一次生产任务

Module
= 原材料

Chunk
= 生产出来的一组产品

Asset
= 最终要出厂的文件

尤其在 Watch 模式:

text 复制代码
一个 Compiler
   │
   ├── Compilation #1
   ├── Compilation #2
   ├── Compilation #3
   └── Compilation #4

因此:

跨多次构建保存的状态放 Compiler;只属于当前这次构建的数据放 Compilation。 (webpack)


五、常见 Plugin 应该怎么分类?

text 复制代码
Webpack Plugin
│
├── ① 构建流程 / 资源处理
│   │
│   ├── HTML
│   │   └── HtmlWebpackPlugin
│   │
│   ├── CSS
│   │   └── MiniCssExtractPlugin
│   │
│   ├── JavaScript
│   │   ├── DefinePlugin
│   │   └── TerserWebpackPlugin
│   │
│   ├── 静态资源
│   │   └── CopyWebpackPlugin
│   │
│   ├── 压缩
│   │   └── CompressionWebpackPlugin
│   │
│   └── 清理
│       └── output.clean / CleanPlugin
│
├── ② 开发体验
│   └── HotModuleReplacementPlugin
│
├── ③ 构建分析
│   └── webpack-bundle-analyzer
│
└── ④ 错误 / 日志 / 调试
    └── FriendlyErrorsWebpackPlugin 等

主要矛盾:

面试官真正想考的不是"你能背多少插件",而是你能不能说明 Plugin 在构建链路中的作用 + 为什么需要它 + 它解决什么问题。


六、常见 Plugin 总结

Plugin 主要作用 关键注意点
HtmlWebpackPlugin 自动生成 HTML 并注入构建产物 多页面应用可配置多个实例
MiniCssExtractPlugin 将 CSS 提取成独立文件 不是 CSS 压缩插件
DefinePlugin 编译期替换常量 不是真正的运行时环境变量
TerserWebpackPlugin JavaScript 压缩/丑化 Webpack 生产构建默认已使用相关能力
CopyWebpackPlugin 拷贝不需要经过模块处理的静态文件 Webpack 5 Asset Modules 能解决部分资源场景
CompressionWebpackPlugin 生成 gzip / Brotli 等压缩资源 服务器还必须正确配置 Content-Encoding
webpack-bundle-analyzer 分析 Bundle 体积和依赖关系 用于定位体积问题
HotModuleReplacementPlugin 热模块替换 主要用于开发环境
FriendlyErrorsWebpackPlugin 优化构建错误输出 属于开发体验类
CleanWebpackPlugin 清理输出目录 Webpack 5 通常直接 output.clean
ProgressPlugin 显示构建进度 Webpack 内置
ProvidePlugin 自动注入模块变量 与 DefinePlugin 不同

七、HtmlWebpackPlugin

面试题

HtmlWebpackPlugin 有什么作用?

核心思路(一句话)

根据模板生成 HTML,并把 Webpack 生成的 JS、CSS 等资源自动注入 HTML。

流程图

text 复制代码
src/index.html
      │
      ▼
HtmlWebpackPlugin
      │
      ├── 获取 compilation 资源
      ├── 获取 JS Chunk
      ├── 获取 CSS Asset
      └── 生成 HTML
             │
             ▼
          dist/index.html
             │
             ├── <script src="...">
             └── <link href="...">

使用场景

最典型:

text 复制代码
SPA
多页面应用
HTML 模板自动注入 hash 文件

示例

js 复制代码
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",

  output: {
    filename: "js/[name].[contenthash].js",
    path: require("node:path").resolve(__dirname, "dist"),
  },

  plugins: [
    new HtmlWebpackPlugin({
      // 使用项目中的 HTML 模板
      template: "./public/index.html",

      // 最终生成 dist/index.html
      filename: "index.html",

      // 自动把 webpack 生成的 JS/CSS 注入 HTML
      inject: "body",

      // 生产环境可以开启 HTML 压缩
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
};

八、MiniCssExtractPlugin

MiniCssExtractPlugin 主要负责把 CSS 从 JavaScript Bundle 中提取成独立 CSS 文件;CSS 压缩属于另外的优化阶段。

例如:

text 复制代码
JavaScript
   │
   ├── import "./index.css"
   │
   ▼
MiniCssExtractPlugin
   │
   ├── main.js
   └── main.css

为什么要提取?

开发阶段可以使用:

text 复制代码
style-loader

直接把 CSS 注入 <style>。

生产环境通常希望:

text 复制代码
CSS → 独立文件

便于:

  • 浏览器缓存
  • 并行加载
  • CSS 与 JS 分离
  • 避免把大量 CSS 放进 JavaScript

示例

js 复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,

        use: [
          // 生产环境:
          // 不再通过 JavaScript 动态插入 <style>
          MiniCssExtractPlugin.loader,

          // 负责解析 CSS
          "css-loader",
        ],
      },
    ],
  },

  plugins: [
    new MiniCssExtractPlugin({
      // CSS 最终输出位置
      filename: "css/[name].[contenthash].css",
    }),
  ],
};

CSS 压缩和 CSS 提取是两个不同职责。


九、DefinePlugin

面试题

DefinePlugin 是不是用来设置环境变量?

核心思路(一句话)

DefinePlugin 本质是编译期字符串/表达式替换,而不是运行时环境变量系统。

Webpack 官方定义也是:DefinePlugin 会在编译阶段把代码中的变量替换成指定值。(webpack)

例如:

js 复制代码
const webpack = require("webpack");

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      // 注意:这是代码替换,不是运行时读取 process.env
      __DEV__: JSON.stringify(true),

      "process.env.API_URL": JSON.stringify(
        "https://api.example.com"
      ),
    }),
  ],
};

源代码:

js 复制代码
if (__DEV__) {
  console.log("开发环境");
}

编译后类似:

js 复制代码
if (true) {
  console.log("开发环境");
}

重要价值

这会进一步配合压缩:

text 复制代码
DefinePlugin
     ↓
__DEV__ → false
     ↓
条件分支确定
     ↓
Terser
     ↓
删除无用代码

因此它常用于:

text 复制代码
开发环境
生产环境
Feature Flag
编译期常量

十、JavaScript 压缩:不要再背 UglifyJsPlugin

TerserWebpackPlugin 是现代 Webpack 体系中常见的 JavaScript 压缩方案;Webpack 生产模式默认已经包含 JavaScript 压缩能力。

Webpack 的现代 minimizer 体系默认使用 Terser 相关能力。(webpack)

原理

text 复制代码
源代码
  ↓
JavaScript Module
  ↓
Webpack Bundle
  ↓
Terser
  ├── 删除注释
  ├── 删除无效代码
  ├── 压缩变量名
  ├── 优化表达式
  └── 删除不可达代码
  ↓
更小的 JavaScript

示例

js 复制代码
const TerserWebpackPlugin = require("terser-webpack-plugin");

module.exports = {
  mode: "production",

  optimization: {
    minimize: true,

    minimizer: [
      new TerserWebpackPlugin({
        // 是否生成 source map
        // 生产环境是否开启要结合安全性和调试需求考虑
        parallel: true,

        terserOptions: {
          compress: true,

          mangle: true,
        },

        extractComments: false,
      }),
    ],
  },
};

面试注意

不要说:

"UglifyJsPlugin 是现在 Webpack 压缩 JavaScript 的主要方案。"

应该说:

"历史上有 UglifyJsPlugin,现在主流是 Terser;Webpack 生产模式本身就已经提供默认压缩能力,需要定制压缩行为时再配置对应 minimizer。"


十一、CompressionWebpackPlugin

面试题

CompressionWebpackPlugin 是干什么的?

核心思路(一句话)

在构建阶段提前生成 gzip、Brotli 等压缩版本,部署服务器直接返回压缩资源。

注意:

text 复制代码
CompressionWebpackPlugin
≠
JavaScript 压缩

它做的是:

text 复制代码
main.js
   │
   ├── main.js
   ├── main.js.gz
   └── main.js.br

而 Terser 做的是:

text 复制代码
原始 JavaScript
      ↓
代码最小化
      ↓
main.js

两者完全是不同层面的压缩:

text 复制代码
代码压缩
Terser
   ↓
减少代码本身大小

传输压缩
gzip / Brotli
   ↓
减少网络传输体积

CompressionWebpackPlugin 当前仍在维护,例如其 2026 年版本继续发布。(GitHub)

关键边界

生成 .gz 不等于浏览器自动使用 .gz。

还需要服务器根据:

http 复制代码
Accept-Encoding: gzip, br

选择:

http 复制代码
Content-Encoding: gzip

或者:

http 复制代码
Content-Encoding: br

十二、CopyWebpackPlugin

面试题

为什么需要 CopyWebpackPlugin?

核心思路(一句话)

对于不需要 Loader/模块系统处理的静态文件,可以直接复制到输出目录。

例如:

text 复制代码
public/
├── favicon.ico
├── robots.txt
└── static/
    └── config.json

直接:

text 复制代码
public
   ↓
CopyWebpackPlugin
   ↓
dist

示例

js 复制代码
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          // 源文件
          from: "public",

          // 输出到 dist
          to: ".",

          // 不复制 index.html
          // 因为 index.html 由 HtmlWebpackPlugin 负责
          globOptions: {
            ignore: [
              "**/index.html",
            ],
          },
        },
      ],
    }),
  ],
};

边界场景

Webpack 5 已经提供 Asset Modules,因此:

text 复制代码
图片
字体
普通资源

很多场景已经不需要 CopyWebpackPlugin。

所以不要形成:

"静态资源必须使用 CopyWebpackPlugin。"

正确理解:

只有不适合进入 Webpack 模块依赖图、又需要原样复制到输出目录的资源,CopyWebpackPlugin 才特别合适。


十三、CleanWebpackPlugin

以前:

js 复制代码
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

plugins: [
  new CleanWebpackPlugin()
]

现在 Webpack 5 更推荐直接:

js 复制代码
module.exports = {
  output: {
    path: require("node:path").resolve(__dirname, "dist"),

    // Webpack 构建前自动清理输出目录
    clean: true,
  },
};

Webpack 官方已经提供 output.clean。(webpack)

为什么需要清理?

假设:

text 复制代码
第一次构建:

dist/
├── main.abc.js
└── vendor.def.js

第二次构建:

text 复制代码
dist/
└── main.xyz.js

如果不清理:

text 复制代码
dist/
├── main.abc.js   ← 旧文件
├── main.xyz.js   ← 新文件
└── vendor.def.js

旧 hash 文件就可能残留。

所以:

text 复制代码
构建开始
   ↓
清理 dist
   ↓
重新构建
   ↓
输出最新产物

十四、webpack-bundle-analyzer

面试题

如何分析 Webpack 打包体积?

核心思路(一句话)

把 Bundle 的体积和模块依赖关系可视化,从"感觉慢"变成"知道哪个模块占空间"。

典型流程:

text 复制代码
Webpack
  ↓
Stats / Bundle
  ↓
webpack-bundle-analyzer
  ↓
可视化
  │
  ├── 哪个 Chunk 大?
  ├── 哪个模块大?
  ├── 哪个依赖重复?
  └── 为什么进入 Bundle?

例如发现:

text 复制代码
main.js
├── React                 100 KB
├── lodash                500 KB
├── moment                300 KB
└── business-code         200 KB

进一步才能决定:

text 复制代码
Tree Shaking
Code Splitting
按需加载
替换依赖
减少重复依赖

所以它不是"优化插件"。它是发现性能问题的工具。


十五、HotModuleReplacementPlugin

面试题

HotModuleReplacementPlugin 是干什么的?

核心思路(一句话)

开发过程中只替换发生变化的模块,尽量避免整个页面刷新。

普通开发:

text 复制代码
修改 Button.js
     ↓
重新构建
     ↓
浏览器刷新
     ↓
整个应用重新执行

HMR:

text 复制代码
修改 Button.js
     ↓
Webpack 检测变化
     ↓
重新编译 Button.js
     ↓
生成 HMR 更新
     ↓
浏览器接收更新
     ↓
替换模块
     ↓
尽可能保留当前页面状态

一个关键误区

不能简单说:

"HotModuleReplacementPlugin = 热更新。"

更准确:

text 复制代码
Webpack Hot Module Replacement 能力
        │
        ├── Webpack 重新编译发生变化的模块
        ├── 生成更新内容
        ├── 浏览器端 HMR Runtime 接收
        └── 模块自身/框架决定如何接受更新

因此 HMR 并不是任何模块修改后都一定能够无刷新更新。


十六、FriendlyErrorsWebpackPlugin

作用比较简单:

text 复制代码
Webpack 原始错误
       ↓
FriendlyErrorsWebpackPlugin
       ↓
整理、过滤、格式化
       ↓
更适合开发者阅读的错误

它属于:

text 复制代码
开发体验

而不是核心构建产物优化。

构建反馈 / 开发体验。


十七、一个完整的现代 Webpack 配置示例

把前面的核心 Plugin 串起来:

js 复制代码
const path = require("node:path");
const webpack = require("webpack");

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CompressionWebpackPlugin = require("compression-webpack-plugin");

module.exports = {
  mode: "production",

  entry: "./src/index.js",

  output: {
    path: path.resolve(__dirname, "dist"),

    filename: "js/[name].[contenthash].js",

    // Webpack 5 内置清理能力。
    // 每次构建前清理 output.path 中的旧资源。
    clean: true,
  },

  module: {
    rules: [
      {
        // JavaScript 通过 Babel Loader 进行语法转换。
        test: /\.js$/i,

        exclude: /node_modules/,

        use: {
          loader: "babel-loader",
        },
      },

      {
        // CSS 处理流程:
        // css-loader 负责解析 CSS 模块依赖,
        // MiniCssExtractPlugin.loader 负责把 CSS 提取成独立文件。
        test: /\.css$/i,

        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
        ],
      },

      {
        // Webpack 5 Asset Modules。
        // 图片不再必须依赖 file-loader/url-loader。
        test: /\.(png|jpe?g|gif|svg)$/i,

        type: "asset",

        parser: {
          dataUrlCondition: {
            // 小于 8KB 的资源可以直接内联,
            // 大于 8KB 的资源输出为独立文件。
            maxSize: 8 * 1024,
          },
        },

        generator: {
          filename: "images/[name].[contenthash][ext]",
        },
      },
    ],
  },

  optimization: {
    // 生产环境启用压缩。
    minimize: true,
  },

  plugins: [
    new HtmlWebpackPlugin({
      // HTML 模板。
      template: "./public/index.html",

      // 自动注入 webpack 生成的 JS/CSS。
      inject: "body",

      // 压缩 HTML。
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),

    new MiniCssExtractPlugin({
      // CSS 独立输出。
      filename: "css/[name].[contenthash].css",
    }),

    new webpack.DefinePlugin({
      // 编译阶段把 __DEV__ 替换成 false。
      // 注意:这不是运行时环境变量。
      __DEV__: JSON.stringify(false),
    }),

    new CopyWebpackPlugin({
      patterns: [
        {
          // 将 robots.txt 等不需要模块化处理的静态文件
          // 直接复制到 dist。
          from: "public/robots.txt",
          to: "robots.txt",
        },
      ],
    }),

    new CompressionWebpackPlugin({
      // 为最终产物生成 gzip 文件。
      algorithm: "gzip",

      // 只处理 JavaScript、CSS、HTML、SVG。
      test: /\.(js|css|html|svg)$/i,

      // 只有压缩后体积达到 10KB 才生成 gzip。
      threshold: 10 * 1024,

      // 至少压缩 80% 才保留压缩文件。
      minRatio: 0.8,
    }),
  ],
};

这个配置体现的是:

text 复制代码
Webpack 生产构建
│
├── output.clean
│   └── 清理旧产物
│
├── HtmlWebpackPlugin
│   └── 生成 HTML
│
├── MiniCssExtractPlugin
│   └── CSS 独立输出
│
├── DefinePlugin
│   └── 编译期常量替换
│
├── Asset Modules
│   └── 图片等资源处理
│
├── CopyWebpackPlugin
│   └── 原样复制特殊静态资源
│
└── CompressionWebpackPlugin
    └── 生成 gzip 传输版本

十八、手写一个真正有意义的 Webpack Plugin

这个是 Plugin 面试最重要的代码题之一。

面试题

如果让你自己实现一个 Webpack Plugin,你怎么实现?

核心思路(一句话)

在 apply() 中注册合适的生命周期 Hook,在 Hook 中读取 Compilation,并通过 Webpack 提供的 Asset API 创建或修改构建产物。


完整代码

BuildInfoPlugin.js

js 复制代码
class BuildInfoPlugin {
  constructor(options = {}) {
    this.options = options;
    this.pluginName = "BuildInfoPlugin";
  }

  apply(compiler) {
    /**
     * compiler 代表整个 Webpack 构建系统。
     *
     * thisCompilation:
     * 当一次新的 Compilation 创建出来时触发。
     *
     * 为什么这里使用 thisCompilation?
     *
     * 因为我们接下来要操作本次构建产生的 Compilation。
     */
    compiler.hooks.thisCompilation.tap(
      this.pluginName,
      (compilation) => {
        /**
         * processAssets 是 Webpack 5 推荐的资源处理入口。
         *
         * 不推荐使用过去常见的 compilation.assets 直接修改方式。
         *
         * stage 用来决定:
         * 当前 Plugin 在资源处理阶段的哪个位置执行。
         */
        compilation.hooks.processAssets.tap(
          {
            name: this.pluginName,

            /**
             * ADDITIONS:
             * 表示当前 Plugin 主要负责增加新的资源。
             *
             * compiler.webpack 可以拿到当前 Webpack 实例,
             * 避免 Plugin 自己再加载另一份 Webpack。
             */
            stage:
              compiler.webpack.Compilation
                .PROCESS_ASSETS_STAGE_ADDITIONS,
          },

          (assets) => {
            /**
             * compilation.assets:
             * 可以理解为本次构建已经产生的资源集合。
             *
             * Object.keys(assets) 可以拿到:
             *
             * [
             *   "main.js",
             *   "main.css",
             *   "index.html"
             * ]
             */
            const assetNames = Object.keys(assets);

            /**
             * 生成一个简单的构建报告。
             */
            const content = [
              `Webpack 构建资源数量:${assetNames.length}`,
              "",
              "构建资源:",
              ...assetNames.map(
                (name) => `- ${name}`
              ),
            ].join("\n");

            /**
             * 使用 Webpack 官方提供的 sources.RawSource
             * 创建新的资源。
             */
            const { RawSource } = compiler.webpack.sources;

            /**
             * emitAsset:
             * 向当前 Compilation 添加一个新的输出资源。
             *
             * 最终会生成:
             *
             * dist/build-info.txt
             */
            compilation.emitAsset(
              "build-info.txt",
              new RawSource(content)
            );
          }
        );
      }
    );
  }
}

module.exports = BuildInfoPlugin;

Webpack 官方目前推荐通过 compilation.hooks.processAssets 处理生成/修改资源,并通过不同 stage 控制处理顺序,而不是继续在 emit 中直接生成资源。(webpack)


webpack.config.js

js 复制代码
const path = require("node:path");
const BuildInfoPlugin = require("./BuildInfoPlugin");

module.exports = {
  mode: "production",

  entry: "./src/index.js",

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
    clean: true,
  },

  plugins: [
    new BuildInfoPlugin(),
  ],
};

最终:

text 复制代码
dist/
├── main.js
└── build-info.txt

十九、为什么 Plugin 要选择不同 Hook?

这就是 Plugin 真正的底层原理。

text 复制代码
Webpack 生命周期
│
├── 初始化阶段
│   ├── environment
│   ├── afterEnvironment
│   └── entryOption
│
├── 构建准备
│   ├── beforeRun
│   ├── run
│   ├── beforeCompile
│   └── compile
│
├── Compilation 创建
│   ├── thisCompilation
│   └── compilation
│
├── 构建
│   ├── make
│   ├── buildModule
│   └── finishModules
│
├── 优化
│   ├── optimize
│   ├── optimizeModules
│   ├── optimizeChunks
│   └── optimizeTree
│
├── 资源处理
│   └── processAssets
│       ├── 添加资源
│       ├── 优化资源
│       ├── 压缩资源
│       └── 总结资源
│
└── 输出
    ├── emit
    ├── afterEmit
    ├── done
    └── afterDone

Compiler Hook 和 Compilation Hook 是两个不同层级。Webpack 官方文档分别维护了完整的 Hook 列表。(webpack)


二十、这里最容易被面试官追问的 5 个问题

1. Plugin 为什么比 Loader 更强?

因为:

text 复制代码
Loader
→ 模块级

Plugin
→ 构建生命周期级

Loader 主要处理:

text 复制代码
一个文件
↓
转换
↓
另一个模块结果

Plugin 可以处理:

text 复制代码
整个 Compiler
整个 Compilation
Modules
Chunks
Assets
构建流程

2. Plugin 为什么必须 new?

通常配置:

js 复制代码
plugins: [
  new MyPlugin({
    ...
  })
]

因为 Plugin 通常需要保存:

text 复制代码
配置参数
内部状态
Hook 注册逻辑

Webpack 在安装 Plugin 时会调用:

js 复制代码
plugin.apply(compiler);

官方 Plugin API 也是这种结构。(webpack)


3. DefinePlugin 和 ProvidePlugin 有什么区别?

DefinePlugin

text 复制代码
代码中的常量
     ↓
编译期替换

例如:

js 复制代码
new webpack.DefinePlugin({
  __DEV__: JSON.stringify(false),
});

ProvidePlugin

text 复制代码
代码中使用某个变量
       ↓
Webpack 自动提供模块

例如:

js 复制代码
new webpack.ProvidePlugin({
  $: "jquery",
});

所以:

text 复制代码
DefinePlugin
= 替换值

ProvidePlugin
= 自动提供模块变量

二十一、主要矛盾与次要矛盾

主要矛盾

① Plugin 和 Loader 的本质区别

text 复制代码
Loader → 转换模块
Plugin → 扩展构建流程

这是最重要的。

② Compiler 和 Compilation

text 复制代码
Compiler
= 整个构建系统

Compilation
= 一次具体构建

这是理解 Plugin 源码的关键。

③ Hook

text 复制代码
Plugin
 ↓
apply
 ↓
Hook
 ↓
Webpack 执行到该阶段
 ↓
Plugin 回调

这是 Plugin 能工作的真正原因。


次要矛盾

具体 Plugin 的名字。

例如:

text 复制代码
HtmlWebpackPlugin
MiniCssExtractPlugin
CopyWebpackPlugin
CompressionWebpackPlugin

这些需要记,但不能只停留在背名字。

真正应该形成:

text 复制代码
问题
 ↓
选择 Hook
 ↓
选择 Plugin
 ↓
Plugin 改变构建过程
 ↓
得到解决方案

二十二、面试回答的结构化逻辑

遇到:

"Webpack 常见 Plugin 有哪些?"

不要直接开始背:

text 复制代码
HtmlWebpackPlugin
CleanWebpackPlugin
...

应该按照:

text 复制代码
第一层:Plugin 是什么?
        ↓
通过 Hook 扩展 Webpack 构建流程
        ↓
第二层:怎么工作?
        ↓
apply
  ↓
compiler / compilation
  ↓
Hook
  ↓
执行回调
        ↓
第三层:有哪些常见 Plugin?
        ↓
构建/资源
├── HTML
├── CSS
├── JS
├── 静态资源
├── 压缩
└── 清理
        ↓
开发体验
├── HMR
└── 错误反馈
        ↓
构建分析
└── Bundle Analyzer
        ↓
第四层:举一个实际项目
        ↓
HTML 自动生成
CSS 独立提取
JS 压缩
静态资源复制
gzip/Brotli
Bundle 分析

这样面试官听到的不是:

"我背过几个 Webpack Plugin。"

而是:

"我理解 Webpack 的构建架构,并且知道 Plugin 在哪里解决什么问题。"


二十三、满分答案

问题:Webpack 中有哪些常见 Plugin?Plugin 的作用是什么?

Webpack Plugin 的核心作用不是单纯优化打包,而是通过 Webpack 的生命周期 Hook 扩展和定制整个构建过程;Loader 主要负责模块内容转换,Plugin 负责参与构建流程。

text 复制代码
Webpack Plugin
│
├── 原理
│   └── apply(compiler)
│          ↓
│       注册 Hook
│          ↓
│       Webpack 执行生命周期
│          ↓
│       Plugin 回调
│
├── 构建/资源
│   ├── HtmlWebpackPlugin
│   │   └── 生成 HTML + 自动注入 JS/CSS
│   │
│   ├── MiniCssExtractPlugin
│   │   └── CSS 提取成独立文件
│   │
│   ├── DefinePlugin
│   │   └── 编译期替换常量
│   │
│   ├── TerserWebpackPlugin
│   │   └── JavaScript 压缩
│   │
│   ├── CopyWebpackPlugin
│   │   └── 复制特殊静态资源
│   │
│   └── CompressionWebpackPlugin
│       └── 生成 gzip/Brotli 传输资源
│
├── 开发体验
│   ├── HotModuleReplacementPlugin
│   │   └── 热模块替换
│   └── FriendlyErrorsWebpackPlugin
│       └── 优化错误输出
│
└── 构建分析
    └── webpack-bundle-analyzer
        └── 分析 Bundle 体积和依赖

如果让我自己实现 Plugin,核心就是在 apply() 中拿到 compiler,根据需求选择 compiler 或 compilation 的生命周期 Hook;如果要操作最终资源,Webpack 5 推荐使用 compilation.hooks.processAssets,然后通过 compilation.emitAsset() 等 API 增加或修改资源。

实际项目中,我不会单纯背 Plugin,而是根据问题选择 Plugin:HTML 自动生成用 HtmlWebpackPlugin,CSS 独立提取用 MiniCssExtractPlugin,编译期常量用 DefinePlugin,JavaScript 压缩用 TerserWebpackPlugin,特殊静态文件用 CopyWebpackPlugin,传输压缩用 CompressionWebpackPlugin,体积问题用 webpack-bundle-analyzer;Webpack 5 本身已经提供 output.clean,所以清理目录通常不再需要额外安装 CleanWebpackPlugin。

最后用一句话记住:

Loader 解决"模块怎么转换",Plugin 解决"Webpack 整个构建过程怎么扩展"。Plugin 的灵魂就是 apply → Hook → 回调 → 改变构建过程。 (webpack)

相关推荐
xxwl5851 小时前
vue3的入门学习
前端·vue.js·学习
用户847172102841 小时前
.shot 文件格式规范
前端·后端
web打印社区1 小时前
政务办事大厅:叫号小票、办理回执网页怎么静默出纸
前端·javascript·vue.js·pdf·html·政务
C++ 老炮儿的技术栈1 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_bichart_natalvssecondary2接口指南
前端·php
liangshanbo12152 小时前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
去伪存真2 小时前
从乱码到高精度检索:探矿业务中 TXT、Word、PDF 与网页的 RAG 清洗之道
前端·agent
两个西柚呀2 小时前
JS事件总结
前端·javascript·css
小狼154542 小时前
拼多多订单数据导出 CSV 实战:接口分页、字段平铺与 5 个数据坑,多多开票助手
java·前端·javascript