关键在于结合静态分析、编译器能力与运行时反馈构建可验证、可收敛的隔离流程:通过ts ast标记死分支、插件识别未引用导出、jsdoc标记隔离、覆盖率反哺判定,并将死逻辑归档至src/dead/实现可审计回滚。

自定义构建脚本可以在打包阶段有效识别并隔离死逻辑,但关键不在于“完全自动发现”,而在于**结合静态分析、编译器能力与运行时反馈,构建可验证、可收敛的隔离流程**。纯靠脚本扫描关键词(如if (false)或未调用函数)容易误判,需分层设计。
利用 TypeScript + TSC 的 --removeComments 与 --noEmit 配合 AST 分析
TypeScript 编译器本身不直接删除死代码,但它输出的 AST(抽象语法树)是可靠起点。在自定义脚本中:
- 用
ts.createSourceFile()加载源码,启用skipLibCheck: true和完整语法解析 - 遍历 AST,标记明显死分支:字面量条件为
false、return后的语句、无出口的throw/process.exit()后代码 - 对函数声明做“可达性打标”:从入口文件(如
index.ts)出发,递归追踪所有显式调用点;未被任何路径触达的函数体视为候选死逻辑 - 注意:箭头函数赋值给变量、事件回调、字符串
eval等动态调用需白名单豁免,避免误删
借助 Webpack / Vite 插件在 bundle 前注入“死逻辑标记层”
在构建管道中插入自定义插件(如 Webpack 的 compilation.hooks.optimizeChunks),实现更精准的上下文感知:
- 在
import解析阶段记录模块依赖图,识别导出但从未被import或require引用的命名导出(如export function utilsA()却全项目无import { utilsA }) - 对带有特定 JSDoc 标记的代码块自动隔离:
/** @dead-code reason="legacy api" */,脚本将其提取到./dead/目录并替换为/* DEAD: utilsA removed */占位注释 - 生成
dead-code-report.json,包含文件路径、行号、触发规则、置信度(高/中/低),供人工复核
运行时覆盖率反哺构建:用 Jest + c8 生成“执行热力图”
静态分析无法覆盖条件分支的实际执行情况。将单元测试覆盖率数据回填到构建流程:
- 运行
jest --coverage --collectCoverageFrom="src/**/*.{ts,tsx}" --coverageProvider=v8 - 解析
coverage/coverage-final.json,提取每行lcount(执行次数)为 0 且非空行、非注释行的代码段 - 在构建脚本中比对:若某函数体 100% 行数未被执行,且静态分析也判定其不可达,则自动添加
/* DEAD BY COVERAGE + AST */注释并移入隔离区 - 注意保留至少 3 次连续全量测试未覆盖才触发,避免因测试遗漏误判
隔离 ≠ 删除:建立可审计、可回滚的“死逻辑仓库”
直接 rm 风险极高。推荐隔离策略:
- 新建
src/dead/目录,按日期+模块名归档(如2024-06-api-v1-utils.ts),保留原始git blame信息 - 在原位置插入编译期报错桩:
throw new Error("[DEAD CODE] utilsA removed on 2024-06-15. See src/dead/..."); - CI 流程中加入检查:若新增代码引用了
/dead/中的符号,立即失败并提示迁移路径 - 每季度运行脚本扫描
/dead/中超 6 个月未被git log -S搜索到的文件,发起自动 PR 归档到 Git LFS 或文档库
不复杂但容易忽略:死逻辑治理是持续过程,不是一次构建就能清零。重点是让每次打包都产出可读报告、留痕操作、限制扩散,而不是追求 100% 自动剔除。










