Husky 是一个 Git Hook 工具,可以触发 Git 提交的各个阶段:pre-commit 、commit-msg 、pre-push 、pre-rebase 等等。Husky支持所有 Git 钩子,可以根据项目的需要选择适当的钩子来运行自定义脚本
npm install --save-dev lint-staged husky
npx husky init
为了保证每个成员npm install 的时候会init Husky
需要在package.json 中添加 "prepare" 脚本
"scripts": {
"prepare": "husky install"
}
当项目其他成员克隆我们的代码后运行 npm install 时,"prepare" 会被触发,husky init 会运行
在.husky 文件中 加入钩子脚本
如pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# 获取当前分支
current_branch=$(git rev-parse --abbrev-ref HEAD)
echo -e "\033[33m ------------------- 当前分支: $current_branch 正在对提交的代码执行commit操作 -------------------- \033[0m"
# 检查是否有待提交的文件
if [ -z "$(git diff --cached --name-only)" ]; then
echo "暂存区域没有文件"
exit 1
fi
npx --no-install lint-staged
# 添加格式化后的文件到暂存区
git add $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|json|css|scss|md)$')
# 运行 Jest 单元测试
echo "Running Jest tests..."
npx jest --passWithNoTests
if [ $? -ne 0 ]; then
echo "一些测试失败了。请在提交之前修复它们。"
exit 1
fi
echo -e "\033[33m ------------------- 代码校验通过准备提交 ✅ -------------------- \033[0m"
exit 0
#!/bin/sh:指定脚本解释器,告诉操作系统用一个 shell 程序来执行该脚本; . "0")/_/husky.sh":确保了 Husky 的环境和变量在钩子脚本中被正确设置;
下面就是具体的任务脚本
使用lint-staged 对暂存代码进行优化
lint-staged 的配置项允许你在提交代码之前对特定类型的文件进行 lint 检查和格式化,确保代码质量和一致性。通过这种方式,可以避免将不符合规范的代码提交到版本库中。
"lint-staged": {
"*.{ts,tsx,js}": [
"eslint --config .eslintrc.js"
],
"*.{css,less,scss}": [
"stylelint --config .stylelintrc.js"
],
"*.{ts,tsx,js,json,html,yml,css,less,scss,md}": [
"prettier --write"
]
},
-
"*.{ts,tsx,js}"-
匹配规则:匹配所有扩展名为
.ts,.tsx, 和.js的文件。 -
命令:
"eslint --config .eslintrc.js"- 作用:使用 ESLint 对匹配的文件进行 lint 检查。
- 配置文件:
.eslintrc.js,这是 ESLint 的配置文件。
-
-
"*.{css,less,scss}"-
匹配规则:匹配所有扩展名为
.css,.less, 和.scss的文件。 -
命令:
"stylelint --config .stylelintrc.js"- 作用:使用 Stylelint 对匹配的文件进行 lint 检查。
- 配置文件:
.stylelintrc.js,这是 Stylelint 的配置文件。
-
-
"*.{ts,tsx,js,json,html,yml,css,less,scss,md}"-
匹配规则:匹配所有扩展名为
.ts,.tsx,.js,.json,.html,.yml,.css,.less,.scss, 和.md的文件。 -
命令:
"prettier --write"- 作用:使用 Prettier 对匹配的文件进行格式化。
- 配置文件:Prettier 的配置文件通常是
.prettierrc或者在package.json中的prettier字段。
-
颜色美化
-
文本颜色:
- 黑色:
\033[0;30m - 红色:
\033[0;31m - 绿色:
\033[0;32m - 黄色:
\033[0;33m - 蓝色:
\033[0;34m - 紫色:
\033[0;35m - 青色:
\033[0;36m - 白色:
\033[0;37m
- 黑色:
-
背景颜色:
- 黑色:
\033[40m - 红色:
\033[41m - 绿色:
\033[42m - 黄色:
\033[43m - 蓝色:
\033[44m - 紫色:
\033[45m - 青色:
\033[46m - 白色:
\033[47m
- 黑色:
-
文本样式:
- 加粗:
\033[1m - 下划线:
\033[4m - 反显:
\033[7m
- 加粗:
-
重置所有属性:
\033[0m
"scripts": {
"lint-staged": "lint-staged"
}
lint-staged 配置会对文件进行ESLint校验和Prettier格式化
ESLint校验会拦截代码冲突和代码错误,Prettier会格式化代码格式后再提交保证仓库代码的统一性,减少多余的格式化提交。
# 添加格式化后的文件到暂存区
git add $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|json|css|scss|md)$')
ESLint
.eslintrc.js
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true, // 指定此配置文件为根配置,ESLint 将不会查找父目录中的配置文件
env: {
node: true, // 启用 Node.js 环境
browser: true, // 启用浏览器环境
es2021: true // 启用 ES2021 的全新语法
},
extends: [
'plugin:vue/vue3-essential', // 基本 Vue 3 规则
'eslint:recommended', // ESLint 推荐的规则
'@vue/eslint-config-typescript', // Vue + TypeScript 的 ESLint 配置
'@vue/eslint-config-prettier/skip-formatting' // 与 Prettier 兼容的 ESLint 配置,跳过格式化规则
],
overrides: [
{
files: ['cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}'], // 对 Cypress 测试文件的特定规则
extends: ['plugin:cypress/recommended'] // 启用 Cypress 的推荐规则
}
],
parserOptions: {
ecmaVersion: 2021, // 支持 ECMAScript 2021 的语法
sourceType: 'module' // 使用 ES 模块的语法
},
rules: {
// 代码风格规则
'semi': [2, 'never'], // 不使用分号
'indent': [
2,
4,
{ SwitchCase: 1 } // switch 语句中的 case 分支使用 1 个空格缩进
],
'no-multi-spaces': 2, // 不允许多个连续的空格
'space-unary-ops': [2, { words: true, nonwords: false }], // 一元运算符后必须有空格
'space-infix-ops': 2, // 中缀操作符周围必须有空格
'space-before-blocks': [2, 'always'], // 代码块前必须有空格
'no-mixed-spaces-and-tabs': 2, // 不允许混合使用空格和制表符
'no-multiple-empty-lines': [2, { max: 1 }], // 连续空行不超过 1 行
'no-trailing-spaces': 2, // 行尾不允许有空格
'no-whitespace-before-property': 2, // 属性名和点运算符之间不能有空格
'no-irregular-whitespace': 2, // 不允许出现不规则的空白字符
'space-in-parens': [2, 'never'], // 圆括号内不能有空格
'comma-dangle': [2, 'never'], // 逗号不允许有拖尾
'array-bracket-spacing': [2, 'never'], // 数组括号内不允许有空格
'object-curly-spacing': [2, 'never'], // 对象括号内不允许有空格
'max-len': ['error', { code: 120 }], // 行宽最大为 120 字符
'operator-linebreak': [2, 'before'], // 运算符换行时,运算符在行首
'comma-style': [2, 'last'], // 逗号风格:换行时在行尾
'no-extra-semi': 2, // 不允许出现多余的分号
'curly': [2, 'all'], // 使用大括号包裹所有控制结构
'key-spacing': [2, { beforeColon: false, afterColon: true }], // 属性名与冒号之间不能有空格,冒号后必须有空格
'comma-spacing': [2, { before: false, after: true }], // 逗号后必须有空格
'semi-spacing': [2, { before: false, after: true }], // 分号后必须有空格
'camelcase': [1, { properties: 'always' }], // 强制使用驼峰命名法
'new-cap': ['error', { newIsCap: true, capIsNew: false }], // 构造函数首字母必须大写
'spaced-comment': [2, 'always'], // 注释后必须有空格
'no-inline-comments': 2, // 不允许行内注释
'eqeqeq': [2, 'always', { null: 'ignore' }], // 强制使用全等 (===) 运算符
'no-else-return': [1, { allowElseIf: false }], // 禁止 else 语句,如果 if 语句中已返回值
'no-loop-func': 2, // 禁止在循环中定义函数
'no-restricted-syntax': [
1,
{
selector: 'BinaryExpression[operator=\'instanceof\']',
message: 'Use \'instanceof\' for object type detection.' // 不建议使用 instanceof 来检测对象类型
},
{
selector: 'BinaryExpression[operator=\'typeof\']',
message: 'Use \'typeof\' for type detection.' // 不建议使用 typeof 来检测类型
},
{
selector: 'CallExpression[callee.name=\'parseInt\']',
message: 'Use Math.floor, Math.round, or Math.ceil instead of parseInt to remove decimal points.' // 不建议使用 parseInt 来移除小数点
}
],
'no-implicit-coercion': [1, { allow: ['!!'] }], // 禁止隐式类型转换
'radix': [2, 'always'], // parseInt 函数必须指定进制
'quotes': [2, 'single'], // 强制使用单引号
'no-array-constructor': 2, // 不允许使用 Array 构造函数
'max-lines-per-function': [
1,
{
max: 50, // 函数最大行数为 50 行
skipComments: true, // 跳过注释行
skipBlankLines: true, // 跳过空行
IIFEs: true // 对立即调用的函数表达式 (IIFE) 应用规则
}
],
'max-params': [1, 6], // 函数参数最大数量为 6
'no-eval': 2, // 禁止使用 eval
'prefer-const': 1, // 建议使用 const 声明不变的变量
'no-var': 1, // 建议使用 let/const 替代 var
'prefer-destructuring': [
1,
{ object: true, array: false } // 建议使用解构赋值
],
'prefer-template': 1, // 建议使用模板字符串
'template-curly-spacing': [2, 'never'], // 模板字符串中的花括号内不允许有空格
'no-duplicate-imports': 2, // 禁止重复导入
// TypeScript 特定规则
'@typescript-eslint/no-unused-vars': 'error', // 禁止未使用的变量
'@typescript-eslint/explicit-module-boundary-types': 'off' // 允许省略函数的返回类型
},
globals: {
withDefaults: true, // Vue 3 特性
defineExpose: true, // Vue 3 特性
defineEmits: true, // Vue 3 特性
defineProps: true // Vue 3 特性
}
}
Prettier
{
"printWidth": 200,
"tabWidth": 4,
"useTabs": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "always",
"rangeStart": 0,
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "ignore",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto",
"semi": true,
"overrides": [
{
"files": "*.json",
"options": {
"tabWidth": 4
}
},
{
"files": "*.md",
"options": {
"printWidth": 100
}
}
]
}
git 提供的钩子有:
pre-commit: 在执行 git commit 命令时,在提交被创建之前触发。它允许你在执行提交之前自定义一些操作,例如代码风格检查、代码静态分析、单元测试等。
prepare-commit-msg:在提交消息编辑器打开之前触发,如果使用-m传递提交信息,则不会触发该钩子
commit-msg: 它在执行 git commit 命令时,编辑提交信息之后、提交之前触发。具体来说,commit-msg 钩子会在提交信息(commit message)被写入提交文件(如 .git/COMMIT_EDITMSG)后被触发。
post-commit: 在执行 git commit 命令时,在提交被创建之后触发。
pre-push:在执行 git push 命令之前触发
post-update:在执行 git push 命令后,远程仓库中的更新已成功推送到目标仓库后触发。
pre-receive:运行在服务端,在远程仓库接收推送操作时,在所有分支引用更新之前触发
update:运行在服务端,在执行 git push 命令后,远程仓库中的更新被成功推送到目标仓库,在每个分支引用被更新之前触发,pre-receive 先于 update。
pre-applypatch:在应用 patch 到工作目录之前触发。
applypatch-msg: 在 git 应用 patch 时被触发。具体来说,applypatch-msg 钩子会在 git 应用补丁到工作目录之前,对补丁的提交信息(commit message)进行处理。
pre-rebase:在执行 git rebase 命令之前触发
pre-merge-commit:在执行合并操作之前触发。具体来说,当你执行 git merge 命令时,git 将会在执行合并操作之前触发 pre-merge-commit 钩子。
push-to-checkout:运行在服务端,在客户端强制推送到当前检出分支时触发。
fsmonitor-watchman: fsmonitor-watchman 是一个可选的特性,git 可以通过 Watchman 服务来实现高效的文件系统监视功能。执行 git 的一些操作,比如 git status、git diff、git commit、git pull 等,需要检查文件系统的状态,在较大的代码库中,每次使用这些操作都会将整个项目文件夹检查一遍,频繁使用这些操作会导致较长时间的耗时,git 可以利用 WatchMan 提供的高效文件系统监视功能,从而减少状态检查操作的耗时。要使用 WatchMan,首先确保系统上已经安装了 Watchman ,并且 git 版本支持该特性。然后,通过配置 git,启用 core.fsmonitor 选项,并将其设置为 Watchman 来启用该特性。
watchman 通过减少不必要的操作来提高文件系统的检测性能,在检测时只关注文件变化的部分,而不是每次检测都将所有的项目文件都遍历一遍。fsmonitor-watchman 会在你执行任何与文件系统变更相关的 git 操作和文件系统变化时触发。
sendemail-validate:是 git 的一个配置选项,要想将其开启 sendemail-validate,可以通过 git config --global sendemail.validate true 设置,该选项的默认值取决于 git 版本。sendemail-validate 钩子在邮箱被发送之前调用。
对指定分支push限制
pre-push:在执行 git push 命令之前触发
npx husky add .husky/pre-push "npm run i"
推送代码的时候拦截指定分支的内容,如果有exit 1 终止push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# 获取当前分支
currBranch=$(git rev-parse --abbrev-ref HEAD)
# 获取当前分支已经合并的分支列表
mergedList=$(git branch --merged $currBranch)
# 打印当前分支信息
echo -e "\033[0;32m ------------------- 当前分支: \033[33m $currBranch \033[0;32m 正在对提交的代码执行 \033[33m push \033[0;32m 操作 --------------------"
# 检查 mergedList 是否为空
if [ -z "$mergedList" ]; then
echo "\033[0;33m 当前分支没有任何合并记录。"
exit 0
fi
# 确保换行符是 Unix 格式的
mergedList=$(echo "$mergedList" | tr '\r' '\n')
# 使用 while 循环读取每一行
array=()
while IFS= read -r line; do
array+=("$line")
done <<< "$mergedList"
# 定义要搜索的分支名称
search="test"
found=false
# 遍历合并列表,检查是否存在 test 分支
for item in "${array[@]}"; do
item_without_spaces=${item//[[:space:]]/}
if [ "$item_without_spaces" == "$search" ]; then
found=true
break
fi
done
# 如果找到 test 分支,阻止合并操作
if $found; then
echo "\033[33m 当前分支 \033[0;32m $currBranch 包含了 \033[0;31m $search 分支合并记录"
echo "\033[0;31m test禁止合并到其他分支"
exit 1
fi
# 如果未找到 test 分支,允许合并操作
exit 0
使用commitlint 限制提交信息的格式
创建commitlint.config.js 文件,编辑commit信息
添加钩子
npx husky add .husky/commit-msg "npm run commitlint"
配置
"scripts": {
"commitlint": "commitlint --config commitlint.config.js -e -V",
}
commitlint.config.js
module.exports = {
ignores: [commit => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'], // 主体前有空行
'footer-leading-blank': [1, 'always'], // 末行前有空行
'header-max-length': [2, 'always', 108], // 首行最大长度
'subject-empty': [2, 'never'], // 标题不可为空
'type-empty': [2, 'never'], // 类型不可为空
'type-enum': [ // 允许的类型
2,
'always',
[
'wip', // 开发中
'feat', // 新增功能
'merge', // 代码合并
'fix', // bug 修复
'test', // 测试
'refactor', // 重构
'build', // 构造工具、外部依赖(webpack、npm)
'docs', // 文档
'perf', // 性能优化
'style', // 代码风格(不影响代码含义)
'ci', // 修改项目继续集成流程(Travis,Jenkins,GitLab CI,Circle等)
'chore', // 不涉及 src、test 的其他修改(构建过程或辅助工具的变更)
'workflow', // 流水线
'revert', // 回退
'types', // 类型声明
'release', // 版本发布
],
],
},
};
若commit信息信息不规范会被拦截