使用Git钩子+ husky + lint语法检查提高前端项目代码质量

@

作为开发经理,是不是常常有一个疑问:怎样保证项目代码质量和可维护性?今天我将通过几个我在团队中常用的工具,提高代码质量,降低隐性的故障率与积累的维护成本,避免“技术债”。

首先,要保证代码仓库每次提交的代码质量,需要在提交前引入检查机制,可以使用 Git hooks。在提交前运行各检查,如果检查不通过,就阻止提交。

本地 Git Hook 默认不会随仓库共享,所以如果只放在 .git/hooks/pre-commit 里,其他人 clone 下来是没有这个 hook 的。

团队里常用的办法是借助 Husky,把钩子配置放在项目里,随代码共享。

当然,由于是在本地,组员可以通过跳过 Git 钩子的方式绕过代码检查,最权威的方案还是在 GitLab 服务端强制检查:用 GitLab CI/CD pipeline 配置,在 pipeline 阶段执行 tsc,不通过则拒绝 merge request。本篇文章只讨论本地 Git Hook 的做法。以后有机会再介绍服务端检查。

配置 Git Hook

原理介绍

.git/hooks 目录不会随着代码被提交,所以要用第三方库,Husky 是用 Git 自带的 hook 原理,只是做了几件事:

  • Git 在 .git/hooks/ 目录下放置一堆钩子脚本(例如 pre-commitpre-pushcommit-msg)。

  • 当执行 git commitgit push 等操作时,Git 会去 .git/hooks/ 找对应的脚本并执行。

  • 如果脚本返回非零退出码(exit 1),Git 会中止操作。

安装 Husky

运行如下命令:

yarn add husky --dev

启用 Husky

运行如下命令:

yarn husky install

此时项目会有 .husky/ 目录,里面放着 Git Hook 脚本。

为了保证每次别人 yarn install 时 Husky 自动启用,需要在 package.json 里加一个 postinstall 脚本:

{   "scripts": {     "postinstall": "husky install",     "type-check": "tsc --noEmit"   } } 

当组员提交代码时,会执行如下的流程:

  1. Husky 会在 .git/hooks/ 目录写入一个 代理脚本。

  2. 代理脚本会去执行 .husky/ 目录下的对应 hook 文件,比如 .husky/pre-commit

  3. .husky/pre-commit 里通常就写需要的命令,比如 npm run lint && npm run type-check

  4. 如果命令失败(退出码非 0),Git 就会阻止 commit。

添加 Git Hook

在项目根目录的 .husky/ 创建 pre-commit 文件:

使用Git钩子+ husky + lint语法检查提高前端项目代码质量

将如下内容写入文件:

#!/usr/bin/env sh . "$(dirname "$0")/_/husky.sh"  echo "🔍 Running type check before commit..."  # 运行 TS 检查 yarn type-check RESULT=$?  if [ $RESULT -ne 0 ]; then   echo "❌ Type check failed. Commit aborted."   exit 1 fi  echo "✅ Type check passed. Proceeding with commit." exit 0  

测试脚本

保证脚本编码和换行符符合规范:

使用Git钩子+ husky + lint语法检查提高前端项目代码质量

在Windows下,git是通过Windows Git Bash 环境里执行 hook, 不是命令行环境也不是Powershell!

使用Windows Git Bash,运行sh .husky/pre-commit,查看打印:

使用Git钩子+ husky + lint语法检查提高前端项目代码质量

能打印出脚本的输出,说明脚本执行正常。

执行效果

  • 当执行 git commit 时,Husky 会先跑 yarn type-check

  • 如果 tsc 报错(退出码非 0),commit 会被阻止。

简单来说:Husky = Git hook 的自动安装器 + 管理器

添加语法检查

除了提交前运行 tsc 检查,防止硬性语法错误以外,代码格式统一和规范性检测,也是 Git Hook 其中一个任务。

这里使用 ESLint + TypeScript ESLint + Prettier 组合来统一代码规范与格式

安装Prettier

在项目根目录下执行:

yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin yarn add -D prettier eslint-config-prettier eslint-plugin-prettier  

配置Prettier

在项目根目录下创建 eslint.config.js

完整的配置

