外观
ESLint 系统学习文档
适合人群:已经掌握 JavaScript / TypeScript 基础,想系统掌握代码规范、静态检查、团队规则治理和前端工程化质量门禁的学习者。
学习目标:从会安装和运行 ESLint,逐步掌握 flat config、规则配置、TypeScript 接入、CLI、CI 集成、自定义规则与大型团队治理。
核对时间:2026-06-30。ESLint v10 已于 2026-02-06 发布,本文以 ESLint v10 的 flat config 为主,同时说明旧项目迁移注意点。
相关文档:建议先阅读 前端架构师技术栈全景学习文档.md 中的工程化与治理部分,再学习本文;Babel 可参考 Babel系统学习文档.md,工程化落地可参考 前端工程化与构建发布系统学习文档.md。
目录
- ESLint 是什么
- 为什么前端项目需要 ESLint
- ESLint 的工作原理
- 版本与生态现状
- 快速开始
- flat config 核心结构
- rules 规则系统
- languageOptions、globals 与运行环境
- files、ignores 与分层配置
- TypeScript 项目接入
- Vue、React、Node 项目中的 ESLint
- ESLint 与 Prettier 的关系
- CLI 常用命令
- 编辑器、Git Hooks 与 CI 集成
- 迁移到 ESLint v10
- 进阶:自定义规则
- 高级:团队规则治理
- 性能优化与排错
- 学习路线与练习
- 官方参考资料
1. ESLint 是什么
ESLint 是 JavaScript / TypeScript 生态中最常用的静态代码检查工具之一。它会读取源代码,在不真正执行业务逻辑的情况下,分析代码是否存在潜在错误、不一致风格、危险写法或团队约定不允许的模式。
可以把 ESLint 理解为三件事的组合:
- 代码扫描器:找到项目中的源文件。
- 语法与语义分析器:把代码解析成 AST,并构建作用域信息。
- 规则执行器:让一条条规则检查 AST,输出问题、修复建议或自动修复结果。
ESLint 不等同于类型系统、格式化工具、测试框架或构建工具。
- 类型系统负责证明类型关系,例如 TypeScript 的类型检查。
- 格式化工具负责统一排版,例如 Prettier。
- 测试框架负责验证行为,例如 Vitest、Jest、Playwright。
- 构建工具负责转换和打包,例如 Vite、Webpack、Rollup。
- ESLint 负责在代码层面持续发现风险和约束团队写法。
一个成熟前端项目通常会同时使用这些工具,各自负责不同质量维度。
2. 为什么前端项目需要 ESLint
2.1 发现真实 bug
ESLint 的价值不只是“风格统一”,更重要的是提前发现真实风险。
常见例子:
js
function loadUser() {
try {
return fetch("/api/user");
} catch (error) {
// fetch 的网络错误不会被这个同步 catch 捕获
return null;
}
}这类问题可以通过 no-useless-catch、@typescript-eslint/no-floating-promises 等规则在代码阶段提前暴露。
2.2 保持团队代码一致
多人协作时,每个人的习惯不同。如果没有自动化规则,代码审查会陷入大量低价值争论:
- 是否允许
var - 是否允许未使用变量
- Promise 是否必须处理异常
- React Hooks 是否正确声明依赖
- 导入顺序是否统一
- 是否允许深层相对路径
ESLint 的目标是让机器处理确定性问题,把 code review 留给架构、业务、可维护性和风险判断。
2.3 构建质量门禁
ESLint 可以进入本地开发、提交前、CI、发布前多个阶段:
- 本地编辑器实时提示。
pnpm lint在命令行检查。- Git Hooks 阻止明显错误进入仓库。
- CI 把 lint 作为合并前门禁。
- Monorepo 中按包、按改动范围执行 lint。
当项目规模变大,ESLint 会从“开发辅助工具”升级为“工程质量基础设施”。
3. ESLint 的工作原理
3.1 读取配置
ESLint v10 只使用 flat config,也就是 eslint.config.js、eslint.config.mjs、eslint.config.cjs 等文件。
旧的 .eslintrc.* 与 .eslintignore 在 ESLint v10 中不会再生效。
3.2 找到待检查文件
命令示例:
bash
npx eslint .
npx eslint "src/**/*.{js,jsx,ts,tsx}"ESLint 会根据命令传入的路径、配置中的 files、ignores 等规则决定哪些文件参与检查。
3.3 解析代码为 AST
ESLint 默认使用 Espree 解析 JavaScript。TypeScript、Vue SFC、Babel 语法等场景需要不同 parser 或生态插件配合。
解析后,代码会变成抽象语法树 AST。例如:
js
const count = 1 + 2;会被表示为变量声明、标识符、字面量、二元表达式等节点。规则并不是直接用字符串查找代码,而是在 AST 节点上运行。
3.4 构建作用域与引用关系
ESLint 会分析变量声明、引用、函数作用域、模块作用域等信息。规则如 no-unused-vars、no-undef、no-shadow 都依赖作用域分析。
ESLint v10 增强了 JSX 引用追踪,JSX 中使用的组件名会被当作正常变量引用,减少 React / JSX 场景下的误报。
3.5 执行规则
每条规则会注册一个或多个 AST 访问器:
js
export default {
meta: {
type: "problem",
docs: {
description: "disallow foo",
},
schema: [],
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
message: "Do not use foo.",
});
}
},
};
},
};规则输出问题后,ESLint 会统一展示报告。可自动修复的规则还会提供 fixer。
4. 版本与生态现状
4.1 2026 年版本重点
截至 2026-06-30,学习 ESLint 要优先理解 v10 的变化:
- ESLint v10 于 2026-02-06 发布。
- Node.js 要求为
^20.19.0 || ^22.13.0 || >=24。 - flat config 成为唯一配置系统。
.eslintrc.*与.eslintignore不再被读取。ESLINT_USE_FLAT_CONFIG不再生效。- CLI 中与 eslintrc 相关的参数被移除,例如
--env、--ignore-path、--rulesdir。 - 配置查找从“当前工作目录向上找”变为“从每个被 lint 文件所在目录向上找”,更适合 Monorepo。
- JSX 引用追踪被增强。
4.2 v10 的其他重要行为变化
除了移除 eslintrc,v10 还有几项容易踩到的变化:
| 变化 | 影响 | 应对 |
|---|---|---|
eslint:recommended 更新 | 新增 no-unassigned-vars、no-useless-assignment、preserve-caught-error 三条规则 | 升级后修复新报错,或显式关闭不想要的规则 |
/* eslint-env */ 注释变成报错 | 旧代码里用注释声明环境会直接失败 | 改用 languageOptions.globals |
Linter 的 configType 只能是 "flat" | 传 "eslintrc" 会抛错 | 移除相关代码 |
FlatESLint、LegacyESLint API 被移除 | 使用 Node.js API 的集成代码要改 | 统一用 ESLint 类 |
规则里废弃的 context 方法被移除 | context.getSourceCode() 等不再可用 | 换成 context.sourceCode(见第 16 章) |
RuleTester 更严格 | valid 用例不允许再写 errors/output | 清理测试用例 |
官方提供了 codemod 自动处理大部分迁移:
bash
npx @eslint/codemods v9-to-v10它包含配置、自定义规则、RuleTester、Linter API 四个方向的子 codemod,可以单独运行。迁移后仍要人工复查共享配置和插件兼容性。
4.3 为什么要学习 flat config
flat config 的核心思想是:配置就是一个数组,数组里的每个对象描述一类文件的规则。
旧配置中常见的 extends、overrides、env、.eslintignore、字符串形式插件引用,在 flat config 下都更显式:
- 插件需要
import进来。 - 全局变量通过
languageOptions.globals声明。 - 忽略文件通过
ignores声明。 - 文件匹配通过
files声明。 - 多段配置按数组顺序合并。
这种方式初看更啰嗦,但长期更利于调试、组合和大型项目维护。
5. 快速开始
5.1 初始化安装
普通 JavaScript 项目:
bash
pnpm add -D eslint @eslint/js globalsTypeScript 项目:
bash
pnpm add -D eslint @eslint/js globals typescript typescript-eslint也可以使用 npm:
bash
npm i -D eslint @eslint/js globals5.2 创建最小配置
新建 eslint.config.mjs:
js
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
ignores: ["dist/**", "coverage/**", "node_modules/**"],
},
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser,
...globals.node,
},
},
extends: [js.configs.recommended],
rules: {
"no-console": "warn",
"no-debugger": "error",
},
},
]);5.3 配置 npm scripts
json
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}运行:
bash
pnpm lint
pnpm lint:fix5.4 第一次学习时不要一口气打开所有规则
推荐顺序:
- 先启用官方推荐规则。
- 再启用框架推荐规则。
- 再加入 TypeScript 类型感知规则。
- 最后按团队需要加入风格类、复杂度类、架构类规则。
一开始规则太多会造成大量噪声,使团队误以为 ESLint 只是“添麻烦”。
6. flat config 核心结构
6.1 配置数组
flat config 导出的是数组:
js
export default [
{
ignores: ["dist/**"],
},
{
files: ["src/**/*.js"],
rules: {
semi: ["error", "always"],
},
},
];每个对象可以理解为一段配置。ESLint 会根据文件路径匹配这些配置,并按顺序合并。
6.2 常见字段
| 字段 | 作用 |
|---|---|
files | 指定这段配置作用于哪些文件 |
ignores | 忽略哪些文件 |
languageOptions | 配置语法版本、模块类型、parser、globals |
plugins | 注册插件 |
rules | 配置规则 |
settings | 给插件共享额外设置 |
linterOptions | 控制 ESLint 自身行为 |
extends | 在 defineConfig() 中继承共享配置 |
6.3 配置顺序
数组越靠后的配置,在同一规则上越可能覆盖前面的配置。
js
export default [
{
rules: {
"no-console": "warn",
},
},
{
files: ["scripts/**/*.js"],
rules: {
"no-console": "off",
},
},
];含义:
- 默认不鼓励
console。 scripts目录允许使用console。
6.4 defineConfig() 的意义
defineConfig() 来自 eslint/config,它可以提供更好的类型提示、配置规范化和 extends 组合体验。
js
import { defineConfig } from "eslint/config";
export default defineConfig([
{
rules: {
eqeqeq: "error",
},
},
]);建议新项目统一使用。
6.5 配置合并的内部机制
理解合并机制,才能解释“为什么我配了规则却没生效”:
- 匹配:对每个被检查的文件,按数组顺序找出所有
files匹配该路径的配置对象(没写files的对象默认匹配所有文件)。 - 合并:命中的配置按顺序合并,后面的覆盖前面的。
rules按规则 id 覆盖。plugins按插件名合并,同名插件对象被后者替换。languageOptions.globals按变量名合并,同名变量后者生效。
- 忽略:只包含
ignores的对象是全局忽略;与files同级的ignores只作用于该段配置。node_modules/默认被忽略。
一个典型调试场景:
js
export default [
js.configs.recommended, // no-console 未启用
{
rules: { "no-console": "warn" }, // 生效
},
{
files: ["scripts/**"],
rules: { "no-console": "off" }, // scripts 目录覆盖为关闭
},
];调试命令:
bash
# 查看某个文件最终应用了哪些配置段
npx eslint --print-config scripts/deploy.js
# 输出配置到文件慢慢看
npx eslint --inspect-config💡
--print-config是排查“规则为什么不生效”的第一工具。它直接打印该文件合并后的完整配置,比读源码猜顺序可靠得多。
6.6 ignores 的两种语义
ignores 在 flat config 中有两种完全不同的行为,非常容易写错:
js
export default [
// ① 全局忽略:对象里只有 ignores,没有 files
{
ignores: ["dist/**", "coverage/**", "node_modules/**"],
},
// ② 局部忽略:和 files 同级,只在本段配置内生效
{
files: ["**/*.js"],
ignores: ["scripts/**"], // 本段规则不检查 scripts 目录
rules: { "no-console": "warn" },
},
];注意:
- 全局忽略写在数组最前面即可,不需要匹配任何文件。
.gitignore不会自动成为 lint 忽略,需要显式声明。- 忽略构建产物是必须的,否则
dist里的压缩代码会拖慢检查并产生海量报错。
7. rules 规则系统
7.1 规则严重级别
规则值可以是字符串或数字:
js
rules: {
"no-debugger": "error",
"no-console": "warn",
"eqeqeq": "error",
"curly": ["error", "all"]
}等价数字:
| 值 | 含义 |
|---|---|
"off" / 0 | 关闭规则 |
"warn" / 1 | 警告,不一定让进程失败 |
"error" / 2 | 错误,通常让 lint 命令失败 |
7.2 problem、suggestion、layout
ESLint 规则通常可以分为三类:
problem:潜在 bug,例如no-undef、no-unreachable。suggestion:更好的写法,例如no-var、prefer-const。layout:格式类规则,例如缩进、空格、分号。
现代项目中,格式类规则更建议交给 Prettier,ESLint 重点处理 bug、可维护性和团队约束。
7.3 自动修复
部分规则支持 --fix:
bash
npx eslint . --fix自动修复适合:
- 明确无歧义的语法替换。
- 删除未使用 import。
- 统一简单写法。
- 修复格式类问题。
不适合:
- 改变业务语义的修复。
- 涉及副作用顺序的修复。
- 需要人工判断的架构问题。
7.4 常用基础规则
js
rules: {
"no-debugger": "error",
"no-alert": "warn",
"no-var": "error",
"prefer-const": "error",
"eqeqeq": ["error", "always"],
"curly": ["error", "all"],
"no-duplicate-imports": "error",
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }]
}学习建议:
- 先理解每条规则解决什么风险。
- 不要为了“严格”而开启不理解的规则。
- 每条团队自定义规则都应该能回答:它减少了什么 bug、维护成本或协作成本。
8. languageOptions、globals 与运行环境
8.1 ecmaVersion
js
languageOptions: {
ecmaVersion: "latest"
}表示解析最新 ECMAScript 语法。多数现代项目可使用 "latest"。
8.2 sourceType
js
languageOptions: {
sourceType: "module"
}常见值:
"module":ESM,支持import/export。"script":传统脚本。"commonjs":CommonJS 场景。
8.3 globals
旧配置里常见:
json
{
"env": {
"browser": true,
"node": true
}
}flat config 中推荐使用 globals 包:
js
import globals from "globals";
export default [
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
defineProps: "readonly",
},
},
},
];如果没有声明浏览器全局变量,ESLint 可能会把 window、document、fetch 等识别为未定义。
9. files、ignores 与分层配置
9.1 忽略构建产物
js
export default [
{
ignores: [
"dist/**",
"coverage/**",
"node_modules/**",
"*.min.js",
],
},
];ESLint v10 不再读取 .eslintignore,需要把忽略规则写进 flat config。
9.2 按文件类型配置
js
export default [
{
files: ["**/*.js"],
rules: {
"no-undef": "error",
},
},
{
files: ["**/*.test.js"],
rules: {
"no-console": "off",
},
},
];9.3 按目录配置
js
export default [
{
files: ["src/**/*.js"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["../*/*/*"],
},
],
},
},
{
files: ["scripts/**/*.js"],
languageOptions: {
sourceType: "commonjs",
},
},
];目录级配置适合大型项目:
src使用浏览器环境。scripts使用 Node 环境。tests允许测试全局变量。packages/*有各自规则。
10. TypeScript 项目接入
10.1 基础安装
bash
pnpm add -D eslint typescript typescript-eslint10.2 推荐配置
js
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig([
{
ignores: ["dist/**", "coverage/**"],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/consistent-type-imports": "warn",
},
},
]);10.3 类型感知 lint
有些规则必须依赖 TypeScript 类型信息,例如:
@typescript-eslint/no-floating-promises@typescript-eslint/no-misused-promises@typescript-eslint/await-thenable@typescript-eslint/no-unnecessary-type-assertion
配置示例:
js
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig([
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
]);10.4 类型感知规则的代价
类型感知 lint 更强,但也更慢,因为它需要读取 TypeScript 项目并构建类型信息。
实践建议:
- 本地开发可以只对改动文件运行。
- CI 中对全量代码运行。
- 大型 Monorepo 按 package 拆分。
- 避免让测试、构建产物、脚本临时目录进入类型感知 lint。
- 单独维护
tsconfig.eslint.json,只包含需要 lint 的源文件。
10.5 no-unused-vars 的 TypeScript 版本
TypeScript 项目中通常关闭核心规则,启用 TypeScript 版本:
js
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_"
}
]
}原因是 TS 语法有类型参数、接口、类型导入等结构,核心 JS 规则不一定理解。
11. Vue、React、Node 项目中的 ESLint
11.1 React 项目
React 项目常见规则维度:
- JSX 语法检查。
- Hooks 规则检查。
- 组件命名与导出约定。
- 可访问性检查。
- React Refresh 约束。
常见插件:
eslint-plugin-reacteslint-plugin-react-hookseslint-plugin-jsx-a11yeslint-plugin-react-refresh
示意配置:
js
import reactHooks from "eslint-plugin-react-hooks";
export default [
{
files: ["**/*.{jsx,tsx}"],
plugins: {
"react-hooks": reactHooks,
},
rules: {
...reactHooks.configs.recommended.rules,
},
},
];11.2 Vue 项目
Vue 项目需要能解析 .vue 单文件组件,通常使用 eslint-plugin-vue 和 vue-eslint-parser。
关注点:
- template 中变量引用。
- script setup 宏。
- 组件命名。
- emits / props 约定。
- Vue 2 与 Vue 3 规则差异。
Vue 项目不要只 lint *.ts,还要包含 *.vue。
11.3 Node 项目
Node 项目关注点:
- CommonJS / ESM 模块类型。
- Node 全局变量。
- 安全风险,例如动态执行、路径拼接。
- 脚本目录允许
console。
示例:
js
import globals from "globals";
export default [
{
files: ["scripts/**/*.js"],
languageOptions: {
sourceType: "commonjs",
globals: {
...globals.node,
},
},
rules: {
"no-console": "off",
},
},
];12. ESLint 与 Prettier 的关系
12.1 分工
推荐分工:
- ESLint:代码质量、潜在 bug、最佳实践、团队约束。
- Prettier:格式化、换行、缩进、空格、引号风格。
不要让 ESLint 和 Prettier 同时争夺格式化权,否则会出现保存后反复改动。
12.2 常见误区
误区一:用 ESLint 管所有格式问题。
现代项目不推荐。格式化不是 ESLint 的核心价值,Prettier 更稳定。
误区二:把 Prettier 当作 ESLint 规则运行。
有些项目使用 eslint-plugin-prettier,把格式问题变成 lint error。这样做会让 lint 变慢,且在大型项目中噪声更大。更推荐单独运行:
json
{
"scripts": {
"lint": "eslint .",
"format": "prettier . --write",
"format:check": "prettier . --check"
}
}误区三:把风格争论都写成规则。
团队规则应该服务于可维护性,不是把每个人的偏好都制度化。
12.3 推荐的集成方式:关闭冲突规则
ESLint 和 Prettier 同时存在时,要确保两者不抢同一段代码的格式化权。推荐做法分两层:
- 只关规则,不跑 Prettier(最常见、最轻量):
bash
pnpm add -D eslint-config-prettierjs
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
import prettierConfig from "eslint-config-prettier";
export default defineConfig([
js.configs.recommended,
{
rules: { /* 团队规则 */ },
},
// 放在最后:关闭所有与 Prettier 冲突的格式类规则
prettierConfig,
]);- 脚本分离:
eslint .只管质量,prettier . --write只管格式,两者互不调用。
json
{
"scripts": {
"lint": "eslint .",
"format": "prettier . --write",
"format:check": "prettier . --check"
}
}要点:
eslint-config-prettier只负责“关规则”,不包含任何 Prettier 运行逻辑,所以不会拖慢 lint。eslint-plugin-prettier是另一种方式(把格式问题变成 lint 报错),在大型项目里通常不推荐,原因见 12.2。- CI 里同时跑
lint和format:check,两者都通过才算质量门禁通过。
13. CLI 常用命令
13.1 检查项目
bash
npx eslint .13.2 自动修复
bash
npx eslint . --fix13.3 只修复建议类问题
bash
npx eslint . --fix-type suggestion常见 --fix-type:
problemsuggestionlayoutdirective
13.4 开启缓存
bash
npx eslint . --cache缓存适合本地和 CI 加速。但如果规则、配置、依赖版本变化,需要注意缓存失效。
13.5 控制 warning
bash
npx eslint . --max-warnings 0含义:只要有 warning,命令也失败。适合 CI 严格门禁。
团队引入新规则时可以先设为 warn,等问题收敛后再改为 error。
13.6 输出格式
bash
npx eslint . --format stylish
npx eslint . --format jsonJSON 输出可用于平台集成、质量报表、代码扫描系统。
14. 编辑器、Git Hooks 与 CI 集成
14.1 VS Code
建议安装 ESLint 插件,并开启保存时修复:
json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}注意:团队不应只依赖编辑器插件,因为每个人的编辑器环境不同。最终仍要以命令行和 CI 为准。
14.2 lint-staged
提交前只检查暂存文件:
bash
pnpm add -D husky lint-stagedpackage.json:
json
{
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": "eslint --fix"
}
}适合减少提交前耗时。
14.3 CI 门禁
GitHub Actions 示例:
yaml
name: Quality
on:
pull_request:
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm lint -- --max-warnings 014.4 Monorepo 中的 lint
Monorepo 里常见策略:
- 每个 package 有局部配置。
- 根目录保留通用配置。
- 使用 Turborepo / Nx 只 lint 受影响包。
- CI 按 package 并行。
- 类型感知 lint 按 tsconfig 拆分。
ESLint v10 的配置查找算法更适合多个 package 拥有各自 eslint.config.* 的场景。
15. 迁移到 ESLint v10
15.1 迁移前检查
bash
node -v
npx eslint --version
dir .eslintrc*
dir .eslintignore确认:
- Node 版本满足 ESLint v10 要求。
- 依赖插件支持 flat config。
- 项目中没有仍依赖
.eslintrc.*的工具链。
15.2 使用迁移工具
如果仍使用 .eslintrc.*,可先运行官方迁移工具:
bash
npx @eslint/migrate-config .eslintrc.json迁移工具生成的配置通常需要人工整理,尤其是:
- 插件导入。
- parser 迁移。
- env 到 globals 的迁移。
- ignore 规则迁移。
- 共享配置兼容性。
15.3 常见迁移项
| 旧写法 | 新写法 |
|---|---|
.eslintrc.* | eslint.config.mjs |
.eslintignore | ignores |
env.browser | languageOptions.globals + globals.browser |
parserOptions | languageOptions.parserOptions |
plugins: ["react"] | plugins: { react: reactPlugin } |
extends 字符串 | 导入配置对象或用 defineConfig() 组合 |
--env | 配置中声明 globals |
--rulesdir | 本地 plugin 或直接导入规则 |
15.4 分阶段迁移策略
大型项目不要直接“升版本 + 改规则 + 清理历史问题”一起做。
推荐顺序:
- 先在当前版本迁移到 flat config。
- 保持规则结果尽量不变。
- 升级 ESLint 和插件。
- 修复破坏性变化。
- 再逐步提高规则严格度。
这样可以把风险拆小,避免一次 PR 里混入太多变量。
16. 进阶:自定义规则
16.1 什么时候需要自定义规则
适合写自定义规则的场景:
- 团队重复犯同一类代码错误。
- 架构约束无法靠 TypeScript 表达。
- 某些 API 使用必须遵守公司规范。
- 想限制跨层 import。
- 想禁止已废弃组件或工具函数。
- 想把 code review 中反复出现的问题自动化。
不适合写规则的场景:
- 只是个人偏好。
- 一次性迁移问题。
- 业务语义过复杂,静态分析很难准确判断。
- 自动修复可能改变运行行为。
16.2 最小规则结构
js
const rule = {
meta: {
type: "problem",
docs: {
description: "disallow legacy request util",
},
schema: [],
messages: {
noLegacyRequest: "Use httpClient instead of legacyRequest.",
},
},
create(context) {
return {
ImportDeclaration(node) {
if (node.source.value === "@/utils/legacyRequest") {
context.report({
node,
messageId: "noLegacyRequest",
});
}
},
};
},
};
export default rule;16.3 本地插件方式
js
import noLegacyRequest from "./eslint-rules/no-legacy-request.js";
const localPlugin = {
rules: {
"no-legacy-request": noLegacyRequest,
},
};
export default [
{
plugins: {
local: localPlugin,
},
rules: {
"local/no-legacy-request": "error",
},
},
];16.4 RuleTester
自定义规则必须有测试。示例:
js
import { RuleTester } from "eslint";
import rule from "../eslint-rules/no-legacy-request.js";
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
});
ruleTester.run("no-legacy-request", rule, {
valid: [
'import httpClient from "@/utils/httpClient";',
],
invalid: [
{
code: 'import legacyRequest from "@/utils/legacyRequest";',
errors: [{ messageId: "noLegacyRequest" }],
},
],
});ESLint v10 对 RuleTester 有更严格的断言能力,适合让规则测试更可靠。
16.5 AST 学习方法
学习自定义规则时,推荐使用:
- ESLint Code Explorer
- AST Explorer
@typescript-eslint/typescript-estree
先观察代码对应的 AST,再写 visitor。
16.6 v10 下自定义规则的 API 现状
写规则时经常看到两种 context 用法,v10 里只有新写法可用:
js
// ❌ 已移除:ESLint v10 不再提供这些方法
const sourceCode = context.getSourceCode();
const filename = context.getFilename();
// ✅ v10 写法:直接使用属性
const sourceCode = context.sourceCode;
const filename = context.filename;
const cwd = context.cwd;context 上被移除的方法与替代:
| 被移除 | 替代 |
|---|---|
context.getCwd() | context.cwd |
context.getFilename() | context.filename |
context.getPhysicalFilename() | context.physicalFilename |
context.getSourceCode() | context.sourceCode |
context.parserOptions | context.languageOptions.parserOptions |
context.parserPath | 无替代,用 context.languageOptions.parser |
SourceCode 上被移除的方法与替代:
| 被移除 | 替代 |
|---|---|
getTokenOrCommentBefore(n, skip) | getTokenBefore(n, { includeComments: true, skip }) |
getTokenOrCommentAfter(n, skip) | getTokenAfter(n, { includeComments: true, skip }) |
isSpaceBetweenTokens(a, b) | isSpaceBetween(a, b) |
getJSDocComment(n) | 无替代 |
其他写规则要注意的 v10 变化:
- Fixer 的修复函数必须返回字符串文本(
text必须是 string)。 Program节点的range现在覆盖整个源码文本。RuleTester的 valid 用例不允许再写errors/output;invalid 用例必须断言errors。- 官方提供
@eslint/compat包,给需要同时兼容 v9 和 v10 的插件提供过渡垫片。
⚠️ 维护第三方插件时,最低成本的做法是先跑官方 codemod(
@eslint/v9-to-v10-custom-rules),再人工检查没有直接替代的属性(如parserPath)。
17. 高级:团队规则治理
17.1 规则分层
成熟团队通常会把规则分成四层:
- 基础正确性:不允许明显错误。
- 类型安全:Promise、any、类型断言、空值等。
- 框架约束:React Hooks、Vue 组件、Next.js、Nuxt 等。
- 架构约束:模块边界、导入路径、废弃 API、目录依赖方向。
17.2 规则落地节奏
建议:
- 新规则先以
warn运行。 - 对新代码严格,对历史代码渐进清理。
- 高价值规则提升为
error。 - 无法稳定执行的规则要么修正配置,要么关闭。
17.3 共享配置包
大型组织可以维护内部共享配置:
text
@company/eslint-config
@company/eslint-plugin适合沉淀:
- 基础 JS / TS 规则。
- React / Vue 项目规则。
- Node 服务规则。
- Monorepo 边界规则。
- 公司内部 API 禁用规则。
共享配置要保持版本化和变更说明,不要悄悄改变大量规则。
17.4 架构师视角
前端架构师使用 ESLint 不只是为了“规范代码”,更是为了构建可执行的工程约束。
可以约束:
- UI 层不能直接请求底层接口。
- 业务模块不能跨域调用另一个业务模块内部文件。
- 公共组件不能依赖业务 store。
- 页面层不能导入实验性工具包。
- 禁止使用已废弃组件。
- 禁止
any扩散到公共 API。
这些规则让架构不只停留在文档里,而是变成每天运行的自动检查。
17.5 用 Bulk Suppressions 渐进启用新规则
在老代码库里把新规则直接设为 "error" 往往不现实:存量违规太多,改不完就不敢开。ESLint 提供了 Bulk Suppressions(批量抑制)机制解决这个死锁:
bash
# 先把规则配成 "error",然后一次性抑制所有存量违规
eslint --fix --suppress-all
# 或只抑制某条规则
eslint --fix --suppress-rule no-floating-promises执行后,ESLint 会在项目根目录生成 eslint-suppressions.json,记录被抑制的违规位置:
- 这个文件必须提交到仓库,让抑制对全团队生效。
- 抑制只覆盖“存量违规”,新写的代码仍会被规则严格检查。
- 修掉某处违规后,对应抑制变成“未使用”,再次运行会提示清理:
bash
eslint --prune-suppressions这套机制让规则落地节奏变成:
text
新规则设为 error -> 抑制全部存量 -> 提交抑制文件 -> 新代码立即受约束 -> 存量随迭代逐步清理 -> 清零后删除抑制文件💡 相比无限期
warn,Bulk Suppressions 能保证“新代码零违规”这条底线;相比// eslint-disable注释,它不会污染业务代码。注意:Node.js API 只能通过applySuppressions: true应用已有抑制,创建和清理抑制只能用 CLI。
18. 性能优化与排错
18.1 使用缓存
bash
eslint . --cache缓存能显著提升本地重复检查速度。
18.2 控制检查范围
不要 lint:
distcoverage.next.nuxtnode_modules- 生成代码
- 第三方复制代码
18.3 类型感知规则很慢怎么办
解决思路:
- 分拆 tsconfig。
- 只对源文件启用类型感知规则。
- 对测试文件降低规则严格度。
- CI 中并行执行。
- Monorepo 中按 package 运行。
- 本地使用 lint-staged 只检查暂存文件。
18.4 常见报错
找不到配置
ESLint v10 只识别 eslint.config.*,不再读取 .eslintrc.*。
插件无法解析
flat config 需要显式安装并 import 插件。
--env 不可用
把 env 改为 languageOptions.globals。
TypeScript 文件解析失败
检查:
- 是否安装
typescript-eslint files是否匹配*.ts/*.tsxparserOptions是否正确- tsconfig 是否包含被 lint 的文件
IDE 与命令行结果不同
检查:
- VS Code ESLint 插件版本
- 工作区根目录
- Node 版本
- 包管理器依赖是否安装完整
- IDE 是否读取了正确的
eslint.config.*
19. 学习路线与练习
19.1 基础阶段
目标:能在普通 JS 项目里安装、运行、修复 ESLint。
练习:
- 创建一个小项目。
- 安装 ESLint。
- 配置
eslint.config.mjs。 - 故意写出
var、未使用变量、debugger。 - 运行
pnpm lint和pnpm lint:fix。
19.2 进阶阶段
目标:能在 TS / Vue / React 项目中稳定配置 ESLint。
练习:
- 在 TS 项目中接入
typescript-eslint。 - 启用
recommendedTypeChecked。 - 配置
tsconfig.eslint.json。 - 对测试文件单独放宽规则。
- 加入 lint-staged。
19.3 高级阶段
目标:能把 ESLint 用作团队工程治理工具。
练习:
- 写一条禁止导入废弃模块的自定义规则。
- 用 RuleTester 覆盖 valid / invalid case。
- 把规则作为本地 plugin 接入。
- 在 CI 中设置
--max-warnings 0。 - 为 Monorepo 的两个 package 配置不同规则。
19.4 架构师阶段
目标:让架构约束自动化。
练习:
- 定义项目分层:
shared、features、pages、services。 - 禁止低层模块依赖高层模块。
- 禁止业务模块互相导入内部实现。
- 把规则写成自定义 ESLint 插件。
- 给规则配文档和迁移示例。
20. 官方参考资料
- ESLint v10 发布说明:https://eslint.org/blog/2026/02/eslint-v10.0.0-released/
- ESLint 配置文件文档:https://eslint.org/docs/latest/use/configure/configuration-files
- ESLint CLI 文档:https://eslint.org/docs/latest/use/command-line-interface
- ESLint 规则配置:https://eslint.org/docs/latest/use/configure/rules
- ESLint 配置迁移指南:https://eslint.org/docs/latest/use/configure/migration-guide
- ESLint 自定义规则教程:https://eslint.org/docs/latest/extend/custom-rule-tutorial
- typescript-eslint 快速开始:https://typescript-eslint.io/getting-started/
- typescript-eslint 类型感知 lint:https://typescript-eslint.io/getting-started/typed-linting