使用 ESLint 对 TypeScript 进行 Lint 检查
TypeScript 的 ESLint 10 flat config:用 typescript-eslint 配置,开启 projectService 类型感知 lint,并正确接入 Prettier。
截至 2026 年 7 月,对 TypeScript 进行 lint 检查的现行做法是:ESLint 10 搭配 typescript-eslint 包,并使用扁平配置(flat config,即 eslint.config.mjs)——而不是绝大多数搜索结果里仍在展示的旧式 .eslintrc 方案。
如果你曾经从 2022 年的教程里复制过一份配置,却发现 ESLint 完全无视它,原因就在这里:那份配置是为一套已经不存在的配置系统编写的。替代方案很简洁,不过其中涉及类型感知(type-aware)的部分需要一个额外选项,而这个选项很容易被忽略。
ESLint 10 彻底移除了 eslintrc 配置系统,这在该项目推进扁平配置的规划中早有预告。仅此一项变更就足以让几乎所有 2024 年之前的教程失效,因为 ESLint 已经完全不再读取 .eslintrc 或 .eslintignore 文件。本文将为你提供一份正确、可直接复制使用的 TypeScript 扁平配置,演示如何启用类型感知规则,并将 lint 检查接入你的 npm 脚本、编辑器和 CI 流程。
关键要点
- 现代技术栈是 ESLint 10 加上扁平配置下的 typescript-eslint v8;自 ESLint 10 起,
.eslintrc/.eslintignore已经彻底退场。 - 一份最小可用配置只需在名为
eslint.config.js/.mjs的文件中,把js.configs.recommended和tseslint.configs.recommended传给来自eslint/config的defineConfig()。 - 像
no-floating-promises这类类型感知规则需要parserOptions: { projectService: true }。空的parserOptions并不会启用它们。 - 类型化 lint 需要 TypeScript 在检查前先构建项目,因此速度较慢;建议在 CI 中运行,编辑器里则依赖 IDE 的缓存。
- 在扁平配置中,
--ext标志已经过时:文件匹配范围由各配置块的filesglob 决定,所以 lint 脚本只需写成eslint .。
ESLint 和 TypeScript 做的是同一件事吗?
ESLint 和 TypeScript 是互补关系,而非竞争关系。确实有少数 typescript-eslint 规则会深入调用 TypeScript 的类型检查器来更透彻地理解代码,但这两个工具回答的是不同的问题:TypeScript 编译器检查类型是否匹配,而 ESLint 在整个代码库中强制执行代码风格并捕获潜在缺陷(未使用的变量、未处理的 Promise、不安全的写法)。两者都要用。
如果你正在从 TSLint 迁移过来,请注意它早已停止维护多年。其支持方在 2019 年宣布将弃用 TSLint,转而支持 typescript-eslint,此后 ESLint 生态成为 TypeScript lint 检查的事实标准。在新项目中没有任何理由再选择 TSLint。
安装前还有一个前提条件:ESLint 10 放弃了对旧版本 Node 的支持。它现在要求 Node.js v20.19.0 及以上、v22.13.0 及以上,或 v24 及以上,v21.x 和 v23.x 不再受支持。
如何为 TypeScript 配置 ESLint?
Discover how at OpenReplay.com.
安装你真正需要的四个包:
npm i -D eslint @eslint/js typescript typescript-eslint
typescript-eslint 这个辅助包已经打包了 parser 和 plugin,因此你无需手动接入 @typescript-eslint/parser 和 @typescript-eslint/eslint-plugin。它支持当前主版本:typescript-eslint 文档中列出的 ESLint 版本范围涵盖 ^8.57.0 || ^9.0.0 || ^10.0.0,因此 typescript-eslint@latest(v8.x)可以在 ESLint 10 上正常运行。
创建 eslint.config.mjs(扁平配置,不是 .eslintrc):
// eslint.config.mjs
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(
js.configs.recommended,
tseslint.configs.recommended,
);
这就是一份可用的基线配置:ESLint 核心推荐规则,加上 typescript-eslint 的推荐规则集(后者会自动为你指定 typescript-eslint 的 parser 和 plugin)。defineConfig() 来自 ESLint 核心,是目前应当采用的辅助函数,因为 typescript-eslint 已经弃用了自家的 tseslint.config() 并转而推荐它。旧的辅助函数仍能运行,因此已经跑通的配置不会被破坏,但新项目应当使用 defineConfig()。无论采用哪种方式,都仍需继续导入 tseslint,因为你还要用到 tseslint.configs.* 以及相关的 glob 辅助工具。
提高严格度,再逐条微调规则
recommended 只是起点;另有两套可选预设能进一步抬高标准。tseslint.configs.strict 增加了更具倾向性的正确性规则,tseslint.configs.stylistic 则增加了无需类型信息的一致性规则。把它们与 recommended 一起加入配置数组即可。
任何规则都可以在 rules 块中覆盖。规则严重级别分为三档:off(或 0)完全关闭规则,warn(或 1)报告问题但不影响退出码,error(或 2)报告问题并让 ESLint 以退出码 1 结束。对于希望可见但不阻断流程的问题使用 warn;对于绝不允许进入仓库的问题使用 error,因为它会返回非零退出码并导致 CI 失败。
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'error',
}
在现代配置中,建议使用字符串形式的严重级别('warn'/'error'),而非数字形式。前者可读性更好,而只用数字的写法是过时 .eslintrc 教程的典型特征。
类型感知 lint:那些需要类型信息的规则
一些最有价值的规则——no-floating-promises 和 no-misused-promises 就在其中——需要类型信息,你可以通过添加 parserOptions: { projectService: true } 来启用它。自 typescript-eslint v8 起,这一直是官方推荐的启用类型化 lint 的方式,它取代了较旧的 project 选项,因为配置更少、运行更快。同时,还要把预设切换为其类型检查版本(recommendedTypeChecked、strictTypeChecked、stylisticTypeChecked)。空的 parserOptions: {} 不会开启类型感知 lint,这是复制粘贴配置时常见的错误。
{
files: ['**/*.ts', '**/*.tsx'],
extends: [tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
类型化 lint 是有实际代价的。开启它意味着 TypeScript 必须先构建你的项目,ESLint 才能开始检查;在小型代码库上这不过一两秒,在大型代码库上则会明显更久。typescript-eslint 官方给出的建议正是基于这种不对称性:编辑器插件会缓存类型信息,基本能规避这份开销,因此可以在 CI 和 pre-commit 中运行完整的类型化 lint,日常开发则交给编辑器覆盖。projectService 还免去了以往维护单独 tsconfig.eslint.json 的变通做法,因为它使用的正是编辑器所用的同一个项目配置。
把 JS 与 TS 分开,并设置忽略规则
类型检查规则只对 TypeScript 能理解的文件有意义,因此应将其作用范围限定在 **/*.ts/**/*.tsx,并对纯 JavaScript 文件关闭。typescript-eslint 恰好为此提供了一套预设。其官方文档将 tseslint.configs.disableTypeChecked 应用于 **/*.js 配置块,以剥离 TypeScript 专有的设置。在扁平配置中,忽略规则就是一个只包含 ignores 键的配置块,它正是 .eslintignore 的替代品。
// eslint.config.mjs
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
export default defineConfig(
{ ignores: ['dist/', 'node_modules/', 'coverage/', '**/*.d.ts'] },
js.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
extends: [tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
},
rules: { '@typescript-eslint/no-explicit-any': 'warn' },
},
{ files: ['**/*.js', '**/*.mjs'], extends: [tseslint.configs.disableTypeChecked] },
prettier, // 必须放在最后
);
把格式化交给 Prettier,再打通整个工作流
不要让 ESLint 承担格式化职责。将 eslint-config-prettier 放在最后,用它关闭那些会与 Prettier 冲突的 ESLint 风格规则,并将版本锁定在 ^10.1.8 或更高。版本很关键:2025 年 7 月,一次针对维护者 npm 凭据的钓鱼攻击导致四个被篡改的版本被发布,该事件记录为 CVE-2025-54313。8.10.1、9.1.1、10.1.6 和 10.1.7 这几个版本携带了一个 postinstall 脚本,会在 Windows 机器上运行内置的 DLL 载荷,修复版本为 8.10.2、9.1.2 和 10.1.8。只有那四个版本受影响,且载荷仅在 Windows 上执行,因此 10.1.5 等更早的干净构建从未被污染。通过 eslint-plugin-prettier 把 Prettier 作为 ESLint 规则来运行也是可行的,但并非必需;许多团队会跳过这种做法,因为它会让 lint 变慢,并产生更多噪声。
添加一个 lint 脚本。不需要 --ext 标志,因为文件匹配范围已由各配置块的 files glob 决定:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
在此基础上,可以借助 Husky 和 lint-staged 在每次提交前对暂存文件运行 eslint --fix;在 VS Code 中通过 codeActionsOnSave 里的 "source.fixAll.eslint": "explicit" 启用保存时自动修复;并把 eslint . 作为 CI 步骤运行,让规则失败直接阻断合并。
最后还有一件值得立即处理的事:ESLint 9 已于 2026-08-06 结束生命周期,不再接收任何更新。如果你还停留在 ESLint 9,上面的配置在 ESLint 10 上无需改动即可运行,所以直接升级运行时然后继续前进即可。从最小的两行配置起步,当你需要 Promise 安全相关规则时,再加上带 projectService 的 recommendedTypeChecked,并把 eslint-config-prettier 放在最后。
常见问题
我应该启用类型感知 lint 吗?代价是什么?
如果你想使用 no-floating-promises 和 no-misused-promises 这类价值最高的正确性规则,就应该启用,因为它们离开类型信息就无法工作。代价在于 ESLint 会要求 TypeScript 在 lint 之前先构建项目,这在小型项目上可以忽略不计,但在大型项目上会比较明显。大多数团队会在 CI 和 pre-commit 中运行完整的类型化 lint,而在编辑器中依赖 IDE 缓存,从而规避这部分开销。
在类型化 lint 中,projectService 和 project 有什么区别?
两者都能启用类型化 lint,但自 v8 起 typescript-eslint 推荐使用 projectService,因为它配置更简单、lint 更快,并且复用编辑器已经在使用的那个 tsconfig.json。较旧的 project 选项要求你通过路径指定一个或多个 TSConfig 文件,常常迫使团队额外维护一个 tsconfig.eslint.json。除非有特殊理由,否则请使用 projectService: true。
在 ESLint 扁平配置中,--ext 标志还有效吗?
无效,扁平配置中已不再需要 --ext。文件匹配范围位于各配置块的 files glob 中,例如 files: ['**/*.ts', '**/*.tsx'],因此 ESLint 已经知道要检查哪些文件。你的 lint 脚本简化为 eslint .,无需扩展名标志。那些仍在传 --ext 的脚本,都是从为已被移除的 eslintrc 系统编写的、扁平配置之前的教程中复制来的。
我应该用 eslint-config-prettier 还是 eslint-plugin-prettier?
大多数项目应使用 eslint-config-prettier。它会关闭与 Prettier 冲突的 ESLint 风格规则,且不带来运行时开销;把它放在配置数组的最后即可。eslint-plugin-prettier 的做法是把 Prettier 当作真正的 lint 规则来运行,这是可选的且速度更慢,并且会把每一处格式差异都报告为 lint 错误。请将 eslint-config-prettier 锁定在 10.1.8 或更高版本,以避开 2025 年 7 月的供应链安全事件。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k