代码规范工具深度解析:ESLint、Prettier与Husky的工程化实践
前端代码规范体系是职责分离三件套:ESLint 管质量(写得对不对)、Prettier 管风格(好不好看)、Husky 加 lint-staged 管提交门槛。 三者组成从编辑器到仓库的完整质量防线,面试常考 ESLint 基于 AST 的工作方式与 lint-staged 的暂存区过滤。
一句话概括
前端代码规范体系的本质是职责分离三件套:ESLint 管「代码质量」(你写得对不对)、Prettier 管「代码风格」(你写得好不好看)、Husky + lint-staged 管「提交门槛」(不规范不让提交)——三者组成从编辑器到仓库的完整质量防线。
核心知识点
1. ESLint:基于 AST 的代码质量分析器
ESLint 将源码解析为 AST,遍历每种节点类型并执行规则。你可以用 Flat Config 清晰声明规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// eslint.config.js(ESLint 9+ Flat Config)
import js from '@eslint/js';
export default [
js.configs.recommended,
{
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-var': 'error',
'prefer-const': 'error',
'eqeqeq': ['error', 'always'], // 必须用 === 而非 ==
},
},
{
ignores: ['dist/**', 'node_modules/**', '.next/**'],
},
];
Flat Config 的核心优势:数组顺序即应用顺序,后面的规则可以覆盖前面的,彻底告别旧版 extends 的隐式继承迷宫。
2. Prettier:固执己见的确定性格式化
Prettier 和 ESLint 的根本区别:ESLint 告诉你”应该(不)做什么”,Prettier 强制”代码必须长什么样”。Prettier 基于 Philip Wadler 的”A prettier printer”论文,使用 Doc IR(中间表示)实现确定性格式化——相同代码 + 相同配置 = 永远相同输出
1
2
3
4
5
6
7
8
9
// prettier.config.js
export default {
printWidth: 100,
tabWidth: 2,
singleQuote: true,
semi: true,
trailingComma: 'all',
arrowParens: 'always',
};
与 ESLint 的职责分离:用 eslint-config-prettier 关闭 ESLint 中所有格式化相关规则,让 ESLint 只做质量检查,格式化全部交给 Prettier。
1
2
3
4
5
6
7
8
// eslint.config.js
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
// ...自定义质量规则
eslintConfigPrettier, // ⚠️ 必须放最后,关闭冲突的样式规则
];
3. Husky 9+:Git Hook 的轻量管理器
Husky 9+ 彻底重写,不再依赖 package.json 的配置字段,纯粹基于 .husky/ 目录的 shell 脚本:
1
2
3
4
5
6
7
8
9
10
# 初始化
npx husky init
# .husky/pre-commit — commit 前自动检查
#!/usr/bin/env sh
npx lint-staged
# .husky/commit-msg — 校验 commit message
#!/usr/bin/env sh
npx --no -- commitlint --edit $1
底层机制:npx husky 在 .git/hooks/ 创建符号链接指向 .husky/_/husky.sh,运行时自动查找 .husky/ 下对应 hook 名的脚本。设计师之所以巧妙:钩子脚本在仓库版本控制中,而 .git/hooks 本身不提交。
4. lint-staged:只检查暂存区文件
1000 个文件的仓库只改了 3 个,为什么跑全量 ESLint?lint-staged 只读取 git diff --staged --name-only 的输出,对暂存区文件运行指定命令:
1
2
3
4
5
6
// lint-staged.config.js
export default {
'*.{ts,tsx}': ['eslint --fix', 'prettier --write'],
'*.{json,md,css}': ['prettier --write'],
'*.{png,jpg,svg}': ['imagemin-lint-staged'], // 图片压缩检查
};
核心价值:1 秒完成 3 个文件的检查,不会因为其他 997 个文件的历史遗留问题阻塞 commit。
5. 自定义 ESLint 规则:防魔数
理解 ESLint 核心机制的最佳实践是手写一条规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// no-magic-numbers.js
export default {
meta: { type: 'suggestion', docs: { description: '禁止未命名的数字常量' } },
create(context) {
return {
Literal(node) {
if (typeof node.value !== 'number') return;
// 排除默认值、数组索引等合理场景
const { parent } = node;
if (['VariableDeclarator', 'ArrayExpression', 'SwitchCase'].includes(parent.type)) return;
if (parent.type === 'BinaryExpression') {
context.report({ node, message: `魔数 ${node.value},建议定义为命名常量` });
}
},
};
},
};
其实你每天都在用
- VS Code 保存时自动格式化:ESLint + Prettier 扩展的
editor.codeActionsOnSave配置,保存瞬间自动修复 git commit被 lint-staged 拦截:commt 前eslint --fix自动修复可修复的问题,失败时 commit 被阻止console.log被 ESLint 报 warning:no-console: 'warn'防止调试代码遗漏到生产- 团队成员用不同缩进却不出冲突:Prettier 在 commit 前统一格式化,个人编辑器偏好无所谓
- CI 流水线中
--max-warnings 0:任何 ESLint 警告都会导致 CI 失败,强制团队零警告
常见误解(FAQ)
❌ 误区 1:「ESLint 和 Prettier 可以互相替代」
ESLint 管”质量”(有没有未使用的变量、是否用了 ==),Prettier 管”格式”(换行、缩进、引号)。它们是互补关系,不是替代关系。eslint --fix 虽然也能格式化,但远不如 Prettier 的确定性算法稳定。
❌ 误区 2:「lint-staged 只是锦上添花,不是必须品」
不做 lint-staged 的危害:项目中有 1000 个文件,早期遗留了 50 个 lint 错误。每次 commit 都跑全量检查,你修改的 3 个文件没问题,但被第 501 个文件的旧错误阻塞——而且那不是你造成的。lint-staged 就是解决”谁污染谁治理”的工具。
❌ 误区 3:「Husky 和 lint-staged 是一回事」
Husky 管理 Git Hook(什么时候触发),lint-staged 执行具体命令(触发后干什么)。两者职责不同:你可以用 Husky 触发其他 hook(如 pre-push 跑测试),而 lint-staged 也可以直接通过 npm scripts 调用,不依赖 Husky。
❌ 误区 4:「配置好工具就行了,Rule 越严格越好」
过度严格的规则适得其反:max-lines-per-function: 20 在工具函数里会让人强行拆分出无意义的”伪函数”;无差别禁止 any 会导致大量 // eslint-disable-next-line。好的规范是约束关键问题,而非寸草不生。
一句话总结
代码规范工具不是给开发者戴枷锁,而是用自动化约束消除团队无意义的风格争论——与其在 Code Review 里讨论”这里该用单引号还是双引号”,不如让 Prettier 在保存那一瞬间就替你决定了。