前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

ESLint 配置文件详解 从 eslintrc 字段到扁平配置

首页2018-11-23 10:40:08Front-End
ESLint代码风格Flat Config前端工程化

接手一个跑了几年的老项目,根目录躺着一份两百多行的 .eslintrc.json,env、parserOptions、extends、rules 一字排开,谁也不敢动。想加一条规则,不确定会不会被 extends 里的某个包覆盖掉;想让 test/ 目录松一点,又不知道该写在哪一层。

ESLint 是一个 JavaScript 静态检查工具,它能帮你养成良好的编程习惯。但要让它真的听话,得先看懂它的配置文件。这篇就把这个文件拆开,一个字段一个字段说清楚它管什么、什么时候用得上、写错了会出什么症状。那份带中文注释的完整规则清单我一条没删,只是按类别拆成了几段,每段前面补上背景。最后再补一节 ESLint 9 扁平配置的对应写法,老项目和新项目都能对上号。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • ESLint 到底去哪里找配置文件,root: true 这一行在挡什么
  • env 和 globals 的分工,以及为什么 ESLint 9 里 env 消失了
  • parserOptions 的几个子字段分别影响解析的哪一步
  • plugins 和 extends 的关系,插件装了不等于规则开了
  • rules 的严重度写法、参数写法,以及一份逐条注释的完整规则清单
  • overrides 怎么给不同目录配不同规则
  • 多份配置叠在一起时,到底谁覆盖谁
  • 行内注释临时关掉校验的三种写法
  • 上面这些字段在 ESLint 9 扁平配置里对应成了什么

# 一、ESLint 先得找到配置文件

写规则之前,先搞清楚 ESLint 是怎么找到这个文件的。这一步不清楚,后面所有的「我明明改了但没生效」都没法排查。

.eslintrc.* 这套体系(下面统称 eslintrc)用的是级联查找。ESLint 拿到一个待检查的文件,从它所在的目录开始往上翻,每一层只要有配置文件就读进来,一直翻到某一层写了 root: true,或者翻到文件系统根目录为止。翻到的所有配置自上而下合并,离被检查文件越近的那份优先级越高。

同一个目录里放了多种格式的配置文件时,ESLint 只取第一个,其余的直接忽略。顺序是固定的。

.eslintrc.js
.eslintrc.cjs
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
package.json 里的 eslintConfig 字段
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意。项目里同时存在 .eslintrc.js 和 .eslintrc.json 时,.json 那份是完全不生效的,而且没有任何警告。我见过好几次「改了半天规则纹丝不动」,最后发现是在改一份根本没被读的文件。

root: true 的作用是「到我为止,别再往上翻了」。这一行在 monorepo 里几乎是必写的,否则子包的 lint 会一路读到仓库根目录甚至用户主目录下的配置,行为在不同人的机器上还不一样。

还有一个独立于配置文件的 .eslintignore,语法和 .gitignore 一致,用来把 dist/、node_modules/、生成代码排除掉。它和 .eslintrc 是两个文件,很多人以为忽略规则也写在配置里,结果构建产物被一起 lint,报出几万条错误。

先记住这两点,因为待会儿讲 ESLint 9 的时候,这两条恰好都变了。

# 二、env 声明代码跑在什么环境里

env 解决的是一个很具体的问题:ESLint 不知道 window 和 process 是什么。

它只是个语法分析器,看到一个没声明过的标识符,no-undef 规则就会报 'window' is not defined。env 做的事情就是告诉 ESLint「这份代码跑在浏览器里,浏览器那一整套全局变量都是合法的」,ESLint 内部维护着这些环境对应的全局变量表,打开开关就把整张表塞进作用域。

