问题:definePlugin 是否能在插件中(构建时)使用(不能)
node 环境可以使用 node 原生环境变量 process.env.xxx,但是不能使用 webpack definePlugin 中的变量,需要区分每种环境变量的生效范围。
“构建范围”(即哪些代码会被 Webpack/Rspack 等构建工具处理,哪些不会)其实是由你的项目结构和构建工具的配置共同决定的,但不是通过 Node.js 的类型声明或全局变量配置来界定的。
1. 构建范围的本质
- 被构建的代码:指的是被 Webpack/Rspack 等工具“打包”的代码,通常是你的前端业务源码(如
src/目录下的.js、.ts、.vue等文件),以及它们的依赖。 - 未被构建的代码:指的是直接由 Node.js 执行的代码,比如构建配置文件(如
webpack.config.js、formula.config.ts)、插件的setup逻辑、脚本等。
2. 构建范围的界定方式
a. 入口文件(Entry)
- 构建工具会从配置的入口文件(如
entry: './src/index.ts')开始递归分析依赖,把所有依赖的模块都打包进最终产物。 - 这些文件和它们的依赖就是“构建范围”。
b. 构建工具的配置
- 例如 Webpack 的
entry、include、exclude、module.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 执行时机
┌───────────────────────────────┐
│ 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,
})