// @ts-check  import eslint from "@eslint/js"; import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; import unusedImports from "eslint-plugin-unused-imports"; import { defineConfig } from "eslint/config"; import tseslint from "typescript-eslint";  export default defineConfig(   eslint.configs.recommended,   tseslint.configs.recommended,   eslintPluginPrettierRecommended,    {     plugins: {       "unused-imports": unusedImports     },     files: ["*.js", "src/**/*.{js,jsx,mjs,cjs,ts,tsx}"],     ignores: ["dist/**/*.*", "node_modules/**/*.*"],     languageOptions: {       parserOptions: {         projectService: true,         // eslint-disable-next-line @typescript-eslint/ban-ts-comment         // @ts-expect-error         tsconfigRootDir: import.meta.dirname       }     },     rules: {       "max-len": "off",       "object-curly-spacing": "off",       "@typescript-eslint/no-explicit-any": "off",       "@typescript-eslint/no-unused-vars": "off",       "@typescript-eslint/no-unsafe-function-type": "off",       "@typescript-eslint/no-this-alias": "off",       "@typescript-eslint/no-unused-expressions": "off",       "no-var": "off",       "prefer-const": "off",       "no-empty": "off",       "unused-imports/no-unused-imports": "error",       "unused-imports/no-unused-vars": [         "warn",         {           vars: "all",           varsIgnorePattern: "^_",           args: "after-used",           argsIgnorePattern: "^_"         }       ],       quotes: "off",       "prettier/prettier": [         "warn",         {           semi: true,           singleQuote: false,           trailingComma: "none",           printWidth: 180,           tabWidth: 2,           useTabs: false,           bracketSpacing: true,           arrowParens: "avoid",           endOfLine: "auto"         }       ]     }   } );  

格式化规则调优

每个团队对于代码规范有自己的理解,不同的团队有不同的格式化规则,请根据你的实际情况来调整。

我的代码规范遵循如下原则:

  1. 兼顾可读性与灵活性:某些严格规则被关闭,只保留关键检查。具体配置如下
规则 说明
"max-len": "off" 不限制每行最大长度
"object-curly-spacing": "off" 不强制对象花括号的空格风格
"@typescript-eslint/no-explicit-any": "off" 允许使用 any 类型
"@typescript-eslint/no-unused-vars": "off" 改用 unused-imports 插件来检测未使用变量
"@typescript-eslint/no-unsafe-function-type": "off" 允许函数类型较宽松
"@typescript-eslint/no-this-alias": "off" 允许使用 const self = this 等别名方式
"@typescript-eslint/no-unused-expressions": "off" 允许未被使用的表达式(如短路逻辑)
"no-var": "off" 允许使用 var
"prefer-const": "off" 不强制将变量声明为 const
"no-empty": "off" 允许空代码块
"quotes": "off" 不强制单双引号风格
  1. 注重重用:关注未使用 import / 变量、代码格式一致性。

额外启用了 “eslint-plugin-unused-imports” 插件,专门用于检测未使用的 import 与变量,配置如下

"unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [   "warn",   {     vars: "all",     varsIgnorePattern: "^_",     args: "after-used",     argsIgnorePattern: "^_"   } ]  

使用 Prettier 实现 IDE 和 ESLint 一体化:格式规则通过 Prettier 统一控制,避免 ESLint 规则重复,同时避免 VS Code 格式化工具与 ESLint 产生冲突。

选项 含义
semi true 语句末尾加分号
singleQuote false 使用双引号
trailingComma "none" 不使用尾随逗号
printWidth 180 每行最大宽度 180
tabWidth 2 每个缩进为 2 个空格
useTabs false 使用空格而非 Tab
bracketSpacing true 对象花括号内保留空格
arrowParens "avoid" 箭头函数参数单个时省略括号
endOfLine "auto" 自动适配操作系统换行符
报告级别 "warn" 仅提示警告,不阻止构建

所有 JS/TS/React/JSON 文件均采用 Prettier 作为唯一格式化器,与 ESLint 中的 Prettier 配置保持一致:

使用Git钩子+ husky + lint语法检查提高前端项目代码质量

在开发组成员的VSCode中,安装esbenp插件,并配置如下:

