Husky 是一个流行的 Git 钩子(Git Hooks) 管理工具,它允许你在 Git 工作流中的特定事件(例如 pre-commit 提交前、pre-push 推送前等)自动执行脚本。通过 Husky,团队可以轻松地在代码提交或推送前强制执行代码规范、运行测试、检查代码质量等操作,从而标准化开发流程,有效防止不符合要求的代码进入版本库。

核心思想:

  • 自动化 Git 钩子:将 Git 钩子集成到 package.json 中,方便管理和版本控制。
  • 规范代码提交:在提交或推送前执行脚本,确保代码质量和提交信息符合团队标准。
  • 团队协作效率:统一开发环境中的代码质量检查和预提交操作,减少人工审查成本。

一、为什么需要 Husky?

在团队协作开发中,代码质量和规范一致性是至关重要的。然而,仅仅依靠人工审查或后期修复往往效率低下,且容易遗漏。常见的问题包括:

  1. 不规范的代码进入仓库:忘记运行代码格式化工具、提交了未经 ESLint 检查的代码。
  2. 提交了失败的测试:在本地没跑完测试就提交,导致 CI/CD 流程失败。
  3. 提交信息不一致:团队成员使用不同的提交信息格式,导致难以追溯和管理。
  4. 环境差异导致问题:不同开发者的本地环境配置差异,导致代码在某些机器上可以运行但提交后出问题。

Husky 通过利用 Git 钩子机制,将这些检查和自动化任务前置到提交或推送的本地阶段,从而有效地解决了上述问题:

  • 强制执行标准:在代码进入版本库之前进行校验,确保所有提交都符合预设标准。
  • 提前发现问题:在开发者本地工作流中发现并修复问题,避免将问题带入共享仓库。
  • 提升团队效率:减少代码审查中的格式和风格问题,让团队更专注于业务逻辑。
  • 简化配置:将 Git 钩子配置集中在 package.json 中,便于项目共享和版本控制。

二、Git 钩子 (Git Hooks) 机制

在深入 Husky 之前,理解 Git 钩子是关键。

2.1 Git 钩子定义

Git 钩子 是用户或系统在 Git 仓库中特定事件发生时自动触发脚本的机制。这些脚本是可执行程序(通常是 Shell 脚本),它们存储在 .git/hooks 目录下,并以相应的事件名称命名。

2.2 常见的 Git 钩子类型

Git 钩子大致分为三类:客户端钩子服务端钩子。Husky 主要管理客户端钩子。

客户端钩子 (Client-side Hooks)

在开发者的本地仓库中执行,常见的有:

  • pre-commit (提交前):在 git commit 命令运行,但提交信息还没有编辑时触发。常用于 Lint 代码、运行单元测试、格式化代码。如果脚本以非零状态退出,Git 会停止提交。
  • prepare-commit-msg (准备提交信息):在提交信息编辑器启动之前,默认信息创建之后运行。可用于生成自动化提交信息。
  • commit-msg (提交信息):在提交信息编辑器关闭后,提交信息被读取时触发。常用于验证提交信息是否符合特定的规范(如 Conventional Commits)。
  • pre-push (推送前):在 git push 命令运行前触发。常用于运行更全面的测试、检查大型文件等。如果脚本以非零状态退出,推送会被取消。
  • post-merge (合并后):在 git merge 成功运行后触发。常用于恢复权限、生成文档等。

服务端钩子 (Server-side Hooks)

在 Git 服务器上执行,例如 pre-receive, update, post-receive,常用于强制执行仓库政策,如拒绝不符合规范的提交,或者在收到推送后集成 CI/CD 系统。

2.3 Git 钩子的局限性

默认的 Git 钩子存在以下问题:

  1. 难以版本控制.git/hooks 目录不在 Git 仓库本身的管理范围内,无法直接通过 Git 版本控制共享给团队成员。
  2. 管理复杂:对于大型项目和多个钩子,手动编写和维护 Shell 脚本效率较低。
  3. 跨平台兼容性:Shell 脚本在不同操作系统(Windows/macOS/Linux)上的行为可能存在差异。

Husky 正是为了解决这些问题而生。 它将 Git 钩子的管理抽象到 package.json 中,并通过 Node.js 环境提供跨平台兼容性。

三、Husky 的工作原理

Husky 通过以下方式集成和管理 Git 钩子:

  1. 安装钩子脚本:当你运行 husky install 命令时,Husky 会在你的 .git/hooks/ 目录中创建一个特殊的脚本。这个脚本是 Husky 的入口点。
  2. 代理执行:当 Git 触发任何一个钩子(例如 pre-commit)时,它会执行 .git/hooks/pre-commit 脚本。这个脚本实际上会调用 Husky 的内部逻辑。
  3. 读取配置:Husky 的内部逻辑会读取你的 package.json 文件或 .husky/ 目录中的配置,找到与当前 Git 钩子相对应配置。
  4. 执行用户定义命令:Husky 随后会执行你为该钩子定义的命令(例如 npm testlint-staged)。

这个流程有效地将 Git 钩子的执行从本地 .git 目录转移到了项目根目录下的配置文件,从而实现了版本控制和团队共享。

四、安装与基本使用

4.1 基本安装

