@babel/plugin-transform-dead-code-elimination 在老旧项目中效果有限,因其仅识别 if (false) 或 /*#__PURE__*/ 标注的代码,无法处理由 process.env.FEATURE_X === 'false' 等语义化死分支、配置驱动条件、未使用导出等真实场景;需自定义插件结合环境变量模拟求值、跨工具链(如 ESLint)分析未使用导出,并谨慎处理副作用与模块解析风险。

为什么 @babel/plugin-transform-dead-code-elimination 在老旧项目里基本没用
它只处理明确的 if (false) 或带 /*#__PURE__*/ 标注的函数调用,对真实项目中大量存在的「逻辑上不可达但语法上合法」的代码(比如被条件编译屏蔽的模块导入、永远不执行的分支、已废弃但未删除的工具函数)完全无感。老旧项目里 dead code 往往藏在配置驱动的条件判断、环境变量开关、或历史遗留的 feature flag 里,Babel 默认插件根本看不到语义。
怎么写一个能识别 process.env.FEATURE_X === 'false' 这类死分支的插件
核心是劫持 BinaryExpression 节点,在遍历 AST 时提前求值环境变量表达式。不能依赖运行时——得在编译期模拟简单常量折叠。
- 用
@babel/types的isBooleanLiteral和evaluate方法判断左侧是否为确定的环境变量引用(如process.env.XXX),右侧是否为字面量 - 维护一个
envMap配置对象(从.babelrc或插件选项传入),例如{ FEATURE_LOGIN: 'false', API_VERSION: 'v2' } - 当匹配到
process.env.FEATURE_LOGIN === 'false'且envMap.FEATURE_LOGIN === 'false',就把整个IfStatement替换为null(即删除) - 注意:只处理严格相等(
===)、不等(!==)、真值判断(!process.env.X),避免误删类型转换场景
如何安全剔除已导出但从未被 import 的模块成员
这需要跨文件分析,单靠 Babel 插件做不到。必须配合 webpack 或 rollup 的 module graph,或用 eslint-plugin-unused-imports 先扫描,再把结果喂给自定义 Babel 插件。
- 先用
eslint --ext .js,.ts --no-error-on-unmatched-pattern -c eslint-config-unused.js .输出所有未使用导出到 JSON 文件 - 插件启动时读取该 JSON,构建
unusedExports = { 'utils.js': ['deepClone', 'throttle'] }映射表 - 在
ExportNamedDeclaration遍历时,检查node.specifiers中每个ExportSpecifier的local.name是否在对应文件的未使用列表里 - 匹配则返回
undefined(删除该导出),但保留export default和export * from——它们无法静态判定
剔除后为什么某些模块突然报 Cannot resolve module 'xxx'
因为插件删了 import 语句,但没删其副作用(比如模块顶层有立即执行的初始化逻辑)。Webpack/Vite 仍会尝试解析该模块路径,而路径可能已被删或重命名。
- 默认不删除任何
import语句,除非显式配置removeImports: true - 若开启,必须同时检查该
ImportDeclaration是否有副作用:看源文件是否含顶层console、require、赋值语句、或__esModule标记 - 更稳妥的做法是改用
sideEffects: false+package.json声明,让打包器自己裁剪,Babel 插件只负责逻辑分支和导出清理
真正难的不是删代码,而是确认「删了不影响行为」——尤其当项目混用 CommonJS/ESM、动态 require、或运行时拼接模块名时,静态分析必然漏判。上线前务必跑全量测试,且保留一份剔除前的 sourcemap 对照。

















