Knip:死代码与无用依赖检测
概述
Knip 是一个面向 TypeScript monorepo 的死代码检测工具。它会从每个包的入口文件出发,追踪整个 import 图,自动发现:
- 未使用的文件 —— 源码文件没有被任何地方 import
- 未使用的 exports —— 函数/类型/变量 被导出但无消费者
- 未使用的 npm 依赖 ——
package.json中声明但代码未 import 的包 - 孤立的 catalog 条目 ——
pnpm-workspace.yaml中列了但无package.json引用的依赖
本项目已集成 Knip 6.29.0,配置文件位于仓库根目录 knip.config.ts。
快速使用
# 全仓扫描(含 apps + packages)
pnpm knip
# 只扫共享包(跨平台 + tooling)
pnpm knip:packages
# 只扫应用
pnpm knip:apps
# 自动清理 unused exports
pnpm knip:fix实际命令为
cross-env NODE_OPTIONS=--max-old-space-size=8192 knip ...(防 OOM + Windows 兼容)。knip:packages使用--workspace ./packages/*/*——此前写的packages/*匹配不到platform-any/*这类两层目录,knip 静默空操作,当时声称的"零发现"是假象;2026-08-08 修复为两层通配后才是真实结果。
预期结果
| 命令 | 预期 | 说明 |
|---|---|---|
pnpm knip:packages | exit 0,零发现 | 共享包(contract / types / utils-core / client / http / ui / eslint-config)的 import 链完全显式,knip 可精确追踪(修复为 --workspace ./packages/*/* 后的真实结果) |
pnpm knip(全仓) | exit 1,少量发现 | 大部分是框架隐式组装的已知误报,少量可能是真实死代码(exit 1 属预期门禁,见下文 CI 建议) |
配置设计
knip 的核心原理是从入口文件出发,沿 import 链追踪。因此每个 workspace 包都需要在 knip.config.ts 中声明 entry files。
共享包(packages/*)
"packages/platform-any/contract": {
entry: ["src/index.ts"], // barrel export → 追踪所有子模块
},共享包的 import 链完全显式(import { xxx } from '@walnut/contract'),knip 可 100% 精确追踪。
前端应用(apps/admin)
"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)
"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 3 | unplugin-auto-import(自动注入 composables) | ❌ |
| Vue 3 | unplugin-vue-components(自动注册组件) | ❌ |
| Vue 3 | Pinia store 动态注册 | ❌ |
| NestJS | @Module() 装饰器隐式组装 | ❌ |
| NestJS | @Controller() / @Injectable() 依赖注入 | ❌ |
| NestJS | Mongoose schema 动态加载 | ❌ |
| Admin | vite.config.ts 依赖 loadEnv / 回调执行(useBuildEnv() 内 JSON.parse(env.VITE_PROXY)),knip 的 jiti 无法安全加载 | ❌ |
对策:这些文件类型在 knip.config.ts 中通过 ignore 排除在 unused files 检测之外。knip 仍会对它们做 export 级别的检测。
已知技术局限
~build/* 路径别名(已解决)
admin 中曾有文件 import ~build/package 和 ~build/time,旧版 knip 无法解析——vite-tsconfig-paths 在构建时动态解析这些别名,knip 无此上下文,输出 Unresolved imports。
已解决(2026-08-08):knip.config.ts 中的 paths 块已删除,knip 不再做别名解析,对应问题不再出现。
vite.config.ts 加载失败
admin 的 vite.config.ts 在 useBuildEnv() 函数内调用 JSON.parse(env.VITE_PROXY)(由 loadEnv 提供 env),knip 的 jiti 加载器无法安全执行 → 加载抛异常。
对策:在 apps/admin workspace 配置中设置 vite: false,改为手动指定 entry files。
Vite 插件 devDependencies
30+ 个 Vite 插件(如 @vitejs/plugin-vue、unplugin-auto-import)在 devDependencies 中声明但不会在源码中 import——它们只在 vite.config.ts 中引用。knip 无法解析 vite.config.ts,所以报告为 "unused devDependencies"。
对策:已加入 ignoreDependencies 列表。
如何解读输出
Unused files (7) ← 值得排查:可能真的有死文件
Unused dependencies (4) ← 值得排查:可能真的有没用到的包
Unlisted binaries (1) ← CLI 工具在 scripts 中引用但未列在 dependencies
Unresolved imports ← 路径别名问题,已知局限(见上文)
Unused exports (40+) ← 导出但无消费者,可按需清理
Unused catalog entries ← pnpm-workspace.yaml 中的孤立条目
Configuration hints (69)← knip 建议清理的冗余配置项(可忽略)以上为全仓实测数字(2026-08-08),pnpm knip 当前退出码为 1——作为门禁属预期行为(见下文 CI 建议)。
日常维护
添加新 workspace 包
在 knip.config.ts 的 workspaces 中添加:
"packages/platform-any/新包名": {
entry: ["src/index.ts"],
},添加新 app
参照现有 apps/admin 或 apps/server 的配置,根据框架类型选择合适的 entry 和 ignore 策略。
CI 集成建议
可以将 pnpm knip:packages 加入 CI 门禁(packages 零误报,exit 0 即健康;若直接调 knip,务必用 --workspace ./packages/*/* 的两层通配写法——packages/* 匹配不到 platform-any/* 这类两层目录,会静默空操作)。全仓 pnpm knip 由于存在已知误报,建议作为信息性检查而非硬门禁(当前实测 exit 1,属预期)。
# .github/workflows/ci.yml 示例片段
- name: Dead code check (packages)
run: pnpm knip:packages相关资源
- Knip 官方文档
- Knip Configuration 参考
- Knip Known Issues
- Knip GitHub 仓库(11.6k+ stars,周下载 770 万)