核心是编译期基于AST静态识别并删除确定未被引用的导出或表达式,需依赖eslint或打包器提供的跨文件未使用导出清单构建映射表,在ExportNamedDeclaration节点中匹配删除,同时对无副作用纯表达式做常量折叠,并规避动态import等副作用导致的模块解析失败。

要实现对不再使用的“死代码”在生产环境的静态剔除,核心不是靠运行时判断,而是利用 Babel 插件在编译期基于 AST 精准识别并删除确定未被引用的导出、表达式或语句。它必须配合构建工具的模块图能力,不能孤立工作。
必须先获取“未使用”的明确依据
Babel 本身不分析跨文件引用关系,所以得依赖外部扫描结果:
- 用
eslint-plugin-unused-imports配合自定义 ESLint 配置,扫描整个项目,输出 JSON 格式的未使用导出清单 - 或借助 Webpack/Rollup 的 module graph,在插件初始化阶段读取打包器提供的
compilation.modules,提取每个模块的 exports 及其实际被引用情况 - 最终构建一个映射表,例如:
{ 'utils.js': ['deepClone', 'throttle'], 'api.js': ['mockFetch'] }
在 ExportNamedDeclaration 节点中做匹配删除
插件遍历 AST 时,重点监听 ExportNamedDeclaration 类型节点:
- 提取当前文件路径(
path.hub.file.opts.filename) - 获取
node.specifiers中每个ExportSpecifier的local.name - 若该 name 出现在对应文件的未使用列表里,直接返回
undefined(Babel 会跳过该节点,等效于删除) - 注意保留
export default和export * from 'xxx'—— 它们无法静态判定是否被用,强行删会导致运行时报错
常量折叠与纯表达式消除可同步进行
这类优化属于“死代码”的另一类:可静态求值且无副作用的表达式。
- 在
BinaryExpression、UnaryExpression、StringLiteral等节点中调用path.isPure()判断是否安全 - 对确认无副作用的表达式(如
1 + 2 * 3、"Hello" + "World"),用@babel/helper-evaluate-path求值 - 将原节点替换为计算后的字面量(
t.numericLiteral(7)或t.stringLiteral("HelloWorld"))
剔除后出现 Cannot resolve module 'xxx' 的原因
这不是插件误删,而是连锁反应:
- 某个模块 A 导出了函数
foo,但foo本身又动态import('./feature') - 当
foo被标记为未使用并剔除后,Webpack 不再把./feature加入依赖图 - 构建产物里就缺失该 chunk,运行时加载失败
- 解决方法:在
exports剔除前,先做一次轻量级副作用追踪,对含动态import()、require()、全局变量赋值的导出,跳过剔除
不复杂但容易忽略

















