ESLint 与 Prettier 自动格式化,前端代码风格统一指南
2026/9/18 2:41:27 网站建设 项目流程

不知道你有没有遇到过这种现场:团队里5个前端,代码风格至少能凑出3套。有人双引号有人单引号,有人缩进两格有人四格,有人坚定加分号有人坚持裸奔。每次提代码评审,评论区永远有一半篇幅在争论格式问题,核心代码反而没人认真看。

我后来花了一周时间,把ESLint、Prettier和编辑器里的Ctrl+S自动保存格式化彻底打通,这类争论基本归零。新同事拉下仓库,npm install完,随便打开一个文件改两行,保存的瞬间代码风格自动统一。这篇就把整个链路拆开讲清楚,从工具分工、VS Code配置、WebStorm和Vim的替代方案,到husky + lint-staged的提交兜底,以及我实际踩过的各种坑,全部摊开说。


1. 保存那一下,格式化为什么值得专门做一件事

1.1 人工统一格式:团队前三个月还能忍,后面全在吵架

大部分前端项目刚起步时,代码风格问题是靠“约定”解决的。团队拉个群,发一份《前端代码规范》,里面有缩进、引号、分号、命名规则。听起来挺像回事,但人类的记忆和执行力都靠不住。

我见过一个挺典型的中型项目,代码量到十万行级别之后,git blame里密密麻麻都是“style: format”这种提交。功能逻辑没人动,今天这个人格式化了一部分文件,明天那个人又格式化了一部分。结果代码评审时,老同事看到一堆无关的空白字符变化,根本分不清哪行才是真正的改动。

有人会说:“我自己注意点不就行了?”问题在于,注意成本太高。人每次写完代码都要想“这里该不该换行、这个函数参数要不要拆行”,这些决策本质上跟业务逻辑没有任何关系,纯粹消耗脑力。机器能干的活,没必要让人用纪律去扛。

把格式化交给工具,反而是在给团队减负。真正该花的是配置时间,不是每天反复纠结风格的时间。

1.2 ESLint 不是格式化的唯一主角,它和 Prettier 的分工很微妙

很多刚入门的前端,一提代码规范就想到ESLint,以为装完ESLint就有了一切。这个理解不算错,但不够完整。

ESLint的核心能力是静态检查。它扫描代码里的逻辑问题和潜在隐患,比如声明了没用的变量、意外使用全局变量、条件判断里漏了括号、React组件里缺了依赖项等等。它也能修复一部分问题,eslint --fix可以自动处理很多可修复规则,比如自动加空格、去掉多余分号。但ESLint不是专门做排版的,它的规则重点在“代码哪里写错了”,而不是“代码长得是否整齐划一”。

Prettier则完全不同。它不关心你的业务逻辑,不关心变量命名,只看输出文本本身。你把一段乱七八糟的代码丢给它,它按照统一配置重新排版:缩进几格、单引号双引号、行宽多少、尾逗号怎么处理、文件末尾要不要换行,全部由它说了算。

用一个不精确但好理解的类比:ESLint像机场安检,负责检查行李箱里有没有违禁品;Prettier像流水线上的打包机,保证每个箱子外观都一模一样。安检和打包是两件事,但一个完整的交付流程两者都需要。


2. 你以为 ESLint 能直接帮你排版?责任边界先掰扯清楚

2.1 检查错误和统一排版是两件事,拆开才能少掉头发

ESLint本身确实带不少风格类规则,比如缩进、引号、函数签名空格,这些规则也能通过--fix自动修复。但问题在于,ESLint的风格规则和Prettier的风格输出经常不是同一套标准,两边都开着,很容易出现“ESLint改完,Prettier又改回去”的鬼畜循环。

我来整理一个实用的对比表:

维度ESLintPrettier
核心目标发现代码错误和反模式统一代码排版风格
关注内容未使用变量、全局变量、代码复杂度、可维护性引号、缩进、分号、行宽、换行、尾逗号
自动修复能力只修复规则中标记为fixable的问题全量重排,没有“部分修复”概念
配置风格规则极多,可自定义每个开关和参数配置项很少,故意限制自定义空间
适合场景保证代码质量底线保证代码观感一致

