适用场景:Vite + React + TypeScript 项目。Vue、Webpack 或其他工程也可以沿用相同思路,只需要替换对应的 ESLint 插件与文件匹配规则。
多人协作时,代码规范最容易遇到两个问题:一是每个人的编辑器配置不同,二是规范只写在文档里,却没有进入开发流程。结果往往是本地看起来没有问题,提交后才在代码审查或 CI 中暴露大量格式差异。
一套稳定的前端代码工作流应当把职责拆开:
- ESLint 负责发现潜在错误和不合理的代码写法;
- Prettier 负责统一缩进、引号、换行等格式;
- Husky 负责管理 Git Hooks;
- lint-staged 只检查暂存区中的文件,避免每次提交都扫描整个项目。
最终流程如下:
sql
编写代码
↓
编辑器保存时格式化
↓
git add 将文件加入暂存区
↓
git commit 触发 pre-commit
↓
lint-staged 对暂存文件执行 ESLint 与 Prettier
↓
检查通过后完成提交
下面从一个新的 Vite 项目开始完成配置。
1. 创建 Vite + React + TypeScript 项目
sql
npm create vite@latest frontend-workflow -- --template react-ts
cd frontend-workflow
npm install
npm run dev
新版 Vite 模板通常已经包含基础 ESLint 配置。如果是已有项目,也可以直接从下一节开始,根据当前依赖补装缺少的包。
2. 配置 ESLint
ESLint 负责处理代码质量问题,例如未使用变量、不安全语法和 React Hooks 使用错误。ESLint 9 已默认使用 Flat Config,配置文件通常命名为 eslint.config.js,不再推荐新项目继续使用 .eslintrc.*。
2.1 安装依赖
bash
npm install -D eslint @eslint/js globals typescript-eslint
npm install -D eslint-plugin-react-hooks eslint-plugin-react-refresh
如果项目尚未生成任何 ESLint 配置,也可以运行初始化向导:
kotlin
npm init @eslint/config@latest
初始化时根据实际项目选择浏览器环境、React 和 TypeScript 即可。
2.2 编写 Flat Config
在项目根目录创建或修改 eslint.config.js:
php
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist', 'coverage']),
{
files: ['**/*.{js,jsx,ts,tsx}'],
extends: [js.configs.recommended],
languageOptions: {
ecmaVersion: 'latest',
globals: globals.browser,
},
},
{
files: ['**/*.{ts,tsx}'],
extends: [
...tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
])
这里做了几件事:
- 忽略构建产物与测试覆盖率目录;
- 对 JavaScript 和 TypeScript 启用 ESLint 推荐规则;
- 为 TypeScript 增加对应解析与规则;
- 检查 React Hooks;
- 对 Vite 的 React Fast Refresh 使用方式进行约束。
先执行一次检查:
erlang
npx eslint .
如果需要自动修复可修复的问题:
css
npx eslint . --fix
3. 接入 Prettier
ESLint 更关注代码是否合理,Prettier 更关注代码长什么样。把两者的职责分开,配置更容易维护,也能减少规则冲突。
3.1 安装依赖
lua
npm install -D --save-exact prettier
npm install -D eslint-config-prettier
eslint-config-prettier 会关闭与 Prettier 冲突或重复的 ESLint 格式规则。
3.2 创建 Prettier 配置
在根目录创建 prettier.config.mjs:
yaml
/** @type {import('prettier').Config} */
export default {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
trailingComma: 'all',
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
}
规则没有绝对标准,团队统一即可。已经存在历史代码的项目,建议先保持原有分号、引号和换行习惯,避免一次格式化产生过大的无意义 Diff。
再创建 .prettierignore:
lua
node_modules
dist
coverage
package-lock.json
*.min.*
3.3 避免 ESLint 与 Prettier 冲突
在 eslint.config.js 中引入 eslint-config-prettier,并把它放在配置数组的最后:
javascript
import eslintConfigPrettier from 'eslint-config-prettier/flat'
export default defineConfig([
// 其他 ESLint 配置
eslintConfigPrettier,
])
放在最后的原因是让它覆盖前面配置中可能与 Prettier 冲突的格式规则。
本文不把 Prettier 作为 ESLint 规则运行,而是让 eslint --fix 与 prettier --write 分别执行。这样职责更清晰,命令输出也更容易定位。
4. 增加项目脚本
在 package.json 中增加以下脚本:
json
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"typecheck": "tsc -b --pretty false"
}
}
日常使用:
arduino
npm run lint
npm run lint:fix
npm run format
npm run format:check
npm run typecheck
建议在 CI 中至少执行 lint、format:check 和项目构建。提交前的 Git Hook 是第一层保护,CI 是无法绕过的最终检查。
5. 配置 VS Code 保存时自动处理
安装以下扩展:
- ESLint
- Prettier - Code formatter
在项目中创建 .vscode/settings.json:
swift
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"files.eol": "\n"
}
提交 .vscode/settings.json 后,团队成员打开项目即可获得一致的基础保存行为。即使某位成员不使用 VS Code,后面的 Git Hook 仍会在提交阶段执行统一检查。
6. 安装 Husky 与 lint-staged
6.1 安装依赖
npm install -D husky lint-staged
6.2 初始化 Husky
csharp
npx husky init
该命令会创建 .husky/pre-commit,并在 package.json 中增加 prepare 脚本。新版 Husky 不需要再使用旧教程中的 husky install 或 husky add 命令。
将 .husky/pre-commit 内容改为:
npx lint-staged
这表示每次执行 git commit 时,先运行 lint-staged;只有检查成功后才继续提交。
7. 配置 lint-staged
在项目根目录创建 lint-staged.config.mjs:
arduino
/** @type {import('lint-staged').Configuration} */
export default {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'*.{json,css,scss,less,html,md,yml,yaml}': 'prettier --write',
}
lint-staged 会自动把匹配到的暂存文件路径追加到命令后面,因此不需要手动拼接路径。它也会处理修复后的暂存状态,新版配置无需再额外执行 git add。
与全量执行 eslint . 相比,这种方式有两个优势:
- 提交速度更快,只处理本次修改的文件;
- 老项目接入时不会突然格式化全部历史代码。
8. 验证完整工作流
故意在某个 TypeScript 文件中加入格式不规范的代码,然后执行:
sql
git add .
git commit -m "test: verify code workflow"
正常情况下会依次发生:
- Git 触发
.husky/pre-commit; - lint-staged 获取暂存区文件;
- ESLint 修复可自动处理的问题;
- Prettier 统一文件格式;
- 如果仍存在 ESLint 错误,提交被中止;
- 检查全部通过,提交继续完成。
也可以不提交,先手动测试:
npx lint-staged
9. 常见问题
9.1 为什么不在 Vite 开发服务器中强制运行 ESLint?
可以使用 Vite 插件在开发阶段实时检查,但它会把代码检查和构建过程绑定在一起,大型项目中可能影响启动或热更新速度。更通用的方案是编辑器即时提示、Git Hook 检查增量文件、CI 检查全量文件。
9.2 ESLint 与 Prettier 为什么会互相打架?
通常是 ESLint 中仍开启了缩进、引号、分号等格式类规则。确保安装 eslint-config-prettier,并将其放到 Flat Config 数组靠后的位置。
9.3 为什么提交时没有触发 Hook?
依次检查:
arduino
npm run prepare
git config core.hooksPath
同时确认 .husky/pre-commit 已提交到 Git,并且项目确实位于 Git 仓库内。
9.4 老项目如何逐步接入?
不要一开始就全量运行 prettier . --write。可以先接入 lint-staged,让新增和修改的文件逐渐符合规范;等团队确认规则后,再单独提交一次全量格式化,避免格式变化与业务代码混在一起。
9.5 能否绕过 Git Hook?
Git Hook 可以被 --no-verify 跳过,因此不能代替 CI。可靠的团队流程应该在 CI 中再次运行:
arduino
npm run lint
npm run format:check
npm run typecheck
npm run build
10. 总结
完成以上配置后,代码质量约束不再只依赖个人习惯:
- 编辑器负责快速反馈与保存格式化;
- ESLint 负责代码质量;
- Prettier 负责统一格式;
- lint-staged 控制检查范围;
- Husky 把检查接入提交动作;
- CI 对整个仓库进行最终兜底。
工具本身并不是目标。真正有价值的是把团队认可的规范固化到开发流程中,让问题尽量在代码进入仓库之前被发现。