每一名开发人员都有自己独特的代码风格。但对于多人协作项目,保持统一的风格有利于项目维护。我们可以在项目中引入 ESLint、Prettier 来规范代码,但这无法约束 Git commit message,这时候可以借助 Husky 等工具来把好最后一关。

Husky

Git 能在特定的重要事件发生时触发自定义脚本,也称钩子(githooks - Hooks used by Git)。因为一个项目通常是团队合作,我们不能保证每个人在提交代码之前执行一遍 lint 校验,所以需要 Git Hooks 来自动化校验的过程,否则禁止提交。

Husky 是一款 Git Hooks 工具,可以在执行特定的 git 命令时(如: git commit, git push)触发对应的脚本。

一般可以定义 pre-commit钩子commit-msg钩子,分别触发 提交前提交信息时 要执行的脚本。

  • 相关网址

    1. npm包 https://www.npmjs.com/package/husky
    2. GitHub https://github.com/typicode/husky
    3. 文档 https://typicode.github.io/husky/
  • 安装Husky(以pnpm为例)

    1
    2
    # npm i husky -D
    pnpm add husky -D
  • 生成.husky文件夹

    1
    npx husky install
  • 执行下方命令向增加一个执行脚本

    1
    npm pkg set scripts.prepare="husky install"

    注意:比较老的教程中用的命令是 npm set-script prepare "husky install" ,但是新版 npm 的 set-script 命令已经废弃了,所以建议使用 npm pkg set 命令来代替。

    相当于手动向 package.json 中写入以下内容:

    1
    2
    3
    4
    5
    6
    // package.json
    {
    "scripts": {
    "prepare": "husky install"
    }
    }

    安装依赖后执行 prepare,执行 huskey 命令,安装钩子,确保每个 clone 项目的人一旦安装依赖就会将 husky 钩子初始化到本地,从而保证每个人在提交 git 之前都能执行 lint 验证。

  • 接下来添加对应的pre-commit钩子脚本命令,使其执行lint操作

    1
    npx husky add .husky/pre-commit "npm run lint"

    相当于手动在.husky/pre-commit文件写入以下内容:

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

    npm run lint

  • 测试钩子是否生效

    1
    2
    git add .
    git commit -m 'test husky'

    从命令行可以看出来,在commit前是成功触发了npm run lint脚本的,并且无报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ git commit -m 'test husky'

    > vuepress2-component-demo@0.1.0 lint
    > npm run lint:style && npm run lint:script

    > vuepress2-component-demo@0.1.0 lint:style
    > stylelint packages/**/*.{vue,css,less,scss,sass} --fix

    > vuepress2-component-demo@0.1.0 lint:script
    > eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix

    [main 448bfaa] test husky
    6 files changed, 85 insertions(+), 35 deletions(-)

Commitlint

检测 git commit 内容是否符合定义的规范

commit message 是程序员开发的日常高频操作,自然状态下commit message 呈现五花八门的书写风格,不利于阅读和维护,规范的 commit message 有助于团队做 code review, 输出清晰明了的 CHANGELOG, 有利于项目的长期维护。