把两者拆开之后,团队规则会清晰很多:业务代码写得烂不烂、有没有低级错误,由ESLint把关;代码排版丑不丑、风格统不统一,由Prettier负责。不要把Prettier挂在ESLint里面当成规则去跑,后面我会细说。

2.2 规则打架的典型现场,以及行业通用的两条出路

直接说结论:只要你在项目里同时启用ESLint和Prettier,就一定会遇到规则冲突。最常见的几个打架点:

  • 行宽:ESLint的max-len设了120,Prettier默认printWidth是80,两边较劲。
  • 引号:ESLint的quotes要求双引号,Prettier配置又设了singleQuote: true,保存时来回顶牛。
  • 缩进:ESLint的indent规则和Prettier的tabWidth偏好不一致。
  • 运算符换行:ESLint的operator-linebreak和Prettier的排版策略不同。

行业里目前有两条主流处理路线:

第一条,使用eslint-config-prettier关掉ESLint里所有与格式化冲突的规则。这是我最推荐的做法。先把检查工具和排版工具的职责彻底分开,ESLint专注逻辑,排版交给Prettier。

第二条,使用eslint-plugin-prettier把Prettier当成ESLint的一条规则来跑。这样确实能在ESLint输出里看到格式化报错,但代价是保存时多跑一道完整排版,速度会变慢,而且报错信息里混杂着“该加分号”和“这里有个未使用变量”两种噪音。我个人的经验是,小项目无所谓,中大型项目不建议这么干。

如果你问社区里的主流方案,大概率也是第一条:ESLint + Prettier + eslint-config-prettier,Prettier负责打印,ESLint负责挑错,各干各的。


3. VS Code 下从零配置:让 Ctrl+S 真正触发一次“整容”

3.1 安装依赖:eslint、prettier、eslint-config-prettier 各自的作用

先创建项目,然后在根目录安装依赖:

npm install -D eslint prettier eslint-config-prettier

三个包各司其职:

  • eslint:提供代码检查核心能力,负责跑规则、报告错误、执行--fix
  • prettier:提供代码格式化能力,生成统一排版的文本。
  • eslint-config-prettier:官方提供的配置包,专门用来关闭ESLint中与Prettier冲突的规则。

如果你是Vue项目,还需要装eslint-plugin-vuevue-eslint-parser;React项目一般需要eslint-plugin-reacteslint-plugin-react-hooks。TypeScript项目要加@typescript-eslint/parser@typescript-eslint/eslint-plugin。这些根据技术栈灵活补,核心思路一样。

这里特别提醒一句:不要全局安装Prettier,也不要让团队里的人各自用自己电脑上的Prettier版本。格式化工具版本不统一,今天你格式化一个文件、明天同事格式化同一个文件,结果可能完全不同。Prettier和ESLint必须装成项目本地依赖,统一锁定版本。

3.2 settings.json 每一项的配置逻辑,别抄完就完事

VS Code端要改工作区设置。打开项目的.vscode/settings.json,写入:

{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "editor.tabSize": 2, "editor.detectIndentation": false, "files.eol": "\n" }

一字一句拆开讲为什么这么配。

editor.defaultFormatter指定默认格式化器是Prettier扩展。如果不指定,VS Code可能用一个内置的格式化器或者别的插件,风格不受Prettier控制,等于白搭。

editor.formatOnSave是全局开启保存时格式化。但光有这个还不行,它只触发格式化器,也就是Prettier,不会自动修复ESLint里的逻辑类问题。

editor.codeActionsOnSave里那行source.fixAll.eslint才是真正让ESLint在保存时自动修复问题的地方。注意我写的是explicit,不是true。在新版VS Code里,用explicit表示只有当用户明确开启这个动作时才执行,避免有些代码动作在后台被静默触发。即使你用的是旧版VS Code写true,效果也是让ESLint在保存时跑一遍fix,把可自动修复的问题处理掉。

editor.tabSizeeditor.detectIndentation是为了避免VS Code根据历史内容自动推断缩进。很多老项目混着2空格和4空格,开着自动检测会导致你每次保存都只能格式化当前缩进,推倒重来。关掉检测,固定2空格,让Prettier统一接管。

