前端代码规范与工程化实战:ESLint + Prettier + Husky 全流程搭建
为什么你的团队需要代码规范工程化
说两个真实场景。
场景一:同事 A 用 2 空格缩进,同事 B 用 4 空格。每次 Code Review,80% 的 diff 是缩进和换行。评审人精力消耗在格式上,逻辑问题反而被忽略。
场景二:一个实习生提交了一段代码,npm run build 通过了,合并到主分支。第二天生产环境报错:「typo in variable name」。如果有自动化检查,这个提交根本过不了 pre-commit hook。
代码规范不是「条条框框约束人」,而是「用工具自动保证底线,让人把精力放在真正有价值的决策上」。这篇文章基于我在三个团队的实践——从最开始的 .eslintrc 一团糟,到现在 ESLint Flat Config + Prettier + Husky 全自动化的完整流程。
快速预览:搭建完后的效果
git add .
git commit -m "feat: add user login"
# Husky 自动触发 pre-commit hook
# → lint-staged 对暂存文件运行 ESLint --fix + Prettier --write
# → commitlint 校验 commit message 格式
# → 全部通过后才真正提交
如果 ESLint 报错或 commit message 不符合 Conventional Commits,提交会被拒绝。这意味着不会再有格式不统一或类型不安全的代码进入仓库。
ESLint Flat Config:告别 .eslintrc
2024 年底 ESLint 9.x 将 Flat Config 设为默认,旧的 .eslintrc.* 进入弃用周期。如果你还在用旧配置,现在就该迁移。
Flat Config 基础结构
// eslint.config.mjs(注意是 .mjs,Flat Config 默认使用 ESM)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';
export default tseslint.config(
// 1. 基础推荐规则
js.configs.recommended,
// 2. TypeScript 规则
...tseslint.configs.recommended,
// 3. 全局配置
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parserOptions: {
project: './tsconfig.json',
},
},
},
// 4. 自定义规则
{
rules: {
// TypeScript 严格模式
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
// 通用规则
'no-console': ['warn', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
'no-var': 'error',
'eqeqeq': ['error', 'always'],
},
},
// 5. 忽略文件
{
ignores: [
'dist/**',
'node_modules/**',
'.next/**',
'coverage/**',
'*.config.{js,mjs,ts}',
],
}
);
为什么迁移到 Flat Config
| 旧格式 (eslintrc) | 新格式 (Flat Config) | 说明 |
|---|---|---|
.eslintrc.js / .eslintrc.json |
eslint.config.mjs |
统一为单个文件 |
extends 链式继承 |
tseslint.config() 组合 |
函数组合替代继承 |
overrides + 文件匹配 |
配置对象中的 files |
更直观的覆盖规则 |
parserOptions 分散 |
统一在 languageOptions |
结构更清晰 |
| 插件用字符串名称 | import pluginX from '...' |
ESM 原生 import |
从旧配置迁移
# 如果你的项目还在用 eslintrc:
# 1. 升级 ESLint 到 9.x
npm i -D eslint@^9.0.0
# 2. 其他插件也要升级到支持 Flat Config 的版本
npm i -D @eslint/js typescript-eslint@^8.0.0 globals
# 3. 运行迁移向导(会自动读取旧配置生成新配置)
npx @eslint/migrate-config .eslintrc.js
Prettier:格式化交给专业工具
ESLint 管代码质量,Prettier 管代码格式。不要让 ESLint 去操心缩进和换行——这不是它的本职工作,反而会导致规则冲突。
配置
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"jsxSingleQuote": false
}
// .prettierignore
dist
.next
node_modules
coverage
pnpm-lock.yaml
package-lock.json
让 ESLint 和 Prettier 和平共处
使用 eslint-config-prettier 关闭 ESLint 中与 Prettier 冲突的规则:
// eslint.config.mjs
import prettier from 'eslint-config-prettier';
export default tseslint.config(
// ...前面的配置
prettier, // 放最后,确保覆盖所有冲突规则
);
Husky + lint-staged:在提交前拦住问题
只配置 ESLint 和 Prettier 是不够的——团队成员可能忘记运行。自动化 Git Hook 才是最后的防线。
安装
npm i -D husky lint-staged
npx husky init
lint-staged 配置
// package.json
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{json,css,scss,md}": [
"prettier --write"
],
"*.{ts,tsx}": [
"bash -c 'npx tsc --noEmit'"
]
}
}
Husky pre-commit hook
# .husky/pre-commit
npx lint-staged
注意:--max-warnings=0 意味着即使只有一个 warning 级别的问题也会阻止提交。这在团队初期会很痛苦,但长期来看能保证代码库质量不上涨。
commitlint:统一提交信息格式
「fix bug」「update」「修改了一下」——这种提交信息会让半年后的你恨死自己。Conventional Commits 规范让 changelog 可以自动生成,让版本号可以自动推断。
npm i -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.mjs
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修 bug
'docs', // 文档
'style', // 格式(不影响代码逻辑)
'refactor', // 重构
'perf', // 性能优化
'test', // 测试
'chore', // 构建/工具
'ci', // CI 配置
'revert', // 回滚
],
],
'subject-case': [0], // subject 不限制大小写
},
};
# .husky/commit-msg
npx --no -- commitlint --edit $1
正确的提交格式:
git commit -m "feat: add user authentication module"
git commit -m "fix: resolve token refresh race condition"
git commit -m "perf: reduce bundle size by tree-shaking unused utils"
git commit -m "docs: update API documentation for v2.0"
VS Code 团队配置同步
.vscode/settings.json 不加入 .gitignore——它应该进仓库,保证团队统一:
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.useFlatConfig": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
],
"unwantedRecommendations": []
}
新成员 clone 仓库后打开 VS Code,右下角会自动提示安装推荐插件。配置也同步到位——零沟通成本。
CI 流水线集成
本地 Hook 可能被 --no-verify 跳过,CI 里再校验一层:
# .github/workflows/lint.yml
name: Lint & Format Check
on:
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npx eslint . --max-warnings=0
- name: Check formatting
run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css}"
- name: Type check
run: npx tsc --noEmit
迁移真实项目的经验
第一步:先格式,后规则
不要一上来就开启所有 ESLint 规则——项目会爆几百上千个错误。
# 1. 先用 Prettier 格式化整个项目
npx prettier --write "src/**/*.{ts,tsx}"
# 2. 提交格式化变更(一次巨大的 diff,但内容是安全的)
git add .
git commit -m "style: format all files with Prettier"
# 3. 逐步开启 ESLint 规则
# 先只开 error,warning 暂时关闭
第二步:渐进式收紧
在 eslint.config.mjs 中:
// 第一期:只开 error 级别
{
rules: {
'@typescript-eslint/no-explicit-any': 'warn', // 先 warn
'no-console': 'off', // 先不开
'@typescript-eslint/no-unused-vars': 'error', // 直接 error
},
}
// 第二期:提升 warn 为 error
{
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
}
第三步:处理历史遗留
对于实在不想改的历史代码:
// 方案 A:文件级忽略
/* eslint-disable */
// 整个文件跳过(不推荐)
// 方案 B:行级忽略 + 说明
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacyData: any = window.__LEGACY_DATA__;
// 方案 C:目录级忽略(eslint.config.mjs)
{
ignores: ['src/legacy/**'],
}
常见坑与解决
坑 1:ESLint Flat Config 和 VSCode 插件不兼容
现象:VS Code 编辑器里不显示 lint 错误,但命令行 eslint 正常。
解决:确认 VSCode 的 ESLint 插件版本 ≥ 3.0,并在 .vscode/settings.json 中设置:
{
"eslint.useFlatConfig": true,
"eslint.options": {
"overrideConfigFile": "eslint.config.mjs"
}
}
坑 2:lint-staged 对 ts(x) 文件做类型检查太慢
方案:类型检查单独在 CI 里跑,pre-commit 不做:
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=0"
]
}
}
坑 3:Windows 和 macOS 换行符打架
.prettierrc 中设 "endOfLine": "lf",再加 .gitattributes:
* text=auto eol=lf
相关工具
- JSON 格式化 — 校验和格式化配置文件
- 文本差异对比 — 对比格式化前后的代码差异
- Base64 编码解码 — 处理构建产物分析与对比
本站提供浏览器本地工具,免注册即可试用 →