Vue 3 + TypeScript项目ESLint与Prettier完整配置指南
前阵子接手一个Vue 3 TypeScript项目的代码审查功能全是好的就是代码风格没法看有人用单引号有人用双引号缩进从上到下能跳出三四种花样组件名有的PascalCase有的kebab-case还有几处没删干净的console.log。整个项目看起来像一个多人各写各的拼盘。要解决这种混乱靠的不是某一个人的自律而是给项目装上ESLint Prettier这套代码格式校验管线。这篇文章就把我自己的完整配置方案摊开讲从脚手架怎么选、配置文件每一行是什么意思、Prettier和ESLint怎么分工到编辑器保存、提交钩子、CI检查怎么一气呵成。不管你是刚接触Vue 3的新手还是被历史代码格式问题折磨的老开发照着这份方案走都能在半天内把项目从“能跑”收敛到“整齐”。1. 这套组合分工在哪Lint管“错”Prettier管“丑”1.1 为什么两个工具缺一不可很多刚开始配的同学会问ESLint不是也能管格式吗为什么还要单独装Prettier这个问题确实问到点子上了。ESLint的定位是“代码质量检查器”它发现的是逻辑层面和潜在错误层面的问题声明了没用的变量、隐式any、在Vue模板里用了没有注册的组件、switch分支漏掉default等等。它确实也带了一部分风格规则比如indent、quotes、semi但ESLint自己的风格规则设计初衷是给团队“够用就行”真正做排版时它的能力远不如Prettier细致。Prettier的定位则完全不同它是一门“代码格式化器”不关心你的代码逻辑对不对只关心排版长什么样。引号是单还是双、行宽到多少换行、运算符两边要不要空格、对象最后一项加不加逗号这些都用一套高度一致的算法处理而且基本没有复杂的分支配置。把两者放一起分工就清晰了ESLint负责告诉你“这里有BUG风险”“那里写法不规范”Prettier负责在保存或提交时把整个文件的外观统一刷一遍。打个比方ESLint像审稿编辑关注内容有没有事实错误Prettier像排版工人关注字间距、标点、行距是否统一。只请排版工人错别字没人抓只请审稿编辑版式五花八门。Vue 3 TypeScript这种文件结构天生复杂的项目里两者缺一个都会出问题。1.2 Vue 3 TypeScript让复杂度上了一个台阶如果是纯JavaScript项目装上ESLint选个社区风格就基本完事了。但引入TypeScript后解析器就变了TS文件用的是专门的TypeScript解析器它才能读懂类型标注、接口、泛型。引入Vue后更麻烦.vue文件本身不是标准JS它是单文件组件SFC内部有template、script、style三大块ESLint默认的解析器压根没法直接解析。所以就有了“解析器套娃”外层用vue-eslint-parser去拆解.vue文件的三个块script块内部再交给TypeScript解析器去理解TS语法。这也是为什么Vue 3 TS的ESLint配置经常让人一头雾水——你会看到extends一长串parserOptions里还有一个parser字段。搞清楚这个套娃结构后面读任何人的配置都不会懵。这一层复杂度不搞清楚最常见的后果是新建的.vue文件在编辑器里满屏红色波浪线不是代码有错而是配置里根本没人告诉ESLint该怎么读这个文件。这类问题排查起来最磨人因为它不像语法错误那样有明确提示纯粹是配置层面的“语言不通”。2. 最快的启动方式用官方脚手架生成一份标准配置2.1 create-vue交互项里怎么选如果你是新建项目我强烈建议直接用官方脚手架起步不要一上来就手动装依赖。Vue团队维护的create-vue在交互式提问里已经包含了ESLint、Prettier的支持它生成的配置虽然不一定百分之百符合每个团队的审美但结构是目前社区公认最干净的范本。创建项目的命令很简单npm create vuelatest按提示填完项目名后会进入一系列选择。和代码校验相关的几项建议这样选TypeScript选是JSX按实际需要Vue Router / Pinia按业务需要ESLint选是核心项Prettier选是核心项单元测试 / E2E测试按业务需要。如果项目已经建好了也没关系往下看第3章手动配的方式本质就是把脚手架生成的东西自己复刻一遍。理解生成物的来龙去脉比单纯点Yes更有价值因为你迟早要往里面加自己团队的规则。2.2 生成出来的配置文件到底是什么意思选完ESLint和Prettier后项目里会多出一串文件eslint.config.js、.prettierrc.json、.editorconfig还有.vscode目录下的extensions.json和settings.json。这几个文件各司其职我逐个说下它们到底是干嘛的。eslint.config.js是核心。新版本脚手架生成的内容依赖三个东西vue/eslint-config-typescript封装了TypeScript的解析与推荐规则eslint-plugin-vue提供所有Vue专属规则比如组件命名、模板里v-if/else配对vue/eslint-config-prettier本质上就是eslint-config-prettier在Vue生态里的封装负责关掉所有和Prettier冲突的格式规则。配置文件里会调用pluginVue.configs[flat/essential]和vueTsConfigs.recommended这两套预设它们分别是Vue侧和TS侧的规则套餐。用了这套配置之后新增的.vue文件会被正确解析模板里的书写规范和TS文件的类型规范都能被检查到。.prettierrc.json则接管外观风格。脚手架默认生成的是一个基本空壳通常只有几个字段但建议你打开它把singleQuote、semi、printWidth这些团队共识写清楚不然后面第一次格式化时会突然刷出几百个diff。这块我在第4章详细展开。.editorconfig大家容易忽略它管的是换行符和缩进宽度这类“编辑器行为”。它和Prettier配合能让不同系统的同事打开文件时基础行为一致。脚手架生成的默认内容一般够用不建议删。.vscode下的两个文件也很关键extensions.json会在团队成员打开项目时推荐安装ESLint和Prettier扩展settings.json则把“保存时自动修复”这个关键体验提前配置好了。如果你的编辑器用的是VS Code装好推荐的扩展后新项目基本开箱即用。3. 手动配置的真正细节parser、extends、rules如何协作3.1 新版flat config的eslint.config.js逐行讲解对于已经跑起来的项目手动配一次能帮你彻底搞懂前面说的“套娃结构”。ESLint 9已经全面转向flat config也就是eslint.config.js这种单一导出数组的结构。我基于自己项目里比较顺手的一份配置逐段讲它为什么这么写。import { globalIgnores } from eslint/config import { defineConfigWithVueTs, vueTsConfigs } from vue/eslint-config-typescript import pluginVue from eslint-plugin-vue import skipFormatting from vue/eslint-config-prettier/skip-formatting export default defineConfigWithVueTs( pluginVue.configs[flat/essential], vueTsConfigs.recommended, globalIgnores([**/dist/**, **/coverage/**, **/node_modules/**]), { name: app/additional-rules, rules: { vue/multi-word-component-names: off, typescript-eslint/no-explicit-any: warn, vue/no-v-html: off, vue/valid-define-props: error, }, }, skipFormatting, )先看defineConfigWithVueTs。它来自vue/eslint-config-typescript作用是把TypeScript规则配置正确关联到.vue文件的script块上。如果不用它你手写数组时很容易漏掉.vue和TS文件的关联导致parser接不上这正好对应1.2节说的“解析器套娃”。pluginVue.configs[flat/essential]是eslint-plugin-vue提供的flat配置。essential档位覆盖的是必查的可靠性规则比如模板语法错误、事件命名错误。更严的类型还有strongly-recommended和recommended团队想更激进可以换成recommended档。vueTsConfigs.recommended对应TS侧推荐规则比如typescript-eslint/no-explicit-any默认是warn未使用变量会报error。注意推荐档并不包括所有类型检查规则它更偏向“不拖累构建速度”的那部分。globalIgnores很简单告诉ESLint哪些目录不要管。dist、coverage这类构建产物没必要扫描写了反而拖慢lint速度。接着是自定义rules对象。放在这个位置的对象优先级会覆盖前面extends进来的预设规则。比如多数团队会把vue/multi-word-component-names关掉因为Vue官方要求组件名至少两个单词但业务里难免有Index.vue、Home.vue这种单文件组件vue/no-v-html也常被关掉因为做内容管理系统时渲染后端HTML文本是刚需。最后一项skipFormatting是vue/eslint-config-prettier导出物。它放在数组末尾的意义是用它把前面所有可能和Prettier冲突的格式类规则一次性关掉。顺序很关键——必须放在预设之后否则前面规则又会把冲突项加回来。3.2 老项目还在eslintrc时该怎么办如果你的项目锁在ESLint 8或者历史包袱没法切flat config也不是不能用。老式.eslintrc.js的写法核心逻辑一样只是形态更偏向对象module.exports { root: true, env: { node: true, browser: true, es2021: true, vue/setup-compiler-macros: true, }, extends: [ plugin:vue/vue3-recommended, eslint:recommended, vue/typescript/recommended, vue/prettier, ], parserOptions: { ecmaVersion: latest, sourceType: module, }, }这里配置里出现了env字段vue/setup-compiler-macros: true这是老配置里专门为script setup宏准备的否则defineProps、defineEmits会被当成普通函数调用报“未定义”。新版flat config因为vue/eslint-config-typescript内部已经处理了这个点就不需要你手动开。eslintrc写法方便的地方是层层extends的优先级很直觉后面的 extends 覆盖前面的你自己的 rules 字段直接写在对象里。不方便的地方是它有很多隐藏的注册名比如vue/prettier实际上是某个包在extends里的注册名新手想搞清楚到底启用了什么规则得逐个包翻文档。flat config的哲学是把所有东西“显式化”每个配置项就是一个对象你自己决定放哪些规则、以什么顺序应用没有隐式的extends。这也是我推荐新项目直接上flat config的根本原因排错时能找到头。如果你升级到ESLint 9但目录里还留着.eslintrc.jsESLint会直接提示找不到eslint.config.js所以老项目迁移时要把旧文件清掉。3.3 规则优先级与自定义rules的放置位置flat config里的规则优先级遵循一个简单原则后出现的对象覆盖先出现的。这也是为什么prettier兼容层要放在数组最后——它要把前面的格式规则全部关掉必须最后执行。那自定义rules放在哪我的习惯是放在预设后面、prettier兼容层前面。原因很简单自定义rules里我只会写语义类规则比如某个Vue规则关掉、TS的any警告降级这些不会被prettier兼容层影响。但如果有人把quotes、semi这类格式规则写进自定义rules又放在skipFormatting后面就会把prettier刚关掉的冲突项重新打开格式校验直接失效。一句话总结放置逻辑预设放前面语义类自定义规则放中间prettier兼容层压轴格式规则永不手写交给Prettier和eslint-config-prettier处理。4. Prettier与ESLint的边界处理两个长得像的包怎么选4.1 eslint-config-prettier和eslint-plugin-prettier完全不同在处理好Lint之后另一个最常见的困惑是网上配Prettier时有的文章让你装eslint-config-prettier有的让你装eslint-plugin-prettier还有的让你两个都装。这两个包名字就差一个词作用天差地别。eslint-config-prettier做的事是“关闭ESLint里所有和Prettier重叠的格式规则”。它的价值是让两个工具各管各的ESLint只保留逻辑检查排版全交给Prettier。这是一个配置层级的包你只需要把它放进extends或flat数组的末尾它不执行任何格式化逻辑。eslint-plugin-prettier做的事是把Prettier作为一条ESLint规则跑起来也就是说你执行eslint --fix的时候它会在内部调用Prettier来格式化。看起来方便一条命令全搞定但实际体验有两处问题一是每次lint都要先跑一遍Prettier文件多了之后明显慢二是报错信息比较啰嗦ESLint的error列表里会混入大量prettier/prettier格式错误。把它当代码质量错误看时间久了容易产生“反正都是样式错误无视好了”的麻木心态。我的观点很明确新项目一律用eslint-config-prettier不装eslint-plugin-prettier。格式的事交给Prettier自己IDE里让Prettier扩展负责保存时排版命令行里用prettier --write负责批量排版提交钩子里让lint-staged调用Prettier。ESLint只回答一个问题代码逻辑和规范有没有问题。4.2 一份实用的.prettierrc和.editorconfig配合Prettier本身的配置项不多但恰恰是这些选项决定了团队代码的统一审美。下面是我比较常用的一份复制过去基本不用改{ $schema: https://json.schemastore.org/prettierrc, semi: false, singleQuote: true, printWidth: 100, tabWidth: 2, trailingComma: all, vueIndentScriptAndStyle: true, endOfLine: auto }逐项说下为什么这么定。semi设false是不加分号。这是JavaScript社区非常主流的风格配合ASI机制跑得好好的模板字符串、函数声明里几乎不会碰到陷阱。当然如果团队写惯了分号也可以保留true重点是全员完全一致。singleQuote设true是因为单引号在视觉上更轻键盘上不用按Shift。很多团队默认模板是双引号但我个人建议新建项目就用单引号老项目则看git历史里大多数情况不要强行翻案。printWidth设100是因为Vue SFC里template行本身就长80会让模板属性换行非常频繁反而更不易读。100是个比较平衡的值。vueIndentScriptAndStyle是Vue专属坑很多人在没设这个选项时发现.vue文件里script和style标签内部的内容格式化后贴着最左对齐而不是在标签内缩进。设成true之后script和style里面的代码会整体保持两级缩进这在视觉上跟template结构更协调。endOfLine这里写auto是为了避免Windows和macOS/Linux协作时换行符全被改掉。如果想让团队强制统一LF也要在.editorconfig里同步配置别只改Prettier这边。.editorconfig是另一个容易被忽视的环节。一个典型的配置长这样root true [*] charset utf-8 indent_style space indent_size 2 end_of_line lf insert_final_newline true trim_trailing_whitespace true [*.md] trim_trailing_whitespace false它能保证无论同事用什么系统、什么编辑器基础缩进和换行行为从源头一致。注意Prettier和editorconfig偶尔会打架比如editorconfig要求indent_size4而Prettier里tabWidth2就会导致保存时文件在两个规范之间反复横跳。让两者的缩进设置保持一致是避免“格式化打架”的第一步。5. 把校验接到日常流编辑器保存、提交钩子、CI检查5.1 VS Code里保存就格式化且不打架的设置配置写好了剩下的关键是把“检查和格式化”嵌进日常开发动作里否则靠人手动跑命令很快就会被遗忘。VS Code是Vue生态里用得最多的编辑器这套设置我建议每个团队都在项目里固化下来。首先项目根目录.vscode/extensions.json要声明两个推荐扩展{ recommendations: [ dbaeumer.vscode-eslint, esbenp.prettier-vscode ] }然后.vscode/settings.json里做三件事保存时ESLint自动修复、Prettier担当默认格式化器、针对vue文件单独锁定。{ editor.codeActionsOnSave: { source.fixAll.eslint: explicit }, editor.defaultFormatter: esbenp.prettier-vscode, editor.formatOnSave: true, [vue]: { editor.defaultFormatter: esbenp.prettier-vscode } }有几个细节必须注意。第一个codeActionsOnSave里source.fixAll.eslint的取值新版VS Code要求写成explicit而不是true写true虽然旧版本兼容但新版本会提示已废弃。第二个因为Prettier已经作为默认格式化器工作这里不要再装eslint-plugin-prettier避免保存时ESLint和Prettier两个动作抢文件形成改过来改过去的死循环。第三个如果是老项目跑eslintrccodeActionsOnSave的配置同样适用但defaultFormatter这一项一定要检查是否有多个扩展抢占vue文件的格式化权限Volar和Prettier都声明支持vue时会弹窗让选选Prettier就行。这套设置的核心思路是ESLint负责修“错”Prettier负责排“版”两者在同一保存动作里各干各的互不干涉。只要不引入插件层面的重叠打架概率基本为零。5.2 npm scripts 与 lint-staged 的配合编辑器体验解决后还要保证命令行和提交环节也能跑。package.json scripts里我建议至少放四个命令{ scripts: { lint: eslint . --fix, lint:check: eslint ., format: prettier --write \src/**/*.{js,ts,vue,json,css,scss,md}\, format:check: prettier --check \src/**/*.{js,ts,vue,json,css,scss,md}\ } }lint用于本地顺手修复format用于全量排版。lint:check和format:check是给CI用的它们不做修改只报告差异保证流水线是可重复的。提交环节最有用的组合是husky加lint-staged。husky负责管理git hookslint-staged负责只处理暂存区里的文件避免把整个项目几万个文件都格式一遍。安装和初始化命令如下npm install -D husky lint-staged npx husky inithusky init之后会在.husky目录生成pre-commit文件默认内容是npm test改成npx lint-staged然后package.json里加lint-staged配置{ lint-staged: { *.{js,ts,tsx,vue}: [ eslint --fix, prettier --write ], *.{json,css,scss,html,md}: [ prettier --write ] } }这个配置的逻辑是JS/TS/Vue类型的文件先让ESLint检查修复再让Prettier排一下版其余纯样式、文档类文件直接交给Prettier统一格式。lint-staged会在每个命令执行成功后自动把修改过的文件重新git add所以提交时暂存的内容永远是格式化后的版本。5.3 提交前检查在CI的落地本地有个痛点同事可能漏装husky或者改到一半直接提交。所以真正兜底的还是CI。脚本之前已经分好了这里只需要在流水线里加两步跑lint:check再跑format:check。建议把这两步放在单元测试之前因为格式错误应该最先被发现成本最低。另外一点CI里千万不要用lint和format这两个带修改的命令。大多数CI环境的git checkout是只读工作区--fix或--write如果想改文件要么报权限错误要么让工作区变脏最终导致后续步骤拿到被改动过的源码。固定用check版本把差异作为失败原因反馈是最稳的。6. 实战中的坑与我的处理方式从缩进到宏命令6.1 解析器报错与script setup宏命令我在接手一些历史项目时最常见到的一类问题是ESLint配置里只配了TypeScript解析器没配vue-eslint-parser。结果打开.vue文件顶上一行红色波浪线“Parsing error: Unexpected token”——ESLint根本读不懂template标签。解决办法就一句话.vue文件的外层解析器必须是vue-eslint-parser。flat config下你不需要手动写parser因为pluginVue.configs[flat/essential]已经内置eslintrc下你也不需要手动写因为vue/typescript/recommended已经内置。容易出问题的恰恰是那些从旧帖子复制配置、自己东拼西凑的人。第二个常见问题是eslintrc环境下的script setup宏。老配置里经常看到defineProps、defineEmits下面一条“DefineProps is not defined”的红线。要处理它需要在env里补上vue/setup-compiler-macros: true或者干脆升级到新版create-vue生成的flat配置新方案内部已经处理宏了。如果你在flat config里还遇到类似报错多半是vue/eslint-config-typescript版本太旧升级到最新版就行。6.2 缩进、引号、换行符引发的大规模diff配好Prettier后第一次跑format项目里可能出现成百上千行diff。这个现象几乎每个接入Prettier的团队都会遇到但往往不是规则写错而是三个“底层审美”没统一单双引号、分号有无、多行对象末尾是否加逗号。如果团队没有既定的强约定我的建议是接受Prettier默认值别在配置文件里反复横跳。Prettier的默认风格虽然不一定人人喜欢但那是被几十万项目测试过的稳定排版。你们可以约定“使用Prettier默认值”省掉一半讨论时间。如果团队有明确偏好记住一点所有偏好都要写进.prettierrc并且第一次format造成的diff最好单独一个commit不要让后续功能提交夹带格式化痕迹这样git blame才能保持干净。换行符问题也值得单独说。如果团队里有Windows和macOS成员git的core.autocrlf设置不一致时明明只改了一行diff里却是整个文件都被标记。Prettier里endOfLine设成auto只能减少一半问题真正管用的是.editorconfig里统一end_of_line为lf并建议git core.autocrlf保持一致。这里没有银弹关键是全团队统一并写进自己的开发设备设置文档。6.3 格式化循环与git预检的冲突“保存时改一下过两秒又改回去”这种循环根源通常是两个formatter同时抢文件。最经典的组合是代码里装了eslint-plugin-prettierVS Code里又装了Prettier扩展且formatOnSave为true。保存时ESLint先跑prettier规则把代码排成A风格紧接着Prettier扩展又跑排成B风格下一秒ESLint又发现不符合A……就无限循环。遇到循环先自查组件关系formatOnSave交给谁、codeActionsOnSave交给谁、有没有装冗余插件。按照第5章那套设置循环基本不会出现。还有一个容易忽视的细节是多个ESLint配置文件的递归子目录里藏着一个旧版.eslintrc导致同一文件被两个配置处理结果建议以globalIgnores把不需要的子目录排除掉。提交钩子冲突是另一个坑lint-staged里如果先跑prettier --write再跑eslint --fix而项目里确实装了eslint-plugin-prettier那么后面eslint又会把文件格式改一遍这时候lint-staged判断文件内容变了会拦截提交提示“found modifications”。解决办法很简单要么升级到eslint-config-prettier方案要么把lint-staged里的命令顺序调整为先eslint后prettier保证最后一步是排版终结者。我在第5章写的配置顺序正是基于这个考虑。最后提醒一个容易被忽略的点添加Prettier之后记得让团队成员重启VS Code并加载工作区设置。旧版本VS Code对codeActionsOnSave的值类型比较敏感如果升级了VS Code还发现保存不触发看看右下角是否提示插件未激活手动重启一次就稳定了。整套方案跑顺之后我最明显的感受是ESLint和Prettier存在的意义不是限制自由而是把有限的脑力从排版琐事里解放出来。代码风格讨论是团队里最消耗感情的话题之一有了统一工具评审时看到的是逻辑和设计而不是引号与缩进。如果你也打算给项目配上这套检查别一步到位追求所有规则全开先跑通基础管线再把团队最在意的几条规则慢慢加进去效果会比“一把梭”好得多。