☰
ESLint 与 Prettier 前端工程化实战:从扁平配置到提交钩子
2026/10/5 15:41:52 网站建设 项目流程

我记得很清楚,第一次在团队里引入代码规范时遭遇的场景:一个老哥修了一下午的 bug,推完代码腼腆地说“帮我看看这个文件”,我打开 Pull Request 一看——单引号、双引号混用,分号一会儿有一会儿没有,缩进两个空格四个空格交替,还有一个 200 行的对象字面量穿着三层嵌套格式化出来能当迷宫看。代码本身没问题,但那一眼下去,那种窒息感和规则感全无的挫败感,真的能瞬间消磨掉检查者的耐心。后来我们在项目里认真把 ESLint 和 Prettier 接进来,蔡绕了半年的“代码格式自由搏击”才算彻底画上句号。这篇文章我会从团队实践的角度,完整拆解一套在前端工程化项目中落地 ESLint + Prettier 的方法。不管你是几个人维护的小项目,还是几十人的中大型团队,只要还停留在“靠纪律约束”的阶段,这套方案都能帮你从机制上解决问题。我会重点讲清楚当下 ESLint 新版本(V9 到 V10 阶段)的扁平配置文件写法、Prettier 重要配置项的取舍逻辑,以及 VSCode 里最容易踩的格式化配置误区,最后会带上提交前自动检查的工程化实践。适合刚接手团队代码规范的小组长,也适合想把自己开发环境收拾利索的独立开发者。

1. 第一个坑:为什么我的代码一提交就被“教育”

先说一个我判断很多团队都会经历的场景:项目里装了 lint 工具,配置也写了,npm script 也存在 package.json 里,但实际 push 代码的时候,没人在意它。你问团队成员“为什么不用 ESLint”,回答非常统一——“它报了我也不知道怎么改”。这个现象背后的原因其实很清晰:之前的工具选型走偏了。

1.1 把质量检查和代码格式化混为一谈

早期很多项目的ESLint配置里堆满了格式类规则,比如indent、quotes、semi、comma-spacing。这些规则确实能检查格式,但踩过的都知道,ESLint 做格式纠错的体验非常生硬。我举一个真实案例:把一段对象里的双引号全部改成单引号,ESLint 会一口气报出十几个strings must use singlequote。此时你要是没有开启--fix,就得一个个手动去改。就算开了--fix,遇到和 Prettier 同时接管格式的场景,两边的规则会互相打架:Prettier 刚刚把箭头函数参数括号删掉了,ESLint 的arrow-parens又要求必须加上;Prettier 用了 Windows 换行符,ESLint 又在那里报警告。最后团队达成有效共识的方式,反而是把代码规范默默降级为“仅供参考”——这是最失败的工程化状态。

1.2 从“敢检查”到“愿意保存”的转变关键

后来我们想明白了,问题的核心不是规则不够多,而是工具的分工没有理清。前端的代码检查体系应当分成两条完全独立的管道:

  • 质量检查:代码里有没有 bug、有没有不符合规范的写法、有没有遗留比较危险的危险片断——这是ESLint的主场。
  • 格式整理:所有关于缩进、换行、引号、分号、尾逗号的表现形式——这是Prettier的主场。

理顺这两件事,团队成员的工作流才真正变成“保存时自动把格式归位,提交前跑一遍质量检查”。既然拿到我手里的任务是“规范代码开发”,这篇内容我就把两个工具从分工、配置、编辑器集成到提交前钩子的完整链路串起来。下面的小节都基于我们的真实配置来展开,你可以直接照着抄,再根据自己项目口味微调。

2. 分工线:ESLint 管对错,Prettier 管好看

这个区分看着简单,但很多人拿到工具就混着用。所以我单独拿出一个章节,把两者的职责边界用一段通俗的解释讲透,然后给出实操上怎么配合的框架。

2.1 用“作文批改”来理解 ESLint 和 Prettier

