使用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 文件:

将如下内容写入文件:

复制代码
#!/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

测试脚本

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

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

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

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

执行效果

  • 当执行 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 配置保持一致:

在开发组成员的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

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

相关推荐
王同学要变强3 小时前
【深入学习Vue丨第二篇】构建动态Web应用的基础
前端·vue.js·学习
程序定小飞3 小时前
基于springboot的web的音乐网站开发与设计
java·前端·数据库·vue.js·spring boot·后端·spring
Hello_WOAIAI3 小时前
2.4 python装饰器在 Web 框架和测试中的实战应用
开发语言·前端·python
FinClip3 小时前
凡泰极客亮相香港金融科技周,AI助力全球企业构建超级应用
前端
阿四4 小时前
【Nextjs】为什么server action中在try/catch内写redirect操作会跳转失败?
前端·next.js
诸葛思颖4 小时前
一个本地 Git 仓库关联多个远程仓库
git
申阳4 小时前
Day 6:04. 基于Nuxt开发博客项目-LOGO生成以及ICON图标引入
前端·后端·程序员
中国lanwp4 小时前
npm中@your-company:registry 和 registry 的区别
前端·npm·node.js
Bacon4 小时前
Electron 应用商店:开箱即用工具集成方案
前端·github