前端工程化最佳实践:ESLint+Prettier+Git Hooks 统一开发规范

目标与收益
- 一致风格与质量:统一代码风格、导入顺序与常见错误拦截
- 提交即校验:在提交阶段自动修复与拦截不合规改动
- 持续集成:CI 对齐本地规则,保证主干稳定
安装与基础脚本
bash
pnpm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue eslint-plugin-react eslint-plugin-import eslint-plugin-simple-import-sort
pnpm i -D prettier eslint-config-prettier eslint-plugin-prettier
pnpm i -D husky lint-staged @commitlint/cli @commitlint/config-conventional
package.json 脚本与 lint-staged:
json
{
"scripts": {
"lint": "eslint --ext .ts,.tsx,.js,.vue src",
"lint:fix": "eslint --ext .ts,.tsx,.js,.vue src --fix",
"format": "prettier --write .",
"test": "vitest run || jest"
},
"lint-staged": {
"src/**/*.{ts,tsx,js,vue}": ["eslint --fix", "prettier --write"],
"src/**/*.{css,scss,md}": ["prettier --write"]
}
}
ESLint 配置(TS + Vue/React 通用示例)
.eslintrc.cjs:
js
module.exports = {
root: true,
env: { browser: true, es2021: true, node: true },
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['@typescript-eslint', 'import', 'simple-import-sort', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier'
],
settings: { react: { version: 'detect' } },
rules: {
'prettier/prettier': ['error'],
'import/order': ['off'],
'simple-import-sort/imports': ['error'],
'simple-import-sort/exports': ['error'],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'vue/multi-word-component-names': 'off'
},
overrides: [
{ files: ['**/*.vue'], rules: { 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off' } },
{ files: ['**/*.tsx'], rules: { 'vue/no-unused-components': 'off' } }
]
}
Prettier 配置
.prettierrc.json:
json
{ "semi": false, "singleQuote": true, "printWidth": 100, "trailingComma": "es5" }
可选 .editorconfig:
ini
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
insert_final_newline = true
Git Hooks(Husky)
初始化与钩子:
bash
npx husky init
.husky/pre-commit:
sh
npx lint-staged
.husky/commit-msg:
sh
npx commitlint --edit "$1"
.husky/pre-push:
sh
pnpm test
commitlint.config.cjs:
js
module.exports = { extends: ['@commitlint/config-conventional'] }
提交信息示例:
text
feat(auth): 支持短信验证码登录
fix(router): 修复刷新后白屏问题
chore(deps): 升级 eslint 到最新版本
Monorepo 与工作区建议
- 使用 pnpm workspaces 与 Turbo/Changesets 管理多包;在根目录统一 ESLint/Prettier 配置
lint-staged可在根目录配置并针对各包的src/路径匹配- 在 CI 中按变更范围运行 lint/test(如 Turbo pipeline
lint/test)
CI 配置(GitHub Actions)
.github/workflows/quality.yml:
yaml
name: Quality
on: { pull_request: { branches: [main] }, push: { branches: [main] } }
jobs:
lint_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'pnpm' }
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: pnpm lint
- run: pnpm test -- --coverage
规则扩展与导入排序
- 导入排序统一:
simple-import-sort以组为单位排序,避免冲突 - 不同栈规则:Vue 项目开启
plugin:vue/vue3-recommended;React 项目开启react-hooks规则 - 忽略与例外:对自动生成文件与构建产物设置
.eslintignore与.prettierignore
.eslintignore 与 .prettierignore:
text
dist
node_modules
coverage
*.min.js
常见坑与修复
- ESLint 与 Prettier 冲突:确保
extends中最后是prettier,开启prettier/prettier错误级别 - Husky 不生效:确认
.husky目录在仓库根且钩子文件可执行;在 CI 环境只执行脚本不启用钩子 - lint-staged 过慢:控制匹配范围与并发;避免在钩子里运行全量测试
- 多栈共存:通过
overrides针对.vue与.tsx设置规则差异
IDE 集成与本地一致性
json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": { "source.fixAll.eslint": true },
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue" ]
}
提交规范与自动发布(可选)
feat 映射 minor、fix 映射 patch、含 BREAKING CHANGE 映射 major。
Changesets 或 semantic-release 可自动生成版本与变更日志。
性能优化与门禁
json
{
"lint-staged": {
"src/**/*.{ts,tsx,js,vue}": ["eslint --fix --cache", "prettier --write"],
"*.{css,scss,md}": ["prettier --write"]
}
}
json
{
"coverageThreshold": { "global": { "branches": 70, "functions": 70, "lines": 70 } }
}
规则模板与扩展建议
- Vue:
vue/no-mutating-props、vue/require-prop-types - React:
react/jsx-no-useless-fragment、react/no-array-index-key、react-hooks/exhaustive-deps - 样式与测试:按需加入
stylelint与eslint-plugin-jest或@testing-library/eslint-plugin
规范约束矩阵(示例)
- 代码风格:Prettier 统一;ESLint 禁止未用变量与未排序导入
- 提交信息:Conventional Commits(type(scope): subject)
- 质量门禁:pre-commit 运行 lint-staged;pre-push 运行测试;CI 覆盖率阈值
- 编辑器一致性:保存即格式化与自动修复;统一换行与缩进
commitlint 高级配置(示例)
commitlint.config.cjs:
js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat','fix','chore','docs','refactor','perf','test','build','ci','revert']],
'scope-enum': [2, 'always', ['app','ui','api','deps','lint','release']],
'subject-case': [2, 'never', ['sentence-case','start-case','pascal-case','upper-case']],
'header-max-length': [2, 'always', 72]
}
}
导入排序自定义分组
.eslintrc.cjs 片段:
js
rules: {
'simple-import-sort/imports': ['error', {
groups: [
['^react', '^vue', '^@?\w'],
['^(@/)(.*)$'],
['^\.\.(?!/?$)', '^\.'],
['^.+\.s?css$']
]
}]
}
行尾与换行一致性
.gitattributes:
text
* text=auto eol=lf
统一跨平台换行(LF),避免格式化差异。
lint-staged 性能优化
在钩子中开启 ESLint 缓存、控制并发与匹配范围:
json
{
"lint-staged": {
"src/**/*.{ts,tsx,js,vue}": ["eslint --fix --cache", "prettier --write"],
"*.{css,scss,md}": ["prettier --write"]
}
}
迁移步骤(落地)
- 第一步:在根目录新增 ESLint/Prettier/commitlint 配置与忽略文件
- 第二步:初始化 Husky 与 lint-staged,开启 pre-commit/commit-msg/pre-push
- 第三步:在 CI 增加质量工作流(lint/test/coverage)
- 第四步:统一编辑器设置与扩展,保存即格式化
- 第五步:每月回顾规则与规范产出,按反馈迭代(新增或降低规则级别)
落地清单(10 项)
- 在根目录统一 ESLint/Prettier 配置并落库
- 初始化 Husky 并启用
pre-commit/commit-msg/pre-push - 配置 lint-staged 针对源码目录匹配
- 引入 commitlint 并规范提交信息
- 为导入排序与未用变量设定统一规则
- 设置 ignore 列表,避免构建产物被格式化
- 在 CI 中运行
lint与test并采集覆盖率 - 为 Monorepo 统一配置并按变更范围运行管线
- 在编辑器启用 ESLint 与 Prettier 扩展(保存即格式化)
- 每月回顾规则与产出,按团队反馈迭代
总结
- 通过 ESLint+Prettier+Git Hooks 的组合,在提交与 CI 阶段统一质量门禁
- 配合导入排序、类型规则与提交规范,团队可持续保持高一致性与低维护成本