commitlint 需要结合 Husky 使用。定义 commit-msg 钩子,检查提交信息是否符合规范。使用 commitlint 可以规范我们提交信息,可以用来自动生成 changeLog 等文件,更清晰的查看每一次代码提交记录。为什么需要 Commitlint,除了在后续的生成changelog文件和语义发版中需要提取commit中的信息,也利于其他同学分析你提交的代码,所以我们要约定commit的规范。

  • 官网:https://commitlint.js.org/

  • 安装依赖包

    1
    2
    # npm i -D @commitlint/cli @commitlint/config-conventional
    pnpm add -D @commitlint/cli @commitlint/config-conventional

    其中这几个依赖包分别是:

    1. @commitlint/cli:是 Commitlint 命令行工具
    2. @commitlint/config-conventional:强制执行常规提交的可共享commitlint配置。
  • 执行命令创建一个提交校验配置文件 commitlint.config.cjs

    1
    echo "module.exports = {extends: ['@commitlint/config-conventional'],rules: {},};" > commitlint.config.cjs

    相当于手动在commitlint.config.cjs文件中写入了如下内容:

    1
    2
    3
    4
    module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {},
    };

    可以看出来,配置文件的写法和eslint的配置文件写法比较类似,都是通过extends字段加载社区共享的校验规则,这里我们用的是@commitlint/config-conventional(基于Angular约定),官方文档还列出了一些其他的可共享配置

  • 接下来就是完善配置文件,通过rules字段进行规则自定义。可以参考 commitlint官网 - Rules规则 写入自定义校验规则:

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // commitlint提交检验规则 https://commitlint.js.org/#/reference-rules
    // Commit message 包括三个部分:Header(必需)、Body(可选)和 Footer(可选)
    // Header包括三个字段:type(必需)、scope(可选)和 subject(必需)
    // <type>(<scope>): <subject>
    // 提交类型(影响范围): 简短描述

    module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
    // 限制Header最多200字
    // 'header-max-length': [2, 'always', 200],
    // 提交类型<type>枚举
    'type-enum': [
    2,
    'always',
    [
    'build', // 改变构建流程、或者增加依赖库、工具等 如webpack.config.js,package.json yarn.lock
    'chore', // 构建过程或辅助工具的变动,各种配置文件的修改,如.gitignore,tsconfig.json,.vscode,.tenone, eslint/stylelint,envConfig
    'ci', // 对CI自动化流程配置文件或脚本进行了修改
    'docs', // 只修改了项目说明文档
    'feat', // 新增功能,一个新的特性
    'fix', // 修复一个Bug
    'perf', // 性能优化
    'refactor', // 代码重构,既不是修复Bug(fix)也不是添加功能(feat)
    'revert', // 版本回滚,代码回退
    'test', // 修改或添加一个测试用例
    'clean', // 清理过时无用文件
    'merge', // 合并代码分支
    'style', // 只修改了样式文件(包括css/less/sass,图片,字体文件)
    'format', // 格式化,不影响代码含义的修改,比如空格、格式缩进、缺失的分号等
    ],
    ],
    // 'type-case': [0, 'always', 'start-case'],
    // 'type-empty': [0],
    // 'scope-empty': [0],
    // 'scope-case': [0],
    // 'subject-full-stop': [0, 'never'],
    // 'subject-case': [0, 'never'],
    // 'header-max-length': [0, 'always', 72],
    },
    }
  • package.json中添加commitlint指令

    1
    2
    3
    4
    // package.json文件
    scripts:{
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
    }
  • 最后添加到 commit-msg hooks钩子中,在每次 git commit 提交信息时 触发执行Commitlint校验

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

    这行命令相当于手动创建 .husky/commit-msg 文件并添加下方内容:

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

    npx --no-install commitlint --edit "$1"

  • 接下来测试钩子是否生效,我们先提交一个不规范的commit是否规范:

    1
    2
    git add .
    git commit -m 'add commitlint'

    稍等片刻,待它执行完lint后才会检查commit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $ git commit -m "add commitlint"

    > vuepress2-component-demo@0.1.0 lint
    > npm run lint:style && npm run lint:script


    > vuepress2-component-demo@0.1.0 lint:style
    > stylelint packages/**/*.{vue,css,less,scss,sass} --fix


    > vuepress2-component-demo@0.1.0 lint:script
    > eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix

    ⧗ input: add commitlint
    ✖ subject may not be empty [subject-empty]
    type may not be empty [type-empty]

    ✖ found 2 problems, 0 warnings
    ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

    husky - commit-msg hook exited with code 1 (error)

    果然,由于提交不规范导致 commit-msg 钩子退出代码执行

  • 接下来我们提交一个规范的commit,根据规范,我们此次是对CI自动化流程配置文件或脚本进行了修改。根据提交公式<type>(<scope>): <subject>,提交类型选择ci,影响范围省略,并添加简短描述。

    1
    git commit -m 'ci: add commitlint'

    再次稍等片刻,待它执行完lint后才会检查commit。执行结果如下,可以看到提交成功:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ git commit -m "ci: add commitlint"

    > vuepress2-component-demo@0.1.0 lint
    > npm run lint:style && npm run lint:script


    > vuepress2-component-demo@0.1.0 lint:style
    > stylelint packages/**/*.{vue,css,less,scss,sass} --fix


    > vuepress2-component-demo@0.1.0 lint:script
    > eslint --ext .js,.jsx,.ts,.tsx,.vue packages --fix

    [main 1311f51] ci: add commitlint
    4 files changed, 614 insertions(+), 1 deletion(-)
    create mode 100644 .husky/commit-msg
    create mode 100644 commitlint.config.cjs

