谁动了我的代码!

2/13/2022 JavaScript

当你某一天 fetch 代码时,发现很多文件是这样的 👆 你是不是很崩溃?

# 前言

公司一个老项目,没有做代码提交前的规范校验,我拿到后,想着老项目嘛也没时间帮它弄这些,反正就是改一点点小东西;尽量跟着它的代码风格写,写完提交就行;

直到某一天,又有一个人加入了进来。好家伙,直接干出事了。
很多个文件一起提交的,然后 commit-msg 不规范,代码缩进也不规范,换行符也不一致,里面还有很多没用的代码,可读性极差;

但问题终归是得去解决的;治理污水的最好方法就是从能污水产生的地方开始整治。我来帮它加上吧。🥰

下面我将带大家一起,先通过husky+eslint完成一个最简单的代码规范校验,然后逐步优化,最后通过husky+eslint+lint-stage+commitlint+pritter实现一个强有力的限制。最后再配合commitizen,通过commitlint-config-cz+cz-customizable实现自定义的提交模板和限制规则来实现团队最终的项目提交限制规约

# eslit+pritter

这里我为该项目适配了一套 eslit 规则,按照这套规则提交的代码不会有冲突。
eslint 和 pritter 大家平时项目应该都用到过,应该都很熟悉了,这里将不占用篇幅。(想看的可以私聊我,我可以单独出一篇 eslint 和 pritter 主题的文章)

🦄 在这篇文章中,主要讲解如何在团队协同工作时,在 git 提交代码更改前,对不规范的代码和提交信息进行校验,修复,并限制不规范的提交。

# husky

首先要介绍的是husky,搞工程化,我们肯定都少不了 husky,它能很方便的帮我们阻挡小可爱们的进攻,不,是为我们的项目添加git hooks

# 具体方法

首先我们将 husky 安装到开发依赖中

npm i husky -D
# or
yarn add husky -D
1
2
3

# 注意

目前我所安装的版本是husky@7.0.4,由于husky@6.0.0 后做了breaking change,所以在6.0.0版本之前的那种设置钩子的方法已经不适用了,这里我们只介绍最新的方式

安装完后,我们需要在当前项目中创建一个.husky目录并指定该目录为 git hooks 所在的目录。

使用以下命令快速创建 👇

#--no-install 参数表示强制npx使用项目中node_modules目录下的husky依赖包
npx --no-install husky install
1
2

为了让其他人在此项目中安装依赖后也能自动创建.husky目录并指定该目录为 git hooks 所在的目录,我们需要在 package.json 里面添加一条脚本"prepare": "husky install"

使用以下命令快速添加 👇

npm set-script prepare "husky install"
1

prepare 脚本会在 npm i或者其他yarn or yarn add 之后自动执行。也就是说当我们安装依赖后会自动执行 husky install 命令,从而自动创建.husky目录并指定该目录为 git hooks 所在的目录。

使用以下命令快速创建 👇

npx --no-instal husky add .husky/pre-commit "npm run [你要执行的命令]"
1

完成后可以看到.husky目录下新增了一个pre-commit文件,其中的内容为 👇

这里我用的是npm run lint,这样我们就可以配合 Eslint 的代码校验,来限制不规范代码的提交了

可以看到,不符合 Eslint 校验规则的代码是没法提交的;

当然,这里的报错问题只是由于缩进不规范引起的,类似这种的问题还有引号,句尾分号,换行符等等...都可以通过 eslint 的参数 --fix来自动修复,这样就可以在提交前,先将能实现自动修复的简单代码风格问题后 commit。复杂的情况还是要自己去手动处理的。

说到换行符,这里我们需要了解的是:在 Windows 上默认的是回车换行(Carriage Return Line Feed, CRLF),然而,在 Linux/MacOS 上则是换行(Line Feed, LF)。

我们可以试一下将原先换行符为crlf的文件格式化为换行符为lf后,执行git add .的情况。

可以看到最终 LF 换行符还是被 CRLF 转化了;

如果你们不会跨平台协作(都在 Mac/Linux,或者都在 Windows 上协同),只需要在当前项目中通过git config core.autocrlf false来阻止这种情况的发生。

为了保险起见,你需要新建一个.gitattributes文件(主要用于定义每种文件的属性,以方便 git 帮我们统一管理。),设置 eol(end of line)为指定的换行符(lf/crlf),这里我把所有文件*.*的换行符都设置为了 LF,并且将一些非文本文件进行了标记(排除它们),你也可以对每一种文件类型分别设置对应的属性 => *.js eol=lf*.ts eol=lf...

*.* eol=lf
*.jpg -text
*.png -text
  ...
# 或者👇

*.js eol=lf
*.ts eol=lf
*.json eol=lf
  ...
1
2
3
4
5
6
7
8
9
10

文件内容如下 👇

这样,我们不管在什么平台上开发,文件换行符都统一为 LF。

