前端工程化基石:为什么需要 ESLint + Prettier + Husky?

写代码这件事,换个角度看,从来不是一个人的单打独斗。哪怕是个人项目,过几个月回头看自己写的代码,如果没有规范,大概率也会骂一句“这写的什么鬼”。在团队协作里,这种情况更常见:有人习惯用双引号,有人死磕单引号;有人写分号,有人觉得那是累赘;还有人把逻辑写得像意大利面条一样绕。这时候,前端工程化这套“组合拳”就派上用场了。

很多新手会问,为什么不能只用一个工具?咱们来拆解一下这三个工具各自的“岗位职责”。

ESLint 是代码的“质量检查员”。它负责找茬,哦不,是找错误。比如你定义了一个变量却没用,或者在一个条件判断里写了恒为真的表达式,或者用了 var 这种过时的声明方式,ESLint 会毫不留情地给你标红。它关注的是代码逻辑是否正确、是否存在潜在 Bug。截至目前的最新版本 ESLint v9.15.0 (2024年11月),它已经成为了前端静态分析的标配。

Prettier 则是“强迫症式”的“美容师”。它不管你代码写得好不好,只管你代码长得好不好看。缩进是 2 格还是 4 格?尾逗号要不要加?换行在哪里换?Prettier 的态度非常 opinionated(自以为是),意思就是:别废话,按我的来。最新版 Prettier v3.4.1 (2024年10月) 支持几乎所有主流的前端语言,包括 JS/TS/CSS/JSON 等,它能移除你所有的原始样式,然后按照统一标准重新输出。

那为什么还要加个 Husky?这就好比你在路口立了个牌子说“请红灯停”,但总有人闯。Husky 就是那个电子警察,它利用 Git Hooks 机制,在你执行 git commit 的那一刻,强制检查你的代码。如果不符合规范,直接拒绝提交。现在 Husky v9.1.7 (2024年10月) 非常轻量且零依赖,用起来很顺手。

为什么不能只用其中一个?

很多刚入行的同学容易避雷经验,觉得装一个就够了。其实 ESLint 和 Prettier 虽然偶尔会“打架”,但它们的关注点完全不同。

📌 要点提醒:解决“打架”问题

既然 ESLint 也管格式(比如缩进、空格),Prettier 也管格式,那它们肯定会冲突。解决办法就是引入 eslint-config-prettier。这个包的作用是关闭 ESLint 中所有与 Prettier 冲突的规则,让 Prettier 全权接管格式化。

可以这么理解,就是告诉 ESLint:“兄弟,格式化这块你别管了,交给 Prettier 吧,你专心抓逻辑错误就行。”

{ "extends": [ "eslint:recommended", "plugin:prettier/recommended" ] }

现在的趋势是,工具链在向原生语言(Rust/Go)迁移,比如新兴的 Biome 试图挑战传统组合。但在 2024 年,ESLint + Prettier 依然是最稳妥、生态最丰富的选择,特别是当你面对复杂的遗留系统重构或者大型开源项目时,这套组合的成熟度是无敌的。

---

2024 最新实战:ESLint v9 Flat Config 配置与 Prettier 集成

如果你还在用 .eslintrc.js 或者 .eslintrc.json,那你真的有点落伍了。2024 年的今天,ESLint v9 已经默认抛弃了传统的“层级查找”配置模式,全面拥抱 Flat Config

简单来说,以前 ESLint 会在你项目的每一个目录里找 .eslintrc,如果找不到就往父级找,效率低且容易出 Bug。现在的 Flat Config 就是一个文件:eslint.config.js(或者 .mjs)。这个文件导出一个数组,所有的配置都在这里,清晰明了,性能还更好。

初始化与安装

咱们先安装必要的依赖。确保你用的 Node 版本不要太老。

npm init -y npm install eslint@^9.15.0 prettier@^3.4.1 eslint-config-prettier@^9.1.0 --save-dev

