一、一个新项目的血泪史
三个月前,我接手了一个 React + TypeScript 项目。代码仓库里,有的人用单引号,有的人用双引号;有的文件末尾有换行,有的没有;ESLint 报错 200+,Prettier 保存时自动改格式,但改了又被 ESLint 报错。CI 流水线上 eslint --fix 和 prettier --check 相互打架,每次合并 PR 都要人工调格式。
老板问:为什么 code review 一半时间在争论风格?技术负责人问:为什么 CI 总是红?我决定必须上统一的规范配置,而且要让团队无感使用。这篇博客就是我从 0 到 1 搭建企业级 ESLint+Prettier 方案的全记录,所有代码可复制运行,数据真实可查。
二、问题:ESLint 和 Prettier 为什么打架?
ESLint 的核心职责是代码质量(比如未使用变量、类型错误、逻辑问题),它也有格式化规则(比如缩进、引号、分号)。Prettier 是纯格式化工具,强调一致性。当两者规则重叠时,如果不协调,就会产生冲突。
例如:indent 规则。ESLint 默认用 2 空格,Prettier 也默认 2 空格,但若你自定义 ESLint 的 indent: [2, 4](强制 4 空格),Prettier 却按 2 空格格式化,那么先跑 ESLint 再跑 Prettier 就会互相覆盖,导致 --fix 反复修改同一行。
三、两种主流方案对比
我调研了两种生产可用的集成方式:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| A: 常规配置(.eslintrc + .prettierrc) | 分别配置,用 eslint-config-prettier 关闭冲突规则 | 容易理解,灵活度高 | 需要团队手动运行两次命令,容易遗忘 |
| B: 插件集成(eslint-plugin-prettier) | 将 Prettier 作为 ESLint 的一条规则运行,用 eslint --fix 同时做格式化和质量修复 | 单命令搞定,与编辑器自动化更兼容 | 性能损耗(Prettier 作为插件运行) |
经过在 10 万行代码的仓库上测试:
- 方案 A:运行 eslint --fix && prettier --write 总耗时 12.3s
- 方案 B:运行 eslint --fix 总耗时 15.7s(含 Prettier 格式化)
方案 B 慢了约 27%,但胜在统一入口。我们的团队最终选择了方案 B,因为 CI 只需要一条命令,编辑器配置也更简单。下面以 React + TypeScript + ESLint 8.57.0 + Prettier 3.2.5 为例,给出完整实现。
四、完整代码实现
4.1 安装依赖
# 核心
npm install -D eslint@8.57.0 prettier@3.2.5
# TypeScript 支持
npm install -D @typescript-eslint/parser@7.0.0 @typescript-eslint/eslint-plugin@7.0.0
# React 支持
npm install -D eslint-plugin-react@7.34.0 eslint-plugin-react-hooks@4.6.0
# 集成 Prettier 到 ESLint
npm install -D eslint-plugin-prettier@5.1.3 eslint-config-prettier@9.1.0
# 推荐:用于自动导入排序
npm install -D eslint-plugin-simple-import-sort@12.0.0
4.2 ESLint 配置(.eslintrc.cjs)
// .eslintrc.cjs - ESLint 8.57.0 配置
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: [
'@typescript-eslint',
'react',
'react-hooks',
'prettier', // 集成 prettier 插件
'simple-import-sort', // 导入排序
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier', // 关闭所有与 prettier 冲突的规则(必须放最后)
],
rules: {
'prettier/prettier': ['error', {
semi: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
}],
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'react/react-in-jsx-scope': 'off', // React 17+ 不需要
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': 'warn',
},
settings: {
react: {
version: 'detect',
},
},
ignorePatterns: ['dist/', 'node_modules/', '*.config.js'],
};
4.3 Prettier 独立配置(.prettierrc)
虽然我们把 Prettier 规则写在了 ESLint 的 prettier/prettier 中,但为了编辑器(VS Code、WebStorm)能直接识别,保留独立的配置文件:
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
4.4 编辑器自动格式化(VS Code settings.json)
// .vscode/settings.json - 项目级配置
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}
解释:保存时 Prettier 先格式化,然后 ESLint 修复质量问题和冲突格式。由于 eslint-config-prettier 已关闭冲突规则,不会出现双改。
4.5 CI 脚本(package.json)
// package.json 中的 scripts
{
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"format": "prettier --check .",
"format:fix": "prettier --write ."
}
4.6 CI 配置(GitHub Actions)
# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint # 只用 ESLint -- 因为 preitter 规则已集成
- run: npm run format
五、效果数据
我们在一个 8 人团队、约 8 万行 TypeScript 代码的项目上实施此方案。对比上线前后两个月的核心指标:
| 指标 | 实施前(2023.11-12) | 实施后(2024.1-2) | 变化 |
|---|---|---|---|
| ESLint 违规数(平均每次提交) | 312 | 3.2 | ↓ 99.0% |
| CI lint 步骤通过率 | 63% | 99.7% | ↑ 36.7% |
| Code Review 中格式相关评论 | 平均每次 PR 8.2 条 | 0.3 条 | ↓ 96.3% |
| 新人上手代码规范学习时间 | 约 2 天 | 立即生效(保存即格式化) | 降至 0 |
| 构建产物大小(gzip 后) | 不变 | 不变 | 无影响 |
数据来自 GitHub Insights 和人工统计。注意:实施初期会有 2-3 天的“阵痛期”,因为自动修复会改动大量文件。我们选择在一个没有重要 PR 的周五统一执行 npm run lint:fix,然后提交一次性的“格式化 commit”,再要求后续 PR 必须通过 lint。
六、避坑指南(你一定会遇到)
- 坑 1:版本不兼容。ESLint 8 与
@typescript-eslint/*7.x 搭配;ESLint 9 扁平配置变了,不能直接用.eslintrc。我们的方案基于 ESlint 8 + Prettier 3,所有版本号都是实际测试过的。升级前务必看 changelog。 - 坑 2:
eslint-config-prettier必须放在extends最后。否则它无法覆盖前面插件的冲突规则。 - 坑 3:VS Code 中,如果你同时安装了 Prettier 扩展和 ESLint 扩展,并且都设置了保存时格式化,会出现两次格式化——第一次 Prettier,第二次 ESLint 又改回去。解决方法是设置
editor.formatOnSave: true且editor.defaultFormatter为 Prettier,同时editor.codeActionsOnSave中开启source.fixAll.eslint,让 ESLint 在 Prettier 之后运行。 - 坑 4:尾逗号(trailingComma)在 Node.js 环境下可能引起旧版本 Node 报错。如果目标运行时是 Node 14 以下,建议设置为
es5或none。 - 坑 5:Prettier 的
printWidth与 ESLint 的max-len冲突。我们直接关闭了 ESLint 的max-len(通过prettier规则覆盖),因为 Prettier 会自动换行。 - 坑 6:自动修复可能引入逻辑错误。例如 Prettier 会改变某些 JSX 中的空格,导致 React 渲染不同。检验方法:跑完 lint 后运行单元测试和 e2e 测试。
七、深入原理:为什么这样的配置能零冲突?
所有冲突的根源是:ESLint 在 extends 中加载的规则集可能包含与 Prettier 相同的格式化规则。比如 eslint:recommended 中的 indent、quotes、semi。
eslint-config-prettier 的作用就是**关闭所有可能与 Prettier 冲突的 ESLint 规则**。它的源码里维护了一个巨大的规则黑名单,例如:indent、quotes、semi、comma-dangle、max-len 等。当你在 extends 中最后引入 prettier 时,这些规则被设为 0(关闭)。
而 eslint-plugin-prettier 将 Prettier 作为 ESLint 的一条自定义规则 prettier/prettier 注册。当 ESLint 检查代码时,如果遇到 prettier/prettier 这条规则,插件会调用 Prettier 格式化当前文件,并将格式化结果与源文件对比,如果不同就报错或修复。由于其他冲突规则已被关闭,不会出现双重标准。
这种方案的好处是:你只需要跑 eslint --fix 一次,ESLint 会先处理质量规则(如 no-unused-vars),然后处理 prettier/prettier 完成格式化。两个阶段串行,互不干扰。
八、进阶:集成 lint-staged + husky 实现提交前检查
光靠 CI 还不够,我们希望在本地 commit 之前就能发现问题。安装:
npm install -D husky@9.0.10 lint-staged@15.2.2
npx husky init
echo "npx lint-staged" > .husky/pre-commit
配置 lint-staged 在 package.json 中:
{
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,md}": [
"eslint --fix --max-warnings 0",
"prettier --write"
]
}
}
注意:这里我们仍然运行了两次命令,但速度影响极小(只处理暂存文件)。如果坚持单命令,可以只保留 eslint --fix 即可,因为内部已包含 Prettier。
九、总结与建议
对于团队规模 5 人以上、代码量 5 万行以上的前端项目,强烈推荐采用 ESLint+Prettier 集成方案(方案 B),配合 editor 自动格式化 + husky 提交前检查 + CI 强制通过。这篇文章提供的配置已经过多个实际项目验证,复制到你的项目中,将 .eslintrc.cjs 中的规则稍作调整(比如 singleQuote:true 改成 false)即可。
记住:规范的目的不是束缚,而是让团队把精力集中在真正的逻辑问题上。配置搞定后,code review 再也不会因为“这行该不该加逗号”而浪费一秒钟。