0%

Knip 工具概述——检测未使用的代码与依赖项

knip 这个宝藏工具,简直是代码洁癖患者的福音!它能帮你找出项目里那些早就没人用却还在 package.Json 里的依赖包,还有各种死代码。用了几天下来,发现了不少已经被遗忘的代码,项目瞬间清爽了许多。

什么是 knip?

  knip(Kniepertje,荷兰语中的”小雪橇”)是一个用于分析 Node.JS 和 Typescript/Javascript 项目的工具,专门用来检测项目中的未使用依赖、导出、导入、类型、函数等。它可以帮助开发者保持代码库的清洁,减少包体积,提升项目维护性。

  在现代 Javascript 开发中,随着项目复杂度的增加,很容易积累各种未使用的依赖和死代码。这些”技术债务”不仅增加了项目的复杂度,还可能带来安全风险和性能问题。knip 通过静态分析项目代码和配置文件,自动识别这些问题并提供详细的报告。

knip 的核心功能

  1. 未使用的依赖检测: 识别 package.Json 中定义但从未在代码中使用的依赖
  2. 导出未使用检测: 找出导出但从未被导入的模块
  3. 导入未定义检测: 发现已导入但未定义的模块
  4. 未使用的类型和接口: 识别未使用的 Typescript 类型定义
  5. 未使用的函数和变量: 找出定义但从未使用的函数和变量
  6. 配置文件分析: 检查各种配置文件中的未使用项

安装和基本使用

安装 knip

1
2
3
4
5
6
7
# 作为开发依赖安装 npm install --save-dev knip

# 或使用 yarn
yarn add -D knip

# 或使用 pnpm
pnpm add -D knip

全局安装(可选)

1
2
3
# 全局安装以便在任何项目中使用 npm install -g knip

# 检查版本 knip --version

基本使用命令

1
2
3
4
5
6
7
8
9
10
# 检查当前目录(默认行为)
npx knip

# 检查指定目录 npx knip src/

# 检查特定文件类型 npx knip --extensions JS,TS,Json

# 详细输出 npx knip --verbose

# Json 输出格式 npx knip --reporter Json

配置文件

基础配置 (knip.Json)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"$schema": "https://unpkg.com/knip@3/schema.Json",
"entry": ["src/index.TS", "src/cli.TS"],
"project": ["src/**/*.TS", "src/**/*.JS"],
"ignore": [
"dist/",
"build/",
"node_modules/"
],
"ignoreDependencies": [
"@types/Node",
"Typescript"
],
"ignoreBinaries": [
"nodemon",
"TS-Node"
],
"workspaces": {
"packages/*": {
"entry": ["index.TS"],
"project": ["**/*.TS", "**/*.JS"]
}
}
}

高级配置 (knip.config.JS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/** @type {import('knip').KnipConfig} */
module.exports = {
// 入口文件 entry: [
'src/index.TS',
'src/cli.TS',
'scripts/**/*.JS'
],

// 项目文件 project: [
'src/**/*.TS',
'src/**/*.JS',
'test/**/*.TS',
'test/**/*.JS'
],

// 忽略的路径 ignore: [
'dist/**/*',
'build/**/*',
'coverage/**/*',
'*.d.TS',
'temp/**/*'
],

// 忽略的依赖项 ignoreDependencies: [
'@types/*', // Typescript 类型定义
'Typescript',
'prettier',
'eslint',
'jest',
'vitest',
'@vitejs/plugin-Vue' // 构建工具相关
],

// 忽略的二进制文件 ignoreBinaries: [
'nodemon',
'TS-Node',
'babel-Node',
'webpack'
],

// 工作区配置 workspaces: {
'apps/*': {
entry: ['index.TS', 'src/index.TS'],
project: ['**/*.TS', '**/*.JS'],
ignoreDependencies: ['@internal/utils']
},
'packages/*': {
entry: ['index.TS'],
project: ['**/*.TS', '**/*.JS']
}
},

// 规则配置 rules: {
'no-unused-exports': 'warn', // 未使用的导出警告级别
'no-unused-deps': 'error' // 未使用的依赖错误级别
}
};