注意,这里我们安装的是 ESLint v9.15.0(2024年11月最新版)和 Prettier v3.4.1

配置 ESLint Flat Config

现在,在项目根目录创建 eslint.config.js。这是核心步骤,也是很多人容易避雷经验的地方。

// eslint.config.js import js from '@eslint/js' import prettierConfig from 'eslint-config-prettier' export default [ // 1. 基础配置:使用 ESLint 推荐的规则 js.configs.recommended, // 2. 针对特定文件配置规则 { files: ['**/*.js', '**/*.mjs'], languageOptions: { ecmaVersion: 2022, sourceType: 'module', globals: { console: 'readonly', document: 'readonly', window: 'readonly' } }, rules: { 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-console': ['warn', { allow: ['warn', 'error'] }] } }, // 3. 关闭与 Prettier 冲突的规则(必须放在最后或靠后) prettierConfig, // 4. 忽略特定文件或目录 { ignores: ['dist/', 'node_modules/', 'build/'] } ]

配置 Prettier

Prettier 的配置相对简单,创建一个 .prettierrc 文件。

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

把两者集成起来

你可能会看到有些教程让你装 eslint-plugin-prettier 把 Prettier 当 ESLint 规则跑。但在 2024 年,更推荐的做法是eslint-config-prettier

核心要点:eslint-config-prettier 的作用是“关灯”。它把 ESLint 里那些管格式的灯关掉,这样 Prettier 跑起来就不会被 ESLint 干扰。

package.json 里加上运行脚本:

{ "scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write ." } }

📖 学习建议:处理迁移痛点

如果你是从旧项目升级上来的,可能会发现很多插件不支持 Flat Config。这时候你可以尝试在 eslint.config.js 里用 FlatCompat 来兼容旧插件,但这通常是下策。

我的建议是:如果是新项目,直接上 Flat Config。如果是老项目,先别急着升 ESLint v9,先用 v8 过渡,或者看看你的核心插件是否已经支持了 eslint.config.js。另外,Flat Config 里没有 extends 这种字符串数组了,而是直接导入对象,这种“配置即代码”的方式虽然刚开始不习惯,但确实更强大。

---

Git 提交守卫:Husky 与 lint-staged 高效配置指南

代码规范配好了,但怎么保证队友提交代码时一定会跑?靠自觉是不行的,必须上技术手段。这就是 Huskylint-staged 登场的时候了。

Husky 负责“拦截”,lint-staged 负责“精准打击”。

其实,如果你每次提交都全量检查整个项目的代码,那速度慢得你会想砸电脑。特别是老项目,几万行代码,全跑一遍 Lint,光等就要几分钟。lint-staged 的意思就是:只检查你这次 git add 添加到暂存区(staged)的文件。这样速度快,反馈也及时。

安装与初始化

咱们先把这两个工具装上。注意 Husky 现在是 v9.1.7 (2024年10月),它的初始化方式跟以前不一样了。

npm install husky@^9.1.7 lint-staged --save-dev npx husky init

运行 npx husky init 后,你会发现项目根目录多了个 .husky 文件夹,里面有个 pre-commit 文件。

配置 pre-commit 钩子

打开 .husky/pre-commit 文件,把里面的 npm test 改成运行 lint-staged

#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npx lint-staged

配置 lint-staged

接下来,我们需要在 package.json 里告诉 lint-staged 该对哪些文件做什么操作。

{ "scripts": { "prepare": "husky" }, "lint-staged": { "*.{js,mjs,cjs,ts,jsx,tsx}": [ "eslint --fix", "prettier --write" ], "*.{css,scss,md,json}": [ "prettier --write" ] } }

这个配置的意思是:当你试图提交 JS/TS 文件时,先跑 eslint --fix 自动修复,再跑 prettier --write 格式化;如果是样式或文档文件,只跑 Prettier。

模拟一次提交

现在你可以试试看效果了。

