ESLint 从入门到实战:作用、配置字段与 JavaScript / TypeScript / Vue 配置详解
项目接入 ESLint 后,为什么还是不检查 .vue?为什么浏览器代码提示 window is not defined?为什么安装了插件,规则却没有生效?
这些问题通常来自三件事:文件没有匹配到配置、解析器不理解文件语法,或者规则与运行环境没有正确设置。
本文围绕这条链路解释 ESLint,并提供可独立使用的配置示例。文章以 Flat Config 为主,使用 eslint.config.mjs。
示例要求 ESLint 支持
eslint/config中的defineConfig、globalIgnores和extends,插件也需要支持 Flat Config。安装前检查 Node.js、ESLint、TypeScript 和插件的兼容要求,安装后提交锁文件。
一、ESLint 到底解决什么问题
ESLint 是静态代码检查工具。它读取源文件,解析代码结构,再按照启用的规则报告问题;部分问题可以自动修复。
例如:
const count = 1;
console.log(coutn);coutn 很可能是拼写错误。启用 JavaScript 的 no-undef 规则后,可以在运行前发现这个未声明变量。
再如:
let total = 10;
console.log(total);如果后续没有重新赋值,prefer-const 可以建议改成 const。
ESLint 可以帮助团队:
- 提前发现未声明变量、重复声明、错误的控制流等代码问题。
- 通过框架插件检查组件模板、框架约定和特定 API 用法。
- 统一约定,例如未使用变量的处理、是否允许
debugger。 - 在编辑器、提交检查和 CI 中执行同一套规则。
但 ESLint 无法证明业务逻辑正确,也不自动检查所有运行时问题。规则有没有开启、规则能否获得类型信息,以及插件能力,都会影响检测范围。
二、ESLint、TypeScript、Prettier 和测试的区别
| 工具 | 主要关注点 | 例子 |
|---|---|---|
| ESLint | 已启用规则约束的代码质量和用法 | 未使用变量、错误 Promise 用法、框架约定 |
| TypeScript 编译器 | 类型系统、编译语义与类型兼容性 | 把字符串传给只接受数字的参数 |
| Prettier | 代码排版 | 换行、缩进、引号和行宽处理 |
| 单元 / 集成测试 | 输入输出和实际行为 | 订单金额、权限判断、接口交互 |
这些工具有部分重叠,但不能相互替代。开启 ESLint 的 TypeScript 类型感知规则,也不意味着可以省略 tsc --noEmit 或测试。
对于常规项目,可以让 Prettier 处理排版,ESLint 处理代码规则,减少两个工具反复修改同一段代码的问题。
三、理解检查过程:文件、解析器、规则
ESLint 的工作过程可以概括为:
输入文件
↓
判断 files / ignores,合并匹配配置
↓
解析器读取语法,生成代码结构及作用域信息
↓
运行规则;部分 TypeScript 规则进一步使用类型信息
↓
输出问题,或执行规则提供的自动修复这也解释了几类常见问题:
- 文件没被匹配到:修改规则通常没有帮助。
- 出现解析错误:要先检查解析器和语法选项。
- 没有规则报告:可能根本没启用相关规则。
- 类型规则报缺少类型信息:需要额外的 TypeScript 项目配置。
插件、解析器和共享配置是三个不同概念:
| 概念 | 职责 |
|---|---|
| Parser,解析器 | 让 ESLint 理解一种语法,例如 TypeScript 或 Vue 单文件组件 |
| Plugin,插件 | 提供规则,也可能提供处理器、语言和预设配置 |
| Shareable config,共享配置 | 将规则与选项打包,供其他配置复用 |
安装插件不等于启用它的规则。 注册插件后,还需要继承它的预设,或者在 rules 中逐条开启规则。官方插件说明
四、安装并运行第一个检查
以下以 pnpm 为例,在项目根目录执行:
pnpm add -D eslint @eslint/js globals三个依赖分别负责:
| 包 | 作用 |
|---|---|
eslint | 命令行、配置引擎、规则执行等核心能力 |
@eslint/js | JavaScript 官方预设,例如 recommended |
globals | 浏览器、Node.js 等环境的全局变量集合 |
创建 eslint.config.mjs:
import js from '@eslint/js';
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
globalIgnores(['**/dist/**', '**/coverage/**']),
{
name: 'app/browser-js',
files: ['src/**/*.{js,mjs}'],
extends: [js.configs.recommended],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: globals.browser,
},
rules: {
eqeqeq: ['error', 'always'],
'prefer-const': 'error',
'no-debugger': 'error',
},
},
]);检查代码:
pnpm exec eslint src自动修复支持修复的问题:
pnpm exec eslint src --fix建议在工作区变更可查看、可回退的情况下执行 --fix,然后审阅 diff。不是每条规则都能自动修复,有些问题需要开发者明确判断。
五、Flat Config 如何生效
Flat Config 通常导出一个配置数组。每个对象可以针对一类文件设置规则,匹配同一文件的对象按顺序参与合并,后面的配置可以覆盖前面的冲突项。
export default defineConfig([
{
files: ['src/**/*.js'],
rules: { 'no-console': 'warn' },
},
{
files: ['src/dev-tools/**/*.js'],
rules: { 'no-console': 'off' },
},
]);src/main.js 使用第一条约定,src/dev-tools/debug.js 同时匹配两项,后面的配置关闭 no-console。
defineConfig 是配置组织和类型提示辅助,不会自动开启检查规则。本文使用 .mjs,是为了明确配置文件采用 ESM,避免与项目 package.json 的模块类型混淆。
files 与 ignores 通常相对配置位置匹配;使用 --config 或 basePath 时应注意其基准变化。运行位置、配置查找和多包仓库结构也会影响最终结果,遇到问题要对具体文件检查解析后的配置。配置文件文档
六、配置字段逐项解释
下面讨论的是 Flat Config。不要将 .eslintrc 的字段原样搬进来。
1. name:配置块的名字
{
name: 'app/browser-code',
files: ['src/**/*.js'],
}它用于识别配置块,排查复杂配置时比“第几个匿名对象”更容易理解。它不是文件匹配条件,也不决定优先级。
2. files:配置作用于哪些文件
files: ['src/**/*.{js,ts}', 'scripts/**/*.mjs']同一数组里常规字符串表示多个可匹配范围。上面的配置可作用于 src 下的 JS、TS 和 scripts 下的 MJS。
没有 files 并不等于自动检查所有扩展名。增加 .ts、.vue 等文件时,应明确列入匹配范围,并配置理解其语法的解析器。
3. ignores:排除配置作用范围
这里最容易混淆的是全局忽略与局部忽略:
globalIgnores(['**/dist/**'])用于整个配置的忽略范围。
{
files: ['src/**/*.js'],
ignores: ['src/generated/**'],
rules: { 'no-console': 'warn' },
}表示 generated 文件不应用这个配置块,但它们仍可能被其他配置块检查。局部忽略不能当成整个项目的免检名单。
目录匹配与文件匹配的细节不同;如果目的是让构建产物整体不被检查,优先显式使用 globalIgnores。
4. basePath:为一个目录组织配置
{
basePath: 'packages/web',
files: ['src/**/*.js'],
rules: { 'no-alert': 'error' },
}这里的文件模式相对 packages/web。它适合组织 monorepo 子包配置,但不是切换 Node.js 工作目录,也不等于 TypeScript 的 tsconfigRootDir。
5. extends:继承配置
{
files: ['src/**/*.js'],
extends: [js.configs.recommended],
rules: { 'no-unused-vars': 'warn' },
}先应用推荐配置,再设置项目覆盖项。extends 可以继承配置对象、配置数组,也可以引用插件配置名称;它与 .eslintrc 中字符串配置的解析方式有所区别。
如果所用 ESLint 不支持这里的 extends 写法,应先检查其 API 和依赖兼容范围。
6. languageOptions:代码采用什么语言语义
常见子字段如下:
| 字段 | 作用 | 示例 |
|---|---|---|
ecmaVersion | 配置支持的 ECMAScript 语法版本及相关语言语义 | 'latest'、2022 |
sourceType | 区分模块、传统脚本和 CommonJS | 'module'、'script'、'commonjs' |
globals | 告诉检查器哪些标识符由环境提供,以及是否可写 | window: 'readonly' |
parser | 指定解析器对象 | tseslint.parser |
parserOptions | 向解析器传递选项 | JSX、TS 项目、Vue 内层解析器配置 |
ecmaVersion: 'latest' 不会把代码编译成兼容旧浏览器的版本,也不提供 polyfill。实际运行兼容性仍由编译链与目标环境决定。
languageOptions: {
sourceType: 'module',
globals: {
...globals.browser,
__APP_VERSION__: 'readonly',
},
}声明 __APP_VERSION__ 只代表它可被代码引用,不会真的创建这个变量;运行时仍要由构建工具或宿主注入。语言选项说明
7. plugins:注册插件命名空间
import examplePlugin from 'eslint-plugin-example';
// 示意代码:需要先安装真实存在且兼容的插件
const config = {
plugins: { example: examplePlugin },
rules: { 'example/some-rule': 'error' },
};example 是命名空间,example/some-rule 是规则名。Flat Config 注册的是导入的插件对象,而不是旧版插件名称字符串数组。
上面仅用于解释结构,不是可以直接安装运行的业务配置。实际规则名以插件文档为准。
8. rules:规则、等级和选项
rules: {
'no-debugger': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
eqeqeq: ['error', 'always'],
}| 等级 | 数字写法 | 含义 |
|---|---|---|
off | 0 | 不执行该规则 |
warn | 1 | 报告警告 |
error | 2 | 报告错误,通常使检查失败 |
数组第一个元素是等级,其余元素由具体规则定义。不能把一个规则的配置选项套到另一个规则上。
继承与覆盖还存在一个细节:只改规则等级时,可以保留已有选项。因此调试规则结果时,应查看最终配置,而不是只看最后写下的一行。规则配置说明
9. settings:传给规则的共享数据
settings: {
'example/project-kind': 'frontend',
}这个字段提供共享数据,但必须有规则或插件读取这些数据才会产生作用。随意写 settings.alias 不会自动让所有导入检查插件理解 Vite 或 TypeScript 的路径别名。
10. linterOptions:控制检查器行为
linterOptions: {
noInlineConfig: false,
reportUnusedDisableDirectives: 'error',
}noInlineConfig 控制源代码中的 ESLint 配置注释是否可以修改配置。
reportUnusedDisableDirectives 用于发现不再需要的 eslint-disable 注释,避免代码已经修好,关闭规则的注释却一直遗留。
11. processor:先处理非普通 JS 文件
某些插件会从 Markdown 等文件中提取代码块交给 ESLint,再映射回原文件位置。处理器既可能由插件预设配置,也可以显式设置。
不要为了处理 Vue 单文件组件就盲目填写 processor;常见 Vue 配置主要依赖 vue-eslint-parser,应遵循 Vue 插件文档。
12. language:选择插件提供的语言
一些插件支持 Markdown、JSON 等语言,可以使用 插件命名空间/语言名称 指定语言。它与 JavaScript 的 sourceType 不同,也不是通过填一个字符串就能让 ESLint 理解任意格式;必须安装并注册提供该语言的插件。
七、JavaScript 项目的完整配置
浏览器代码和 Node.js 工具脚本应分别设置全局变量:
// eslint.config.mjs
import js from '@eslint/js';
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
globalIgnores(['**/dist/**', '**/coverage/**']),
{
name: 'app/browser',
files: ['src/**/*.{js,mjs}'],
extends: [js.configs.recommended],
languageOptions: {
sourceType: 'module',
globals: globals.browser,
},
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
rules: {
eqeqeq: ['error', 'always'],
'prefer-const': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
},
{
name: 'app/node-esm',
files: ['scripts/**/*.{js,mjs}', '*.config.mjs', 'eslint.config.mjs'],
extends: [js.configs.recommended],
languageOptions: {
sourceType: 'module',
globals: globals.nodeBuiltin,
},
},
{
name: 'app/node-cjs',
files: ['scripts/**/*.cjs'],
extends: [js.configs.recommended],
languageOptions: {
sourceType: 'commonjs',
globals: globals.node,
},
},
]);不要为了消除报错给所有文件同时加入 browser 和 node 全局变量。这样可能让浏览器代码误用 process 等环境标识符而不被发现。需要构建时注入的值,应按实际机制单独配置。
八、TypeScript 基础检查
安装依赖:
pnpm add -D eslint @eslint/js typescript typescript-eslint globals基础配置:
// eslint.config.mjs
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
globalIgnores(['**/dist/**', '**/coverage/**']),
{
files: ['src/**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
],
languageOptions: {
globals: globals.browser,
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
]);typescript-eslint 的 recommended 预设会提供解析器及必要的规则配置,并处理部分与 TypeScript 不兼容或重复的核心规则。
这份配置支持 TS/TSX 语法检查,但没有开启类型感知,也没有自动加入 React 组件或 Hooks 规则。.tsx 能解析,与是否符合框架使用约定,是两件事。TypeScript 快速入门
核心规则与 TypeScript 扩展规则
例如下面两个名字属于不同规则:
no-unused-vars
@typescript-eslint/no-unused-varsTypeScript 版本理解类型语法和 TS 代码结构。手动开启扩展规则时,通常需要关闭对应核心规则,避免冲突或重复报告;使用推荐配置时,先检查预设已经做了哪些处理,不要重复拼装。
九、TypeScript 类型感知检查
基础语法检查不一定能判断某个函数的返回值是不是 Promise。要使用这类规则,需要为规则提供类型信息。
// eslint.config.mjs
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import { defineConfig, globalIgnores } from 'eslint/config';
const rootDir = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig([
globalIgnores(['**/dist/**', '**/coverage/**']),
{
files: ['src/**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommendedTypeChecked,
],
languageOptions: {
globals: globals.browser,
parserOptions: {
projectService: true,
tsconfigRootDir: rootDir,
},
},
rules: {
'@typescript-eslint/no-floating-promises': 'error',
},
},
]);| 字段 | 作用 |
|---|---|
recommendedTypeChecked | 启用推荐规则,并包含需要类型信息的检查 |
projectService | 使用 TypeScript 项目服务为文件提供类型信息 |
tsconfigRootDir | 指明 TS 项目相关路径处理的根目录 |
类型感知检查需要额外工作,通常比纯语法检查更慢。应确保被检查文件属于正确的 TypeScript 项目,并与编辑器采用的 TypeScript 配置一致。类型感知配置
如果出现“文件未被 project service 包含”的错误,不要直接关闭全部类型规则:先确认文件是否应该加入对应 tsconfig.json。对于构建脚本,可以单独使用不需要类型信息的配置;少量项目外文件才考虑有针对性的默认项目设置。排错说明
projectService 与 parserOptions.project 不需要同时开启;使用项目服务时,应移除不必要的 project 设置。解析器选项
十、Vue 3 + TypeScript 配置
Vue 单文件组件同时包含 template、script、style,不能简单把 .vue 文件当普通 TypeScript 文件交给 TS 解析器。
安装依赖:
pnpm add -D eslint @eslint/js typescript typescript-eslint \
eslint-plugin-vue vue-eslint-parser globals以下是不启用类型感知的基础配置:
// eslint.config.mjs
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import vue from 'eslint-plugin-vue';
import vueParser from 'vue-eslint-parser';
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
globalIgnores(['**/dist/**', '**/coverage/**']),
{
files: ['src/**/*.{ts,tsx,vue}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
],
languageOptions: {
globals: globals.browser,
},
},
...vue.configs['flat/recommended'],
{
files: ['src/**/*.vue'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
extraFileExtensions: ['.vue'],
},
},
},
]);关键是解析器有两层:
.vue 文件
→ vue-eslint-parser 理解组件结构和模板
→ typescript-eslint parser 理解 script 中的 TypeScript最外层 languageOptions.parser 不能被 TS 解析器覆盖,否则 Vue 模板可能解析失败。内层解析器配置在 languageOptions.parserOptions.parser。Vue 配置说明
开启 Vue 类型感知检查时,还要考虑项目服务、.vue 文件支持与框架配置的协作,不能简单把上一节所有参数复制进来。复杂项目可从框架官方提供的配置开始,再按项目调整。Vue TypeScript 配置项目
recommended 可能包含模板排版规则。若项目使用 Prettier,继续配置下一节的冲突处理。
十一、与 Prettier 配合
安装:
pnpm add -D prettier eslint-config-prettier在已有配置的末尾加入:
import eslintConfigPrettier from 'eslint-config-prettier/flat';
export default defineConfig([
// 项目规则、TS 或 Vue 预设……
eslintConfigPrettier,
]);eslint-config-prettier 关闭其已知的冲突规则,本身不负责格式化,也不会自动运行 Prettier。后面若再次开启冲突规则,仍然可能出现反复修改。
将格式检查单独执行:
pnpm exec prettier . --check
pnpm exec prettier . --writeeslint-plugin-prettier 则是另一种集成方式:把 Prettier 结果作为 ESLint 问题报告。并非必须同时安装两种方案;对大多数项目,单独执行 lint 和 format 更容易看清各自职责。配置说明
十二、接入 package.json 和 CI
在 package.json 中合并以下 scripts,不要覆盖项目其他字段:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"lint:ci": "eslint . --max-warnings 0",
"format": "prettier . --write",
"format:check": "prettier . --check",
"typecheck": "tsc --noEmit"
}
}Vue 项目的类型检查通常使用项目已配置的 vue-tsc 等工具,不能把 tsc --noEmit 示例当成所有框架的统一命令。
CI 中通常检查而不静默修复:
pnpm install --frozen-lockfile
pnpm lint:ci
pnpm format:check
pnpm typecheckwarn 默认不会像规则错误那样使检查失败;--max-warnings 0 可以把任何警告视为未通过,因此“只是 warning”不代表 CI 一定成功。
大项目可考虑 --cache,但缓存不是跳过完整验收的理由。--fix-dry-run 可用于不写入文件的修复预演,不过是否展示修复后的文本取决于输出格式,不能把它当成默认生成补丁的命令。CLI 文档
十三、编辑器保存时修复
以安装了 ESLint 扩展的 VS Code 为例,可以配置:
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
]
}这只是编辑器接入,不会替代项目规则文件。显式保存时尝试修复的具体行为,还受扩展版本、工作区信任、语言模式和配置匹配影响。
不要同时让多个格式化器处理同一文件,却不明确哪个负责哪类修改。命令行能运行、编辑器不工作时,应看 ESLint 扩展输出和实际工作目录。VS Code ESLint 扩展说明
十四、旧版 .eslintrc 如何对应
下面用于理解 .eslintrc 与 Flat Config 的对应关系,不需要同时维护两份配置。
| 旧式配置 | Flat Config 中通常的处理方式 |
|---|---|
.eslintrc.* | eslint.config.mjs 等 Flat Config 文件 |
env.browser / env.node | languageOptions.globals 中添加环境全局变量 |
顶层 parser 字符串 | languageOptions.parser 导入解析器对象 |
顶层 parserOptions | 放入 languageOptions.parserOptions,部分 JS 语言选项直接位于 languageOptions |
plugins: ['vue'] | 导入插件对象并注册命名空间,或使用插件预设 |
overrides | 拆分为多个带 files 的配置块 |
.eslintignore / ignorePatterns | 显式迁移到全局或局部 ignores |
root: true | Flat Config 不使用这个旧字段 |
extends: ['eslint:recommended'] | 导入 @eslint/js 后复用推荐配置 |
迁移时需要重新检查忽略路径,不能认为 .eslintignore 会被 Flat Config 自动沿用。.eslintrc 项目中的 temp.js 等模式,也应按 Flat Config 的匹配语义决定是否写成 **/temp.js。官方迁移指南
十五、怎样排查“配置没有生效”
优先针对一个确切文件排查:
pnpm exec eslint src/main.ts
pnpm exec eslint --print-config src/main.ts
pnpm exec eslint --debug src/main.ts
pnpm exec eslint --inspect-config--print-config 用于看一个文件最终合并后的配置,--debug 帮助追踪加载与执行,配置检查器适合查看配置匹配关系。检查器相关组件可能需要额外安装或下载,按实际工具提示处理。调试文档
| 症状 | 优先检查 |
|---|---|
.ts 或 .vue 不检查 | 是否存在对应 files、解析器与预设 |
window 或 document 未声明 | 浏览器文件是否配置 globals.browser |
| ESM 中出现 require 使用问题 | 是否混用了 ESM 与 CommonJS 配置 |
| 插件规则找不到 | 插件是否安装、注册名是否与规则前缀一致 |
| 未使用变量重复报错 | 是否同时启用了核心和 TS 扩展规则 |
| TS 类型规则缺少信息 | 是否启用正确项目服务,文件是否属于项目 |
| Vue template 解析失败 | 最外层解析器是否仍是 vue-eslint-parser |
| 某规则总被覆盖 | 匹配到哪些配置块,后面是否重写了该规则 |
| 构建产物仍被检查 | 是否错误地把局部 ignores 当成全局忽略 |
| 本地正常、CI 失败 | 版本、锁文件、执行目录、warning 门槛是否一致 |
十六、合理使用禁用注释
临时例外可以写明规则和原因:
// eslint-disable-next-line no-console -- 此命令行入口需要输出结果
console.log(result);尽量不要不带规则名地关闭整个文件所有检查。先判断是规则配置不适合这个目录、可以修正的代码问题,还是需要有理由保留的例外。
对工具脚本统一允许日志,可以通过 files 配置表达,比在几十个文件中重复 disable 更容易维护。
十七、团队落地时的配置顺序
对已有项目,可以先从官方 recommended 起步,明确 JS、TS、Vue 的文件范围,解决解析和环境问题,再逐步增加项目规则。
每增加一组规则,都应该知道它解决什么问题、误报如何处理、是否需要类型信息,以及在 CI 中采用什么门槛。不要一开始启用所有规则,再通过大量关闭规则让项目“变绿”。
配置变更应和依赖锁文件一起审阅;升级预设可能改变启用的规则,即使自己的 rules 没有变化,也要重新检查差异和执行结果。
ESLint 的配置最终应能回答四个问题:检查哪些文件、如何理解这些文件、执行哪些规则,以及在哪些环节阻止问题进入代码库。
评论 (0)