files.eol设置默认换行符为LF,避免Windows环境下把整个项目文件变成CRLF,产生无意义的全文件diff。

有几个点值得补充。Prettier扩展和ESLint扩展都需要在VS Code里安装,如果企业内网环境安装不了扩展,最好走命令行npx prettier --write或者借助CI兜底。另外,每台新电脑打开项目时,VS Code会弹“是否信任此工作区”,需要允许工作区设置生效,否则.vscode/settings.json里的配置不会加载。

3.3 根目录配置文件:eslint.config.js 和 .prettierrc 最小可用版

先写.prettierrc,一个最小但够用的配置:

{ "singleQuote": true, "semi": true, "printWidth": 100, "tabWidth": 2, "trailingComma": "es5", "endOfLine": "lf" }

这几个选项的含义:singleQuote统一用单引号;semi在语句末尾加分号;printWidth控制一行代码最大宽度,超过100字符Prettier会尝试换行;tabWidth指定缩进为2空格;trailingComma控制在ES5允许的语法结构里使用尾逗号;endOfLine固定换行符为LF。

再写ESLint配置。现在新项目基本都走Flat Config,也就是根目录下创建eslint.config.js

import js from '@eslint/js'; import prettierConfig from 'eslint-config-prettier'; export default [ js.configs.recommended, prettierConfig, { files: ['**/*.{js,ts,vue}'], rules: { 'no-console': 'warn', 'no-unused-vars': 'warn' } } ];

注意这里eslint-config-prettier要放在js.configs.recommended后面。Flat Config是顺序敏感的,后面的配置会覆盖前面的同名规则。Prettier配置放最后,才能确保ESLint推荐配置里那些与排版冲突的规则被关掉。

这只是最小可用版,实际项目里经常还要加languageOptionspluginsignores。但先跑通这个闭环最重要:保存文件,ESLint的自动修复先执行,Prettier再把排版统一覆盖一遍,输出结果干净一致。


4. 换了 WebStorm 或 Vim,Ctrl+S 自动格式化还能不能玩

4.1 WebStorm 的保存时格式化藏在“动作”里

VS Code是前端主力,但团队里保不齐有人用WebStorm。WebStorm内置ESLint和Prettier支持,不需要装扩展,不过要手动打开几个开关。

设置路径一般在:SettingsLanguages & FrameworksJavaScriptPrettier,勾选“On save”触发保存时格式化。同时在SettingsLanguages & FrameworksJavaScriptCode Quality ToolsESLint里,找到“Run eslint --fix on save”或者“on code save”之类的选项打开。

需要注意,WebStorm默认的格式化器其实不是Prettier,必须把Prettier设为默认的“Formatter”才行,否则保存时是WebStorm自己的格式化和ESLint在打架。社区里matrix用户不少,统一配置时把这些细节写进README,比一个个口头解释靠谱得多。

4.2 Vim/Neovim 用户别慌,一条 autocmd 解决问题

用Vim的人一般都不想装一堆IDE,但格式化需求同样存在。最简单粗暴的方案是直接用Prettier命令行,配合Vim的自动命令:

autocmd BufWritePre *.js,*.ts,*.tsx,*.vue,*.json silent! :!npx prettier --write %

这段的意思是在文件写入磁盘前,先对当前文件执行npx prettier --writeBufWritePre事件发生在文件写之前,格式化完成后再保存,效果和编辑器的“保存时格式化”一模一样。

如果你用的是Neovim且配置了LSP,更现代的做法是用conform.nvim或者null-ls这类格式化工具,按文件类型配置Prettier作为formatter。它们同样工作在BufWritePre阶段,但比裸调命令更可控,也不会有每次npx启动的额外延迟。

4.3 真正的跨编辑器一致性,靠的是项目配置文件而不是IDE

跨编辑器的关键并不是让每个IDE都保持一模一样的配置界面,而是让所有人都基于同一套项目级配置运行。