如果一切正常,你会看到 Husky 触发了 lint-staged,然后它开始检查文件。如果代码有逻辑错误(ESLint 报错),或者格式不对,提交会被直接终止。

🔧 实战技巧:解决“Windows 环境兼容”问题

在 Windows 上用 Husky 有时候会遇到脚本执行权限或者路径问题。

实际案例经验:确保你的 pre-commit 文件第一行是 #!/usr/bin/env sh,并且在 Windows 上安装 Git 时选择了“Use Git and optional Unix tools from the Command Prompt”,这样 sh 命令才能找到。

另外,很多团队会纠结 lint-staged 里该先跑 ESLint 还是先跑 Prettier。其实无所谓,因为 eslint-config-prettier 已经关掉了冲突规则,谁先谁后都不打架。但为了保证逻辑错误优先拦截,建议先跑 eslint --fix,如果 ESLint 崩了,Prettier 就不用跑了,省点时间。

随着 Monorepo 架构的流行,在大型仓库里配置 Husky 也是个技术活。现在流行在根目录配置,利用 lint-staged 的过滤能力,只检查改动包里的文件,这样既保证了规范,又不会拖慢整个 CI/CD 流水线的速度。

4. 进阶技巧:Monorepo 配置策略与解决规则冲突

可以这么理解,当你从单仓库切换到 Monorepo(比如用 pnpm workspace 或者 Turborepo)的时候,你会发现原来的 ESLint 和 Prettier 配置有点不够用了。如果每个 package 都单独搞一套配置,维护起来简直是噩梦,版本一更新,改起来能让你怀疑人生。所以,咱们得搞一套“基地+哨所”的策略。

Monorepo 下的配置策略

核心思路就是:共享基础配置,局部差异化覆盖

在 Monorepo 的根目录,我们通常会有一个基础的 eslint.config.js(注意,咱们现在用的是 ESLint v9.15.0,默认就是 Flat Config 了,别再搞 .eslintrc 那一套了)。然后,在各个子 package 里,我们只需要去继承和微调。

这里有个很骚的操作,因为 Flat Config 本质上就是一个 JavaScript 数组,我们可以直接 import 根目录的配置,然后利用数组的展开运算符 ... 来合并。

假设你有这样一个结构:

根目录配置 (eslint.config.js):

这是咱们的大本营,放那些通用的规则,比如禁止 console.log(生产环境),或者强制使用 const

// eslint.config.js at root import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import prettier from 'eslint-config-prettier'; /** @type {import('eslint').Linter.FlatConfig[]} */ export default [ { ignores: ['dist/**', 'node_modules/**', '*.config.js'], }, ...tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { rules: { 'no-console': ['warn', { allow: ['warn', 'error'] }], '@typescript-eslint/no-unused-vars': 'error', }, } ), prettier, // 一定要放在最后,关闭冲突规则 ];

子 Package 配置 (packages/client/eslint.config.js):

客户端可能需要浏览器环境的全局变量,或者 React 的规则。这时候咱们直接导入根配置,然后追加。

// packages/client/eslint.config.js import rootConfig from '../../eslint.config.js'; import reactPlugin from 'eslint-plugin-react'; /** @type {import('eslint').Linter.FlatConfig[]} */ export default [ ...rootConfig, // 继承根配置 { files: ['**/*.{ts,tsx}'], languageOptions: { globals: { window: 'readonly', document: 'readonly', }, }, plugins: { react: reactPlugin, }, rules: { 'react/react-in-jsx-scope': 'off', // 新版本 React 不需要这个 'no-console': 'off', // 客户端开发时可能允许 console }, }, ];

解决 ESLint 与 Prettier 的“打架”问题

这可是个老生常谈的话题了。换个角度看,ESLint 管的是代码逻辑对不对(比如禁止未使用的变量),Prettier 管的是代码长得好不好看(比如该不该加分号)。但是,有些规则是重叠的,比如换行、引号、空格。

值得留意的是,在 2024 年的今天,如果你用的是 ESLint v9 和 Prettier v3.4.1,解决冲突的方式非常直接。

