(1). Husky安装hooks的原理:

node husky install
// 最终执行./lib/installer/bin中的脚本
// 而hooks的建立逻辑在./lib/installer/hooks.js中

(2). husky核心源码解读:

# Created by Husky v4.2.5 (https://github.com/typicode/husky#readme)
#   At: 2020/8/3 上午11:25:21
#   From: ... (https://github.com/typicode/husky#readme)

debug () {
  if [ "$HUSKY_DEBUG" = "true" ] || [ "$HUSKY_DEBUG" = "1" ]; then
    echo "husky:debug $1"
  fi
}

command_exists () {
  command -v "$1" >/dev/null 2>&1
}

// 做用就是用本地的husky-run指令执行hook
run_command () {
  if command_exists "$1"; then
    "$@" husky-run $hookName "$gitParams"
    exitCode="$?"
    debug "$* husky-run exited with $exitCode exit code"

    if [ $exitCode -eq 127 ]; then
      echo "Can't find Husky, skipping $hookName hook"
      echo "You can reinstall it using 'npm install husky --save-dev' or delete this hook"
    else
      exit $exitCode
    fi

  else
    echo "Can't find $1 in PATH: $PATH"
    echo "Skipping $hookName hook"
    exit 0
  fi
}

// 经过grep指令判断各个配置文件中是否存在pre-commit
hookIsDefined () {
  grep -qs $hookName \
    package.json \
    .huskyrc \
    .huskyrc.json \
    .huskyrc.yaml \
    .huskyrc.yml
}

huskyVersion="4.2.5"
gitParams="$*"
// 获取当前脚本的名称,如:pre-commit
// 后面的指令匹配都是围绕这个名称,后面内容中的hookName都会以pre-commit为例
hookName="$(basename "$0")"

debug "husky v$huskyVersion - $hookName"

# Skip if HUSKY_SKIP_HOOKS is set
if [ "$HUSKY_SKIP_HOOKS" = "true" ] || [ "$HUSKY_SKIP_HOOKS" = "1" ]; then
  debug "HUSKY_SKIP_HOOKS is set to $HUSKY_SKIP_HOOKS, skipping hook"
  exit 0
fi

# Source user var and change directory
. "$(dirname "$0")/husky.local.sh"
debug "Current working directory is $(pwd)"

# Skip fast if hookName is not defined
# Don't skip if .huskyrc.js or .huskyrc.config.js are used as the heuristic could
# fail due to the dynamic aspect of JS. For example:
# `"pre-" + "commit"` or `require('./config/hooks')`)
if [ ! -f .huskyrc.js ] && [ ! -f husky.config.js ] && ! hookIsDefined; then
  debug "$hookName config not found, skipping hook"
  exit 0
fi

# Source user ~/.huskyrc
if [ -f ~/.huskyrc ]; then
  debug "source ~/.huskyrc"
  . ~/.huskyrc
fi

# Set HUSKY_GIT_STDIN from stdin
case $hookName in
  "pre-push"|"post-rewrite")
    export HUSKY_GIT_STDIN="$(cat)";;
esac

# Windows 10, Git Bash and Yarn 1 installer
if command_exists winpty && test -t 1; then
  exec < /dev/tty
fi

# Run husky-run with the package manager used to install Husky
case $packageManager in
  "npm") run_command npx --no-install;;
  "npminstall") run_command npx --no-install;;
  "pnpm") run_command pnpx --no-install;;
  "yarn") run_command yarn run --silent;;
  *) echo "Unknown package manager: $packageManager"; exit 0;;
esac

注:

  • run_command函数,用做本地的husky-run指令执行hook:
  • 如: "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "**/*.js": "eslint" // 执行eslint }

(3). 总结:

  • 安装时建立hooks
  • 提交时从配置文件中(package.json、.huskyrc、.huskyrc.json)读取相应的hook配置
  • 执行配置中的指令/脚本