代码规范不是锦上添花,而是团队协作的底线。本文从 0 搭建一套完整的 Vue3 代码规范体系,覆盖 ESLint 代码质量、Prettier 格式化、Git Hooks 自动校验、Commit 规范约束,让你的团队告别代码风格战争。
一、为什么需要代码规范?
1.1 没有规范的痛
| 痛点 | 场景 | 后果 |
|---|---|---|
| 风格不统一 | A 用 2 空格,B 用 4 空格,C 用 Tab | Code Review 变成格式讨论 |
| 潜在 Bug | 未使用的变量、== 比较、缺失 return |
线上事故 |
| 提交混乱 | "fix bug"、"update"、"111" | 无法自动生成 Changelog |
| 合并冲突 | 每人格式化方式不同 | 无意义的冲突,浪费时间 |
| 新人迷茫 | 不知道该遵循什么风格 | 上手慢,犯错多 |
1.2 规范体系全景
编写代码 → ESLint 代码质量检查 → Prettier 格式化 → Git Hooks 拦截
→ commitlint 校验提交信息 → CI 二次兜底
核心原则:能自动化的绝不人工,能拦截的绝不放过。
二、项目初始化
2.1 创建 Vue3 项目
bash
npm create vite@latest vue3-code-standards -- --template vue-ts
cd vue3-code-standards
npm install
2.2 安装核心依赖
bash
# ESLint 生态
npm install -D eslint @eslint/js typescript-eslint
# Vue 专属 ESLint 插件
npm install -D eslint-plugin-vue
# Prettier
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
# Git Hooks
npm install -D husky lint-staged
# Commit 规范
npm install -D @commitlint/cli @commitlint/config-conventional
依赖说明:
| 包名 | 作用 |
|---|---|
eslint |
代码质量检查核心 |
@eslint/js |
ESLint 官方 JS 规则集 |
typescript-eslint |
TypeScript ESLint 适配器 |
eslint-plugin-vue |
Vue 单文件组件专用规则 |
prettier |
代码格式化 |
eslint-config-prettier |
关闭 ESLint 中与 Prettier 冲突的规则 |
eslint-plugin-prettier |
将 Prettier 规则作为 ESLint 规则运行 |
husky |
Git Hooks 管理 |
lint-staged |
只对暂存区文件执行 lint |
@commitlint/cli |
提交信息校验 |
@commitlint/config-conventional |
Angular 提交规范预设 |
三、ESLint 配置 --- 代码质量守门员
3.1 配置文件(Flat Config,ESLint 9+)
ESLint 9 起采用 Flat Config(eslint.config.js),告别 .eslintrc 时代。
javascript
// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import prettierConfig from 'eslint-config-prettier'
import prettierPlugin from 'eslint-plugin-prettier'
export default tseslint.config(
// 1. 基础规则
js.configs.recommended,
// 2. TypeScript 规则
...tseslint.configs.recommended,
// 3. Vue 规则
...pluginVue.configs['flat/recommended'],
// 4. Prettier 配置(必须放最后,覆盖冲突规则)
prettierConfig,
// 5. 自定义规则
{
files: ['**/*.{ts,tsx,vue}'],
plugins: {
prettier: prettierPlugin
},
rules: {
// ====== Prettier 集成 ======
'prettier/prettier': 'error',
// ====== TypeScript 规则 ======
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_', // _ 开头的参数允许未使用
varsIgnorePattern: '^_' // _ 开头的变量允许未使用
}
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/ban-ts-comment': 'warn',
// ====== Vue 规则 ======
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'warn',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'error',
'vue/no-unused-vars': 'error',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/html-self-closing': [
'error',
{
html: { void: 'always', normal: 'always', component: 'always' },
svg: 'always',
math: 'always'
}
],
'vue/component-api-style': ['error', ['script-setup']],
// ====== 通用规则 ======
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-template-curly-in-string': 'error',
'no-constant-binary-expression': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],
curly: ['error', 'multi-line'],
'prefer-const': ['error', { destructuring: 'all' }],
'no-var': 'error'
}
},
// 6. Vue 文件需要 TypeScript 解析器
{
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
ecmaVersion: 'latest',
sourceType: 'module'
}
}
},
// 7. 忽略目录
{
ignores: [
'dist/**',
'node_modules/**',
'*.d.ts',
'auto-imports.d.ts',
'components.d.ts'
]
}
)
3.2 关键规则解读
vue/require-explicit-emits --- 强制声明 emits
vue
<!-- ❌ 错误:未声明 emit -->
<script setup>
const handleClick = () => {
emit('update') // ESLint 报错
}
</script>
<!-- ✅ 正确 -->
<script setup>
const emit = defineEmits<{
update: [value: string]
}>()
const handleClick = () => {
emit('update', 'hello')
}
</script>
vue/component-api-style --- 强制使用 <script setup>
vue
<!-- ❌ 选项式 API -->
<script>
export default {
data() { return { count: 0 } }
}
</script>
<!-- ✅ 组合式 API -->
<script setup lang="ts">
const count = ref(0)
</script>
@typescript-eslint/no-unused-vars --- 未使用变量检查
typescript
// ❌ 错误
const result = fetchData() // result 未使用
// ✅ 用 _ 前缀标记故意忽略的变量
const _result = fetchData()
// ✅ 解构时忽略
const { data, _loading } = useFetch() // loading 允许未使用
3.3 package.json 脚本
json
{
"scripts": {
"lint": "eslint . --fix",
"lint:check": "eslint ."
}
}
四、Prettier 配置 --- 格式化的终极答案
4.1 配置文件
javascript
// .prettierrc.js
export default {
// ====== 基础格式 ======
semi: false, // 不加分号
singleQuote: true, // 单引号
tabWidth: 2, // 缩进 2 空格
useTabs: false, // 用空格不用 Tab
trailingComma: 'none', // 不加尾逗号
printWidth: 100, // 行宽 100 字符
endOfLine: 'lf', // 统一 LF 换行
// ====== Vue 专属 ======
htmlWhitespaceSensitivity: 'ignore',
// ====== 其他 ======
bracketSpacing: true, // 对象花括号内加空格
arrowParens: 'always', // 箭头函数参数加括号
vueIndentScriptAndStyle: false // Vue 文件不缩进 <script>/<style>
}
4.2 忽略文件
gitignore
# .prettierignore
dist
node_modules
*.min.js
*.min.css
package-lock.json
pnpm-lock.yaml
auto-imports.d.ts
components.d.ts
4.3 EditorConfig --- 编辑器行为统一
ini
# .editorconfig
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
4.4 VSCode 工作区配置
json
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
],
"typescript.tsdk": "node_modules/typescript/lib",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
json
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"vue.volar",
"editorconfig.editorconfig"
]
}
4.5 package.json 脚本
json
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
五、Husky + lint-staged --- Git Hooks 自动拦截
5.1 初始化 Husky
bash
npx husky init
这会:
- 创建
.husky/目录 - 在
package.json中添加prepare脚本 - 创建
.husky/pre-commit钩子
5.2 pre-commit 钩子 --- 代码检查
bash
# .husky/pre-commit
npx lint-staged
5.3 commit-msg 钩子 --- 提交信息校验
bash
# .husky/commit-msg
npx --no -- commitlint --edit $1
5.4 lint-staged 配置
javascript
// lint-staged.config.js
export default {
'*.{js,jsx,ts,tsx,vue}': [
'eslint --fix',
'prettier --write'
],
'*.{json,md,yml,yaml,css,scss,less,html}': [
'prettier --write'
]
}
核心价值:只检查暂存区文件,不检查整个项目,速度极快。
5.5 工作流示意
sql
git commit
→ pre-commit 触发
→ lint-staged 检查暂存文件
→ ESLint --fix 自动修复
→ Prettier --write 自动格式化
→ 修复失败 → 阻止提交 ❌
→ 修复成功 → 重新 stage → 继续
→ commit-msg 触发
→ commitlint 校验提交信息
→ 格式不对 → 阻止提交 ❌
→ 格式正确 → 提交成功 ✅
六、Commit 规范 --- 让提交历史说话
6.1 commitlint 配置
javascript
// commitlint.config.js
export default {
extends: ['@commitlint/config-conventional'],
rules: {
// type 枚举
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修复 Bug
'docs', // 文档变更
'style', // 代码格式(不影响功能)
'refactor', // 重构(不是新功能也不是修复)
'perf', // 性能优化
'test', // 测试
'build', // 构建系统或外部依赖
'ci', // CI 配置
'chore', // 杂务(不修改 src 或测试)
'revert' // 回退
]
],
// subject 不能为空
'subject-empty': [2, 'never'],
// type 不能为空
'type-empty': [2, 'never'],
// subject 不以句号结尾
'subject-full-stop': [2, 'never', '.'],
// subject 大小写不做限制
'subject-case': [0]
}
}
6.2 提交信息格式
xml
<type>(<scope>): <subject>
<body>
<footer>
示例:
bash
# 新功能
git commit -m "feat(user): 添加用户登录页面"
# Bug 修复
git commit -m "fix(api): 修复请求超时未重试的问题"
# 破坏性变更
git commit -m "feat(auth): 重构认证模块
BREAKING CHANGE: token 存储从 localStorage 迁移到 cookie"
# 关联 Issue
git commit -m "fix(cart): 修复购物车数量计算错误
Closes #123"
6.3 commitizen --- 交互式提交工具(可选)
bash
npm install -D commitizen cz-conventional-changelog
json
// package.json
{
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}
运行 npm run commit 后出现交互式选择:
yaml
? Select the type of change that you're committing:
feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code
...
? What is the scope of this change (e.g. component or file name):
? Write a short, imperative tense description of the change:
? Provide a longer description of the change:
? Are there any breaking changes?
? Does this change affect any open issues?
七、完整配置文件一览
7.1 package.json 完整脚本
json
{
"name": "vue3-code-standards",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky",
"commit": "cz"
}
}
7.2 完整文件清单
bash
vue3-code-standards/
├── .editorconfig # 编辑器行为统一
├── .prettierrc.js # Prettier 配置
├── .prettierignore # Prettier 忽略
├── .vscode/
│ ├── settings.json # VSCode 工作区设置
│ └── extensions.json # 推荐扩展
├── eslint.config.js # ESLint Flat Config
├── lint-staged.config.js # lint-staged 配置
├── commitlint.config.js # commit 规则
├── .husky/
│ ├── pre-commit # 代码检查钩子
│ └── commit-msg # 提交信息钩子
├── tsconfig.json
├── vite.config.ts
└── package.json
八、团队落地最佳实践
8.1 渐进式落地策略
| 阶段 | 动作 | 预期效果 |
|---|---|---|
| 第 1 周 | 引入 ESLint + Prettier,配置 VSCode | 代码风格统一 |
| 第 2 周 | 加入 Husky + lint-staged | 提交自动检查 |
| 第 3 周 | 加入 commitlint | 提交信息规范 |
| 第 4 周 | 接入 CI,全量检查兜底 | 完全自动化 |
⚠️ 关键:不要一次性全上,每个阶段给团队适应时间。
8.2 已有项目的迁移策略
bash
# 第一步:只检查,不修复,评估存量问题
npx eslint . --format json | npx eslint-stats -o eslint-report.html
# 第二步:自动修复安全规则
npx eslint . --fix
# 第三步:剩余警告逐步清零
# 在 eslint.config.js 中临时把 error 降级为 warn
# '@typescript-eslint/no-explicit-any': 'warn' // 先 warn 后 error
8.3 规则争议处理
| 争议点 | 推荐方案 | 理由 |
|---|---|---|
| 分号 | semi: false |
Vue 官方模板默认,社区主流 |
| 引号 | singleQuote: true |
JS 社区主流,减少 Shift 按键 |
| 缩进 | 2 空格 | Vue/React 官方模板默认 |
| 尾逗号 | trailingComma: 'none' |
减少无意义 diff,Git blame 更清晰 |
| 行宽 | 100 | 比 80 宽,比 120 窄,平衡可读性 |
any |
warn 不是 error |
渐进式迁移,先 warn 后 error |
8.4 CI 兜底配置(GitHub Actions)
yaml
# .github/workflows/lint.yml
name: Lint
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: ESLint
run: pnpm run lint:check
- name: Prettier
run: pnpm run format:check
- name: Type Check
run: pnpm run type-check
8.5 常见踩坑与解决
| 坑 | 原因 | 解决 |
|---|---|---|
| ESLint 和 Prettier 规则冲突 | 两者都管格式化 | eslint-config-prettier 放配置最后 |
| lint-staged 只检查部分文件 | 只对 staged 文件生效,正确行为 | 理解设计意图,全量检查交给 CI |
| husky 钩子不触发 | npm install 后钩子丢失 | npm run prepare 重新初始化 |
| Vue 文件 TypeScript 报错 | parser 未配置 | Flat Config 中单独给 .vue 配 parser |
no-console 影响调试 |
规则太严格 | allow: ['warn', 'error'] 或开发环境 warn |
| 行尾符 CRLF/LF 冲突 | Windows/Mac 不一致 | .editorconfig + endOfLine: 'lf' + .gitattributes |
| commitlint 不生效 | husky commit-msg 钩子未创建 | 检查 .husky/commit-msg 文件是否存在 |
8.6 .gitattributes 统一换行符
gitignore
# .gitattributes
* text=auto eol=lf
*.bat text eol=crlf
九、扩展:规则集封装为 npm 包
大型团队建议将规范封装为共享包,统一管理:
javascript
// packages/eslint-config-vue3/index.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import prettierConfig from 'eslint-config-prettier'
import prettierPlugin from 'eslint-plugin-prettier'
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
prettierConfig,
{
files: ['**/*.{ts,tsx,vue}'],
plugins: { prettier: prettierPlugin },
rules: {
'prettier/prettier': 'error',
// ... 团队统一规则
}
},
{
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
ecmaVersion: 'latest',
sourceType: 'module'
}
}
},
{
ignores: ['dist/**', 'node_modules/**', '*.d.ts']
}
)
项目引用:
javascript
// eslint.config.js
import vue3Config from '@your-org/eslint-config-vue3'
export default [
...vue3Config,
// 项目级覆盖
{
rules: {
'@typescript-eslint/no-explicit-any': 'off' // 本项目允许 any
}
}
]
十、总结
| 环节 | 工具 | 解决什么 |
|---|---|---|
| 代码质量 | ESLint + typescript-eslint + eslint-plugin-vue | Bug 预防、最佳实践 |
| 代码风格 | Prettier + EditorConfig | 格式统一,消灭风格争论 |
| 提交拦截 | Husky + lint-staged | 不合规代码无法提交 |
| 提交规范 | commitlint + commitizen | 提交信息可追溯、可生成 Changelog |
| CI 兜底 | GitHub Actions / GitLab CI | 绕过本地钩子的代码也能被拦截 |
代码规范的最高境界:开发者无感知,不规范代码进不了仓库。
用工具代替人工,用拦截代替口头约定------这才是工程化思维。