webpack plugin插件入门

203 阅读4分钟

问题:definePlugin 是否能在插件中(构建时)使用(不能)

node 环境可以使用 node 原生环境变量 process.env.xxx,但是不能使用 webpack definePlugin 中的变量,需要区分每种环境变量的生效范围。

“构建范围”(即哪些代码会被 Webpack/Rspack 等构建工具处理,哪些不会)其实是由你的项目结构和构建工具的配置共同决定的,但不是通过 Node.js 的类型声明或全局变量配置来界定的


1. 构建范围的本质

  • 被构建的代码:指的是被 Webpack/Rspack 等工具“打包”的代码,通常是你的前端业务源码(如 src/ 目录下的 .js.ts.vue 等文件),以及它们的依赖。
  • 未被构建的代码:指的是直接由 Node.js 执行的代码,比如构建配置文件(如 webpack.config.jsformula.config.ts)、插件的 setup 逻辑、脚本等。

2. 构建范围的界定方式

a. 入口文件(Entry)

  • 构建工具会从配置的入口文件(如 entry: './src/index.ts')开始递归分析依赖,把所有依赖的模块都打包进最终产物。
  • 这些文件和它们的依赖就是“构建范围”。

b. 构建工具的配置

  • 例如 Webpack 的 entryincludeexcludemodule.rules 等配置项,决定了哪些文件会被 loader、plugin 处理。
  • Rspack、Vite、Rollup 等工具也有类似的配置。

c. 运行时 vs 构建时

  • 构建时:指的是 Node.js 直接执行的代码,比如你的 formula.config.ts、Webpack 插件的 setup 逻辑等。
  • 运行时:指的是最终被打包到浏览器端运行的代码(即构建产物)。

4. 例子说明

  • 你的 formula.config.ts 里写 process.env.NODE_ENV,这是 Node.js 直接执行的,访问的是真实的环境变量。
  • 你的 src/index.ts 里写 process.env.NODE_ENV,如果被 Webpack/Rspack 打包,就会被 DefinePlugin 替换成字符串字面量。
  • 只有被打包进产物的代码,才会被 DefinePlugin 等插件做字符串替换。

5. 总结

  • 构建范围由构建工具的入口和依赖分析决定,和类型声明文件无关。
  • 只有被打包进产物的代码才会被 DefinePlugin 等插件处理。
  • 配置文件、插件 setup 逻辑等 Node.js 直接执行的代码,不在构建范围内,访问的是原生的 process.env

一句话总结:
构建范围由构建工具的入口和依赖决定,和类型声明文件无关,只有被打包的代码才会被构建工具处理和替换。

6. 引申

除了从生效范围看为啥 plugin 不能使用 definePlugin 变量,还可以了解下 plugin 执行时机,definePlugin 执行时机是在构建准备阶段,也无法在后续的插件执行逻辑中生效

6.1 webpack 的生命周期

初始化阶段 (Compiler)
├── environment(初始化环境前调用)
├── afterEnvironment(设置环境变量以后)
├── afterPlugins(插件注册完毕)
├── afterResolvers(解析器准备完毕)
├── initialize(compiler完全初始化)

构建阶段 (Compiler)
├── beforeRun(构建前一步准备)
├── run(调用 run 时)
├── beforeCompile(编译前 准备参数)
├── compile(创建 compilation 对象)
├── thisCompilation(compilation 实例初始化时)
├── compilation(compilation 配置完毕)
├── make(开始从入口递归构建依赖)
├── finishMake(结束 make 阶段)
├── afterCompile(所有模块构建完成)

封装输出阶段 (Compilation)
├── seal(模块打包封装阶段)
├── optimize
│   ├── optimizeModules
│   ├── optimizeChunks
├── afterOptimize
├── beforeEmitAssets
├── emit(输出资源文件前)
├── afterEmit(输出资源后)

完成阶段 (Compiler)
├── done(完整构建完成)
webpack(config)                 // CLI 或 Node API 启动
   │
   ├─▶ Compiler 对象创建
   │
   ├─▶ plugins 应用(apply 方法)
   │   ├─▶ environment
   │   ├─▶ afterEnvironment
   │   ├─▶ afterPlugins
   │   ├─▶ afterResolvers
   │   ├─▶ initialize
   │
   ├─▶ compiler.run() 被调用
   │   ├─▶ beforeRun
   │   ├─▶ run
   │   ├─▶ beforeCompile
   │   ├─▶ compile
   │       ├─▶ thisCompilation
   │       ├─▶ compilation
   │       └─▶ make (依赖分析、loader 执行)
   │           └─▶ finishMake
   │       ├─▶ seal (模块打包优化)
   │       ├─▶ afterCompile
   │
   ├─▶ emit(资源输出前)
   ├─▶ afterEmit
   │
   └─▶ done(构建完成)

6.2 webpack compiler plugin 执行时机

image.png

┌───────────────────────────────┐
│ compiler.run() 被调用         │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.beforeRun      │
│ 异步钩子,构建前的准备工作    │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.run            │
│ 异步钩子,开始读取记录前      │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.beforeCompile  │
│ 异步钩子,编译前的准备工作    │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.compile        │
│ 同步钩子,创建 compilation 对象│
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compilation.hooks.buildModule │
│ 同步钩子,模块构建开始前      │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compilation.hooks.seal        │
│ 同步钩子,模块构建完成后      │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compilation.hooks.optimizeXXX │
│ 同步钩子,各类优化操作        │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compilation.hooks.emit        │
│ 异步钩子,资源输出前          │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.afterEmit      │
│ 异步钩子,资源输出后          │
└──────────────┬────────────────┘
               │
               ▼
┌───────────────────────────────┐
│ compiler.hooks.done           │
│ 异步钩子,构建完成            │
└───────────────────────────────┘
6.3 借此引出如何写一个 plugin

使用时,使用 webpack 插件钩子 compiler.hooks.beforeRun类似相关钩子即可,webpack会在相关时机调用

class MyPlugin {
  apply(compiler) {
    compiler.hooks.beforeRun.tapAsync('MyPlugin', (compiler, callback) => {
      console.log('🚀 beforeRun: 构建前准备');
      callback();
    });

    compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
      console.log('🏃 run: 开始读取记录');
      callback();
    });

    compiler.hooks.compile.tap('MyPlugin', (params) => {
      console.log('🔧 compile: 创建 compilation 对象');
    });

    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      console.log('📦 emit: 资源输出前处理');
      callback();
    });

    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('✅ done: 构建完成');
    });
  }
}

7 问题|define pulgin 注意事项(埋坑记)

下面三种写法有啥不一样的地方?原理是什么

new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true),
  PRODUCTION1: '"true"',
  BROWSER_SUPPORTS_HTML5: true,
})

参考