把一段源码当成一篇作文:ESLint 是那个负责挑逻辑错误和用词不当的语文老师,他会告诉你“这句话有语病”“这个词用在这里不符合语法规则”“这个空对象可能隐藏问题”;Prettier 则是那个负责誊写的排版员,他不关心内容是对是错,只关心标题是否居中、段落间距是不是统一、标点后面是否空一格。Prettier 的目标是“让所有代码长差不多”,它会把整个文件重新打印一遍,换行、缩进、引号、分号全部按统一格式输出,让代码的外观保持一致。听起来很简单对吧?但实际项目里经常会有人问“我把 ESList 的 quotes 关掉,用 Preitter 来管引号不行吗”——行,但那等于你把“内容错误”的检查职责和“外观统一”的整理职责搅在了一起,后面每新增一种格式需求,都要去翻更复杂的规则配置。不如彻底不混。

2.2 Lint 与 Format 的实际分工边界

我们团队现在执行的分工逻辑是:

  • 业务代码正确性规则:比如禁止使用会被丢弃的变量、禁止在 setState 之后读取旧值、强制要求 hook 依赖数组写全,这类归 ESLint 管。ESLint 通过解析器(默认是 Espree,V9之后也可以配置其它Parser)把源码变成抽象语法树,然后对你的代码模式进行交叉验证,所以在“判断对不对”这件事上它不可替代。
  • 模板与空白规则:比如 printWidth、tabWidth、semi、singleQuote、trailingComma、bracketSpacing、arrowParens 这些,全部交给 Prettier。Prettier 不做任何逻辑分析,它把代码解析成 AST 之后,在 AST 层面重新走一遍自己预设的打印引擎,直接输出标准化的源码文本。因为核心理念是“消除了所有可以消除的格式差异”,所以它拿手的工作就是把一个文件格式化之后,第二次、第三次跑都能得到相同结果:稳定是第一要务。

2.3 为什么 Prettier 不能取代 ESLint 的格式类规则却又必须共存

前面说 ESLint 早期喜欢管格式,但是我们必须承认,直到今天 Prettier 也无法覆盖 ESLint 能做的全部事情。比如有些团队希望对象 key 的写法和 value 写法分开检查,事实就是 Prettier 不管 key 使用什么引号(它只负责引号风格统一),但 ESLint 的 quote-props 规则可以强制 key 是否需要引号及具体写法。再比如禁用 debugger、禁用 console、检测是否存在重复的 case、判断 Promise 是否合理 await,这些都超出“排版”范畴。所以实践上的标准搭配是:ESLint 保留质量类规则,格式类规则尽量再存在同一套规范里,方便 CI 统一判断,但真正改写代码的时候,格式化这件事完全交给 Prettier。为了让这两者“不打架”,我们后续还要引入 eslint-config-prettier 来关掉 ESLint 里那些和 Prettier 冲突的格式化规则。这个细节我放在后面的章节专门讲。

3. 环境准备与起步:从 npm init 到 ESLint v10 落地

下面开始动手。我先说明,现在主流项目已经到了 ESLint 的“扁平配置”(Flat Config)时代,也就是文件从.eslintrc.js变成了eslint.config.js。V9 开始推荐默认使用扁平配置,V10 会进一步把旧的 eslintrc 体系清理干净。如果你搜到的大部分博客还在教你在.eslintrc里写extends,那份内容大概率已经过期了。我们接下来的所有步骤,都按新版本逻辑来。

3.1 Node 环境与包管理器选择

ESLint 当前版本要求 Node.js 的 LTS 版本,建议至少 Node 18 或更高。这里不细说 Node 安装,只提醒一个容易被忽略的:如果项目里有多个 Node 版本并存,建议用.nvmrc固定版本,避免团队成员本地 Node 版本差异导致 ESLint 解析器行为不一致。包管理器方面,npm、yarn、pnpm 都能跑通,我个人建议团队锁定一个统一管理器,并把 lock 文件提交到仓库。pnpm 因为依赖的嵌套逻辑和严格性,安装 ESLint 时会遇到 peer 依赖冲突的情况多一点,如果不熟悉 peer dependency 规则,建议先用 npm 起步,等项目跑顺了再换。