Prettier的读取顺序是:项目根目录的.prettierrcprettier.config.js,找不到就用用户级配置。ESLint同样,Flat Config的eslint.config.js在项目根目录。把这些文件提交到仓库,任何编辑器只要支持对应工具,保存时就会自动读取项目配置。

为了更稳固,还可以加一层.editorconfig,它定义基本的缩进、字符集、换行符,VS Code、WebStorm、Vim都原生支持。Prettier很多选项可以直接通过.editorconfig读取,这样不同IDE之间哪怕格式化器实现有差异,基础规则也能对齐。

如果一个同事的VS Code没有加载项目设置,或者他的编辑器扩展版本太老,保存出来的代码可能还是跟团队不一致。这时候就需要提交阶段兜底,我在第5章会讲。


5. 保存格式化不背锅,提交前的自动化才是团队闭环

5.1 为什么光有 Ctrl+S 不够,总有代码没被打开过

Ctrl+S自动格式化解决了“编辑器里打开文件并修改”的场景,但还留了几个洞:

有人从终端里用脚本批量改代码,生成的临时文件根本没有经过编辑器;有人用在线编辑或者低代码平台维护部分页面,保存后的文件直接推到仓库;还有人拉了远程分支之后没做任何操作,但远程分支里的历史代码本来就没被格式化过。

这些情况靠人肉检查是堵不住的。真正稳妥的做法,是把格式化命令挂在提交钩子上。只要有人想git commit,就先跑一遍检查,没通过就不让提交。这样即使有人绕过了编辑器、改了配置文件,到了提交这一步都会被拦下来。

5.2 husky + lint-staged:只处理即将提交的代码

最常用的一套组合是huskylint-staged。husky负责注册Git钩子,lint-staged负责只对暂存区的文件执行命令。先安装:

npm install -D husky lint-staged npx husky init

npx husky init会在项目根目录生成.husky文件夹,里面有个pre-commit钩子文件,默认内容一般是npm test。改成:

npx lint-staged

然后在package.json里加lint-staged配置:

{ "lint-staged": { "*.{js,ts,tsx,vue,json,css,scss,md}": [ "prettier --write", "eslint --fix --max-warnings=0" ] } }

这里的逻辑是:当用户执行git commit时,husky触发lint-staged,lint-staged把暂存区里匹配到的文件分别跑一遍prettier --writeeslint --fix。格式化结束如果文件变了,lint-staged会自动把这些改动重新加入暂存区,一起提交。

--max-warnings=0是为了让ESLint处理任何warning都直接失败,要求提交前必须清零。有些人觉得太严,根据团队情况可以去掉,但我个人建议保留,因为一旦放松,警告只会越堆越多,最后提交钩子就形同虚设。

值得注意的是,prettier --writeeslint --fix写在一起,顺序有一定讲究。我习惯把Prettier放前面,ESLint放后面。理由是Prettier负责统一的排版基线,ESLint再去做逻辑类修复,这样不会出现两个工具互相覆盖的情况。只要配了eslint-config-prettier,ESLint不会来做排版类修改,两者就不会顶牛。

5.3 历史老项目接入时,如何避免一次提交几千个文件

很多人照着教程配完这套流程,信心满满地提交,然后发现lint-staged根本没触发或者没拦下问题,因为老项目的历史代码本身就有成千上万个格式问题。

如果直接在老项目里跑一遍全量prettier --write,结果就是一个PR里多出几千个文件的diff,代码评审完全没法做。

我的建议是分三步走:

第一步,先加.prettierignore,把历史遗留目录和第三方代码暂时忽略掉,让格式化只作用于新代码和修改到的文件。

第二步,用lint-staged只对本次暂存区的文件执行格式化和检查。老文件你没动它,它就不会被格式化,提交时的diff还是干净的。

第三步,等团队有精力了,单独开一个“chore: format all”的分支,挑一个改动少的版本窗口,一次性把所有存量代码格式化完毕,然后尽快合入主干。这个分支建议用机器生成,不带任何业务逻辑,减少评审负担。

这三步走下来,老项目也能平滑接上自动格式化,而不是推倒重来。


6. 配置完成之后的真实踩坑现场,和一条条对应的解法

6.1 diff 污染:格式化不该混在功能改动里