我们以一个 Node.js 项目为例,使用 npm 或 yarn 进行安装。

  1. 安装 Husky 为开发依赖

    1
    2
    3
    4
    5
    npm install husky --save-dev
    # 或者
    yarn add husky --dev
    # 或者对于 pnpm
    pnpm add husky --save-dev

    安装完成后,package.jsondevDependencies 中会新增 husky

  2. 激活 Husky (创建 .husky 目录并配置 prepare 脚本)

    Husky 官方推荐使用 prepare 脚本来自动化 husky install 的过程。这确保了在项目初次安装依赖 (npm install) 后,Husky 能自动在 .git/hooks 中设置好钩子。

    1
    npx husky install

    这会在项目根目录下创建一个 .husky 文件夹,其中包含一些用于管理钩子的脚本。

    接着,将 prepare 脚本添加到 package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // package.json
    {
    "name": "my-project",
    "version": "1.0.0",
    "description": "A sample project",
    "main": "index.js",
    "scripts": {
    "prepare": "husky install" // 添加此行
    },
    "devDependencies": {
    "husky": "^9.0.0"
    }
    }

    现在,当其他人克隆你的仓库并运行 npm install (或 yarn / pnpm install) 时,husky install 命令将自动执行,激活 Git 钩子。

  3. 添加你的第一个 Git 钩子

    pre-commit 钩子为例,我们希望在每次提交前运行测试。

    1
    npx husky add .husky/pre-commit "npm test"

    执行此命令后,会在 .husky/ 目录下创建一个 pre-commit 文件,内容类似于:

    1
    2
    3
    4
    #!/usr/bin/env sh
    . "$(dirname -- "$0")/_/husky.sh"

    npm test

    现在,当你尝试 git commit 时,npm test 命令会在提交前自动运行。如果 npm test 失败(即以非零状态退出),则 Git 提交会被阻止。

4.2 示例:配合 lint-staged 使用 pre-commit

在实际开发中,我们通常不希望在 pre-commit 钩子中对整个项目运行 Lint 和格式化,这样效率低下。我们更希望只对已暂存 (staged) 的文件进行检查。这时,lint-staged 就派上用场了。

lint-staged 是一个工具,它允许你对 Git 暂存区中的文件运行命令,并将这些命令的输出重新添加到暂存区。

  1. 安装 lint-stagedeslint (示例)

    1
    npm install lint-staged eslint prettier --save-dev
  2. 配置 package.jsonlint-staged 配置文件

    package.json 中配置 lint-staged

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // package.json
    {
    "name": "my-project",
    "version": "1.0.0",
    "description": "A sample project",
    "main": "index.js",
    "scripts": {
    "prepare": "husky install"
    },
    "devDependencies": {
    "husky": "^9.0.0",
    "eslint": "^8.0.0",
    "prettier": "^3.0.0",
    "lint-staged": "^15.0.0"
    },
    "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
    "eslint --fix", // 自动修复 ESLint 错误
    "prettier --write", // 自动格式化代码
    "git add" // 重新添加修改后的文件到暂存区
    ],
    "*.{json,css,scss,less,html,md}": [
    "prettier --write",
    "git add"
    ]
    }
    }

    或者,你可以在项目根目录创建 lint-staged.config.js (.mjs / .cjs / .json / .yaml / .yml) 文件来配置 lint-staged

  3. 修改 .husky/pre-commit 钩子

    将之前添加的 npm test 命令替换为 npx lint-staged

    1
    npx husky add .husky/pre-commit "npx lint-staged"

    现在,每当 git commit 时,Husky 会执行 npx lint-staged,后者只会对你修改并暂存的 .js, .ts 等文件运行 ESLint 和 Prettier,并自动将修复后的文件重新添加到暂存区。

五、更多常用钩子和高级用法

5.1 其他常用 Git 钩子

  • commit-msg 钩子
    用于验证提交信息是否符合规范,常配合 commitlint 使用。

    1
    npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

    你需要安装 commitlint 和相应的配置。

    例如:

    1
    2
    npm install --save-dev @commitlint/config-conventional @commitlint/cli
    echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
  • pre-push 钩子
    git push 前运行更全面的测试或检查,防止不稳定的代码被推送到远程仓库。

    1
    npx husky add .husky/pre-push "npm run test:ci"

5.2 跳过钩子

有时你可能需要绕过 Git 钩子的检查(例如,在紧急修复或提交 WIP 代码时)。

  • 跳过所有 pre-commit 钩子

    1
    2
    3
    git commit -n --no-verify
    # 或简写
    git commit --no-verify
  • 跳过所有客户端钩子

    1
    git push --no-verify

5.3 Yarn Berry (Yarn 2/3+) 和 PNPM 支持

Husky 完全支持 Yarn Berry 和 PNPM。由于 Yarn Berry 引入了 Plug’n’Play (PnP) 机制,全局安装的 CLI 可能无法找到。Husky 建议使用 npx 或者 yarn dlx 来运行。上面的示例中已基本使用 npx,这在所有包管理器下都兼容良好。

5.4 多个命令

在一个钩子文件中,你可以像正常的 Shell 脚本一样添加多个命令:

1
2
3
4
5
6
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test
echo "Tests passed, now linting staged files..."
npx lint-staged

如果其中任何一个命令以非零状态退出,整个钩子就会失败,Git 命令就会被中断。

六、总结

Husky 是现代前端和 Node.js 项目不可或缺的工具,它通过优雅地管理 Git 钩子,将代码质量和规范前置到开发者的本地环节。

通过使用 Husky:

  • 代码质量得到了保障:强制执行代码风格、运行测试,减少不规范代码提交。
  • 开发流程标准化:每个开发者在提交和推送时遵循相同的规则。
  • 团队协作更高效:减少了代码审查的琐碎工作,提升了团队整体开发效率。
  • 项目可维护性提高:稳定且规范的代码库更易于长期维护和迭代。

将 Husky 与 lint-stagedeslintprettiercommitlint 等工具结合使用,可以构建一个强大的自动化代码质量保障体系,极大地提升项目的健康度和团队的工作效率。