3.2 初始化 package.json 和安装依赖

在项目根目录执行:

npm init -y

然后一次性安装我们需要的核心依赖:

npm install --save-dev eslint prettier eslint-config-prettier

这里先解释一下为什么要装eslint-config-prettier:它不是一个辅助插件,而是一个专门用于“关闭 ESLint 中所有和 Prettier 冲突的格式类规则”的配置包。如果不装它,ESLint 默认自带的indent、quotes、semi等规则会持续干扰 Prettier 的输出。装完它在配置里引入,ESLint 就会把这些格式规则统统关闭,把格式领域完整让给 Prettier。这是一个非常干净的分工策略。

3.3 生成 eslint.config.js 扁平配置文件

在项目根目录运行:

npx eslint --init

新版本会问一些问题,比如项目类型(EAM 还是 CommonJS)、框架(React/Vue/None)等,然后自动生成eslint.config.js。如果你的项目里还没有这个命令,或者你希望手写配置文件,可以直接创建一个最简单的版本:

import js from '@eslint/js'; export default [ js.configs.recommended, { files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], rules: { 'no-unused-vars': 'error', 'no-undef': 'off', }, }, ];

这里解释一下新版的配置结构。扁平配置的核心是“数组”,数组里的每一项都可以是一个配置对象或一组配置集合。js.configs.recommended是 ESLint 官方推荐规则的集合,它继承了一大批适合日常开发的规则:强制no-unused-vars、检测no-undef、推荐使用eqeqeq等。你可以把这个数组理解为一条流水线,后面的对象会在前面的基础上叠加或覆盖规则。

值得注意的一个点是no-undef在很多新项目里会被关掉,因为现在浏览器、Node、ES 模块的全局变量定义方式已经非常精细,加上 TypeScript 的介入后,未定义变量的检查基本由 TS 编译器接管。但如果你用的是纯 JS 项目且面临很多浏览器全局 API,还是建议开着一一排查。我们团队在 JS 项目里通常保留no-undef,在 TS 项目里关闭。

3.4 常用核心规则取舍与团队约定

扁平配置里写规则非常直白,没有以前那种“rules 层级”的歧义。我挑几个决定性很强的规则来说明:

  • eqeqeq: 强制使用全等===和!==,但允许== null这种特殊场景,写法为'eqeqeq': ['error', 'always', { null: 'ignore' }]。这条能拦截大量隐式类型转换带来的意外。
  • no-console: 团队里如果是纯浏览器项目,建议设为'warn',毕竟console.log排查问题还是有用的,但不想让它留在线上。如果接入了成熟的日志库,可以直接'error'。
  • curly: 要求所有代码块都用花括号。这条对防止写if (x) return;这种单行裸洗式有很大作用,后续加代码时极容易踩到逻辑坑。
  • no-else-return: 当 if 分支里已经 return 之后,禁止再写 else,这会让代码层级更平。这条可以按团队口味设成 warn 或 error。
  • complexity: 控制圈复杂度,比如设置'complexity': ['warn', 10]。一个函数里嵌套分支超过 10 条时就报警告,逼你拆函数。这个规则非常有力量,但也要注意别因为过度使用导致团队频繁投诉。

这些规则都不涉及具体格式,它们的核心目标是“代码逻辑更稳、可维护性更高”。像缩进、引号、分号这类格式规则,在新配置里我不会写,全部交给 Prettier。

3.5 关于 ESLint v10 的几个关注点