// .eslintrc.json 片段
{
    // 环境定义了预定义的全局变量。
    "env": {
        //环境定义了预定义的全局变量。更多在官网查看
        "browser": true,
        "node": true,
        "commonjs": true,
        "amd": true,
        "es6": true,
        "mocha": true
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

这几个开关分别对应什么,值得逐条对一下。browser 给的是 window、document、navigator 这类;node 给的是 process、__dirname、Buffer、require;commonjs 是给 Browserify、webpack 这种在浏览器里用 CommonJS 打包的场景准备的;amd 对应 define 和 require 的 AMD 形式;mocha 给的是 describe、it、before 这些测试全局函数。

es6 这一项要单独说,它不只是加全局变量。打开 es6 会同时把 Promise、Map、Set、Symbol 这些内置对象加进来,并且自动把 parserOptions.ecmaVersion 设成 6。所以你会看到很多老配置里 es6: true 和 ecmaVersion: 6 同时写着,其实后者是冗余的。

我自己的习惯是不要把 browser 和 node 一起全开。全开确实不报错了,代价是你在浏览器代码里写 process.env.XXX 也不会被拦住,直到运行时才炸。更好的做法是根目录只开 browser,用后面讲的 overrides 给构建脚本单独开 node。

# 三、globals 补 env 覆盖不到的全局变量

env 给的是成套的预设,globals 给的是零散的补充。CDN 引进来的 jQuery、后端模板注入的 __INITIAL_STATE__、老项目里挂在 window 上的那些全局对象,都归它管。

// .eslintrc.json 片段
{
    "globals": {
        // true 表示允许重新赋值,false 表示只读
        "jQuery": false,
        "$": false,
        "__INITIAL_STATE__": true
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

值的含义容易记反。false(或者写成 "readonly")是只读,代码里给它重新赋值会被 no-global-assign 拦住;true("writable")才是允许改写。绝大多数场景应该写 false,第三方库的全局对象没有理由被业务代码覆盖。

如果只有单个文件用到某个全局变量,没必要写进配置,直接在文件顶部用注释声明就行。

/* global __INITIAL_STATE__ */
console.log(__INITIAL_STATE__)
@前端进阶之旅: 代码已经复制到剪贴板

这个写法和前面 rules 里 no-undef 那条注释说的是同一件事,「禁用未声明的变量,除非它们在 /*global */ 注释中被提到」。

# 四、parserOptions 决定代码怎么被解析

前面两个字段管的是「有哪些变量」,parserOptions 管的是「这段代码按什么语法解析」。解析这一步失败,后面所有规则都跑不起来,报错通常是 Parsing error: Unexpected token。

// .eslintrc.json 片段
{
    // JavaScript 语言选项
    "parserOptions": {
        // ECMAScript 版本
        "ecmaVersion": 6,
        "sourceType": "module", //设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。
        //想使用的额外的语言特性:
        "ecmaFeatures": {
            // 允许在全局作用域下使用 return 语句
            "globalReturn": true,
            // 全局使用严格模式(impliedStrict)
            "impliedStrict": true,
            // 启用 JSX
            "jsx": true,
            "modules": true
        }
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

ecmaVersion 现在一般直接写 2022、2023 这种年份形式,或者写 "latest" 让它跟着 ESLint 版本走。写 6 的问题是可选链 ?.、空值合并 ?? 这些后来的语法会直接解析失败。老配置里留着 6 是因为当年就到这儿,新项目没必要跟。

sourceType 只有两个值有实际区别。写 "module" 时 ESLint 按 ES Module 解析,import/export 可用,并且代码默认处于严格模式;写 "script" 时按传统脚本解析,顶层 this 指向全局对象。这一项配错的典型症状是 import 那一行报 Unexpected token import。

ecmaFeatures 里几个子项都比较边缘。globalReturn 允许顶层 return,这是 CommonJS 模块的历史遗留;impliedStrict 让所有代码都按严格模式解析,等价于每个文件顶部都有 'use strict';jsx 是让内置解析器认识 JSX 语法。

这里要澄清一个常见误解。ecmaFeatures.jsx 只让 ESLint 能把 JSX 解析成语法树,它不提供任何 React 相关的检查规则,那是 eslint-plugin-react 的活。另外 modules 这一项在现在的 ESLint 里已经不需要了,用 sourceType: "module" 就够,老配置里带着它不会报错,纯属冗余。

还有一个和 parserOptions 平级的 parser 字段,这份配置里没写,但实际项目基本绕不开。内置解析器叫 espree,只认标准 JavaScript;写 TypeScript 要换成 @typescript-eslint/parser,写 Vue 单文件组件要换成 vue-eslint-parser。换解析器之后 parserOptions 里往往还要额外配 project 指向 tsconfig.json,那些依赖类型信息的规则才能跑。

# 五、plugins 和 extends 是两件事

这两个字段挨得近,作用完全不同,混淆的人非常多。

// .eslintrc.json 片段
{
    //-----让eslint支持 JSX start
    "plugins": [
        "react"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ]
    //-----让eslint支持 JSX end
}
@前端进阶之旅: 代码已经复制到剪贴板

plugins 只做一件事,把插件里的规则注册进来,让你可以在 rules 里写 react/jsx-uses-vars 这种带前缀的规则名。注册不等于开启,光写 plugins: ["react"] 而不配任何规则,lint 结果一条都不会变。这是新手最容易懵的地方,装了插件却发现什么都没检查出来。

extends 才是「批量开规则」。它接受三类值:"eslint:recommended" 是 ESLint 内置的推荐集;"plugin:react/recommended" 是插件自己导出的推荐集,写法是 plugin: 加插件名加配置名;直接写包名(比如 "airbnb")则对应 eslint-config-airbnb 这个独立的配置包。

注意第二种写法里插件名可以省略 eslint-plugin- 前缀,react 会被解析成 eslint-plugin-react。这套省略约定在 monorepo 里经常出问题,因为 ESLint 是按自己的路径规则去 node_modules 里找包的,pnpm 那种非扁平的 node_modules 结构下解析失败是家常便饭。

fe
  • 一、ESLint 先得找到配置文件
  • 二、env 声明代码跑在什么环境里
  • 三、globals 补 env 覆盖不到的全局变量
  • 四、parserOptions 决定代码怎么被解析
  • 五、plugins 和 extends 是两件事
  • 六、rules 的严重度和参数写法
  • 七、一份逐条注释的完整规则清单
    • 可能的错误
    • 最佳实践
    • 变量声明
    • Node.js 与 CommonJS
    • 风格指南
    • ES6 相关
  • 八、overrides 给不同目录配不同规则
  • 九、多份配置叠在一起时谁说了算
  • 十、行内注释临时关掉校验
  • 十一、这些字段在扁平配置里对应成了什么
  • 总结
  • 参考

← dll 预编译提高 webpack 打包速度,以及它现在的替代方案webpack 回顾篇,核心概念与配置项全景梳理 →