Skip to content

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。


目录 ​

  1. ESLint 是什么
  2. 为什么前端项目需要 ESLint
  3. ESLint 的工作原理
  4. 版本与生态现状
  5. 快速开始
  6. flat config 核心结构
  7. rules 规则系统
  8. languageOptions、globals 与运行环境
  9. files、ignores 与分层配置
  10. TypeScript 项目接入
  11. Vue、React、Node 项目中的 ESLint
  12. ESLint 与 Prettier 的关系
  13. CLI 常用命令
  14. 编辑器、Git Hooks 与 CI 集成
  15. 迁移到 ESLint v10
  16. 进阶:自定义规则
  17. 高级:团队规则治理
  18. 性能优化与排错
  19. 学习路线与练习
  20. 官方参考资料

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 globals

TypeScript 项目:

bash
pnpm add -D eslint @eslint/js globals typescript typescript-eslint

也可以使用 npm:

bash
npm i -D eslint @eslint/js globals

5.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:fix

5.4 第一次学习时不要一口气打开所有规则 ​

推荐顺序:

  1. 先启用官方推荐规则。
  2. 再启用框架推荐规则。
  3. 再加入 TypeScript 类型感知规则。
  4. 最后按团队需要加入风格类、复杂度类、架构类规则。

一开始规则太多会造成大量噪声,使团队误以为 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 配置合并的内部机制 ​

理解合并机制,才能解释“为什么我配了规则却没生效”:

  1. 匹配:对每个被检查的文件,按数组顺序找出所有 files 匹配该路径的配置对象(没写 files 的对象默认匹配所有文件)。
  2. 合并:命中的配置按顺序合并,后面的覆盖前面的。
    • rules 按规则 id 覆盖。
    • plugins 按插件名合并,同名插件对象被后者替换。
    • languageOptions.globals 按变量名合并,同名变量后者生效。
  3. 忽略:只包含 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-eslint

10.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-react
  • eslint-plugin-react-hooks
  • eslint-plugin-jsx-a11y
  • eslint-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 同时存在时,要确保两者不抢同一段代码的格式化权。推荐做法分两层:

  1. 只关规则,不跑 Prettier(最常见、最轻量):
bash
pnpm add -D eslint-config-prettier
js
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,
]);
  1. 脚本分离: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 . --fix

13.3 只修复建议类问题 ​

bash
npx eslint . --fix-type suggestion

常见 --fix-type:

  • problem
  • suggestion
  • layout
  • directive

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 json

JSON 输出可用于平台集成、质量报表、代码扫描系统。


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-staged

package.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 0

14.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
.eslintignoreignores
env.browserlanguageOptions.globals + globals.browser
parserOptionslanguageOptions.parserOptions
plugins: ["react"]plugins: { react: reactPlugin }
extends 字符串导入配置对象或用 defineConfig() 组合
--env配置中声明 globals
--rulesdir本地 plugin 或直接导入规则

15.4 分阶段迁移策略 ​

大型项目不要直接“升版本 + 改规则 + 清理历史问题”一起做。

推荐顺序:

  1. 先在当前版本迁移到 flat config。
  2. 保持规则结果尽量不变。
  3. 升级 ESLint 和插件。
  4. 修复破坏性变化。
  5. 再逐步提高规则严格度。

这样可以把风险拆小,避免一次 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.parserOptionscontext.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 规则分层 ​

成熟团队通常会把规则分成四层:

  1. 基础正确性:不允许明显错误。
  2. 类型安全:Promise、any、类型断言、空值等。
  3. 框架约束:React Hooks、Vue 组件、Next.js、Nuxt 等。
  4. 架构约束:模块边界、导入路径、废弃 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:

  • dist
  • coverage
  • .next
  • .nuxt
  • node_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 / *.tsx
  • parserOptions 是否正确
  • tsconfig 是否包含被 lint 的文件

IDE 与命令行结果不同 ​

检查:

  • VS Code ESLint 插件版本
  • 工作区根目录
  • Node 版本
  • 包管理器依赖是否安装完整
  • IDE 是否读取了正确的 eslint.config.*

19. 学习路线与练习 ​

19.1 基础阶段 ​

目标:能在普通 JS 项目里安装、运行、修复 ESLint。

练习:

  1. 创建一个小项目。
  2. 安装 ESLint。
  3. 配置 eslint.config.mjs。
  4. 故意写出 var、未使用变量、debugger。
  5. 运行 pnpm lint 和 pnpm lint:fix。

19.2 进阶阶段 ​

目标:能在 TS / Vue / React 项目中稳定配置 ESLint。

练习:

  1. 在 TS 项目中接入 typescript-eslint。
  2. 启用 recommendedTypeChecked。
  3. 配置 tsconfig.eslint.json。
  4. 对测试文件单独放宽规则。
  5. 加入 lint-staged。

19.3 高级阶段 ​

目标:能把 ESLint 用作团队工程治理工具。

练习:

  1. 写一条禁止导入废弃模块的自定义规则。
  2. 用 RuleTester 覆盖 valid / invalid case。
  3. 把规则作为本地 plugin 接入。
  4. 在 CI 中设置 --max-warnings 0。
  5. 为 Monorepo 的两个 package 配置不同规则。

19.4 架构师阶段 ​

目标:让架构约束自动化。

练习:

  1. 定义项目分层:shared、features、pages、services。
  2. 禁止低层模块依赖高层模块。
  3. 禁止业务模块互相导入内部实现。
  4. 把规则写成自定义 ESLint 插件。
  5. 给规则配文档和迁移示例。

20. 官方参考资料 ​

基于 VitePress 构建的个人学习文档知识库