Vue3 项目规范实战 | ESLint+Prettier+Git Hooks 搭建前端代码规范体系

代码规范不是锦上添花,而是团队协作的底线。本文从 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

这会:

  1. 创建 .husky/ 目录
  2. package.json 中添加 prepare 脚本
  3. 创建 .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 绕过本地钩子的代码也能被拦截

代码规范的最高境界:开发者无感知,不规范代码进不了仓库。

用工具代替人工,用拦截代替口头约定------这才是工程化思维。

相关推荐
米丘1 小时前
新一代代码格式化工具 Oxfmt/Oxlint
前端·rust·前端工程化
韭菜炒大葱1 小时前
讲讲 浏览器的缓存机制
前端·面试·浏览器
AI砖家1 小时前
DeepSeek TUI 保姆级安装配置全指南 -Windows||macOS双平台全覆盖
服务器·前端·人工智能·windows·macos·ai编程·策略模式
Apache0121 小时前
chrome调试打开,让AI来操作浏览器
前端·chrome
lbaihao1 小时前
LLVM Cpu0 调用规则解析
开发语言·前端·python·llvm
hexu_blog2 小时前
前端vue 后端springboot如何实现图片去水印
前端·javascript·vue.js
whuhewei2 小时前
React搜索框组件
前端·javascript·react.js
姓王者2 小时前
Cloudflare Pages自定义依赖安装实践 | 姓王者的博客
前端
spmcor2 小时前
前端 RBAC 权限控制实战:从零实现动态路由与细粒度按钮权限
vue.js