但是有个问题大家发现没有,我们每次commit,都会由Husky触发Git pre-commit钩子,对所有满足条件的文件执行lint检查。这导致每次都要等待很长时间,即便不久前刚刚对所有文件检查了一遍,并且大部分文件并没有被改动,但依然要浪费时间等待所有文件格式检查完毕。

我们只修改了个别的文件,就没有必要检测所有的文件代码格式。那么想要处理这个问题,就需要使用另外一个插件 lint-staged


lint-staged

能够在 Git commit 提交之前,获取上次 commit 到现在所有发生变动的文件。也就是说,它可以过滤出 git add 后的暂存区文件,只对那些文件执行脚本进行代码审查和格式化。这样就不会影响到本次未更改的文件,限制了处理范围,缩短了等待时长。

我们已经实现了在提交代码之前自动运行Linter(eslint 、stylelint)来检查代码规范,但检查代码过程会很慢,因为是检查整个项目。而 lint-staged 可以使 linter 只检查将要提交的文件(即 git add 后, 在staged暂存区的文件)。配合 Husky 定义 pre-commit 钩子,在其中执行 lint-staged ,就可以在提交代码之前检查代码。如果代码不合规范,则会中断提交。

  • 相关网址

    1. npm包:https://www.npmjs.com/package/lint-staged
    2. GitHub仓库:https://github.com/okonet/lint-staged#readme
  • 首先安装依赖

    1
    2
    # npm i -D lint-staged
    pnpm add -D lint-staged
  • 接下来修改pre-commit钩子,也就是 .husky/pre-commit 文件,之前我们往里面添加的内容是:

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

    npm run lint

    现在我们要修改成如下内容,使得每次提交前执行npx lint-staged命令:

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

    npx lint-staged

    你可以手动修改,也可以将 .husky/pre-commit 文件删掉,通过命令重新生成:

    1
    npx husky add .husky/pre-commit "npx lint-staged"
  • 配置lint-staged以运行 linters 和其他任务。lint-staged 同样使用 cosmiconfig 支持配置(也就是可以读取像commitlint,stylelintrc,prettierrc,eslintrc那样的独立配置)。或者如果你觉得所需配置内容较少,可以直接在 package.json 中添加lint-staged属性进行设置,它按照文件匹配指定任务,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      {
    "scripts": {
    "lint:style": "stylelint packages/**/*.{vue,css,less,scss,sass} --fix",
    "lint:script": "eslint --ext packages/**/* .js,.jsx,.ts,.tsx,.vue --fix",
    "lint": "npm run lint:style && npm run lint:script",
    "prepare": "husky install",
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
    },
    + "lint-staged": {
    + "packages/**/*.{js,jsx,ts,tsx,vue}": [
    + "stylelint --fix"
    + ]
    + },
    "devDependencies": {
    "husky": "^8.0.3",
    "lint-staged": "^13.1.0"
    }
    }

    但我感觉将配置作为单独文件更加直观,所以我没写在package.json中,而是在项目根目录创建了 .lintstagedrc.cjs 文件,来配置所需要的规则。关于配置文件的使用,可以前往官方文档。在 lint-staged 中支持node-glob通配符的配置,同时也支持配置多个,若发生变动的文件路径满足配置,则触发后面的命令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Lint-staged针对暂存的 git 文件运行 linters
    // 配置文档 https://github.com/okonet/lint-staged#Configuration

    module.exports = {
    "*.{css,less,scss,sass}": [
    "prettier --write",
    "stylelint --fix",
    ],
    "*.{js,jsx,ts,tsx,vue}": [
    "prettier --write",
    "eslint",
    ],
    };
  • 随便修改一个在代码检查范围内的文件,然后提交,测试钩子是否生效

    1
    2
    git add .
    $ git commit -m "add lint-staged"

    可以看到是成功触发了暂存区匹配成功的文件检查的,但我故意没规范书写commit msg所以没有提交成功:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ git commit -m "add lint-staged"
    √ Preparing lint-staged...
    √ Running tasks for staged files...
    √ Applying modifications from tasks...
    √ Cleaning up temporary files...
    ⧗ input: add lint-staged
    ✖ subject may not be empty [subject-empty]
    type may not be empty [type-empty]

    ✖ found 2 problems, 0 warnings
    ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

    husky - commit-msg hook exited with code 1 (error)

    再正确规范提交一次就可以了:

    1
    2
    3
    4
    5
    6
    7
    8
    $ git commit -m "ci: add lint-staged"
    √ Preparing lint-staged...
    √ Running tasks for staged files...
    √ Applying modifications from tasks...
    √ Cleaning up temporary files...
    [main 5eb7299] ci: add lint-staged
    7 files changed, 234 insertions(+), 10 deletions(-)
    create mode 100644 .lintstagedrc.cjs