项目接入了自动格式化后,最让团队头疼的往往不是格式化不生效,而是格式化把整个文件都改乱了。

比如你在一个3000行的大文件里改一行逻辑,保存时Prettier顺手把全文件重排了一遍,diff里瞬间多出几百行变化。代码评审的人看半天,不知道你到底改了什么逻辑,只知道你动了半条命。

解法很朴素:历史问题文件不要急着格式化,先让它们保持原样,等你哪天集中重构或大改时再一并处理。提交钩子里的lint-staged只针对暂存区,但它不会阻止大文件全量格式化——只要你保存了,它就会格式化整个文件。所以编辑器的“格式化保存”和“提交前的增量处理”是两套心智,要跟团队成员讲清楚:保存时格式化只改你正在编辑的文件,要是看到有不相干的大文件被改了,先想想是不是自己不小心保存了。

6.2 .vue、.ts、json 同时存在的项目,格式化顺序会坑你

现代前端项目很少只有纯JavaScript,很可能是Vue组件配TypeScript,再加一堆JSON配置,还有vitest的单测文件。Prettier对这些文件类型基本都能处理,但有一个隐藏坑:eslint.config.js里的files匹配规则写错了,会导致ESLint没跑或者Prettier对某些文件不生效。

比如配了files: ['**/*.{js,ts,vue}'],那.vue文件里的<script setup lang="ts">需要vue-eslint-parser去解析,记得在ESLint配置里指定parser。如果项目有vitest测试文件,最好针对**/*.test.ts单独开规则,避免测试文件里的断言风格被业务代码的规则误伤。

还有项目里的JSON文件,比如package.jsontsconfig.json,Prettier默认也能格式化,但有些特殊文件比如.eslintrc.prettierrc,如果没在格式化范围里,就会被漏掉。把这些通通写进lint-staged的glob匹配里,能省很多事。

6.3 全局安装 prettier 还是项目安装?答案比你想的绝对

很多人图省事,在自己电脑上全局装了一个Prettier,然后VS Code里直接调用全局版本。项目本地没装Prettier,刚开始也没发现问题,直到某天Prettier升级了大版本,格式化规则变了,一下子全项目的文件都变得“不一致”。

正确的做法只有一个:项目本地安装,并且锁版本。prettiereslint都应该出现在package.jsondevDependencies里,团队所有人通过npm install拿到同一版本,IDE扩展也优先读取项目本地的可执行文件。

迫不得已的情况下,你可以在settings.json里把Prettier的路径指到一个固定的全局版本,但前提是你能保证团队所有人的全局版本一模一样。这几乎不可能,所以别走这条路。

6.4 给第三方目录开免死金牌:.prettierignore 的用法

Prettier会尝试格式化它认为属于前端的任何文件,包括node_modules里的文件、打包产物、锁文件。这些文件一旦被格式化,轻则产生巨大diff,重则可能破坏生成逻辑。

在项目根目录创建.prettierignore,内容和.gitignore高度重合:

node_modules dist build coverage package-lock.json yarn.lock pnpm-lock.yaml *.min.js *.min.css

这三个文件的作用边界很容易混淆:.gitignore管的是文件要不要进Git版本库;.prettierignore管的是Prettier格式化时跳过哪些文件;ESLint对应的忽略配置则写在eslint.config.jsignores数组里。三者不要混用,各管各的。

还有一个坑是.prettierignore里写的路径如果没匹配对,可能会出现“明明配了忽略,结果Prettier还是在提交钩子里格式化了某个目录”的诡异现象。这种时候建议直接跑一下:

npx prettier --check .

看它到底把哪些文件视为待格式化,一目了然。


这套流程调通以后,我再也没在代码评审里说过“麻烦统一一下引号”之类的话。工具干工具的事,人干人的事,团队代码风格终于不再靠脾气和记忆维持。如果你正在搭建或者重构前端的工程化规范,可以先从这次最小闭环开始:ESLint抓逻辑错误,Prettier管排版,Ctrl+S触发自动保存格式化,提交钩子兜底。跑顺了再慢慢加更多规则,代码质量自然会被拦在合入之前,而不是合入之后。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询