由于你搜到的热词里有eslint v10,我特别说一点:V10 是扁平配置全面接管后的一个大版本。它最明显的调整是移除对旧的.eslintrc格式的兼容,意味着如果你还在package.json里写eslintConfig,或者保留.eslintrc.js,新版本会直接报错。对于新项目这是好事,配置体系干净了;对于旧项目,升级 v10 要做的第一件事就是把旧的extends体系翻译成扁平配置的数组格式。另一个注意点是@eslint/js和typescript-eslint这类包的版本必须和 ESLint 主版本匹配,否则会出现奇怪的“无法加载配置”错误。建议看官方 upgrade guide,不要盲 upgrade,很多人从 V8 跳 V10 中途踩坑全因为依赖没有同步升级。

4. Prettier 配置项逐个说明:照着抄也要知道你改了什么

很多教程会在最后甩一段.prettierrc让你复制进去。复制没问题,但如果你不搞清楚每个字段背后的取舍逻辑,后续团队开会讨论“为什么尾逗号要打开”的时候,你没法给出有底气的理由,甚至会被带偏。这里把我常用的配置拆开讲。

4.1 一个我推荐的 Prettier 基础配置

.prettierrc.json:

{ "printWidth": 100, "tabWidth": 2, "useTabs": false, "semi": true, "singleQuote": true, "quoteProps": "as-needed", "trailingComma": "all", "bracketSpacing": true, "arrowParens": "always", "endOfLine": "lf", "htmlWhitespaceSensitivity": "css", "insertPragma": false, "requirePragma": false, "proseWrap": "preserve", "rangeStart": 0, "rangeEnd": "Infinity" }

下面逐一拆解。

4.2 printWidth 和 tabWidth:看着简单,实际上影响很大

printWidth是 Prettier 里最影响“观感”的项。它代表一行代码超过多少字符就尝试换行。很多人直接抄 80,但我建议团队统一按 100 来。为什么?因为现代主流显示器和宽屏开发环境,80 字符一行经常导致代码过度换行,对象嵌套几层之后右侧全是折行,反而更难看。100 是多数团队的甜蜜点。注意它不是绝对上限,比如一个很长的字符串不值得拆行,Prettier 会允许它超过,但它会尽量把适合拆分的代码在 100 列附近折行。

tabWidth是“缩进层级用了多少个空格”,配合useTabs: false就是“用 2 个空格缩进且不使用\t制表符”。这个选择很适合前端生态,因为 2 空格缩进确实让嵌套层级不深的时候依旧保持紧凑。有些团队偏爱四空格,我不能说错,但前端项目 2 空格是绝对主流,你百分之九十九的概率会希望向生态看齐。

4.3 引号,分号,尾逗号:团队冲突重灾区

