Skip to content

Knip:死代码与无用依赖检测

概述

Knip 是一个面向 TypeScript monorepo 的死代码检测工具。它会从每个包的入口文件出发,追踪整个 import 图,自动发现:

  • 未使用的文件 —— 源码文件没有被任何地方 import
  • 未使用的 exports —— 函数/类型/变量 被导出但无消费者
  • 未使用的 npm 依赖 —— package.json 中声明但代码未 import 的包
  • 孤立的 catalog 条目 —— pnpm-workspace.yaml 中列了但无 package.json 引用的依赖

本项目已集成 Knip 6.29.0,配置文件位于仓库根目录 knip.config.ts


快速使用

bash
# 全仓扫描(含 apps + packages)
pnpm knip

# 只扫共享包(零误报,最干净)
pnpm knip:packages

# 只扫应用
pnpm knip:apps

# 自动清理 unused exports
pnpm knip:fix

预期结果

命令预期说明
pnpm knip:packagesexit 0,零发现共享包(contract / utils / client / axios / eslint-config)的 import 链完全显式,knip 可精确追踪
pnpm knip(全仓)exit 1,少量发现大部分是框架隐式组装的已知误报,少量可能是真实死代码

配置设计

knip 的核心原理是从入口文件出发,沿 import 链追踪。因此每个 workspace 包都需要在 knip.config.ts 中声明 entry files。

共享包(packages/*)

ts
"packages/contract": {
  entry: ["src/index.ts"],  // barrel export → 追踪所有子模块
},

共享包的 import 链完全显式(import { xxx } from '@walnut/contract'),knip 可 100% 精确追踪。

前端应用(apps/admin)

ts
"apps/admin": {
  entry: [
    "src/main.ts",                    // 应用入口
    "src/router/index.ts",            // 路由(import 所有页面)
    "src/router/routes/**/*.ts",      // 路由配置
  ],
  project: ["src/**/*.{ts,vue}"],
  vite: false,                        // 禁用 Vite 插件(见下文)
  ignore: [
    "src/components/**",              // auto-import 隐式注册
    "src/composables/**",             // 同上
    "src/hooks/**",                   // 同上
    "src/api/**",                     // barrel + store 动态引用
    "src/store/**",                   // Pinia 动态注册
    "src/socket/**",                  // 动态 import
    "src/types/**",                   // ambient 引用
    "src/const/**",                   // 同上
    "src/enums/**",                   // 同上
    "build/**",                       // 构建脚本
  ],
},

后端应用(apps/server)

ts
"apps/server": {
  entry: ["apps/api/src/main.ts"],
  project: ["apps/**/*.ts", "libs/**/*.ts"],
  ignore: [
    "**/*.module.ts",                 // @Module() 装饰器隐式组装
    "**/*.controller.ts",             // @Controller() 同上
    "**/*.service.ts",                // @Injectable() DI
    "**/*.dto.ts",                    // 装饰器引用
    "**/*.schema.ts",                 // Mongoose 动态加载
    "**/*.guard.ts",                  // DI 组装
    "**/*.interceptor.ts",            // 同上
    "**/*.pipe.ts",                   // 同上
    "**/*.filter.ts",                 // 同上
    "**/*.decorator.ts",              // 同上
    "**/*.strategy.ts",               // 同上
    "**/*.middleware.ts",             // 同上
    "libs/*/src/index.ts",            // tsconfig paths 引用
    "infra/**",                       // SWC 构建配置
    "**/*.e2e-spec.ts",              // E2E 测试
    "**/vitest.config.*.ts",         // 测试配置
  ],
},

为什么 apps 有大量文件被忽略

knip 是静态 import 追踪工具。以下框架机制对 knip 不可见,必须手动排除:

框架机制knip 能追踪?
Vue 3unplugin-auto-import(自动注入 composables)
Vue 3unplugin-vue-components(自动注册组件)
Vue 3Pinia store 动态注册
NestJS@Module() 装饰器隐式组装
NestJS@Controller() / @Injectable() 依赖注入
NestJSMongoose schema 动态加载
Adminvite.config.ts 在模块顶层调用 JSON.parse(env)

对策:这些文件类型在 knip.config.ts 中通过 ignore 排除在 unused files 检测之外。knip 仍会对它们做 export 级别的检测。


已知技术局限

~build/* 路径别名

admin 中有 10 个文件 import ~build/package~build/time,knip 无法解析。这是因为 vite-tsconfig-paths 在构建时动态解析这些别名,knip 无此上下文。

影响pnpm knip 输出中会有 10 条 Unresolved imports。不影响代码正确性。

vite.config.ts 加载失败

admin 的 vite.config.ts 在模块顶层调用 JSON.parse(env.VITE_PROXY),knip 的 jiti 加载器执行时 env 未定义 → JSON.parse(undefined) 抛异常。

对策:在 apps/admin workspace 配置中设置 vite: false,改为手动指定 entry files。

Vite 插件 devDependencies

30+ 个 Vite 插件(如 @vitejs/plugin-vueunplugin-auto-import)在 devDependencies 中声明但不会在源码中 import——它们只在 vite.config.ts 中引用。knip 无法解析 vite.config.ts,所以报告为 "unused devDependencies"。

对策:已加入 ignoreDependencies 列表。


如何解读输出

text
Unused files (7)        ← 值得排查:可能真的有死文件
Unused dependencies (5) ← 值得排查:可能真的有没用到的包
Unlisted binaries (4)   ← CLI 工具在 scripts 中引用但未列在 dependencies
Unresolved imports      ← 路径别名问题,已知局限(见上文)
Unused exports (39)     ← 导出但无消费者,可按需清理
Unused catalog entries  ← pnpm-workspace.yaml 中的孤立条目
Configuration hints     ← knip 建议清理的冗余配置项(可忽略)

日常维护

添加新 workspace 包

knip.config.tsworkspaces 中添加:

ts
"packages/新包名": {
  entry: ["src/index.ts"],
},

添加新 app

参照现有 apps/adminapps/server 的配置,根据框架类型选择合适的 entry 和 ignore 策略。

CI 集成建议

可以将 pnpm knip:packages 加入 CI 门禁(packages 零误报,exit 0 即健康)。全仓 pnpm knip 由于存在已知误报,建议作为信息性检查而非硬门禁。

yaml
# .github/workflows/ci.yml 示例片段
- name: Dead code check (packages)
  run: pnpm knip:packages

相关资源

基于 MIT 许可发布