在现代前端团队开发中,代码质量控制和提交规范已成为项目可持续发展的基石。一个结构良好、记录清晰的代码库不仅能提升开发效率,还能降低维护成本,使团队协作更加顺畅。
为实现这一目标,成熟的前端团队通常采用双管齐下的策略:在代码质量和风格统一方面,ESLint 与 Prettier 的组合已成为行业标准;而在提交规范化方面,husky、lint-staged、commitlint、commitizen 以及 cz-git 等工具的协同使用,则构建了一道坚实的质量防线。
这套完整的前端工程化配置不仅能在提交前自动检查和修复代码问题,还能引导开发者遵循统一的提交信息格式,从而打造一个条理分明的项目历史记录。本文将深入探讨这些工具如何协同工作,以及如何在你的项目中构建这套自动化工程体系,让规范成为团队的无形资产而非额外负担。
概念
git hook
git hook也就是常说的Git 钩子,是Git 在特定重要动作发生时触发自定义脚本的机制,分为客户端和服务器端钩子。客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。客户端钩子通常包括了提交工作流钩子、电子邮件工作流钩子和其它钩子。这些钩子通常存储在项目的 .git/hooks
目录下,我们需要关注的主要是提交工作流钩子。提交工作流钩子主要包括了以下四种:
- pre-commit:该钩子在提交信息前运行。 它用于检查即将提交的快照。如果该钩子以非零值退出,Git 将放弃此次提交,你可以利用该钩子,来检查代码风格是否一致。
- prepare-commit-msg:该钩子在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。
- commit-msg:它接收 一个参数 ,该参数是 包含当前提交信息的临时文件路径 。钩子脚本可以通过读取该文件获取提交信息内容。如果该钩子脚本以 非零状态码退出 ,Git 将 中止此次提交 ,从而阻止不符合规范的提交被写入仓库。因此, 常用于 校验提交信息的格式或内容,以及根据项目需求进行其他验证(例如检查提交是否关联 issue)。
- post-commit:该钩子一般用于通知之类的事情。
在上面的钩子中,我们重点关注 pre-commit
和 commit-msg
钩子。
husky
husky
是常见的 git hook
工具,使用 husky
可以挂载 Git
钩子,当我们本地进行 git commit
或 git push
等操作前,能够执行其它一些操作,比如进行 ESLint
检查,如果不通过,就不允许 commit
或 push
。
Lint-staged
在 pre-commit
hook 中,一般来说都是对当前要 commit
的文件进行校验、格式化等,而不是对全局文件校验,因此在脚本中我们需要知道当前在 Git
暂存区的文件有哪些,而 Git
本身也没有向 pre-commit
脚本传递相关参数,Lint-staged这个包为我们解决了这个问题。简单说就是当我们触发 pre-commit
hook 中的脚本命令后,配合 Lint-staged
的配置可以只检查暂存区的文件从而避免我们每次检查都把整个项目的代码都检查一遍的尴尬情况。其次,Lint-staged
允许指定不同类型后缀文件执行不同指令的操作,并且可以按步骤再额外执行一些其它 shell
指令。它通过运行git diff --staged --diff-filter=ACMR --name-only -z
命令获取暂存区文件信息
commitlint
执行 git commit -m 'xxx'
时,用来检查 'xxx'
是否满足固定格式的工具。 为什么使用 commitlint
:团队中规范了 commit
规范可以更清晰的查看每一次代码提交记录,还可以根据自定义的规则,自动生成 changeLog
文件。
commitizen
基于 Node.js 的 git commit
命令行工具,辅助生成标准化规范化的 commit message
,是一个平台,具体职能交给适配器。
什么是adapter(适配器)
更换 commitizen
命令行工具的交互方式 插件。像 cz-emoji
、cz-emoji-chinese
、cz-conventional-changelog
、cz-customizable
,这些都是适配器,至于选择哪个,看个人喜好选择。本文选择cz-git
配置步骤
安装所有依赖
npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional commitizen cz-git
配置husky
- 初始化
npx husky init
,执行完之后项目根目录会多一个.husky
文件夹并创建了一个pre-commit
脚本,并且在package.json
文件添加了prepare
脚本用来
配置lint-staged
-
首先明确一下,lint-staged 仅仅是文件过滤器,不会帮你格式化任何东西,所以没有代码规则配置文件,需要自己配置一下,对特定的文件进行特定的格式化
-
配置文件可以有很多种方式,
package.json
文件里直接配置或者使用单独的配置文件(可参考官方文档),这里使用单独的配置文件.lintstagedrc.json
,在项目根目录下创建json{ "*.{js,jsx,ts,tsx,vue}": ["eslint --fix", "prettier --write"], "*.{css,scss,less,html,md,json}": ["prettier --write"] }
-
在
package.json
增加如下命令json{ "scripts": { "lint:lint-staged": "lint-staged", } }
-
修改刚才husky初始化自动创建的
pre-commit
脚本npm run lint:lint-staged
,至此在执行``git commit -m "xxx"时就会触发
lint:lint-staged,如果
lint:lint-staged命令失败,提交将自动中止。而
lint:lint-staged`则会根据刚才的配置文件对特定文件进行特定的格式化
配置commitlint
-
package.json
添加 config 字段,为commitizen
指定cz-git
适配器json{ "config": { "commitizen": { "path": "./node_modules/cz-git" } } }
-
创建
commitlint.config.js
文件来配置lint
规则cz-git
有多种模板,这里使用中英文对照模版,可参考官方js// commitlint.config.cjs这里要使用cjs的后缀文件名,告诉vite以commonjs规范来处理 module.exports = { // 继承的规则 extends: ["@commitlint/config-conventional"], // 自定义规则 rules: { // @see https://commitlint.js.org/#/reference-rules // 提交类型枚举,git提交type必须是以下类型 "type-enum": [ 2, "always", [ "feat", // 新增功能 "fix", // 修复缺陷 "docs", // 文档变更 "style", // 代码格式(不影响功能,例如空格、分号等格式修正) "refactor", // 代码重构(不包括 bug 修复、功能新增) "perf", // 性能优化 "test", // 添加疏漏测试或已有测试改动 "build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等) "ci", // 修改 CI 配置、脚本 "revert", // 回滚 commit "chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) "wip", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) ], ], "subject-case": [0], // subject大小写不做校验 }, prompt: { messages: { type: "选择你要提交的类型 :", scope: "选择一个提交范围(可选):", customScope: "请输入自定义的提交范围 :", subject: "填写简短精炼的变更描述 :\n", body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', footerPrefixesSelect: "选择关联issue前缀(可选):", customFooterPrefix: "输入自定义issue前缀 :", footer: "列举关联issue (可选) 例如: #31, #I3244 :\n", generatingByAI: "正在通过 AI 生成你的提交简短描述...", generatedSelectByAI: "选择一个 AI 生成的简短描述:", confirmCommit: "是否提交或修改commit ?", }, // prettier-ignore types: [ { value: "feat", name: "特性: ✨ 新增功能", emoji: ":sparkles:" }, { value: "fix", name: "修复: 🐛 修复缺陷", emoji: ":bug:" }, { value: "docs", name: "文档: 📝 文档变更(更新README文件,或者注释)", emoji: ":memo:" }, { value: "style", name: "格式: 🌈 代码格式(空格、格式化、缺失的分号等)", emoji: ":lipstick:" }, { value: "refactor", name: "重构: 🔄 代码重构(不修复错误也不添加特性的代码更改)", emoji: ":recycle:" }, { value: "perf", name: "性能: 🚀 性能优化", emoji: ":zap:" }, { value: "test", name: "测试: 🧪 添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"}, { value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"}, { value: "ci", name: "集成: ⚙️ 修改 CI 配置、脚本", emoji: ":ferris_wheel:"}, { value: "revert", name: "回退: ↩️ 回滚 commit",emoji: ":rewind:"}, { value: "chore", name: "其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"}, { value: "wip", name: "开发中: 🚧 开发阶段临时提交", emoji: ":construction:"}, ], useEmoji: true, emojiAlign: "center", useAI: false, aiNumber: 1, themeColorCode: "", scopes: [], allowCustomScopes: true, allowEmptyScopes: true, customScopesAlign: "bottom", customScopesAlias: "custom", emptyScopesAlias: "empty", upperCaseSubject: false, markBreakingChangeMode: false, allowBreakingChanges: ["feat", "fix"], breaklineNumber: 100, breaklineChar: "|", skipQuestions: [], issuePrefixes: [{ value: "closed", name: "closed: ISSUES has been processed" }], customIssuePrefixAlign: "top", emptyIssuePrefixAlias: "skip", customIssuePrefixAlias: "custom", allowCustomIssuePrefix: true, allowEmptyIssuePrefix: true, confirmColorize: true, maxHeaderLength: Infinity, maxSubjectLength: Infinity, minSubjectLength: 0, scopeOverrides: undefined, defaultBody: "", defaultIssues: "", defaultScope: "", defaultSubject: "", }, };
-
在husky文件夹下添加
commit-msg
钩子脚本npx --no -- commitlint --edit ${1}
最后配置便捷脚本
在package.json
添加以下脚本,方便利用上述工具进行提交。提交代码时执行npm run commit
即可
json
{
"scripts": {
"commit": "git add . && git-cz",
}
}