代码质量与规范体系
代码质量不是"多花时间写好代码"这么简单——它是一套工具链 + 流程 + 团队共识的系统工程。好的规范体系让"做对的事"成为阻力最小的路径:格式化自动完成、低质量代码提交不了、变更历史清晰可追溯。本章从 Nuxt4 项目的 ESLint 配置讲起,覆盖格式化、Git 钩子、TypeScript 严格模式、提交规范到自动化 Changelog 的完整链路。
1. ESLint:从"查错"到"统一风格"
1.1 ESLint 在现代项目中的角色
ESLint 已经不仅仅是"检查语法错误"的工具。在 2024+ 的前端项目中,ESLint 承担三重职责:
- 错误检测:未使用的变量、未处理的 Promise、意外的全局变量
- 风格统一:缩进、分号、引号、import 排序——消除团队中的"格式圣战"
- 最佳实践:Vue 组件命名规范、TypeScript 类型安全、Nuxt 特有规则
1.2 @nuxt/eslint-config:一行配置
Nuxt4 推荐使用 @nuxt/eslint-config,它是 Anthony Fu 的 @antfu/eslint-config 的 Nuxt 定制版,基于 ESLint Flat Config 格式:
// eslint.config.mjs
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
features: {
tooling: true, // 启用 Prettier 集成
stylistic: true, // 启用代码风格规则
},
})这一行配置自动包含了:
- TypeScript 规则:类型安全、未使用变量、any 限制
- Vue 规则:组件命名、模板语法、编译器宏使用
- Nuxt 规则:auto-import 兼容、
server/和app/分区规则 - Import 排序:自动整理 import 语句顺序
1.3 Flat Config vs Legacy Config
ESLint 9+ 默认使用 Flat Config(eslint.config.mjs),取代了旧的 .eslintrc.* 格式。关键区别:
| 特性 | Flat Config | Legacy Config |
|---|---|---|
| 文件格式 | eslint.config.mjs(JS 模块) | .eslintrc.json / .eslintrc.js |
| 配置合并 | 数组拍平,显式覆盖 | extends 链式继承,隐式合并 |
| 插件加载 | 显式 import | 字符串引用,自动 resolve |
| 类型支持 | 原生 TypeScript 类型 | 需要额外类型包 |
Flat Config 的优势在于透明性——你能清楚看到每条规则的来源,不会出现"不知道哪个 extends 覆盖了我的配置"的问题。
1.4 自定义规则调整
// eslint.config.mjs
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
export default createConfigForNuxt({
features: {
stylistic: {
semi: false, // 不要分号
quotes: 'single', // 单引号
indent: 2, // 2 空格缩进
},
},
}).override('nuxt/vue/rules', {
rules: {
'vue/max-attributes-per-line': ['error', { singleline: 3 }],
'vue/no-v-html': 'warn', // v-html 警告而非报错
},
}).override('nuxt/typescript/rules', {
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
}).override() 链式 API 让规则调整非常清晰——你知道自己在覆盖哪个配置块的哪条规则。
2. 格式化:ESLint Stylistic vs Prettier
2.1 争论的本质
"ESLint 还是 Prettier?"这个争论的本质是:格式化应该由 Linter 还是专门的格式化工具来做?
Prettier 阵营:格式化是独立关注点,应该用专门工具处理。Prettier 的"零配置"哲学(故意不提供太多选项)强制团队统一风格,减少争论。
ESLint Stylistic 阵营(Anthony Fu 主导):ESLint 已经在解析 AST 了,格式化规则可以直接复用 AST,不需要再跑一个工具。而且 ESLint 规则的粒度更细,可以做 Prettier 做不到的事(比如强制 import 排序)。
2.2 Nuxt4 的推荐方案
@nuxt/eslint-config 默认使用 ESLint Stylistic——不需要 Prettier。这是 Nuxt 生态的官方推荐。
如果你的团队已经在用 Prettier 且不想迁移,可以关闭 ESLint 的样式规则,让 Prettier 接管:
export default createConfigForNuxt({
features: {
stylistic: false, // 关闭 ESLint 样式规则
},
})然后配置 Prettier:
// .prettierrc
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"vueIndentScriptAndStyle": true
}建议:新项目用 ESLint Stylistic(少一个工具、少一套配置);已有项目如果 Prettier 运行良好就不必迁移。
3. Git 钩子:Husky + lint-staged
3.1 为什么需要 Git 钩子
ESLint 配置再好,如果开发者不跑 lint,等于没有。Git 钩子在 git commit 时自动执行检查——提交不合规的代码时直接拒绝,从源头保证代码质量。
3.2 工作原理
开发者执行 git commit
↓
Husky 拦截 pre-commit 钩子
↓
lint-staged 只对本次暂存的文件执行检查
↓
ESLint 检查 → 通过 → 提交成功
→ 失败 → 提交被拒绝
关键设计:lint-staged 只检查暂存文件(staged files),而非整个项目。这有两个好处:
- 速度快——只检查你改动的文件,不受项目规模影响
- 渐进式修复——旧代码不会阻塞新代码的提交
3.3 配置
// package.json
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepare": "husky"
},
"lint-staged": {
"*.{js,ts,vue,mjs,cjs}": [
"eslint --fix"
]
}
}# .husky/pre-commit
npx lint-stagedeslint --fix 在 lint-staged 中运行时会自动修复格式问题(缩进、分号等),只有无法自动修复的真实错误才会阻止提交。这让开发者体验更顺畅——大部分格式问题静默修复,不会打断工作流。
3.4 commit-msg 钩子
除了代码检查,还可以在 commit-msg 钩子中验证提交信息格式(下文 Conventional Commits):
# .husky/commit-msg
npx commitlint --edit $14. TypeScript 严格模式
4.1 Nuxt4 的 TypeScript 支持
Nuxt4 内置 TypeScript 支持,零配置即可使用 .ts 和 <script setup lang="ts">。但默认的类型检查并不严格——很多隐患会被放过。
4.2 严格模式的价值
TypeScript 的 strict 模式是一组编译选项的集合:
| 选项 | 作用 | 抓到的典型 bug |
|---|---|---|
strictNullChecks | 不允许 null/undefined 隐式赋值 | user.name 在 user 为 null 时崩溃 |
noImplicitAny | 不允许隐式 any 类型 | 函数参数没有类型,传什么都不报错 |
strictFunctionTypes | 函数参数逆变检查 | 回调函数参数类型不匹配 |
strictPropertyInitialization | class 属性必须初始化 | 构造函数遗漏赋值 |
noUncheckedIndexedAccess | 数组/对象索引访问返回 `T | undefined` |
4.3 Nuxt4 配置
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
strict: true,
typeCheck: true, // 构建时类型检查(CI 中推荐开启)
},
})// tsconfig.json — Nuxt4 自动生成,可手动增强
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}4.4 渐进式启用
在已有项目上直接开启 strict: true 可能产生上百个类型错误。推荐渐进策略:
- 先开启
strictNullChecks(最有价值,抓到的 bug 最多) - 再开启
noImplicitAny - 最后开启完整
strict - 每步修复完当前错误再开启下一个
或者用 // @ts-expect-error 临时压制存量错误,新代码严格遵守,逐步消化历史债务。
5. Conventional Commits
5.1 为什么规范提交信息
不规范的提交历史:
fix bug
update
wip
asdfasdf
修了个问题
这种提交历史毫无信息量——三个月后你无法从中了解项目发生了什么。
规范的提交历史:
feat(video): add AI video generation endpoint
fix(auth): prevent token refresh race condition
perf(player): lazy load HLS.js to reduce initial bundle
docs(readme): update deployment instructions
5.2 Conventional Commits 格式
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
type 决定了变更的性质:
| type | 含义 | 触发版本号 |
|---|---|---|
feat | 新功能 | minor(0.x.0) |
fix | Bug 修复 | patch(0.0.x) |
perf | 性能优化 | patch |
refactor | 重构(不改变行为) | 无 |
docs | 文档 | 无 |
style | 格式调整(不影响逻辑) | 无 |
test | 测试 | 无 |
chore | 构建/工具/依赖 | 无 |
ci | CI 配置 | 无 |
scope 标识变更影响的模块(auth、video、player、api),让提交历史可按模块筛选。
BREAKING CHANGE:在 footer 或 type 后加 ! 表示破坏性变更,触发 major 版本号。
5.3 commitlint 强制执行
// commitlint.config.ts
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'scope-enum': [2, 'always', [
'video', 'auth', 'player', 'ai', 'api', 'ui', 'config', 'deps',
]],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100],
},
}配合 Husky 的 commit-msg 钩子,不符合格式的提交信息会被拒绝,并给出修正建议。
5.4 工具辅助
手动记住格式很烦。用 commitizen(cz-conventional-changelog)提供交互式提交向导:
npx cz
# ? Select the type of change: feat
# ? What is the scope: video
# ? Write a short description: add thumbnail auto-generation或者用 VS Code 的 Conventional Commits 插件,在 Source Control 面板中直接选择 type 和 scope。
6. 自动化 Changelog
6.1 从提交历史生成 Changelog
Conventional Commits 的真正威力在于自动化——规范的提交信息可以被工具解析,自动生成 Changelog 和版本号。
git log:
feat(video): add batch upload
fix(auth): session timeout handling
feat(ai): support Stable Diffusion models
fix(player): iOS fullscreen orientation
自动生成:
## v1.3.0 (2025-01-15)
### Features
- **video**: add batch upload
- **ai**: support Stable Diffusion models
### Bug Fixes
- **auth**: session timeout handling
- **player**: iOS fullscreen orientation
6.2 工具选型
| 工具 | 特点 | 适用场景 |
|---|---|---|
| changelogen | Nuxt 团队出品,轻量 | Nuxt 项目(推荐) |
| changelogithub | 从 GitHub Release 生成 | 开源项目 |
| release-it | 全流程自动化(version + tag + changelog + publish) | npm 包发布 |
| semantic-release | 完全自动化,零手动干预 | CI/CD 驱动的项目 |
6.3 changelogen 配置
// package.json
{
"scripts": {
"release": "changelogen --release"
}
}执行 npm run release 后自动:
- 解析自上次发布以来的所有 commit
- 根据 type 计算新版本号(feat → minor,fix → patch)
- 生成
CHANGELOG.md - 更新
package.json的 version - 创建 Git tag
6.4 发布流程
开发 → commit(Conventional Commits)
↓
PR Review → merge to main
↓
CI 运行 changelogen → 更新版本号 + CHANGELOG
↓
创建 Git Tag → 触发部署
7. 完整的质量门禁体系
7.1 质量门禁全景
将所有质量工具串联成一条自动化流水线:
| 阶段 | 工具 | 检查内容 | 触发时机 |
|---|---|---|---|
| 编码时 | ESLint + IDE 插件 | 实时错误提示 | 每次保存 |
| 提交时 | Husky + lint-staged | 代码规范 | git commit |
| 提交时 | commitlint | 提交信息格式 | git commit |
| PR 时 | GitHub Actions + ESLint | 全量代码检查 | Push / PR |
| PR 时 | Vitest | 单元测试 + 覆盖率 | Push / PR |
| PR 时 | TypeScript | 类型检查 | Push / PR |
| 合并后 | changelogen | Changelog + 版本号 | Merge to main |
7.2 CI 工作流
# .github/workflows/quality.yml
name: Quality Gate
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx nuxi typecheck
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx vitest run --coverage三个 job 并行执行,任何一个失败都阻止 PR 合并。
8. 规范落地的心法
8.1 工具先行,文档后补
不要指望一份代码规范文档就能让团队统一风格。可执行的规则 > 写在文档里的规则。ESLint 报错比 Wiki 页面有效 100 倍。
8.2 自动修复优先
能自动修复的就自动修复(eslint --fix)。开发者应该把精力花在业务逻辑上,而不是手动调整缩进和分号。
8.3 渐进式推进
不要一次性引入所有规则。先从影响最大的开始(strictNullChecks、Conventional Commits),稳定后再逐步加严。一次改太多会让团队产生抵触情绪。
8.4 CI 是最后防线
本地钩子可以被 --no-verify 绕过。CI 中的检查无法绕过——它是质量门禁的最后防线,必须严格。
本章小结
- ESLint:
@nuxt/eslint-config一行配置覆盖 TS + Vue + Nuxt 规则,Flat Config 透明可控 - 格式化:ESLint Stylistic(新项目推荐)或 Prettier(已有项目),二选一
- Git 钩子:Husky + lint-staged 在提交时自动检查暂存文件,渐进式修复
- TypeScript 严格模式:
strictNullChecks价值最高,渐进式开启避免一次性爆炸 - Conventional Commits:
type(scope): description格式,commitlint 强制执行 - 自动化 Changelog:changelogen 从规范提交自动生成版本号和变更日志
- 质量门禁:编码时 → 提交时 → PR 时 → 合并后,四层自动化保障