如果你正在接手一个前端项目打开编辑器突然看到满屏的红色波浪线或者提交代码时被 CI 上的 ESLint 报错拦下来那这篇配置流程总结应该能帮上忙。我在不同规模的 Vue、React、Node 项目里反复配置过 ESLint也在 Vite、uniapp 这些工程环境下折腾过规则冲突这里尽量把思路、步骤和踩过的坑一起写清楚。这篇内容适合刚接触 ESLint 的前端新人也适合想把老项目 lint 配置盘活、想弄明白 flat config、Prettier、TypeScript、Vitest 怎么配合的开发者。核心解决三个问题怎么装、怎么配、出问题怎么查。1. 先想明白ESLint 是做什么的配置前要理清哪些概念1.1 代码检查、代码格式化、类型检查不是一回事很多刚入门的同学会把 ESLint 和 Prettier 当成同一个东西甚至以为 TypeScript 报错就是 ESLint 报错。其实它们分工完全不同。ESLint 是代码静态检查工具核心职责是发现代码里的问题比如声明了变量但没用、函数太复杂、隐式的 any、不安全的类型断言、未处理的 Promise、禁用 console 但有人写了 console.log。这类问题靠人的 code review 成本太高ESLint 就像一名不休息的代码审查员在开发阶段和 CI 阶段持续跑。Prettier 是代码格式化工具只管风格统一行尾分号、单双引号、缩进宽度、换行位置、对象括号空格。Prettier 不考虑逻辑是否正确它只看排版是否一致。TypeScript 的类型检查又是另一层tsc 负责告诉你string | undefined可能不是 string但不会管你引号是单还是双。这三者在工程里要互相配合但不要越权。ESLint 里没有规则就不要让 ESLint 去管格式问题否则会跟 Prettier 打架。理解这条边界后续所有配置逻辑都会顺很多。1.2eslintrc与新版 Flat Config 怎么选ESLint 旧版配置核心是.eslintrc.js、.eslintrc.cjs、.eslintrc.json通过extends、plugins、rules这些字段组织配置。新版从 ESLint v9 开始默认使用 Flat Config配置文件叫eslint.config.js。Flat Config 最大的变化是去掉了extends那一套隐式继承逻辑改为导出一个数组数组里每个对象就是一套配置可以精确控制某个文件匹配哪些规则。早期配置要处理.eslintignore文件现在直接在eslint.config.js里用ignores字段解决。如果你是新项目直接用 Flat Config 就好如果是老项目还在用 v8正常工作也没必要强制迁移。但要注意 npm 上不少插件仍然兼容旧式配置混用时需要把文件命名成.cjs或者用defineConfig辅助转换我会在后面的工程实例里演示。1.3 配置前先明确项目边界拿到一个项目先回答这几个问题再动手写配置项目用 JavaScript 还是 TypeScript主要框架是 Vue、React 还是只是 Node 脚本是否需要支持 uniapp、小程序等特殊运行环境是否接入了 Prettier接入到什么程度有没有单测框架Vitest、Jest测试文件是否需要单独放宽规则这些问题直接决定 parser、plugins、overrides 的写法。我见过很多人不区分项目类型直接抄一份前端通用配置结果在 uniapp 里被uni全局变量报错在 React 项目里被 Vue 的规则干扰。好的配置一定是从项目实际场景倒推出来的不是越全越好。2. 从零到一ESLint 安装初始化与最小可用配置2.1 环境准备与初始化命令先确保你的 Node.js 版本和 ESLint 匹配。ESLint v9 需要 Node.js^18.18.0或^20.9.0以上版本。如果项目还在用 Node 16建议要么升级 Node要么固定用 ESLint v8。在项目根目录执行npm install eslint --save-dev注意这里必须是--save-devESLint 只在开发阶段使用不应该出现在生产依赖里。装完之后可以看版本npx eslint --version然后执行初始化npm init eslint/config初始化程序会问你几个问题模块类型、框架、是否用 TypeScript、代码运行环境浏览器还是 Node、配置格式JavaScript / YAML / JSON。根据项目实际情况选择即可。它会在项目里生成一个eslint.config.js开发依赖里自动装上eslint/js、globals等包。如果你是老项目还在用 v8也可以执行npx eslint --init生成的是.eslintrc.js。两种格式不冲突但同一个项目里不要同时出现两种配置文件否则会混乱。2.2 理解核心配置字段parser、plugins、extends、rules以常见的 Flat Config 为例一份极简配置长这样import js from eslint/js; import globals from globals; export default [ js.configs.recommended, { files: [**/*.{js,mjs,cjs}], languageOptions: { ecmaVersion: latest, sourceType: module, globals: { ...globals.browser, }, }, rules: { no-unused-vars: warn, no-console: off, }, }, { ignores: [dist/, node_modules/], }, ];逐个解释js.configs.recommended是 ESLint 官方推荐规则集相当于把基础最佳实践一次引入。files指定这个配置块作用于哪些文件。languageOptions.ecmaVersion告诉解析器按哪个 ECMAScript 版本来解析。languageOptions.globals声明全局变量比如浏览器环境里window、documentNode 环境里process、__dirname。没有声明ESLint 会报window is not defined。rules是规则配置值可以是off、warn、error。ignores等效于老版的.eslintignore把构建产物、依赖目录排除掉。旧式.eslintrc.js的核心字段也类似parser决定语法解析器、parserOptions提供解析配置、extends引入共享配置、plugins加载插件、rules微调规则、env声明运行环境。不管哪种格式本质都是两件事让 ESLint 能正确理解代码然后决定用哪些规则去检查。2.3 规则配置的三个级别off、warn、errorESLint 每条规则都可以设成三个值off0完全关闭该规则不检查。warn1发现问题只给黄色警告不阻塞进程。error2发现问题直接报错命令行执行时进程退出码非 0CI 会拦截。我建议团队内部这样约定明显 bug 类规则用error比如eqeqeq强制使用全等、no-implied-eval风格建议类用warn比如no-console、prefer-const如果某个规则在团队内争议大宁可先off再讨论不要让 warning 刷屏到没人看。也可以针对某个文件动态调整{ files: [src/**/*.test.js], rules: { no-unused-vars: off, // 测试文件经常用全局 describe/it }, }这个能力在后面的 Vitest 配置里会用到。3. 进阶级让 ESLint 和 Prettier、TypeScript、Vue 协同工作3.1 ESLint 与 Prettier 的分工与配合很多人配置完 ESLint 和 Prettier 后发现两者冲突最典型的就是no-mixed-spaces-and-tabs、quotes、semi这类风格规则Prettier 觉得应该没有分号ESLint 旧规则要求必须有分号两个工具反复打架。正确处理方式是什么格式化交给 PrettierESLint 只做代码质量检查。两条配合策略第一安装eslint-config-prettier它会把 ESLint 里所有跟格式化相关的规则全部关掉npm install eslint-config-prettier --save-devFlat Config 模式下引入import prettierConfig from eslint-config-prettier; export default [ js.configs.recommended, prettierConfig, { // 实际规则 }, ];注意prettierConfig要放在数组靠后的位置这样才能覆盖前面的格式化规则。第二直接在项目里用 Prettier 统一格式安装 prettier 后配置.prettierrc{ semi: false, singleQuote: true, tabWidth: 2, trailingComma: es5 }然后让编辑器在保存时先跑 Prettier再跑 ESLint这样基本不会再出现格式层面的冲突。3.2 接入 TypeScript 的配置要点如果项目用 TypeScript继续让 ESLint 用默认解析器解析.ts文件会直接报错。ESLint 默认的 Espree 解析器不识别类型语法需要换成typescript-eslint/parser同时加typescript-eslint插件拿到 TS 专属规则。npm install typescript-eslint/parser typescript-eslint/eslint-plugin typescript --save-devFlat Config 下示例import tseslint from typescript-eslint/eslint-plugin; import tsParser from typescript-eslint/parser; export default [ js.configs.recommended, { files: [**/*.{ts,tsx}], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: latest, sourceType: module, }, }, plugins: { typescript-eslint: tseslint, }, rules: { ...tseslint.configs.recommended.rules, typescript-eslint/no-explicit-any: warn, }, }, ];这里有一个很多人踩过的坑parserOptions.project是让 ESLint 利用 TypeScript 类型信息做规则检查的开关比如no-floating-promises、await-thenable这类规则需要它。但开启后 ESLint 会把整个 TS 项目加载一遍大项目lint速度会明显变慢。建议只在部分规则需要时开启并且要设置tsconfigRootDir。普通的recommended规则集不需要类型信息没必要为了更严格把所有项目都设成 type-aware。3.3 Vue 与 JSX 场景的特殊处理Vue 项目的.vue文件包含 template、script、style 三个块ESLint 需要eslint-plugin-vue来解析 template 部分。对应的 parser 建议用vue-eslint-parser它会先解析整个 SFC再把 script 部分交给底层解析器处理。npm install eslint-plugin-vue vue-eslint-parser --save-devFlat Config 示例import pluginVue from eslint-plugin-vue; import vueParser from vue-eslint-parser; import tsParser from typescript-eslint/parser; export default [ js.configs.recommended, ...pluginVue.configs[flat/recommended], { files: [**/*.vue], languageOptions: { parser: vueParser, parserOptions: { parser: tsParser, // script 块用 ts 解析器 ecmaVersion: latest, sourceType: module, }, }, rules: { vue/multi-word-component-names: off, }, }, ];React 项目则用eslint-plugin-react和eslint-plugin-react-hooks后者专门检查 hooks 调用顺序这类反模式。JSX 语法需要用babel/eslint-parser或者typescript-eslint/parser配合ecmaFeatures.jsx配置。记住一条原则用什么框架就装对应插件不要所有项目套同一套eslint-config-airbnb否则会在 Vue 项目里被 JSX 规则搞得满头包。4. 工程化实战场对接 Vite、Vue Router、Pinia 与 Vitest4.1 用 create-vue 或 create-vite 生成的工程配置现在新建 Vue 项目我推荐直接在脚手架阶段把 ESLint 和 Prettier 集成进去。比如用npm create vuelatest交互式选项里它会询问你是否需要 ESLint、Prettier、Pinia、Vue Router、Vitest。全部勾选后工具会自动生成一份完整的 lint 环境包括 Vite 插件vite-plugin-eslint新版可能不再默认使用和配套的 Vue 规则。如果是手动搭建 Vite Vue配置思路是让 Vite 只负责构建ESLint 独立运行或者通过vite-plugin-checker在 Dev Server 里做类型和 lint 检查。vite-plugin-checker的好处是避免 Dev Server 频繁刷新时 lint 拖慢速度它能以单独 worker 跑类型检查体验好很多npm install vite-plugin-checker --save-devvite.config.ts 里import checker from vite-plugin-checker; export default { plugins: [ checker({ eslint: { lintCommand: eslint src/**/*.{ts,vue}, }, typescript: true, }), ], };注意这个插件的 ESLint 检查是异步的并不会阻断 Vite 启动所以更适合开发阶段提示真正拦截还要靠 CI。4.2 为 Vue Router 与 Pinia 补充的推荐规则Vue Router 和 Pinia 本身不一定要新增很多 ESLint 规则但它们的使用模式会触发一些常见规则点。拿 Vue Router 来说路由懒加载经常这样写const routes [ { path: /user, component: () import(/views/UserView.vue), }, ];ESLint 里如果开了import/no-unresolved这个/views/...别名路径就会被误报。解决办法是在 ESLint 里配置settings或使用import-resolver-alias。以 Flat Config 为例import path from node:path; import { fileURLToPath } from node:url; const __dirname path.dirname(fileURLToPath(import.meta.url)); export default [ ... { settings: { import/resolver: { alias: { map: [ [, path.resolve(__dirname, src)], ], extensions: [.js, .ts, .vue, .json], }, }, }, }, ];不配置别名ESLint 就只能靠默认相对路径去推断/components/Button.vue基本必然报错。Pinia 的 store 文件一般遵循 定义一个 setup store 或者 option store导出useXxxStore 的写法。常见 lint 问题是useStore这个命名不是组件但在组件里调用时Vue 的一些运行时规则不会有限制。ESLint 这边主要注意vue/script-setup-uses-vars、vue/no-unused-refs这类检查避免 script setup 里定义了 store 但没用还标红。4.3 Vitest 测试文件的特殊配置Vitest 的环境下有describe、it、expect、vi这些全局变量ESLint 默认不认识它们会报describe is not defined。有三种解决方式第一在测试文件顶部加三斜线引用但每个文件都写很烦。第二在 ESLint 配置里为测试文件单独声明 globalsexport default [ ... { files: [src/**/*.{test,spec}.{js,ts}], languageOptions: { globals: { describe: readonly, it: readonly, expect: readonly, vi: readonly, beforeEach: readonly, afterEach: readonly, }, }, }, ];第三最省事的是引入eslint-plugin-vitestnpm install eslint-plugin-vitest --save-dev然后import vitest from eslint-plugin-vitest; export default [ ... { files: [src/**/*.{test,spec}.{js,ts}], plugins: { vitest }, rules: { ...vitest.configs.recommended.rules, vitest/expect-expect: off, }, }, ];实测下来第三种最方便推荐优先用vitest.configs.recommended。同时建议测试文件里放宽typescript-eslint/no-explicit-any这类规则因为测试数据经常故意用 any。4.4 在 VSCode 里把 ESLint 和格式化流程接好编辑器配置直接影响开发体验。VS Code 需要先安装 ESLint 扩展和 Prettier 扩展。然后项目根目录创建.vscode/settings.json推荐配置{ editor.codeActionsOnSave: { source.fixAll.eslint: explicit }, editor.defaultFormatter: esbenp.prettier-vscode, editor.formatOnSave: true, eslint.validate: [ javascript, javascriptreact, typescript, typescriptreact, vue ], eslint.format.enable: true, prettier.singleQuote: true, prettier.semi: false }这里有个容易踩的坑editor.codeActionsOnSave和editor.formatOnSave同时开启时保存动作的执行顺序不固定。建议只保留一个做 ESLint fix另一个专注 Prettier。我一般的习惯是formatOnSave开 Prettiersource.fixAll.eslint同时开启因为 ESLint fix 可以自动修掉可修复的规则问题Prettier 只负责排版两者顺序影响不大真正有问题的是某些规则和格式化器修改同一块代码。如果保存后 ESLint 不生效先确认 ESLint 扩展有没有激活再看 VSCode 右下角输出面板里 ESLint 服务是否报错。还有一个非常隐蔽的坑VSCode 里 ESLint 默认不会处理.vue文件所以eslint.validate里必须加vue。5. 常见问题速查我踩过的坑和排查方法5.1 配置了规则却完全不生效优先级最高的排查思路是确认 ESLint 到底加载了哪个配置文件。执行npx eslint --print-config src/App.vue这个命令会打印出针对该文件的最终生效配置能看到所有 rules、parser、plugins。如果发现某条规则不在里面就是配置合并出了问题。还有一种情况是配置文件命名不匹配。Flat Config 只认eslint.config.js如果你项目里还残留了.eslintrc.jsESLint v9 默认不会读它。反之你改的是.eslintrc.js但 ESLint 版本是 v9也会抱怨找不到配置。5.2 编辑器标红但命令行不报错或者反过来这种情况八成是编辑器里的 ESLint 扩展和项目本地 ESLint 版本不一致。VSCode 扩展会优先使用项目本地 node_modules 里的 eslint但如果项目没有安装本地 eslint它就回退到扩展自带的版本而扩展自带的版本往往很老解析不认识新语法。检查方法在 VSCode 命令面板执行ESLint: Show Output Channel看是不是加载了全局或者内置的 eslint。解决办法是把 eslint 作为开发依赖装在项目里必要时重启扩展。反过来命令行报错但编辑器不标红大概率是编辑器没有识别当前文件类型。比如.ts文件没在eslint.validate里或者.vue文件没有加vue解析器。5.3 和 Prettier 冲突的典型误区有一次我在一个老项目里装完 Prettier保存文件后所有单引号都变成双引号ESLint 又疯狂报错。查了一圈发现是 ESLint 配置里的quotes规则还在跑而 eslint-config-prettier 被放错了位置。Flat Config 的数组顺序很重要后面的对象会覆盖前面的同名配置。如果你把prettierConfig放在数组最前面后面的规则会把它的关闭覆盖掉等于没装。正确做法是把 eslint-config-prettier 放在最后面。还有一个误区是同时安装了eslint-plugin-prettier和eslint-config-prettier却不知道它们的作用。前者让 ESLint 以规则的形式跑 Prettier后者只负责关闭冲突规则。个人不建议用 eslint-plugin-prettier因为它会让 ESLint 变得很慢而且格式错误和代码错误混在一起体验不干净。格式化这一步交给 Prettier 本身更合理。5.4 uniapp 等特殊环境的注意点uniapp 项目本质上还是 Vue但运行环境比较特殊uni是一个全局对象getApp、getCurrentPages这些 API 不需要 import。如果你的 ESLint 配置里有no-undef这类规则运行时会疯狂报错。解法是在 ESLint 配置里把 uni 相关 API 声明为全局变量languageOptions: { globals: { uni: readonly, getApp: readonly, getCurrentPages: readonly, wx: readonly, HBuilderX: readonly, }, }另外 uniapp 项目经常用条件编译注释比如// #ifdef MP-WEIXIN。ESLint 不会解析这种注释但配置了no-warning-comments或者某些注释相关规则时可能有奇怪误报建议把no-warning-comments关掉。还有 uniapp 的 template 里有些自定义标签vue/html-self-closing这种 Vue 风格规则可能误伤规则值调成[error, { html: { void: never } }]或者直接off。5.5 大项目 lint 越来越慢的排查方向项目达到一定规模后ESLint 跑一次要十几秒甚至几十秒主要瓶颈有这些配置文件被反复加载旧的 eslintrc 里多个 extends 会层层解析Flat Config 改成数组结构后加载更快但插件实例仍然可能重复创建。没有正确设置ignoresESLint 扫描了 node_modules、dist、.git 这些目录。虽然 ESLint 默认忽略 node_modules但如果你用--ext扫描整个目录还是会被文件列表拖慢。过早启用 type-aware 规则导致 ESLint 等 tsc 生成类型信息慢上加慢。Git 钩子里 lint 全部文件而不是只 lint 暂存区文件。配合 lint-staged 会快很多。npm install lint-staged --save-devpackage.json 里配置{ lint-staged: { *.{js,ts,vue}: eslint --fix, *.{json,md,ts,js,vue,html,css}: prettier --write } }这样每次提交只检查改动的文件速度能快一个数量级。6. 从配置到团队规范配置写出来只是开始让规则真正走进团队日常工作才是重点。根据我的实测经验团队落地 ESLint 最容易失败的场景是一个人把所有规则调到最严然后丢给所有人。结果大家每天被一堆非关键的 warning 轰炸渐渐对 lint 产生疲劳最后连 error 级别的问题也忽略了。我比较推荐的做法是分阶段铺开第一阶段先跑官方 recommended让代码里明显的坏味道暴露出来第二阶段把与 Prettier 的格式冲突全部关掉保证保存即格式化第三阶段再根据团队业务特点加入角度更细的规则比如 Vue 团队加vue/multi-word-component-names、Node 团队加no-process-exit、测试团队加vitest/no-focused-tests。每加一批规则都要在周会或群里说明原因不要做无声变更。还有一个小建议把eslint --fix和 Prettier 一起放进 Git hooks而不是把压力全部放在开发者自觉上。我见过太多本地能跑提交后被 CI 拦截的情况原因就是有人没在本地跑 lint。用husky配lint-staged是最低成本方案基本能保证进入仓库的代码都是干净的。如果你正在迁移到 ESLint v9 的 Flat Config记得把旧项目里分散的.eslintrc.*和.eslintignore清理干净否则很容易出现我改了配置但好像没有生效的灵异事件。遇到看不懂的默认配置多跑npx eslint --print-config比翻文档更快。