用 ESLint 9 的 Flat Config,把团队代码规范“焊死”在 CI 里

85 阅读6分钟

不知道你有没有遇到过这种场景:每次 Code Review,大家一半时间在争论到底该用单引号还是双引号,要不要加分号,缩进是 2 格还是 4 格。代码还没看逻辑,先被风格问题气得血压飙升。

讲真,这种内耗完全可以交给工具来解决。ESLint 就是那个帮你把“代码风格”从玄学变成工程的利器。  今天咱们就基于 ESLint 9 的 Flat Config,从零到一搭一套能直接落地的代码检查方案。

一、先跑起来:初始化 ESLint

如果你用的是 ESLint 9 及以上版本,初始化非常简单:

npm install -D eslint @eslint/js globals
npx eslint --init

npx eslint --init 会引导你选择项目类型、模块方案、是否使用 TypeScript 等,最后生成一个 eslint.config.mjs 文件。不过说实话,自动生成的配置往往不够贴合团队需求,咱们不如直接手写一个清晰的配置,以后维护也方便。

项目根目录新建 eslint.config.mjs:

import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";

export default defineConfig([
  js.configs.recommended,
  {
    files: ["**/*.{js,mjs,cjs}"],
    languageOptions: { globals: globals.node },
    rules: {
      "no-var": 2,
      "no-console": 1,
      "quotes": ["error", "double"],
      "semi": ["error", "always"],
      "indent": ["error", 2]
    }
  }
]);

如果你之前用旧版 ESLint 的 .eslintrc 格式,可能会对 defineConfig 和数组结构感到陌生。这就是 Flat Config,ESLint 9 的主推方式,配置文件本身也是一个 ES Module,类型提示和复用性都更好。

二、拆解配置文件:每个字段都在干什么

别小看上面这 20 行代码,它基本涵盖了 Flat Config 的核心要素。

1. js.configs.recommended

@eslint/js 是 ESLint 官方提供的核心规则包。js.configs.recommended 就是官方推荐的一组规则,包含了 no-unused-vars、no-undef、no-dupe-keys 等最容易踩坑的检查。你会发现,有了它,很多低级错误在写代码阶段就能被揪出来。

2. files: ["**/*.{js,mjs,cjs}"]

这个配置块只对 .js、.mjs、.cjs 文件生效。Flat Config 最大的好处就是可以针对不同文件类型做精细化的规则覆盖,比如后面给测试文件关掉 no-console 也很方便。

3. languageOptions: { globals: globals.node }

声明全局变量。因为我们是 Node.js 项目,所以用 globals.node。如果你写的是浏览器代码,记得换成 globals.browser,否则 ESLint 会误报 document、window 未定义。

4. rules

这里就是自定义规则区,也是团队规范真正落地的地方。下面详细说。

三、规则的三档力度:0/1/2 的哲学

ESLint 规则值有三种:

  • 0 / "off" :关闭规则,完全不检查
  • 1 / "warn" :警告,不阻断流程,但会在终端黄字提示
  • 2 / "error" :错误,会直接让 lint 失败,通常用于强制约束

说白了就是:error 是底线,必须改;warn 是建议,有空就处理;off 是不关心,留给团队自由发挥。

我在配置里挑了四条最经典的规则:

rules: {
  "no-var": 2,                              // 禁用 var,强制 let/const
  "no-console": 1,                          // 开发时警告,生产环境需要移除
  "quotes": ["error", "double"],            // 强制双引号
  "semi": ["error", "always"],              // 语句末尾必须加分号
  "indent": ["error", 2]                    // 缩进必须为 2 个空格
}
  • no-var 设为 error,因为 var 存在变量提升、作用域混乱等问题,现代 JavaScript 已经没有理由再用它。
  • no-console 设为 warn,因为在开发调试时难免要打日志,但提交到生产前应该清理掉。
  • quotes 和 semi 属于纯风格问题,统一后能减少大量无意义的 diff 争论。
  • indent 设为 2 个空格,这是目前前端社区的主流选择,Node.js 项目也基本默认。