可以看到使用.gitattributes配置文件后执行git add,所有不是指定换行符的文件都会被自动更换为你指定的换行符,例如我这里指定了lf,那么 git add . 后,不是以 lf 换行的文件都会被转换为 lf ,并在终端输出warning: CRLF will be replaced by LF in xxxx/filename,如图 👇

.gitattributes有很多用处,具体可以查看 👉gitattributes (opens new window),(但是在这里不是最佳实践!)

🥰 到这里,一个最简单的代码风格限制方法就已经实现了。

既然做了,就肯定要做一套完整的,且好用的。下面我们来继续完善其他功能 ~


# lint-staged

什么是lint-staged?顾名思义,借助这个工具只是用来检查 git 暂存区文件的,就是在你git add file1,2,3... 后的暂存区文件中运行 lint 的一个工具。

每次提交一两个文件,却都要 lint 所有文件话,是很没有必要的,我们只对需要提交的代码进行 lint,这样可以减少很多没必要的时间开销。(如果你每次修改一个文件,都要去 lint 所有文件,这个工具对你来说就没有什么意义了,husky 就管够 🤐)

# 安装

yarn add lint-staged -D
1

# 使用方法

我们将.husky/pre-commit中之前写代码改为 👇

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- npm run lint
+ npx --no-install lint-staged
1
2
3
4

然后在 package.json 中添加以下代码,lint-staged对象中采用键值对的方式进行配置,键名是你想处理的单个文件或一个文件类型,多个类型可以写在{}中,用逗号分隔;键值是一个数组,数组中为 lint 时需要执行的命令组。

{
  "lint-staged": {
    "*.{ts,js,vue}": ["eslint", "echo '没问题!'"]
  }
}
1
2
3
4
5

添加玩上述代码后,我们通过测试,将两个文件的缩进改为不符合规范的情况,然后将其中一个文件暂存后,我们运行git commit后发现终端的报错中,只有一个文件的 lint 报错信息,另一个文件的报错并没有出现。

当所有暂存区代码都符合规范时 👇,才会通过校验执行提交。

✔ Preparing lint-staged...
✔ Hiding unstaged changes to partially staged files...
✔ Running tasks for staged files...
✔ Restoring unstaged changes to partially staged files...
✔ Cleaning up temporary files...
1
2
3
4
5

# commitizen

Commitizen 是一个撰写符合上面 Commit Message 标准的一款工具。通过它可以实现交互式撰写规范的 Commit Message。

如果只在本仓库使用 👇

npm install commitizen -D
# or
yarn add commitizen -D
1
2
3

如果你想全局都用 commitizen 来帮你做 commit

npm install commitizen -g
# or
yarn global add commitizen
1
2
3

安装完成后,一般我们都采用符合 Angular 的 Commit message 格式的提交规范(当然也可以自定义,后面会讲到~),运行以下命令生成符合 Angular 提交规范格式的 Commit message。

如果你项目用的是 npm 👇

# 如果你项目用的是npm
npx --no-install commitizen init cz-conventional-changelog --save-dev --save-exact
1
2

如果你项目用的是 yarn 👇

# 如果你项目用的是yarn
npx --no-install commitizen init cz-conventional-changelog --save-dev --save-exact
1
2

运行了上述命令后,它将为你项目安装 cz-conventional-changelog 适配器模块,把 config.commitizen 的密钥添加到文件的根目录添加到 package.json

可以在package.json 中看到,自动的新增了以下内容 👇

{
  ...
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
  ...
}
1
2
3
4
5
6
7
8
9

如果不是全局安装的commitizen,则需要在 package.json 的 scripts 中添加cz:"cz"命令, 使用以下命令快速添加 👇

npm set-script cz "cz"
1

完成后,通过命令yarn cz,你如果是全局安装的 commitizen,那你直接 git cz,都可以通过以下交互式的撰写 commit messag 然后提交

# 限制 commitlint

由于 commitizen 并不是强制使用的,仍然可以通过git commit来提交,所以我们必须在不管是通过cz还是git commit提交前,都要对 commit messag 进行一次校验,不符合规范的情况下是不允许进行 commit 的

首先我们需要安装commitlint,commitlint/config-conventional

yarn add @commitlint/cli @commitlint/config-conventional -D
1

使用以下命令快速创建 git hooks 的 commit-msg 钩子 👇 这样每次 commit 的时候都会由 commitlint 对 commit message 进行一次检验

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

然后我们创建一个 commitlint 配置文件到项目根目录 👇

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

以上将会在项目目录中生成 commitlint.config.js,代码如下,他将继承@commitlint/config-conventional中的 Commit message 规范。("feat", "fix", "perf", "refactor"...)

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

然后我们在终端进行测试

echo 'feat: bar' | npx --no-install commitlint
1

如果你执行上面这个行命令后出现了以上这种报错。