实际应用场景

检测未使用的依赖

1
2
3
4
5
6
7
# 查看所有未使用的依赖 npx knip --dependencies

# 示例输出:
# Unused dependencies
# └─ chalk (used 0 times)
# └─ lodash-es (used 0 times)
# └─ moment (used 0 times)

检测未使用的导出

1
2
3
4
5
6
7
8
# 查看未使用的导出 npx knip --exports

# 示例输出:
# Unused exports
# └─ src/utils.TS
# └─ formatDate
# └─ validateEmail
# └─ debounce

检测未使用的类型

1
2
3
4
5
6
7
8
# 查看未使用的类型定义 npx knip --types

# 示例输出:
# Unused types
# └─ src/types.TS
# └─ UserConfig
# └─ ApiResponse
# └─ ValidationRule

与 CI/CD 集成

GitHub Actions 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/workflows/knip.yml
name: Knip

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

jobs:
knip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.JS
uses: actions/setup-Node@v4
with:
Node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run knip
run: npx knip
env:
CI: true

在 package.Json 中添加脚本

1
2
3
4
5
6
7
{
"scripts": {
"knip": "knip",
"knip:check": "knip --fail-on-unprocessed-entry",
"knip:ci": "knip --reporter github-actions"
}
}

与 husky 集成

1
2
3
4
5
6
7
{
"lint-staged": {
"*.{JS,TS,Json,yaml,yml}": [
"knip --check"
]
}
}
1
2
3
4
5
6
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
npm run knip:check

高级功能

插件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// knip.config.JS
const React = require('@knip/monorepo/plugins/React');
const Vue = require('@knip/monorepo/plugins/Vue');

/** @type {import('knip').KnipConfig} */
module.exports = {
entry: ['src/index.{JS,TS,tsx,jsx}'],
project: ['src/**/*.{JS,TS,tsx,jsx}'],

// 使用插件 plugins: [
React,
Vue
],

// 插件特定配置 React: {
entry: ['src/main.{jsx,tsx}']
},

Vue: {
entry: ['src/main.Vue']
}
};

自定义检查器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// custom-checker.JS
const fs = require('fs');
const path = require('path');

class CustomChecker {
static async scan(projectDir) {
const files = [];

// 扫描自定义文件类型 const customFiles = fs.readdirSync(projectDir)
.filter(file => file.endsWith('.custom'))
.map(file => path.join(projectDir, file));

for (const file of customFiles) {
const content = fs.readFileSync(file, 'utf-8');
// 分析自定义格式的文件 if (content.includes('TODO')) {
files.push({
path: file,
issues: ['Contains TODO comment']
});
}
}

return files;
}
}

module.exports = CustomChecker;

最佳实践

1. 逐步清理项目

1
2
3
4
5
6
7
# 第一步:生成详细报告 npx knip --reporter Json > knip-report.Json

# 第二步:忽略已知的假阳性
# 在配置文件中添加 ignore 规则

# 第三步:定期运行检查
# 添加到 CI 流程中

2. 团队协作规范

1
2
3
4
5
6
7
// package.Json
{
"scripts": {
"audit": "npm run knip && npm audit",
"clean": "npm run knip:check && npm run lint && npm run test"
}
}

3. 配置文件管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// knip.shared.JS - 共享配置 const sharedConfig = {
ignore: [
'dist/',
'build/',
'node_modules/',
'coverage/',
'*.min.JS'
],

ignoreDependencies: [
'@types/*',
'Typescript',
'vitest',
'happy-dom'
]
};

module.exports = sharedConfig;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// apps/frontend/knip.config.JS
const sharedConfig = require('../../knip.shared.JS');

/** @type {import('knip').KnipConfig} */
module.exports = {
...sharedConfig,
entry: ['src/index.tsx'],
project: ['src/**/*.{TS,tsx,JS,jsx}'],
ignoreDependencies: [
...sharedConfig.ignoreDependencies,
'@emotion/React', // 前端特有依赖
'@emotion/styled'
]
};

与其他工具对比

knip vs depcheck