这里有一点要特别注意:规则值不是数组时,直接写数字;需要配置参数时,用数组,第一个元素是等级,后面是配置。  比如 ["error", "double"] 就是错误级别 + 双引号选项。

四、把 lint 变成肌肉记忆:npm scripts

配置写好了,但没人天天手动敲 npx eslint .。把它接入 npm scripts 才是正道:

"scripts": {
  "lint": "eslint .",
  "lint:fix": "eslint --fix .",
  "test": "echo "Error: no test specified" && exit 1"
}

以后团队里只需要记两个命令:

npm run lint       # 只检查,不修改文件
npm run lint:fix   # 检查并自动修复能修的问题

lint:fix 能帮你自动补分号、统一引号、调整缩进,但像 no-var 这种需要语义判断的规则,它不会自动帮你把 var 改成 let,因为 ESLint 不知道你的真实意图是什么。自动修复是帮你省力,不是替你思考。

五、实战:一个典型违规文件的救赎

光说不练假把式。我们建一个 index.mjs,故意写一些违规代码:

var name = 'lgl'
function hello() {
    console.log('hello')
}
hello()

运行 npm run lint,你会看到一堆报错:

/path/to/index.mjs
  1:1   error  Unexpected var, use let or const instead  no-var
  1:14  error  Strings must use doublequote               quotes
  1:18  error  Missing semicolon                          semi
  3:1   error  Expected indentation of 2 spaces but found 4  indent
  4:1   error  Expected indentation of 2 spaces but found 4  indent
  5:1   error  Expected indentation of 2 spaces but found 4  indent

现在我们试一下 npm run lint:fix:

npm run lint:fix

再次打开文件,你会发现引号、分号、缩进全部自动修好了,但 var 还在:

var name = "lgl";
function hello() {
  console.log("hello");
}
hello();

这时终端会提示 no-var 仍然报错,因为 --fix 只能修复确定性规则。你需要手动把 var name 改成 let name,然后再跑一次 lint,就全部通过了。

let name = "lgl";
function hello() {
  console.log(name + "hello");
}
hello();

这就是一个完整的“发现违规 → 自动修复 → 手动处理剩余问题 → 通过检查”的闭环。你会发现,ESLint 的价值不在于让代码看起来整齐,而在于把团队的编码共识沉淀为机器可以执行的命令。

六、团队落地的几个实用建议

1. 配合 husky + lint-staged 在提交前检查

光有 npm scripts 还不够,总有人会忘记执行。用 husky 和 lint-staged 在 git commit 时自动跑 lint,只有通过检查才能提交。这样就把规范从“自觉”变成了“强制”。

// package.json 里配置 lint-staged
"lint-staged": {
  "*.{js,mjs,cjs}": "eslint --fix"
}

2. 在 CI 流程中加一道 lint 关卡

GitHub Actions、GitLab CI 或 Jenkins 里加一步 npm run lint,如果返回非零状态码就阻断合并。这样即使有人本地跳过了 husky,代码也进不了主干。

3. 规则分层,别把 warn 当 error

error 是底线,必须改;warn 是建议,可以暂缓处理。不要把 no-console 设成 error,否则开发调试时你会被自己逼疯。但可以通过 overrides 在生产构建文件里把它提到 error:

{
  files: ["**/*.prod.js"],
  rules: {
    "no-console": 2
  }
}

4. 不要盲目继承网上的“全家桶”配置

网上的 airbnb、standard 等预设虽然全面,但未必适合你的项目。规则不是越多越好,有些规则过于严格反而会降低开发效率。先用 recommended 兜底,再根据团队实际痛点逐步增加规则,才是可持续的工程化路线。

七、写在最后

ESLint 不是用来限制你的工具,而是帮你把团队的编码共识变成一条条明确的红线。它让你不必在 Code Review 时为了一个分号浪费口舌,也让新同学能快速对齐项目的代码风格。

规范不是束缚,而是让团队新人少踩坑的护栏。