📌 要点提醒:千万别再同时用 eslint-plugin-prettiereslint-config-prettier 了,那是老黄历。eslint-plugin-prettier 是把 Prettier 当 ESLint 规则跑,性能差,而且报出来的错误是格式化错误,很烦人。现在的最佳实践是:让 ESLint 只管逻辑,Prettier 只管格式,用 eslint-config-prettier 让他俩井水不犯河水。

如果你在 Monorepo 里遇到格式冲突,检查一下是不是某个子包的配置里忘了加 prettier。有时候为了省事,我会在根目录的 package.json 里强制所有 package 共享同一个 prettier 配置:

// package.json (root) { "name": "my-monorepo", "scripts": { "lint": "eslint . --cache", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\"" }, "prettier": "@your-scope/prettier-config" // 甚至可以抽成独立的 npm 包 }

---

5. :ESLint v9 迁移痛点与 Biome 等新兴工具展望

咱们做前端的,最怕的就是这种大版本升级。ESLint v9.15.0(2024年11月发布)带来了一个巨大的变化:Flat Config (eslint.config.js) 成为了默认选项。如果你还在用 .eslintrc.json,ESLint 会给你抛警告,甚至直接罢工。

ESLint v9 迁移的那些“坑”

我最近给公司项目做迁移,踩了不少坑,这里给大伙儿排个雷。

1. 配置文件的巨变

以前我们是写对象,现在要写数组。以前有 env,现在得在 languageOptions 里搞 globals

以前:

{ "extends": ["eslint:recommended"], "env": { "es2020": true, "node": true } }

现在(Flat Config):

import js from '@eslint/js'; export default [ js.configs.recommended, { languageOptions: { globals: { console: 'readonly', // 手动定义全局变量 process: 'readonly' }, ecmaVersion: 2020, sourceType: 'module' } } ];

2. 插件导入方式的改变

这是最坑的地方。以前插件名是字符串 "react",现在得 import 进来当对象用。

如果你装了 eslint-plugin-react,以前是 plugins: ['react'],现在得这么写:

import reactPlugin from 'eslint-plugin-react'; export default [ { plugins: { react: reactPlugin // 注意这里的 key 是命名空间 }, rules: { 'react/prop-types': 'off' } } ];

3. 忽略文件的处理

以前 .eslintignore 很好用,现在 Flat Config 里推荐在配置数组里加一个对象专门处理忽略。

export default [ { ignores: ['dist/**', '**/*.min.js', 'node_modules/'] }, // 其他配置... ];

⚡ 效率提示:如果你还没迁移,别硬刚。直接用官方提供的迁移工具:npx @eslint/migrate-config .eslintrc.json。它能帮你自动转成 eslint.config.js,虽然不一定完美,但能省 80% 的力气。

新兴工具展望:Biome 真的能取代它们吗?

聊到 2024-2026 年的趋势,就不得不提 Biome。这玩意儿是用 Rust 写的,号称是 ESLint + Prettier + 更多工具的一站式替代品。

可以这么理解,如果你受够了 npm install 几百个包,受够了 ESLint 启动慢,Biome 确实香。

但是,作为一个有 5 年经验的工程师,我得泼点冷水。

我的看法

对于新项目,特别是中小型项目或纯工具库,直接上 Biome 是完全没问题的,体验极佳。

对于大型 Monorepo 或遗留系统,还是老老实实守着 ESLint v9 + Prettier v3 吧。毕竟稳定性第一,而且 eslint-config-prettier (最新版依然坚挺) 这种组合久经沙场,出了问题好排查。

现在的趋势是,传统的 JS 工具链都在向原生语言迁移(比如 Ruff 之于 Python),前端这边 Biome 和 Oxc 势头很猛。但 ESLint 毕竟是基石,短期(2-3年内)内地位还是很难被撼动的。咱们作为开发者,保持关注,但没必要盲目追新去重构现有项目的基建。