fix-knip-unused-exports
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFix Knip Unused Exports
修复Knip未使用导出问题
Fix knip "Unused exports" violations. There are several categories of violation, each with a different fix strategy.
修复Knip的「未使用导出」违规问题。违规分为多个类别,每个类别对应不同的修复策略。
When to Use
适用场景
- reports "Unused exports"
npm run knip
- 报告「未使用导出」
npm run knip
When NOT to Use
不适用场景
- The export is consumed by non-test production code in another file -- something else is wrong
- 该导出被其他文件中的非测试生产代码使用——此时存在其他问题
Workflow
操作流程
1. Identify Violations
1. 识别违规
bash
npm run knipOutput looks like:
Unused exports (3)
::error file=packages/foo/src/bar.ts,line=42,title=Unused exports::myFunctionbash
npm run knip输出示例:
Unused exports (3)
::error file=packages/foo/src/bar.ts,line=42,title=Unused exports::myFunction2. Classify Each Violation
2. 分类每个违规
For each flagged export, grep the entire repository (not just the package):
bash
rg "myFunction"Determine which category it falls into:
| Category | Callers | Fix |
|---|---|---|
| Test-only export | Used in same file + test files only | Extract to new file |
| Dead barrel re-export | Re-exported from | Remove the re-export from the barrel |
| Internally-only-used export | Used only within the same file, not by tests or other files | Remove the |
| Dead code | No callers anywhere | Delete the export |
| Production consumer exists | Used by non-test code in another file | Not a knip issue -- investigate further |
Important: When grepping, exclude test files to identify production consumers:
bash
rg "myFunction" --glob '!**/*.test.*'针对每个标记的导出,在整个仓库(不仅限于当前包)中搜索:
bash
rg "myFunction"确定其所属类别:
| 类别 | 调用方 | 修复方案 |
|---|---|---|
| 仅测试用导出 | 仅在同一文件和测试文件中使用 | 提取至新文件 |
| 无效桶式重导出 | 在index.ts中重导出,但生产代码通过相对路径或其他子路径导入 | 从桶文件中移除该重导出 |
| 仅内部使用的导出 | 仅在同一文件内使用,未被测试或其他文件引用 | 移除 |
| 无效代码 | 无任何调用方 | 删除该导出 |
| 存在生产环境调用方 | 被其他文件中的非测试代码使用 | 不属于Knip问题——需进一步排查 |
重要提示:搜索时排除测试文件,以识别生产环境调用方:
bash
rg "myFunction" --glob '!**/*.test.*'Fix: Test-Only Exports (Extract to New File)
修复方案:仅测试用导出(提取至新文件)
When a function is exported solely for test access but is also used internally in the same file.
当某个函数仅为测试访问而导出,但同时在同一文件内部被使用时。
Plan the Extraction
规划提取步骤
Before writing code, answer these questions:
a) What moves to the new file?
- The flagged export function/class/const
- All private helper functions it depends on
- All private constants/types it depends on
b) Are any helpers shared with functions staying behind?
- If yes, the helper must be exported from the new file, and the original file imports it
- This means the new file will have 2+ exports (which is fine for any filename-match-export lint rule)
c) Will the new file have exactly one exported function?
- If your project enforces a lint rule, the file MUST be named after that export:
filename-match-exportmyFunction.ts - If the file has 2+ function exports, the name is flexible
d) Does a test file with a matching name exist?
- If stays and
bar.tsexists, the test must still import something frombar.test.ts(if your project enforces a./barrule)test-imports-source - If is deleted (everything moved out), that rule typically only applies when the matching source file exists
bar.ts
e) Any circular dependency risk?
- Draw the import graph: new file -> original file -> new file is circular
- Fix: move the shared dependency to the new file or a third file
f) Does it export a constant?
- If your project enforces a lint rule, exported constants must live in a file named
constants-file-organizationconstants.ts - If the extracted function depends on a constant that other functions in the original file also use, do NOT export the constant from the new file. Instead, call the function (e.g., replace with
BUDGET[effort]) to avoid needing a separategetBudget(effort)constants.ts
编写代码前,先回答以下问题:
a) 哪些内容需要移至新文件?
- 被标记的导出函数/类/常量
- 它依赖的所有私有辅助函数
- 它依赖的所有私有常量/类型
b) 是否有辅助函数与保留在原文件的函数共享?
- 如果是,该辅助函数必须从新文件导出,原文件需导入它
- 这意味着新文件会有2个及以上导出(符合任何「文件名匹配导出」的 lint 规则)
c) 新文件是否仅包含一个导出函数?
- 如果项目强制执行「文件名匹配导出」的 lint 规则,文件名必须与该导出名称一致:
myFunction.ts - 如果文件包含2个及以上函数导出,文件名可灵活设置
d) 是否存在名称匹配的测试文件?
- 如果保留且存在
bar.ts,测试文件仍需从bar.test.ts导入某些内容(如果项目强制执行「测试导入源」规则)./bar - 如果被删除(所有内容移出),该规则通常仅在匹配的源文件存在时适用
bar.ts
e) 是否存在循环依赖风险?
- 绘制导入关系图:新文件 -> 原文件 -> 新文件即为循环依赖
- 修复方案:将共享依赖移至新文件或第三个文件
f) 是否导出常量?
- 如果项目强制执行「常量文件组织」的 lint 规则,导出的常量必须放在名为的文件中
constants.ts - 如果提取的函数依赖某个常量,且原文件中的其他函数也使用该常量,请勿从新文件导出该常量。而是通过函数调用(例如将替换为
BUDGET[effort])来避免需要单独的getBudget(effort)文件constants.ts
Execute the Extraction
执行提取操作
Create the new file in the same directory:
typescript
// myFunction.ts (new file)
import { SomeType } from '../types';
function privateHelper(): void { /* ... */ }
export function myFunction(): SomeType {
return privateHelper();
}Update the original file to import from the new file:
typescript
// bar.ts (original file, updated)
import { myFunction } from './myFunction';
function otherFunction() {
const result = myFunction(); // Now imports from new file
}Update test files to import from the new file:
typescript
// bar.test.ts (updated)
import { myFunction } from './myFunction';
// If bar.ts still exists, you may need to also import something from './bar'
// to satisfy any test-imports-source rule在同一目录下创建新文件:
typescript
// myFunction.ts (新文件)
import { SomeType } from '../types';
function privateHelper(): void { /* ... */ }
export function myFunction(): SomeType {
return privateHelper();
}更新原文件,从新文件导入:
typescript
// bar.ts (原文件,已更新)
import { myFunction } from './myFunction';
function otherFunction() {
const result = myFunction(); // 现在从新文件导入
}更新测试文件,从新文件导入:
typescript
// bar.test.ts (已更新)
import { myFunction } from './myFunction';
// 如果bar.ts仍然存在,可能还需要从'./bar'导入某些内容
// 以满足「测试导入源」规则Watch for Chained Violations
注意连锁违规
After extracting, run again. If function A was extracted to a new file alongside function B that A calls, but B is also only consumed by tests externally, knip will flag B too. You need to extract B to its own file so that A's file creates a genuine production import of B.
npm run knipExample: suppose was first extracted alongside into . If is only called internally within that file (by ), it will still be flagged. Fix: extract it to , making the import from a genuine production consumer.
throwMappedErrormapResponseFailureerror-mappers.tsthrowMappedErrormapResponseFailurethrowMappedError.tserror-mappers.ts提取完成后,再次运行。如果函数A被提取到新文件,同时函数B也被移至该文件且被A调用,但B仅被外部测试代码使用,Knip会标记B。此时需要将B提取到单独的文件,使A所在文件对B的导入成为真实的生产环境导入。
npm run knip示例:假设最初与一起被提取到。如果仅在该文件内部被调用,它仍会被标记。修复方案:将其提取到,使对它的导入成为真实的生产环境调用。
throwMappedErrormapResponseFailureerror-mappers.tsthrowMappedErrormapResponseFailurethrowMappedError.tserror-mappers.tsFix: Dead Barrel Re-Exports (Remove from index.ts)
修复方案:无效桶式重导出(从index.ts中移除)
When a barrel re-exports something, but no production code imports it through the barrel. This happens when:
index.ts- Production code within the same package uses relative imports (e.g., ) instead of the barrel
import { x } from './source' - Production code in other packages imports directly from a subpath (e.g., ) instead of the barrel
@scope/pkg/feature/handlers - The re-export was added speculatively but never consumed
当桶文件重导出某个内容,但没有生产环境代码通过桶文件导入它时会出现这种情况。常见原因:
index.ts- 同一包内的生产环境代码使用相对路径导入(例如)而非桶文件
import { x } from './source' - 其他包的生产环境代码直接从子路径导入(例如)而非桶文件
@scope/pkg/feature/handlers - 重导出是添加的,但从未被使用
How to Identify
如何识别
Grep excluding test files. If the only hits are:
- The barrel itself
index.ts - Source files using relative imports within the same package
- Test files
Then the barrel re-export is unused. Simply remove it from .
index.ts排除测试文件进行搜索。如果仅有的匹配结果是:
- 桶文件本身
index.ts - 同一包内使用相对路径导入的源文件
- 测试文件
则该桶式重导出是无效的,只需从中移除它即可。
index.tsCross-Package Test Imports
跨包测试导入
If a test in another package imports the symbol through the barrel (e.g., ), you need to provide an alternative import path after removing the barrel re-export:
import { x } from '@scope/pkg/feature'-
Add a subpath export in the source package's:
package.jsonjson{ "exports": { "./feature": "./src/feature/index.ts", "./feature/doSomething": "./src/feature/doSomething.ts" } } -
Update the test to import from the new subpath:typescript
import { doSomething } from '@scope/pkg/feature/doSomething';
This pattern follows typical subpath-export conventions used in monorepos.
如果其他包中的测试通过桶文件导入该符号(例如),移除桶式重导出后需要提供替代导入路径:
import { x } from '@scope/pkg/feature'-
在源包的中添加子路径导出:
package.jsonjson{ "exports": { "./feature": "./src/feature/index.ts", "./feature/doSomething": "./src/feature/doSomething.ts" } } -
更新测试文件,从新的子路径导入:typescript
import { doSomething } from '@scope/pkg/feature/doSomething';
此模式遵循 monorepo 中常用的子路径导出约定。
Fix: Internally-Only-Used Exports (Un-export)
修复方案:仅内部使用的导出(取消导出)
When an export is only used within the same file and not imported by anything else (not even tests), just remove the keyword:
exporttypescript
// Before
export const MySchema = z.object({ ... });
// After
const MySchema = z.object({ ... });This is common for Zod schemas that are only used as building blocks for other schemas in the same file.
当某个导出仅在同一文件内使用,未被任何其他内容(甚至测试)导入时,只需移除关键字:
exporttypescript
// 修复前
export const MySchema = z.object({ ... });
// 修复后
const MySchema = z.object({ ... });这种情况常见于Zod模式,它们仅作为同一文件中其他模式的构建块使用。
Verify
验证
Run ALL of these checks on the affected packages:
bash
undefined对受影响的包运行以下所有检查:
bash
undefinedKnip passes (the whole point)
Knip检查通过(核心目标)
npm run knip
npm run knip
Types still compile
类型仍可编译
npm run typecheck
npm run typecheck
Tests still pass
测试仍可通过
npm run test
npm run test
Lint passes (catches filename-match-export, test-imports-source, constants-file-organization, etc.)
Lint检查通过(捕获文件名匹配导出、测试导入源、常量文件组织等问题)
npm run lint
If cross-package imports exist, also verify the consuming package.npm run lint
如果存在跨包导入,还需验证消费包。Interacting Lint Rules
相互作用的Lint规则
Many TypeScript monorepos layer additional custom lint rules on top of knip. Adapt the fixes below to whichever of these your project uses.
许多TypeScript monorepo会在Knip之上添加额外的自定义Lint规则。根据项目使用的规则调整以下修复方案。
filename-match-export
(or similar)
filename-match-exportfilename-match-export
(或类似规则)
filename-match-exportIf a file has exactly ONE exported function (not a React component), the filename must match the function name.
- in
export function loadConfig-- passesloadConfig.ts - in
export function loadConfig-- failshelpers.ts - Two exports in -- rule does not apply (multiple exports)
helpers.ts
如果文件仅包含一个导出函数(非React组件),文件名必须与函数名匹配。
- 在
export function loadConfig中——符合规则loadConfig.ts - 在
export function loadConfig中——违反规则helpers.ts - 中有两个导出——规则不适用(多个导出)
helpers.ts
test-imports-source
(or similar)
test-imports-sourcetest-imports-source
(或类似规则)
test-imports-sourceIf and both exist, the test must import from .
foo.test.tsfoo.ts./foo- Imports like satisfy the rule
import { x } from './foo' - Typically also accepts importing from or
'.'if'./index're-exports fromindex.tsfoo.ts - If is deleted, the rule does not apply
foo.ts
如果和都存在,测试文件必须从导入。
foo.test.tsfoo.ts./foo- 类似的导入符合规则
import { x } from './foo' - 通常也接受从或
'.'导入(如果'./index'从index.ts重导出)foo.ts - 如果被删除,规则不适用
foo.ts
constants-file-organization
(or similar)
constants-file-organizationconstants-file-organization
(或类似规则)
constants-file-organizationExported constants must be defined in a file named .
constants.ts- If you extract a function that depends on a shared constant, do NOT export the constant from the function's file
- Instead, replace direct constant access with function calls (e.g., becomes
BUDGET[effort])getBudget(effort) - Or move the constant to a file
constants.ts
导出的常量必须定义在名为的文件中。
constants.ts- 如果提取的函数依赖共享常量,请勿从函数所在文件导出该常量
- 而是将直接常量访问替换为函数调用(例如改为
BUDGET[effort])getBudget(effort) - 或者将常量移至文件
constants.ts
How Knip Traces Exports
Knip如何追踪导出
- Knip ignores test files (,
**/*.test.*)**/*.spec.* - in
ignoreIssuessuppresses warnings ON the listed file, but does NOT make the source export "used"knip.json - Barrel re-exports () from an
export { x } from './source'withindex.tsdo NOT count as usage of the source exportignoreIssues - Only genuine imports from non-test, non-ignored project files count as usage
- (if set) means exports from entry point files are checked too, so entry-point-style files (migrations, scripts) may need explicit
includeEntryExports: trueignoreIssues
- Knip忽略测试文件(、
**/*.test.*)**/*.spec.* - 中的
knip.json会抑制列出文件的警告,但不会使源导出被标记为「已使用」ignoreIssues - 来自且带有
index.ts的桶式重导出(ignoreIssues)不会被视为源导出的使用export { x } from './source' - 只有来自非测试、非忽略项目文件的真实导入才会被视为使用
- 如果设置了,入口文件的导出也会被检查,因此入口点类型的文件(迁移脚本、CLI)可能需要显式添加
includeEntryExports: trueignoreIssues
Package Subpath Exports
包子路径导出
When removing barrel re-exports that cross-package tests relied on, add subpath exports to :
package.jsonjson
{
"exports": {
"./feature": "./src/feature/index.ts",
"./feature/doSomething": "./src/feature/doSomething.ts"
}
}当移除跨包测试依赖的桶式重导出时,需在中添加子路径导出:
package.jsonjson
{
"exports": {
"./feature": "./src/feature/index.ts",
"./feature/doSomething": "./src/feature/doSomething.ts"
}
}What Not to Do
禁止操作
- Do not add files to in
ignoreIssuesunless they are genuine entry point scripts (migrations, CLIs)knip.json - Do not merge all functions into one file to reduce exports -- same-file usage of an export does not count as usage from knip's perspective
- Do not remove the keyword if tests need it -- the tests would break
export - Do not create circular imports between the new and original files
- Do not export constants from non-files if your project enforces a
constants.tslint ruleconstants-file-organization
- 除非是真实的入口点脚本(迁移脚本、CLI),否则不要将文件添加到的
knip.json中ignoreIssues - 不要将所有函数合并到一个文件以减少导出——Knip不将同一文件内对导出的使用视为有效使用
- 如果测试需要该导出,不要移除关键字——否则测试会失败
export - 不要在新文件和原文件之间创建循环依赖
- 如果项目强制执行「常量文件组织」Lint规则,不要从非文件导出常量
constants.ts