性能故障排除
如 类型感知代码检查文档 中所述,如果您使用类型感知代码检查,您的代码检查时间应该与您的构建时间大致相同。
如果您遇到的时间比这慢得多,那么有一些常见的罪魁祸首。
tsconfig
中的广泛包含
在使用类型感知 linting 时,您会向我们提供一个或多个 tsconfig。然后,我们将预解析所有文件,以便提供完整且完整的类型信息。
如果您在 include
中提供非常广泛的 glob(例如 **/*
),它会导致比您预期的更多文件包含在此预解析中。此外,如果您在 tsconfig 中不提供 include
,那么它与提供最广泛的 glob 相同。
广泛的 glob 会导致 TypeScript 解析诸如构建工件之类的东西,这会严重影响性能。始终确保您提供的 glob 针对您要专门 lint 的文件夹。
ESLint 选项中的广泛包含
在您的 ESLint 命令中指定 tsconfig.json
路径也可能导致比预期更多的磁盘 IO。与使用 **
递归检查所有文件夹的 glob 相比,更喜欢一次使用单个 *
的路径。
- 扁平化配置
- 传统配置
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedRequiringTypeChecking,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: ['./**/tsconfig.json'],
project: ['./packages/*/tsconfig.json'],
},
},
},
);
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./**/tsconfig.json'],
project: ['./packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint'],
root: true,
};
有关更多详细信息,请参阅 解析器选项“project”中的 Glob 模式会减慢 linting 速度。
indent
/ @typescript-eslint/indent
规则
此规则有助于确保您的代码库遵循一致的缩进模式。但是,这涉及对文件中的每个令牌进行大量计算。在大型代码库中,这些计算会累积起来,并严重影响性能。
我们建议不要使用此规则,而是使用 prettier
等工具来强制执行标准化格式。
有关更多信息,请参阅我们的 格式化文档。
eslint-plugin-prettier
此插件在 lint 时会显示 Prettier 格式化问题,有助于确保您的代码始终保持格式。但是,这会带来相当大的成本 - 为了确定是否存在差异,它必须对每个被 lint 的文件进行 Prettier 格式化。这意味着每个文件将被解析两次 - 一次由 ESLint,一次由 Prettier。对于大型代码库来说,这可能会累积起来。
我们建议您不要使用此插件,而是使用 Prettier 的 --check
标志来检测文件是否未正确格式化。例如,我们的 CI 设置为自动运行以下命令,该命令会阻止未格式化的 PR
- npm
- Yarn
- pnpm
npm run prettier --check .
yarn prettier --check .
pnpm run prettier --check .
有关更多详细信息,请参阅 Prettier 的 --check
文档。
eslint-plugin-import
这是我们在这个项目中自己使用的另一个很棒的插件。但是,有一些规则会导致您的 lint 速度非常慢,因为它们会导致插件进行自己的解析和文件跟踪。这种双重解析对于大型代码库来说会累积起来。
有很多规则可以进行单文件静态分析,但我们提供以下建议。
我们建议您不要使用以下规则,因为 TypeScript 提供与标准类型检查相同的检查
import/named
import/namespace
import/default
import/no-named-as-default-member
import/no-unresolved
(只要您使用import
而不是require
)
以下规则在 TypeScript 中没有等效的检查,因此我们建议您只在 CI/push 时运行它们,以减轻本地性能负担。
import/no-named-as-default
import/no-cycle
import/no-unused-modules
import/no-deprecated
import/extensions
强制使用扩展名
如果您想强制始终使用文件扩展名,并且您没有使用 moduleResolution
node16
或 nodenext
,那么您没有更好的选择,您应该继续使用 import/extensions
lint 规则。
如果您想强制始终使用文件扩展名,并且您正在使用 moduleResolution
node16
或 nodenext
,那么您根本不需要使用 lint 规则,因为 TypeScript 会自动强制您包含扩展名!
强制使用扩展名
表面上,import/extensions
似乎应该在这种情况下很快,但是该规则不仅仅是一个纯 AST 检查 - 它必须在磁盘上解析模块,以便它不会在您导入带有扩展名的模块作为其名称的一部分的情况下出现误报(例如,foo.js
解析为 node_modules/foo.js/index.js
,因此需要 .js
)。这种磁盘查找很昂贵,因此会使规则变慢。
如果您的项目没有使用任何名称中带有文件扩展名的 npm
包,并且您也没有将文件命名为两个扩展名(例如 bar.js.ts
),那么这种额外的成本可能不值得,您可以使用更简单的检查,使用 no-restricted-syntax
lint 规则。
以下配置比 import/extensions
快几个数量级,因为它不进行磁盘查找,但是它会在上述 foo.js
模块等情况下出现误报。
function banImportExtension(extension) {
const message = `Unexpected use of file extension (.${extension}) in import`;
const literalAttributeMatcher = `Literal[value=/\\.${extension}$/]`;
return [
{
// import foo from 'bar.js';
selector: `ImportDeclaration > ${literalAttributeMatcher}.source`,
message,
},
{
// const foo = import('bar.js');
selector: `ImportExpression > ${literalAttributeMatcher}.source`,
message,
},
{
// type Foo = typeof import('bar.js');
selector: `TSImportType > TSLiteralType > ${literalAttributeMatcher}`,
message,
},
{
// const foo = require('foo.js');
selector: `CallExpression[callee.name = "require"] > ${literalAttributeMatcher}.arguments`,
message,
},
];
}
module.exports = {
// ... other config ...
rules: {
'no-restricted-syntax': [
'error',
...banImportExtension('js'),
...banImportExtension('jsx'),
...banImportExtension('ts'),
...banImportExtension('tsx'),
],
},
};