likes
comments
collection
share

如何用husky来管理代码规范

作者站长头像
站长
· 阅读数 11

前言

在项目中我们可以使用 prettiereslint 来管理代码规范,开发环境下,可以手动来 lint 代码格式问题,但是如何保证远程仓库中的代码符合团队的规范呢?husky 能够在 git hooks commit 中帮助到你。

主要内容

通过本文你将学习到以下内容:

  • husky 是什么东西,我们为什么要使用 husky
  • husky 究竟怎么做 git hooks
  • husky 内部是怎么实现的。

husky基本介绍

  • Github:github.com/typicode/hu…  使用 Git 钩子变得简单

  • 在做前端工程化时 husky 可以说是一个必不可少的工具。husky 可以让我们向项目中方便添加 git hooks

  • 这个库的名字指的是 “哈士奇”,结合库主要用来在提交前发现、规范代码的作用,应该是这个意思:‘不好好规范你的代码,你就像一个哈士奇一样,会用代码拆家的’  。这个寓意跟另一个经常和 husky 搭配使用的库 lint-staged 很像。

如何用husky来管理代码规范

怎么理解这两个东西呢?

  • 你可以把 husky 当做锄头挖坑,lint-staged 当做萝卜,一个萝卜一个坑!🔥

我们先来探讨一下没有 husky 的情况下,我们该怎么去做 git hooks 操作。

  • 首先我们创建于一个 index 文件夹,在终端执行 git init 命令,就会出现 .git 文件夹,我们可以查看到 hooks

如何用husky来管理代码规范

  • 新建 .pre-commit 文件,在 pre-commit 钩子函数中写入:
npm run file // 执行 node脚本

