# 🐶 Husky x LintStaged

上一章说到我们不能保证所有人提交的代码都是 fix 好的。 比如有的人经常忘记开启 ESLint 插件,提交代码时还以为自己代码写的贼 6,没啥报错,但到队友那 git pull 满屏都是红的。 所以,更好的做法是在代码入库(Commit 和 Push)的时候可以做一次 ESLint 的检查。

正好 Git 提供了很多 Git Hooks (opens new window):

  • pre-commit: 提交代码前做一些事
  • pre-push: 推代码前做一些事
  • pre-merge: 合并代码前做一些事
  • pre-rebase: rebase 前做一些事
  • ...

这些 Hooks 可以使得我们在操作 Git 的某些阶段做一些事情。 Husky (opens new window) 可以在这些 Git Hooks 回调时执行我们定义好的 Bash 脚本。 如果我们把 ESLint 的修复命令放在这些的 Bash 脚本中,那就可以实现 Git Commit/Push/Merge/... 前的 ESLint 自动修复了!

)

注意:Husky v4 和 v7 有非常大的差异,大家一定要注意甄别,最好直接看官网,这里使用最新版跟大家讲解。

# 安装哈士奇
npm install husky -D
# 添加 prepare 命令
npm set-script prepare "husky install"
# prepare 创建 bash 脚本,安装 git hooks
npm run prepare
# 添加 pre-commit 的 git hook 脚本
npx husky add .husky/pre-commit "npx eslint src --fix"

运行之后会发现在 ./.husky/pre-commit 里看到 git commit 前会运行的脚本:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# git commit 前先 eslint fix 一波
npx eslint src --fix

但是这样的命令会让每次 commit 前都把整个 src 都扫描并 fix 一次,速度太慢了,而且很容易把别人的屎山也 fix 掉,然后提交上去。

我们更希望只针对提交的文件进行 Lint 操作。

# LintStaged

Prettier 在 文档的 Pre-commit Hook (opens new window) 已经介绍了很多只针对提交文件做 fix 的工具。这里以 lint-staged (opens new window) 做介绍。

# 安装
npm i -D lint-staged

然后添加 .lintstagedrc.js 配置文件,里面对提交不同的文件进行 eslint --fix 操作。

module.exports = {
  '**/*.{ts,tsx,js,jsx}': [
    "eslint --cache --fix",
  ],
  "**/*.vue": [
    "eslint --cache --fix",
  ],
  "**/*.{css,less}": [
    "stylelint --cache --fix",
  ]
}

lint-staged 配置的含义是对提交上来不同类型的文件执行对应的 lint fix 命令。

最后在刚刚创建的 ./.husky/pre-commit 里改成执行 lint-staged 命令:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

以后每次 commit 前都会跑一次 lint-staged,而 lint-staged 又会对提交的文件进行 ESLint Fix。

# 命令行

如果细心的同学会发现上面提到关于 eslint 的自动修复命令一共有两条:eslint src --fix 以及 eslint --cache --fix。

如果你直接在命令行里跑 eslint --fix,那什么事都不会发生,因为你没有指定要 fix 的文件以及文件目录。

那为什么在 lint-staged 里就可以 eslint --cache --fix 呢?

因为 lint-staged 会把前面的 *.{js,jsx,ts,tsx} 来匹配提交的文件,并把它们作为参数传到 eslint --cache --fix 后面。所以虽然写的是 eslint --cache --fix 时实际上是执行了 eslint 要修复的文件 --cache --fix。

# 性能问题

或许有的同学会发现每次 eslint --fix 的时候跑的有点慢,如果你在前面加一个 TIMING=1:

TIMING=1 eslint src --fix

就可以看到哪个规则跑了多久:

Rule                                    | Time (ms) | Relative
:---------------------------------------|----------:|--------:
prettier/prettier                       |   207.532 |    79.8%
@typescript-eslint/no-unused-vars       |    12.738 |     4.9%
@typescript-eslint/no-floating-promises |     8.053 |     3.1%
@typescript-eslint/no-unsafe-assignment |     7.509 |     2.9%
vue/attributes-order                    |     7.424 |     2.9%
no-unused-vars                          |     1.977 |     0.8%
no-redeclare                            |     1.539 |     0.6%
react/display-name                      |     1.219 |     0.5%
no-global-assign                        |     0.873 |     0.3%
@typescript-eslint/no-unsafe-argument   |     0.795 |     0.3%

毕竟 prettier/prettier 是一大堆的 Prettier 代码风格规则,所以肯定是跑得慢的。 当然也有很多人会很在意这个点,所以也有了 这个 Issue (opens new window) 。

不过这个 Issue 也没能给出太好的解决方案,如果你有更好的方案可以 在这里提 Issue (opens new window) 。

# LintStaged x TypeScript

你以为到这就完了么?Too yong too simple!如果你在 .d.ts 定义一个 interface:

type Hello = {
  name: string;
  age: number;
};

然后在另一个 .ts 里错误地使用它:

// 注意:这里没有 import Hello 是正常的,因为 Hello 是在 .d.ts 里定义
const hello: Hello = {
  name: "xx",
  age: 11,
  xxx: 11, // Error
};

然后直接强行 git add ./, git commit -m 'update',发现竟然可以直接通过而不报错!

不报错的原因是因为:ESLint 本身就不会做类型校验(Type Check)。 理由如下(具体可见 这个 Issue (opens new window)):

  • ESLint 只是作为 TypeScript Type Checking 的补充,只做 Type Checking 之外的一些工作
  • 大多数人用 TS 的 Parser,但是不用 parserOptions.project,所以这种情况下也不能 Type Check
  • 和 TypeScript 相对完整的错误校验上报体系相比,ESLint 只完成了一半的工作

总的来说就是你用 tsc --noEmit 就能做类型检查,ESLint 就不用再重复造一次轮子了,再看看隔离 Babel 大哥,它就是转译器,它也不做 TS 的语法校验呀,还是一个工作做一件事的好。

ESLint 和 TypeScript 依然是各论各的

有些同学估计都会抢答了:我知道我知道,直接在 .lintstagedrc.js 里添加一行 tsc 不就完事了?

module.exports = {
  '**/*.{ts,tsx}': [
    "tsc", // 检查 TypeScript
    "eslint --cache --fix",
  ],
  '**/*.{js,jsx}': [
    "eslint --cache --fix",
  ],
  "**/*.vue": [
    "eslint --cache --fix",
  ],
  "**/*.{css,less}": [
    "stylelint --cache --fix",
  ]
}

年轻人,依然 Too young too naive!让我们走进下一章,看看 tsc 会给我们带来什么样的难题吧。

Last Updated: 4/22/2022, 11:26:49 AM