在今后commit时尽量规范化,不要写的很简单、很随意,但也不要很冗余。要使别人看到commit信息就可以获知该commit用意,让commit msg与你提交的的变更代码建立联系。在此发出倡议:

  1. One Thing,One Commit;
> 在提交 commit 的时候尽量保证这个 commit 只做一件事情,比如实现某个功能或者修改了配置文件。
  1. 不要commit一半的工作;
> 当开发任务没有完整的完成的时候,不要commit。这不是说每次commit都需要开发完成一个非常完整的大功能,而是当把功能切分成许多小的但仍然具备完整性的功能点的时候,开发人员需要完整完成这个功能点之后才能commit。必要时可以使用stash命令对修改进行记录。
  1. 经常commit;
> 经常使用commit能够使你的commit(里的修改内容)越小,并且能使你commit相关的修改,多次commit允许你推送自己代码到远程分支上的频率增加,能有效的减少merge代码时出现的代码冲突问题,因为多次 commit能使你的同事的代码库得到及时的更新。
  1. commit之前的测试;
> 保证你所开发的功能是完整无误的。在commit代码之前的对代码充分测试是非常重要的,可以避免有问题的代码被其他开发人员使用。
  1. 编写规范的commit message;
> 不规范的Commit Message,就是依托答辩!我们要提交规范的commit msg,加油奥利给!

【参考内容】:

[1] 前端代码格式处理-概述(Prettier + Stylelint + ESlint + Husky + lint-staged + commitlint )

[2] 前端工程化配置指南 Eslint、Prettier、Commitlint、Husky、Jest、GitHub Actions、Semantic Release

[3] 使用 Husky + Commitlint + Lint-staged 约束每一次 Git 提交

[4] stylelint最佳实践

[5] 你在寻找Vue3移动端项目框架嘛?请看这里

[6] 前端代码规范化实践eslint+prettier+stylelint+editorconfig+commitlint

[7] 前端规范化实践(一)

[8] 关于 lint 的一些思考

[9] commit规范方案探讨

[10] git commit 提交规范

[11] 前端代码规范化:EditorConfig + Prettier + ESLint

[12] 项目中的 git message 规范

[13] 如何使用 jest 和 lint-staged 只检测发生改动的文件

[14] 六、标准化编程规范解决方案之自动修复 lint-staged

[15] 使用 Vue3+TS+Vite+PNPM 搭建前端工程基础工作流配置

[16] [前端工程化配置] 使用commitlint校验git commit message

[17] vue2项目配置prettier + eslint + commitlint + stylelint

[18] Vue3+Vite+Ts 项目实战 01 Vite 创建项目、ESLint+TS+GitCommit配置、Vue3新特性介绍

[19] 深入理解npm命令,npm i之前可被自动执行的命令prepare

[20] 规范提交git commitizen conventional-changelog-cli

[21] Git Commit 规范(Conventional Commit) Commit Message 规范格式、Commitizen(规范 commit 命令行工具)、Commitlint & Husky(规范 commit message 验证)、Conventional-changelog-cli (自动生成 CHANGELOG 文件信息)