// 异常退出
exitCode="$?"
exit $exitCode
  • 执行 chmod 700 .pre-commit/* , 给文件设置权限。
  • 新建 file.js文件,键入信息 git hooks pre-commit start
  • git add .
  • git commit -m "feat: fist commit" 效果:

如何用husky来管理代码规范

这样我们就可以在 git commit 之前去做想做的事情了,比如添加 eslint 来检查代码问题。当然 git hooks 还有很多,并不是本文的主题,我们只是探讨了一下在没有 husky 的时候,我们应该怎么做。再比如本地的 .git 文件不会提交到远程仓库,我们需要配置脚本来方便协同开发者。

"scripts": { 
    "file": "node file.js", // 这里你可以配置lint来检查代码
    "empowerInstall": "git config core.hooksPath hooks && chmod 700 .pre-commit/*" 
},

那么新起一个项目都要这么配置未免有些麻烦。那么 husky 能够帮助你做这些。

husky的基本使用

  1. 安装 husky
npm install husky --save-dev
  1. 使 git hooks 生效
npx husky install

在这里就会创建一个 .husky 的文件

如何用husky来管理代码规范

  1. package.json 里面创建执行脚本
npm pkg set scripts.prepare="husky install"

如何用husky来管理代码规范

  1. 创建 git hooks 文件
npx husky add .husky/pre-commit "npm run file"

如何用husky来管理代码规范

所以我们现在 commit 的时候也会走 git hooks

如何用husky来管理代码规范

如果有文件没有权限的错误,那就需要执行chmod 700 .husky/*,进行授权。更多linux权限详解

husky配合lint-staged使用

  1. 安装 lint-staged
npm i lint-staged -S
  1. 配置 lint-staged
 // package.json
"lint-staged": {
    "*.{js,ts,vue,json}": [
      "prettier --write"
    ]
}
  1. 执行 lint-staged
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

// 这个命令允许你从 npm 包(本地安装或远程获取)中运行任意命令。在类似于通过 `npm run` 运行它的环境中。
npm exec lint-staged
  1. 检查

如何用husky来管理代码规范 6. 检查通过

如何用husky来管理代码规范 注意lint-staged 只会对有修改记录的文件做检查!!!

所以经过 eslint、prettier、lint-staged 校验的代码,一定是非常符合规范的,而且他会帮你指出来具体的错误地方。

husky的源码解析

  • huskygithub 仓库
  • 把源码下载到本地来,分别执行:
npm i  // 安装所需要的依赖
npm run prepare // 打包并生成lib文件,创建钩子

// package.json
"bin": "lib/bin.js", 

入口文件为 lib/bin.js ,当然这里的是给用户用的文件,源码只有 src 下面的两个文件。

index.ts

import cp = require('child_process') // 子进程
import fs = require('fs') // fs模块
import p = require('path') // path模块

// 日志打印
const l = (msg: string): void => console.log(`husky - ${msg}`)

// 通过node执行shell脚本,拼接返回执行结果,比如 git commit
const git = (args: string[]): cp.SpawnSyncReturns<Buffer> =>
  cp.spawnSync('git', args, { stdio: 'inherit' })


export function install(dir = '.husky'): void {
  if (process.env.HUSKY === '0') {
    l('HUSKY env variable is set to 0, skipping install')
    return
  }

  // Ensure that we're inside a git repository
  // If git command is not found, status is null and we should return.
  // That's why status value needs to be checked explicitly.
  if (git(['rev-parse']).status !== 0) {
    l(`git command not found, skipping install`)
    return
  }

  // Custom dir help
  const url = 'https://typicode.github.io/husky/#/?id=custom-directory'

  // 判断安装目录是否为项目根目录并创建 .husky 文件夹  
  if (!p.resolve(process.cwd(), dir).startsWith(process.cwd())) {
    throw new Error(`.. not allowed (see ${url})`)
  }

  // 判断项目中是否存在 .git 文件。
  // 如果不存在 则不存在 githook 文件。
  if (!fs.existsSync('.git')) {
    throw new Error(`.git can't be found (see ${url})`)
  }

  try {
    // 在 .husky 文件下创建文件夹 '_' 
    fs.mkdirSync(p.join(dir, '_'), { recursive: true })

    // 在 '_' 文件下写入文件 .gitignore, 文件内容为 ‘*’, 忽略该目录下所有文件的 git 提交  
    fs.writeFileSync(p.join(dir, '_/.gitignore'), '*')

    // 复制 husky 项目根目录下的 husky.sh 文件 到 目标项目的 '_' 目录下,名称不变  
    fs.copyFileSync(p.join(__dirname, '../husky.sh'), p.join(dir, '_/husky.sh'))

    // 执行 git 操作,修改 githook 的执行路径为目标项目的 .husky 文件下
    const { error } = git(['config', 'core.hooksPath', dir])
    if (error) {
      throw error
    }
  } catch (e) {
    l('Git hooks failed to install')
    throw e
  }
  // 执行成功 or 失败后的 log 提示  
  l('Git hooks installed')
}

// 创建hooks文件,并写入内容
export function set(file: string, cmd: string): void {
  const dir = p.dirname(file)
  // 如果文件目录不存在,则执行husky install
  if (!fs.existsSync(dir)) {
    throw new Error(
      `can't create hook, ${dir} directory doesn't exist (try running husky install)`,
    )
  }

  // 写入文件, 指定解释器为 sh 执行 shell 脚本, 
  // cmd 动态参数,为开发者想要在这个 githook 阶段执行的操作,一般为脚本  
 // * 例:npm run lint
  fs.writeFileSync(
    file,
    `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

${cmd}
`,
    { mode: 0o0755 },
  )
  // 创建成功,打印log
  l(`created ${file}`)
}

// add: 在已有的 githook 文件中追加命令  
export function add(file: string, cmd: string): void {
  if (fs.existsSync(file)) { // githook 文件是否存在  
    fs.appendFileSync(file, `${cmd}\n`) // 存在,在原有文件基础上追加新的内容  
    l(`updated ${file}`) // 打印成功日志
  } else { // 不存在 
    set(file, cmd) // 执行 set 添加该 githook 文件  
  }
}

// 卸载hooksPath, 恢复到.git的默认路径
export function uninstall(): void {
  git(['config', '--unset', 'core.hooksPath'])
}

上述代码主要就做了,创建 .husky.ignorehusky.sh 以及 gitHooks 等文件,并实现了setadd 方法,操作 gitHooks 文件。

bin.ts

用于接受命令行参数,触发 src/index.ts 中的执行文件。

#!/usr/bin/env node
import p = require('path')  // path模块
import h = require('./') // ./src/index.ts

// Show usage and exit with code
function help(code: number) {
  console.log(`Usage:
  husky install [dir] (default: .husky)
  husky uninstall
  husky set|add <file> [cmd]`)
  process.exit(code)
}

// Get CLI arguments 获取命令行参数
const [, , cmd, ...args] = process.argv
const ln = args.length
const [x, y] = args

// 处理需要参数的主文件的函数并执行, 对错误的参数进行了长度判断
const hook = (fn: (a1: string, a2: string) => void) => (): void =>
  // Show usage if no arguments are provided or more than 2
  !ln || ln > 2 ? help(2) : fn(x, y)

// 执行命令相对应的 src/index.ts 文件中的函数, 
// 没有参数的直接调用,需要参数的,套了一层 hook 函数,用于参数处理
const cmds: { [key: string]: () => void } = {
  install: (): void => (ln > 1 ? help(2) : h.install(x)),
  uninstall: h.uninstall,
  set: hook(h.set),
  add: hook(h.add),
  ['-v']: () =>
    // eslint-disable-next-line 
    // @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-var-requires
    console.log(require(p.join(__dirname, '../package.json')).version),
}

// Run CLI
try {
  // Run command or show usage for unknown command
  // 命令存在则运行指定函数,不存在则打印帮助 log
  cmds[cmd] ? cmds[cmd]() : help(0)
} catch (e) {
  console.error(e instanceof Error ? `husky - ${e.message}` : e)
  process.exit(1)
}

husky.sh

凭借着百度,我大概读懂了这个 .sh 文件的内容。

#!/bin/sh  
// 判断变量 husky_skip_init 的长度是否为 0  
if [ -z "$husky_skip_init" ]; then  
// 为 0 时, 创建 debug 函数, 用来打印报错日志  
debug () {  
// HUSKY_DEBUG 为 “1” 时打印  
if [ "$HUSKY_DEBUG" = "1" ]; then  
// $1 表示参数  
echo "husky (debug) - $1"  
fi  
}  
  
// 声明一个只读参数, 内容为 basename + 文件名称  
readonly hook_name="$(basename "$0")"  
debug "starting $hook_name..."  
  
// 判断变量 HUSKY 是否 = “0”  
if [ "$HUSKY" = "0" ]; then  
debug "HUSKY env variable is set to 0, skipping hook"  
exit 0  
fi  
  
// 判断 ~/.huskyrc 是否为普通文件  
if [ -f ~/.huskyrc ]; then  
debug "sourcing ~/.huskyrc"  
. ~/.huskyrc  
fi  
  
// 声明只读变量  
export readonly husky_skip_init=1  
// 当前文件名 是否在 传进来的参数中 存在则执行  
sh -e "$0" "$@"  
exitCode="$?"  
  
// 当 exitCode 不等于 0 时,打印当前执行的 hook 名称 以及 退出码  
if [ $exitCode != 0 ]; then  
echo "husky - $hook_name hook exited with code $exitCode (error)"  
fi  
  
exit $exitCode  
fi

总结

本文主要讲解了,在没用 husky 的时候是怎么去配置 gitHooks 的,并且使用了 husky + lint-staged 来管理代码规范,在后面我们也一起去看了 husky 的具体实现,知道了如何去修改 gitHooks 的默认执行路径,总之 gitHooks 不仅仅只有 pre-commit ,还有其他的一些钩子也值得我们去探索。