说实话,我之前一直觉得 ESLint 就是个装完就能用的工具,脚手架里自带、公司项目里配好,我只管写代码就行。直到这次自己从零搭一个 demo,一行
npm run lint跑下去,控制台给我甩了个大红叉,我才意识到这玩意儿我根本没真正搞懂过。
跑了一下直接炸了
事情是这样的,我新开了个空目录,想正经给自己的小工具项目加上代码检查。按记忆里的套路:
bash
npm i -D eslint
npx eslint --init
init 过程中选了"检查语法+找问题+统一风格",用的是 JavaScript 模块配置,没有框架,选了 Node 环境。然后在 index.js 里随手写了几行测试代码:
javascript
var name = 'xjl_ai';
// let age = 18;
function hello() {
console.log('hello');
}
hello();
接着在 package.json 里加了脚本:
json
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
敲下 npm run lint,然后我就看到了这个:

满屏红。var 不能用、name 声明了没引用、缩进是 4 个空格它要 2 个、console.log 不允许、字符串必须双引号。5 个问题,4 errors 1 warning。
当时我第一反应是:这不都正常代码吗?var 怎么就不能用了?我写 4 个空格缩进关你什么事?console.log 我调试用的也管?
我以为装完就能用,结果什么都不检查
说实话,看到这一屏报错之前,我还踩了个更蠢的坑。
最开始我只跑了 npm i -D eslint,然后直接 npx eslint .,结果控制台干干净净,什么输出都没有。我当时还觉得"挺不错啊,代码没毛病"。后来越想越不对------我故意写了个 var a = 1; 这种明显"不规范"的代码,它居然一个字都没说。
翻了下文档我才懵了:ESLint 默认所有规则都是关闭的。你光装个 eslint,它就是个空壳子,一条规则都不检查,跟个没装任何插件的 IDE 一样。它不像 Prettier 那样开箱即用,你必须明确告诉它"我要检查哪些东西"。
这就好比你请了个质检员,但你没给他质检标准,他就站那啥也不干。
那怎么加规则?我知道可以在配置文件里一条条写 rules,但总不能把几百条规则全手写吧?这时候就要靠 extends------直接继承别人配好的规则集。@eslint/js 这个包里带了个 recommended 配置,就是官方推荐的那套规则,init 的时候自动帮我加上了。
你看我配置里这行:
javascript
extends: ["js/recommended"],
这行的意思就是"把官方推荐的规则全给我打开"。没有这行,ESLint 就是个哑巴。
照着旧教程写配置,结果它根本不认
搞定了"为什么不报错"的问题,我想加点自定义规则。比如强制双引号、缩进 2 空格、语句末尾加分号。
这时候我又踩了第二个坑,而且这个坑卡了我快两天。
我上网搜"ESLint 自定义规则",搜出来的教程清一色教你写 .eslintrc.js:
javascript
// 网上教程里的旧写法
module.exports = {
rules: {
quotes: ["error", "double"],
indent: ["error", 2]
}
};
我照着在项目根目录建了个 .eslintrc.js,把规则写进去,跑 npm run lint------结果它完全无视我的配置,还是按默认的来。我反复确认文件路径、JSON 格式、拼写,甚至把规则名抄了一遍又一遍,就是不生效。
后来才发现,我装的是 ESLint v10 (npm 上最新版),从 v9 开始,ESLint 默认使用扁平配置模式(Flat Config) ,配置文件名变成了 eslint.config.mjs,写法也完全不一样了。旧的 .eslintrc 格式在新版里默认不读取,你得主动开兼容模式才认。
跑一下 npx eslint --init 就明白了,它生成的配置文件长这样:
javascript
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.node },
rules: {
// 级别 2 = error、1 = warn、0 = off
"no-var": 2, // 不能用 var
"no-console": 1, // console 警告但不报错
"quotes": ["error", "double"], // 引号必须双引号
"semi": ["error", "always"], // 语句末尾必须有分号
"indent": ["error", 2], // 缩进必须 2 个空格 // 就这行,坑了我半小时
},
},
]);
注意看规则的级别,这里用数字表示:2 是 error(直接报错,退出码非零,CI 会卡),1 是 warn(黄字警告但不阻塞),0 是 off(关掉这条规则)。也可以写成字符串 "error" / "warn" / "off",效果一样,看个人习惯。
ESLint v9+ 默认只认
eslint.config.js/eslint.config.mjs,网上大量教程还是.eslintrc的旧格式,照着抄大概率不生效。先npx eslint --version确认版本,v9 及以上直接用新写法。
ESLint 到底在干什么
配置写对了,报错也出来了,我停下来想了想:ESLint 检查代码到底是个什么流程?
简单说,ESLint 干的事就三步:
- 解析(Parse):把你的 JS 代码解析成 AST(抽象语法树),就是把代码拆成一棵对象树,每个节点代表一段代码结构(变量声明、函数调用、字符串字面量等等)
- 遍历(Traverse):从树的根节点开始往下走,每经过一个节点,就把这个节点交给所有注册了的规则去检查
- 报告(Report):某条规则发现节点不符合要求,就记录一个问题(带位置、级别、修复建议)
举个例子,no-var 这条规则,它监听的是 AST 里的 VariableDeclaration 节点,如果发现 kind 属性是 "var",就报错。quotes 规则监听的是 Literal 节点,发现是字符串且用了单引号而配置要求双引号,就报错。
这么理解的话,ESLint 其实就是个"AST 遍历器 + 一堆检查函数"。每条规则就是一个小函数,输入是 AST 节点,输出是"行还是不行"。这个认知帮我理解了为什么规则名长这样------no-var、no-unused-vars、no-console,它们都是精准描述了"要禁止什么",因为每条规则就盯着一个具体的 AST 模式。
改完代码终于绿了
搞明白配置怎么写之后,我把 index.js 按规则要求改了:
javascript
let name = "xjl_ai";
function hello() {
console.log("hello", name); // 改成双引号、2空格缩进,name也用上了
}
hello();
再跑 npm run lint,只剩一个 warning------no-console 那条我设的是 warn 级别,黄字提醒但不报错。开发阶段留着 console 没问题,上线前处理掉就行。
说到这提一句,有些规则是可以自动修复的,不用手动改。比如缩进、引号、分号这些格式类规则,跑 npm run lint:fix(也就是 eslint . --fix),ESLint 会直接帮你把代码改对。但 no-var 能不能自动修?能,但它只会机械地把 var 改成 let,不会智能判断该用 const 还是 let(比如变量从没被重新赋值过,其实该用 const),所以自动修复之后最好还是自己过一眼。
那些让我挠头的小细节
踩了几天坑,有几个点我觉得特别容易搞混,集中说说。
extends 和 plugins 到底啥区别?
我一开始以为这俩是一回事,后来才搞清楚:plugins 是"装了一包规则",但这些规则默认全是关的;extends 是"直接用别人配好的一套规则组合",相当于别人帮你写好了一堆 rules 配置。类比一下,plugin 是买了一箱乐高零件,extend 是买了一份拼好的图纸+按图纸拼好的成品。
globals 是干嘛的?
配置里有一行 languageOptions: { globals: globals.node },这个是告诉 ESLint:"我在 Node 环境下跑,process、__dirname、require 这些全局变量是合法的,别给我报 no-undef。"如果不写这个,你代码里用 process.env,ESLint 会说"process is not defined",因为它不知道这是 Node 的全局变量。同理,浏览器环境就用 globals.browser,这样 window、document 就不会被报错。
规则级别别乱设
我最初图省事把 no-console 也设成了 2(error),结果 lint 命令因为一个 console.log 直接返回非零退出码,CI 直接挂了。后来想明白:格式类、潜在 bug 类规则设成 error 没问题;console 这种开发时确实会用的,设成 warn 提醒就好;不认同的规则直接 0 关掉,别跟自己较劲。比如有人觉得分号必须有,有人觉得 ASI 自动分号插入足够安全,这种就看团队约定,没有绝对对错。
错误代码和正确代码对比一下,差别一目了然:
javascript
// ❌ 被报错的版本
var name = 'xjl_ai';
function hello() {
console.log('hello'); // 4空格、单引号、name没被用到
}
hello();
javascript
// ✅ 通过检查的版本
const name = "xjl_ai";
function hello() {
console.log("hello", name); // 2空格、双引号、name用到了
}
hello();
跑
npx eslint index.js可以只检查单个文件,调试规则的时候比eslint .快很多,不用每次扫描整个目录。
我的一点感想
这几天折腾下来,最大的感受就是:ESLint 没有我之前想的那么"魔法"。它就是个 AST 检查器,你给它什么规则它就查什么,不给就啥也不干。配置文件的格式因为版本大改(v8→v9 的 Flat Config)导致网上教程新旧混杂,是最容易卡人的地方------这也是我为什么折腾了这么久,照着旧教程写的东西根本跑不起来。
另外我发现一个边界:ESLint 擅长的是静态检查,也就是不运行代码就能发现的问题(变量没用到、用了 var、缩进不对)。但有些问题它查不出来,比如逻辑错误、异步问题、内存泄漏,这些得靠 TypeScript 类型系统或者运行时测试去兜底。别指望 ESLint 万能,它管的是"代码写得规不规范",不是"代码写得对不对"。
反正我现在自己搭项目,第一步就是 npm i -D eslint + npx eslint --init,然后打开 eslint.config.mjs 把常用规则加上,写代码的时候实时能看到红波浪线,比写完再统一改舒服多了。你们第一次配 ESLint 的时候,也被版本问题坑过吗?
顺手捋的核心知识点
写到这顺手把核心的点捋了一遍,我自己回头复盘方便,你们要是懒得翻长文也可以直接看这部分。
1. ESLint 默认零规则
- 核心结论:ESLint 安装后不开启任何规则检查,必须通过
extends继承规则集或手动在rules中配置规则才会生效 - 易错提醒:
npx eslint .没有输出不代表代码没问题,可能是根本没配规则;通过"extends": ["js/recommended"]开启官方推荐规则集
2. ESLint v9+ 使用 Flat Config 新格式
- 核心结论:v9 及以上版本默认配置文件为
eslint.config.mjs(或.js),使用defineConfig+ ES Module 导出数组,旧的.eslintrc.js格式默认不再读取 - 易错提醒:网上大量教程基于 v8 旧格式,照着写
.eslintrc不会生效;务必先用npx eslint --version确认版本
3. 规则的三个严重级别
- 核心结论:每条规则的值可以是
0/"off"(关闭)、1/"warn"(警告,不阻塞)、2/"error"(错误,退出码非零,阻塞 CI) - 易错提醒:
no-console这类开发常用语句建议设为 warn,设为 error 会导致 lint 命令挂掉 CI;格式类规则设 error 没问题
4. extends 与 plugins 的关系
- 核心结论:
plugins只是加载规则包(规则默认全关),extends是继承一套已配置好的规则组合,可以来自插件或第三方 - 易错提醒:只加 plugins 不在 extends 或 rules 里开启,规则不会生效,不要以为装了插件就自动检查
5. globals 声明环境全局变量
- 核心结论:通过
languageOptions.globals配置环境预置的全局变量,Node 用globals.node,浏览器用globals.browser,避免no-undef误报 - 易错提醒:不配置 globals 时,
process、window、document等环境变量会被 ESLint 认定为未定义变量报错
6. lint:fix 自动修复的能力边界
- 核心结论:格式类规则(缩进、引号、分号)支持
--fix自动修复,语义类规则(no-var、no-unused-vars)的自动修复可能不完美 - 易错提醒:
--fix会直接改文件且无法撤销,建议先跑npm run lint看问题清单,确认后再 fix,重要代码提前 commit;no-var自动修复只会改为let,不会智能判断const
7. 手动实现一个简易 ESLint 规则检查器的核心步骤
- 核心结论:ESLint 的本质是"解析 AST → 遍历节点 → 规则匹配",核心三步:
- 用
@babel/parser或espree将源码解析为 AST - 用深度优先遍历访问每个 AST 节点
- 对每种节点类型注册检查函数(如检查
VariableDeclaration.kind === 'var'),命中则记录行号和错误信息
- 用
- 易错提醒:自己写规则时要注意 AST 节点类型的准确名称(如字符串是
Literal不是String),建议用 AST Explorer 网站先看节点结构再写逻辑