将文件更改问 UTF-8 的格式即可解决;这个问题目前已经在 👉 Issues (opens new window)中,有不少人遇到了(我也是 😂)。
最简单的方法就是,把文件用记事本打开,选择另存为,然后在弹窗的右下角更改字符编码为 UTF-8 (Windows 用户运行echo "xxx" > xx.js时,文件编码可能为 UTF-16 LE),更改好后,把原来的commitlint.config.js文件替换掉即可。

解决以上问题后,我们再测试一下,可以看到,不符合规范的 commit-msg 是会导致报错的,也就 commit 不了了,说明我们的 commitlint 已经生效了~ 👏👏👏

到此,commit-msg 的校验也已经完成 ✔

如果,你想自定义 commitlint 的交互文本(不用 feat,fix...,很多人都喜欢在 commit message 前面加一个 emoji 表情符号),当然也可以。

我们需要安装 cz-customizable来实现自定义 commit message 规则,以及安装对应的commitlint-config-cz来配套校验(直接从自定义的文件里读取规则)

运行以下命令 👇

yarn add commitlint-config-cz  cz-customizable -D
1

在项目根目录,创建一个.cz-config.js文件,并复制cz-config-EXAMPLE.js (opens new window)中的内容到其中。然后改成你自己想要的规则即可。

当然,你也可以用我写好的:

module.exports = {
  types: [
    { value: "feat", name: "feat: 一个新的特性" },
    { value: "fix", name: "fix: 修复一个Bug" },
    { value: "docs", name: "docs: 变更的只有文档" },
    { value: "style", name: "style: 代码风格,格式修复" },
    { value: "refactor", name: "refactor: 代码重构,注意和feat、fix区分开" },
    { value: "perf", name: "perf: 码优化,改善性能" },
    { value: "test", name: "test: 测试" },
    { alue: "chore", name: "chore: 变更构建流程或辅助工具" },
    { value: "revert", name: "revert: 代码回退" },
    { value: "init", name: "init: 项目初始化" },
    { value: "build", name: "build: 变更项目构建或外部依赖" },
    { value: "WIP", name: "WIP: 进行中的工作" },
  ],
  scopes: [],
  allowTicketNumber: false,
  isTicketNumberRequired: false,
  ticketNumberPrefix: "TICKET-",
  ticketNumberRegExp: "\\d{1,5}",
  // it needs to match the value for field type. Eg.: 'fix'
  /*
  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
  // override the messages, defaults are as follows
  messages: {
    type: "选择一种你的提交类型:",
    scope: "选择一个scope (可选):",
    // used if allowCustomScopes is true
    customScope: "Denote the SCOPE of this change:",
    subject: "简短说明(最多40个字):",
    body: '长说明,使用"|"换行(可选):\n',
    breaking: "非兼容性说明 (可选):\n",
    footer: "关联关闭的issue,例如:#12, #34(可选):\n",
    confirmCommit: "确定提交?",
  },
  allowCustomScopes: true,
  allowBreakingChanges: ["feat", "fix"],
  // skip any questions you want
  skipQuestions: ["scope", "body", "breaking"],
  // limit subject length
  subjectLimit: 100,
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
};
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
42
43
44
45
46
47
48
49
50
51
52
53

创建完.cz-config.js文件后,我们需要回到 package.json 文件中,将 config.commitizen.path 更改为"node_modules/cz-customizable",如果你的.cz-config.js文件在项目根目录下,那么可以不配置下面这条,commitlint-config-cz 会自动在项目根目录下寻找: .cz-config.js.config/cz-config.js

...
{
  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    },
    // 如果你的.cz-config.js文件在项目根目录下,那么可以不配置下面这条,commitlint-config-cz会自动在项目根目录下寻找: .cz-config.js 或 .config/cz-config.js
    "cz-customizable": {
      "config": "你的文件路径/xxxconfig.js"
    }
  }
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13

关于commitlint-config-cz更高级的用法可以查看 👉commitlint-config-cz (opens new window)

最后我们将之前创建过的commitlint.config.js中的代码进行更改 👇

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

或者你也可以在commitlint.config.js中手动添加自定义的规则 👇,他将覆盖 extends 中的规则

module.exports = {
  extends: ["@commitlint/config-conventional""cz"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "init",
        "build",
        "ci",
        "chore",
        "docs",
        "feat",
        "fix",
        "perf",
        "refactor",
        "revert",
        "style",
        "test",
      ],
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

到这里,自定义的 commit message 的校验也 ok 了 ✅

# 最后

提醒:项目的代码风格和规则要和团队一起制定哦 ~
至此,在团队协同的项目中,不符合规范的提交就被扼杀在摇篮里面了。我们大家不管是从书写代码还是提交代码最好都要规范哦~不给自己惹麻烦的同时,也不会给他人或公司带来麻烦。这就是本篇的全部内容啦~如果对你有帮助,记得点赞鼓励 ~
我是荣顶,一个面向快乐编程的前端开发 🥰 我们下次再见 ~