semi: true要求每条语句末尾保留分号。这里有个事实:JavaScript 有自动分号插入机制,不写分号也能跑。但问题在于,当压缩混淆工具处理和某些边界场景(比如一行以([开头)时,不写分号会带来意外结果。我们对团队成员的要求很简单:让写法尽量直白可预测,统一保留分号。这条规则用英语写就是semi: true,含义一目了然。

singleQuote: true要求尽量使用单引号。双引号在 HTML 属性和某些字符串常量里更常见,但在 JS 代码里,单引号看起来更紧凑,而且团队如果同时写 HTML/JS,减少引号切换会舒服一些。需要注意quoteProps控制对象的属性名用什么引号:as-needed表示只有当属性名不是合法标识符时才加引号(比如'foo-bar'就必须带引号),合法标识符一律不加引号。这样的好处是代码干净,同时能兼容绝大多数 JSON 风格的对象写法。

trailingComma: "all"是最容易引起争议的配置。它要求多行数组、对象、函数参数的最后一项后面也加一个逗号。很多人第一反应是“多个逗号碍眼”,但这个配置的核心价值在版本管理里:当你新增一个属性时,git diff 只显示新增那一行,而不是“上一行新增逗号 + 下一行新增属性”两条变动。这意味着代码审查时 diff 更容易看。而且现代 ESLint 和浏览器都能正确处理尾逗号,完全不需要担心。更激进的all甚至会给函数参数加尾逗号,跨环境兼容性也没问题(IE 时代确实不行,但现在不用考虑这个了)。

4.4 bracketSpacing 和 arrowParens 的细节

bracketSpacing: true控制花括号内部的空格,也就是{ foo: 1 }和{foo: 1}的区别。开启后阅读起来更透气,特别是对象嵌套多层的场景,空格作为视觉分隔符能降低误解率。这个配置对数组里的对象字面量影响也很大。

arrowParens: "always"要求箭头函数参数无论是一个还是多个都用括号包裹,也就是(x) => x,而不是x => x。这里有一个官方推荐的默认值是always,但一些精简风格的美学会选择avoid。我为什么坚持always?因为当函数返回一个对象或需要增加第二个参数时,加括号其实是件很恶心的事;统一用括号,后续修改时不需要反复补删,减少结构变化。例如从x => x改成(x, y) => x + y,只多写一个参数即可,不要动括号。

4.5 endOfLine 和编辑器换行符的坑

endOfLine: "lf"强制统一使用 LF(Linux/macOS 默认的换行符)而不是 CRLF(Windows 默认)。这条非常重要:不同系统不同编辑器产生的换行符差异,会导致整个文件在 Git 里显示成“全部被修改”。很多新手被这个问题折磨一整天,最后一行一行 diff 发现全是^M这种东西。lf配置能从 Prettier 侧直接统一换行,强制 Windows 用户也在保存时把文件转成 LF。再配合.gitattributes里的* text=auto eol=lf就彻底锁死了。建议这一步一定要做,否则你在 Windows 上写完代码提交到仓库,同事在 Mac 上打开就会被 git 提示整个文件变了。

4.6 其他几个容易被忽略字段的作用

  • htmlWhitespaceSensitivity: 对 HTML/Vue 模板里空格缩进是否敏感。css是默认值,适合大多数情况;如果模板文件里出现多余空白导致渲染出错,才需要去手动调整。
  • proseWrap: 控制 Markdown 文本是否自动换行。"preserve"表示保留原文换行,不强制包裹。写文档时建议保留原样,避免改一段文字导致整段全被 diff。
  • insertPragma/requirePragma: 是否要求文件顶部必须有@prettier或@format注释才执行格式化。这是给大型增量项目的“选择性格式化”用的,当一个老仓库里有一半文件不想被格式化掉时,给某个目录加注释就可以只格式化这里。新项目完全不用开。

把 Prettier 配置讲这么细,是因为我见过太多团队配置全部默认然后跑起来,却解释不了为什么文件和同事的不一样。每一项配置都对应一种可维护性收益,理解之后你面对异议时就有了完整的技术判断逻辑。

5. VSCode 与 Prettier 格式化:保存即自动整理的正确设定

VSCode 是绝大多数前端工程师的主力编辑器。但“安装插件后直接用”和“正确配置后用”是两种完全不一样的体验。我见过很多同事装了 Prettier 插件,保存也触发了格式化,但格式化结果不是 Prettier 的默认效果,就是一会儿生效一会儿不生效。这里把最容易踩的点全部列清楚。

5.1 插件安装与默认格式化器的锁定

首先确保 VSCode 里装了 ESLint 插件(dbaeumer.vscode-eslint)和 Prettier 插件(esbenp.prettier-vscode)。安装后最关键的一步是:在项目根目录创建.vscode/settings.json,把默认格式化器强制指定为 Prettier。

{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } }

这里解释一下为什么必须手动指定默认格式化器:VSCode 自带一个格式化器,而且每个语言可能还有自己的格式化器(比如内置的 TypeScript 格式化器、Vue 的 Volar 插件也有格式化能力)。如果你不指定,保存时触发的是“当前语言的默认格式化器”,很可能就不是 Prettier。强制指定之后,所有支持 Prettier 的语言(JS/TS/CSS/HTML/JSON/Markdown 等)都会走 Prettier 统一格式化。

editor.formatOnSave: true是“保存时自动格式化”的总开关。source.fixAll.eslint是让 ESLint 的--fix能力在保存时执行,也就是说保存代码时可以先跑一遍 ESLint 自动修复,修不了的错误它会保留并在编辑器里标红,干等提醒你手动处理。另一个容易忽略的身份标识:新版 VSCode 对codeActionsOnSave的值要求是"explicit"或"always",而不是旧版的true。如果你是从老教程里复制true过来的,部分版本会弹警告甚至不生效。这是当前很常见的坑。

5.2 项目级配置 vs 用户级配置

强烈建议把.vscode/settings.json提交进 Git 仓库。这样每个克隆项目的人打开项目时,VSCode 会自动套用这些配置,不必每个人手动折腾一遍自己的用户设置。有一些内容不适合提交到项目里,比如个人主题、字体、文件关联,这些保留在用户设置里;但格式化器、保存动作、ESLint 运行模式这类与项目规范强相关的配置,必须跟着仓库走。团队新成员加入的第一天,打开项目无需任何额外操作,保存按钮一按就是正确格式化结果,这个体验极其宝贵。

5.3 格式化范围问题与 ignore 文件

Prettier 和 ESLint 都有自己的 ignore 文件。.prettierignore里我们一般至少写:

node_modules dist build coverage package-lock.json pnpm-lock.yaml yarn.lock .DS_Store

.eslintignore在新版本里的行为有点变化:扁平配置时代,建议直接在eslint.config.js里用ignores字段,而不是单独维护一个.eslintignore。例如:

export default [ { ignores: ['dist/**', 'build/**', 'node_modules/**'], }, ... ];

注意 node_modules 是默认忽略的,但dist、build、coverage这些目录一定要明确写清楚。否则以后新同事在根目录跑 lint,莫名其妙发现一堆编译产物的报错,非常浪费时间。

5.4 为什么保存时格式化和提交时检查不能互相替代

有一种错误想法是“我已经开了保存时格式化,代码肯定规范了,不需要再在 Git 钩子里跑 lint”。这是两码事。保存时格式化只影响当前编辑器中这个文件,改了格式就变了;但一个团队里,总会有人忘了装插件,或者用 WebStorm 的格式化器,或者直接把 IDE 的“保存时不格式化”开着。真正能兜底的机制是提交前的自动检查,见本文第 7 章。你可以在本地完全自由,但在代码进入仓库的最后一秒,机器会替规范把关。

6. ESLint 和 Prettier 打架时,谁来兜底

即使我们已经明确分工,真实项目里依然会遇到两者互相冲突的场景。这一章节专门讲冲突的根因和应对手段。

6.1 冲突的本质:规则重叠区

ESLint 自带很多格式类规则,Prettier 也能格式化同样的东西。最典型的例子是indent(缩进)和quotes(引号)。如果你在eslint.config.js里写了一条'quotes': ['error', 'single'],Prettier 的singleQuote: true也是单引号,两者通常一致,但一旦某个文件里有复杂的模板字符串、类型断言或者换行样式,两边对“这个引号该不该保留”的判断就可能出现偏差。再比如max-len和printWidth,前者是 ESLint 的“单行最大长度”,后者是 Prettier 的换行宽度,如果数值不同,ESLint 可能报一个“这一行太长”的错,而 Prettier 认为这行拆不了就不拆了,最后开发被一个无法自动修复的报错卡住。

6.2 统一解法:eslint-config-prettier

前面已经提到这个包,这里重点讲它的工作方式。安装:

npm install --save-dev eslint-config-prettier

然后把它加在eslint.config.js数组的最后一位:

import js from '@eslint/js'; import eslintConfigPrettier from 'eslint-config-prettier'; export default [ js.configs.recommended, { rules: { eqeqeq: ['error', 'always'], }, }, eslintConfigPrettier, ];

放在数组末尾很关键——扁平配置的规则覆盖顺序是“后出现的覆盖先出现的”,eslintConfigPrettier在里面实际上是一组把冲突格式规则关闭掉的配置。它本质上是一个命名非常直白的规则集合,比如'indent': 'off'、'quotes': 'off'、'semi': 'off'、'max-len': 'off'。引入它之后,ESLint 不会再对格式指手画脚,你在编辑器里看到的红色波浪线就只剩下真正的质量问题。

需要注意:eslint-config-prettier 只能关掉“规则冲突”,它不能替代 prettier 运行时。也就是说,你仍然需要单独跑prettier才能让代码格式化统一。

6.3 如果 ESLint 插件本身提供了格式化(比如 Vue)

在 Vue 项目里,eslint-plugin-vue的vue/html-indent这类规则和 Prettier 也是重叠的。eslint-config-prettier的另一个版本配置文件(比如eslint-config-prettier/flat)会自动处理一部分,但比较省心的方式是直接关闭vue/html-indent、vue/max-attributes-per-line这类格式规则,交给 Prettier 的统一插值。如果想减少后期维护成本,可以让 Prettier 来格式化 Vue 模板,把 ESLint 只用于校验 template 里的逻辑隐患。这是我们团队在 Vue 项目里的配置思路。

6.4 解决冲突时的排查方法论

如果你在真实项目里遇到某个报错分不清到底是 ESLint 还是 Prettier 在挑刺,有一个快速的判定方法:单独只跑 Prettier 格式化。在 VSCode 里对报错文件执行“Format Document”,如果格式化之后红色波浪线依然存在,那就是 ESLint 的质量规则报错;如果格式化之后波浪线消失了,说明是两部分对同一区域意见不一致。前者去改 ESLint rules,后者优先检查是不是忘了引入 eslint-config-prettier。

另外一个极常见的问题是“保存时格式化后引入了新的 ESLint 错误”。大部分情况是因为 ESLint 配置里打开了太多已废弃的格式规则,Prettier 一改,ESLint 就跟着报。解决方式同样是引入eslint-config-prettier,并把所有你在rules里手写的格式类规则清理干净。别觉得“多写几条规则代表更严格”,真正值钱的规则是那些能在逻辑层面拦截 bug 的规则。

7. 工程化进阶:提交前自动检查,彻底告别手写格式化

到了这一步,编辑器级别的格式化已经能覆盖 90% 的场景了。但剩下的 10% 足以让规范再次崩溃:有人没装插件、有人用命令行提交绕过 IDE、有人在 CI 上才想起来跑 lint。所以工程化的最后一公里是 hook 机制。

7.1 用 husky + lint-staged 实现提交前检查

lint-staged的理念是只对暂存区(git 里即将提交的改动文件)执行检查,而不是对整个仓库跑一遍。这样提交速度快得多,也不会因为仓库里一些遗留历史问题导致无法提交。安装方式:

npm install --save-dev husky lint-staged

然后在package.json里配置:

{ "lint-staged": { "*.{js,jsx,ts,tsx,vue}": [ "eslint --fix", "prettier --write" ] } }

再用 husky 添加 pre-commit 钩子:

npx husky init

这会生成.husky/pre-commit文件,内容默认是npm test。把它改成:

npx lint-staged

完成后,每次执行git commit,都会先跑一遍lint-staged检查当前暂存的代码文件。如果有 eslint 错误,提交直接失败,并告诉你哪里有问题。修复后重新 add 再 commit 即可。

7.2 为什么--fix和--write的顺序很重要

很多团队在 lint-staged 里写了eslint --fix后又写prettier --write,顺序看起来无所谓,但实际有讲究。建议先跑eslint --fix,再跑prettier --write。因为 prettier 重排之后,某些代码结构的缩进和换行可能又触发 ESLint 里个别剩余规则的报错,而 ESLint 的自动修复能力通常比较保守;反过来如果最后一把梭的是 prettier,它会把所有格式统一收尾,最终停留在仓库里的版本必然是被 prettier 重排过的。只要前面引入了eslint-config-prettier,这个顺序下最后结果一定是“格式规整且无 lint 错”。

7.3 提交被 lint 卡住时,新人最容易慌的 3 种情况

  1. 出现大量自动修复无法解决的错误(比如no-unused-vars),此时不要尝试把行内注释写得到处都是,而是认真搜索变量是否真的没用到。如果暂时真的需要“预留变量”,可以用下划线前缀加上argsIgnorePattern等规则配置。
  2. 出现“代码质量没问题但格式还是被改”的困惑,多半是endOfLine差异或本地 Prettier 版本和 package.json 里的版本不一致。请统一用项目本地安装的 prettier,避免依赖全局安装版本。把npx prettier写进 npm scripts 里,比让每个人直接敲prettier更可控。
  3. 有一两次提交时校验特别慢,原因是lint-staged把 node_modules 或 dist 目录的文件也给扫进去了。检查是不是忘了配ignores(上一章已经强调过)。强烈建议在package.json的 lint-staged 配置里再加一层只匹配源码目录的规则,比如"src/**/*.{js,ts,vue}"。

7.4 CI 线上执行 lint 与团队落地节奏

如果项目已经有了 CI 流程,建议在 CI 里加一个 lint 任务,让它跑eslint . && prettier --check .。prettier --check不是修改文件,而是只检查是否存在未被格式化的文件,返回非 0 状态就表示有文件不达标。这条命令在本地也会经常用到:合并大 PR 之前跑一遍,确保整棵目录树里的文件都符合规范,比只靠提交钩子查暂存区更全面。很多仓库后期还会在 CI 上加入类型检查、单测、构建步骤,lint 放在最早执行通常在十几秒到几十秒内完成,成本低收益高。

落地节奏上我的经验是:不要在一夜之间把所有历史代码全部格式化一遍,那会产生一个巨大的 diff,review 无从下手,还容易把一些真实变更淹没在格式改动里。正确策略是分两步走。第一步,从当前提交开始,强制所有新改动必须符合规范;第二步,用增量迁移的方式,每次改到哪个文件就顺手格式化哪个文件,或者用git blame和模块负责人机制逐步把老代码翻新。Prettier 的requirePragma和insertPragma也可以用来做渐进式迁移,但大多数团队用不了那么复杂,一个清晰的.prettierignore+ 主目录收缩范围就够了。

8. 关于这套方案,我最后想说的经验

不要被“规范”这个词吓退。前端工程化里最值得投资的从来不是堆砌规则,而是让机器的归机器,人的归人。ESLint 负责把逻辑风险挡在合并之前,Prettier 负责把代码长相统一到无需讨论,hooks 负责在合入之前兜底。这三样配合起来,你真正从规范里获得的第一份收益,不是代码变漂亮了,而是代码审查终于能回到“逻辑怎么改”而不是“这里为什么少了个分号”。

我自己经历过一个很典型的变化:在引入这套流程之前,每次评审新人的 PR,一大半评论都在讲格式;引入之后,新人保存一下、提交一下,代码格式和团队老成员几乎一模一样。剩下被 review 抓出来的,基本都是逻辑和边界情况。这种变化对一个团队的效率提升是实打实的。如果你刚接触这些概念,建议先把第 3 章和第 4 章的配置落地,再打开 VSCode 的保存时格式化,最后补上 pre-commit 钩子。每一步都能独立提升体验,不需要一次性搞个大改造。最后再分享一下我压箱底的小习惯:任何新项目我第一周一定会把 ESLint、Prettier、husky 全部接好,哪怕这个项目只是写一个几十行的脚本页。因为规范最好的落地时机是在第一行代码之前,补配置永远比迁移历史代码轻松得多。

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

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

立即咨询