特性knipdepcheck
未使用依赖检测
未使用导出检测
未使用类型检测
配置文件分析
性能⭐⭐⭐⭐⭐⭐⭐⭐
Typescript 支持⭐⭐⭐⭐⭐⭐⭐⭐⭐

knip vs webpack-bundle-analyzer

1
2
3
4
5
6
7
// webpack-bundle-analyzer: 构建后分析
// 优点:可视化依赖图,大小分析
// 缺点:只能分析运行时依赖,无法检测死代码

// knip: 源码分析
// 优点:检测死代码,未使用导出,配置文件问题
// 缺点:无法分析实际打包大小

集成使用示例

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.JS
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
// ... 其他配置 plugins: [
// 在开发环境下启用分析 process.env.ANALYZE_BUNDLE && new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.Html'
})
].filter(Boolean)
};

常见问题和解决方案

1. 处理动态导入

1
2
3
4
5
6
7
8
9
10
// 问题:knip 无法静态分析动态导入 const modulePath = `./modules/${moduleName}`;
const module = await import(modulePath);

// 解决方案:使用注释忽略
// knip-ignore
const module = await import(`./modules/${moduleName}`);

// 或者使用条件导入 if (process.env.NODE_ENV === 'development') {
const devModule = require('./dev-module'); // knip-ignore
}

2. 处理插件和主题

1
2
3
4
5
6
7
8
{
"dependencies": {
"my-app": "^1.0.0",
// 这些是插件,knip 会标记为未使用
"plugin-a": "^1.0.0",
"theme-default": "^1.0.0"
}
}
1
2
3
4
5
6
7
8
9
10
11
// knip.config.JS
module.exports = {
ignoreDependencies: [
// 插件系统
'plugin-*',
// 主题
'theme-*',
// 未知但必需的依赖
'some-dynamic-dep'
]
};

3. 处理配置文件引用

1
2
3
4
5
6
7
8
9
10
11
12
13
// webpack.config.JS - 使用某些包但不直接 import
module.exports = {
plugins: [
new SomeWebpackPlugin() // 需要在配置中声明
]
};

// knip.config.JS
module.exports = {
ignoreDependencies: [
'some-webpack-plugin' // 通过配置使用,不是直接 import
]
};

性能优化

大项目优化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// knip.large-project.config.JS
module.exports = {
// 只扫描特定目录 entry: ['src/main.TS'],
project: [
'src/**/*.TS',
'src/**/*.JS'
// 排除测试和构建文件
],

// 限制扫描深度 ignore: [
'node_modules/**/*',
'dist/**/*',
'build/**/*',
'test/fixtures/**/*' // 大型测试数据
],

// 并行处理 concurrency: 4,

// 缓存结果 cache: true
};

增量检查

1
2
3
# 只检查变更的文件 npx knip --since HEAD~1

# 与 Git 集成 git diff --name-only HEAD~1 | grep -E '\.(TS|JS|tsx|jsx)$' | xargs npx knip

总结

  • knip 是检测未使用代码和依赖的强大工具
  • 正确配置可以显著提升项目质量
  • 与 CI/CD 集成可以防止技术债务积累
  • 需要注意处理动态导入等特殊情况
  • 适用于各种规模的 Javascript/Typescript 项目
  • 是代码维护的必备工具之一

用了 knip 后,发现项目里居然有十几个从来没用过的依赖包,还有一些早已被弃用的函数。清理完这些代码后,不仅包体积减小了,代码可读性也提升了。这种”代码审计”的感觉真的很棒,就像给项目做了一次全面的体检。

扩展阅读

  • Knip Official Documentation
  • Clean Code Principles
  • Dependency Management Best Practices
  • Javascript Project Maintenance
  • Static Analysis Tools Comparison

参考资料

  • Knip GitHub Repository: https://github.com/webpro/knip
  • Node.JS Dependency Management: https://nodejs.org/API/packages.Html
  • Typescript Compiler API: https://github.com/microsoft/Typescript/wiki/Using-the-Compiler-API
  • AST Static Analysis: https://astexplorer.net/
bulb