在`/.vscode/settings.json中添加如下配置,并且将文件提交到项目仓库

"[javascript]": { 		"editor.insertSpaces": true, 		"editor.defaultFormatter": "esbenp.prettier-vscode"  	}, 	"[typescript]": { 		"editor.insertSpaces": true, 		"editor.defaultFormatter": "esbenp.prettier-vscode" 	}, 	"[typescriptreact]": { 		"editor.insertSpaces": true, 		"editor.defaultFormatter": "esbenp.prettier-vscode" 	}, 	"[json]": { 		"editor.defaultFormatter": "esbenp.prettier-vscode" 		 	}, 

同时建议开启自动保存

"editor.formatOnSave": true,  "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit" }, 

在项目根目录下创建.prettierrc.js文件,内容如下:

export default {   semi: true,   singleQuote: false,   trailingComma: "none",   printWidth: 180,   tabWidth: 2,   useTabs: false,   bracketSpacing: true,   arrowParens: "avoid",   endOfLine: "auto" };  

如此,当代VS Code中码文件保存,将自动执行与Eslint规则一致的自动格式化。

添加 Git Hook

package.json 里,添加lint相关脚本

{   "scripts": {      ...      "lint": "eslint src/**/*.{ts,tsx}",     "lint:fix": "eslint src/**/*.{ts,tsx} --fix",   } } 

修改项目根目录的 .husky/pre-commit 文件,在之前的基础上,添加“Running ESLint check”:

#!/usr/bin/env sh . "$(dirname "$0")/_/husky.sh"  echo "🔍 Running TypeScript type check..." yarn type-check TYPE_RESULT=$?  echo "🔍 Running ESLint check..." yarn lint LINT_RESULT=$?  if [ $TYPE_RESULT -ne 0 ] || [ $LINT_RESULT -ne 0 ]; then   echo "❌ Commit aborted due to type or lint errors."   exit 1 fi  echo "✅ All checks passed. Proceeding with commit." exit 0  

添加Git提交规范检查

为了保证代码提交历史清晰可读、便于回溯和自动化工具处理,我们统一使用 Conventional Commits 规范

安装commitlint

yarn add -D @commitlint/config-conventional @commitlint/cli 

配置commitlint

制定的规则如下:

  • 类型 - 用于描述本次提交的类别,必须从以下列表中选择:
类型 说明
feat 新功能(feature)
fix 修复 bug
docs 文档修改
style 代码格式修改(不影响功能,如空格、分号、缩进等)
refactor 重构代码(既不新增功能,也不是修复 bug)
perf 性能优化
test 测试相关修改(新增或修改已有测试)
revert 回滚 commit
build 构建流程、依赖变更(如升级 npm 包、修改打包配置)
ci CI 配置或脚本修改
types 类型定义文件修改(如 TypeScript 类型文件)
  • 可选范围 - 用于描述本次提交影响的模块或范围,例如 buttonapilogin 等,非必填:
    feat(login): 增加登录验证码功能

  • 主题 - 简明扼要描述本次提交做了什么,不做大小写限制fix(api): 修复获取用户信息接口报错 docs: 更新 README 文档说明

  • 正文 - 正文为对提交的详细描述,可以分行书写,要求正文前空一行,可用于说明实现思路、原因、可能影响等:

feat(payment): 添加支付宝支付功能  - 支持扫码支付 - 支持手机端支付 - 更新支付相关文档  
  • 脚注 - 用于关联 issue 或记录 BREAKING CHANGE:BREAKING CHANGE: 接口返回字段 userId 修改为 uid Closes #123

在项目根目录下创建 .commitlintrc.json 文件,内容如下:

export default {   // 继承的规则   extends: ["@commitlint/config-conventional"],   // 定义规则类型   rules: {     "body-leading-blank": [2, "always"], // 确保提交消息正文之前有一行空白行     "type-empty": [2, "never"], // 不允许提交消息的 type 类型为空     "subject-case": [0], // subject 大小写不做校验     // type 类型定义,表示 git 提交的 type 必须在以下类型范围内     "type-enum": [       2,       "always",       [         "feat", // 新功能 feature         "fix", // 修复 bug         "docs", // 文档注释         "style", // 代码格式(不影响代码运行的变动)         "refactor", // 重构(既不增加新功能,也不是修复bug)         "perf", // 性能优化         "test", // 添加疏漏测试或已有测试改动         "revert", // 回滚commit         "build", // 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)',         "ci", // 修改CI配置、脚本         "types" // 类型定义文件修改       ]     ]   } };  

添加 Git Hook

package.json 里,添加commitlint脚本:

{   "scripts": {      ...      "commitlint": "commitlint --config commitlint.config.js --edit"   } } 

在项目根目的 .husky/ 目录中创建 commit-msg 文件,内容如下:

#!/usr/bin/env sh . "$(dirname "$0")/_/husky.sh"  echo "🔍 Running commitlint check..." yarn commitlint RESULT=$?  if [ $RESULT -ne 0 ] ; then   echo "❌ Commit aborted due to commitlint errors."   exit 1 fi  echo "✅ All checks passed. Proceeding with commit." exit 0   

至此,我们就完成了前端项目的代码质量检查工具的搭建。

发表评论

评论已关闭。

相关文章