从零开发自己的工具库(二)配置 ESLint + Perttier + Husky + Commitlint

2,479 阅读9分钟

上一篇讲了如何使用 TS + Rollup + Jest 创建开发环境。本文和大家分享如何提高代码的规范和质量,让多人协同变得更高效。成品可以参考 utils ,欢迎 star 🤞❤️

ESLint + Perttier 代码规范配置

关于 ESlint 的项目搭建,可以参考 ESLint 非权威配置指北(下),代码规范几乎每个项目都会配置,这里就不再赘述。

最终,我们添加了 .eslintrc.js.eslintignore.prettierrc.js 配置文件,并且添加了以下两条 Scripts 命令:

{
    "scripts": {
        "lint:eslint": "eslint --cache --max-warnings 0 \"packages/**/*.{js,ts}\" --fix",
        "lint:prettier": "prettier --write \"packages/**/*\""
    }
}

还有一个与 TS 相关的 ESLint 问题,值得拿出来讨论下,请看 FAQ。

FAQ

1. ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file"

typescript-eslint

这些错误是由 ESLint 配置请求类型信息引起的,因为在 tsconfig.json 中并未包含 ESLint 的配置文件,所以 TS 并没有对这些文件起作用。

如果你想关闭该错误提示,可以直接在 .eslintignore 中进行忽略,也可以做如下两步操作,解决报错:

  1. 项目目录下新建 tsconfig.eslint.json 文件(与其他 TS 配置区分),将所有配置文件包含进去。

    {
        "compilerOptions": {
        "types": ["@types/node"],
        "noEmit": true,
        "allowJs": true
        },
        "extends": "./tsconfig.base.json",
        "include": [
            ".eslintrc.js",
            ".prettier.js",
            "rollup.config.js",
            "jest.config.js",
            "babel.config.js",
        ]
    }
    
  2. 接着,在 .eslintrc.js 中提供 TS 配置文件的路径。所有需要使用类型信息的规则,都要在此配置:

    module.exports = {
        /** ...... */
        parserOptions: {
            /** ...... */
            project: ['./tsconfig.base.json', './tsconfig.eslint.json', './tsconfig.json'],
            tsconfigRootDir: __dirname,
        },
    }
    

关于该报错的详尽信息,可以查阅 ESLint was configured to run ...

关于 ESLint parserOptions.project 选项的说明,可以查阅 project

关于官方的解决方案示例,可以查阅 tsconfig.eslint.json

配置 Editorconfig

editorconfig 也是用来帮助开发者定义和维护代码风格的。但是它与 Prettier 不同的是,Prettier 是 JS 特有的格式化工具,里面有很多配置项是 JS 语言特有的规范,而 editorconfig 适应性更广泛,它可以跨编辑器(或 )维护统一的代码风格,专注于比较基础的格式化,比如 Tab 缩进、文件编码、末尾换行符等,这些规范与使用哪种编程语言无关。

VSCode 需要安装 EditorConfig 扩展来搭配使用,部分配置如下:

# .editorconfig
root = true

[*]
charset = utf-8
indent_style = space              # 空格缩进
indent_size = 4                   # 缩进空格为4个
end_of_line = lf                  # 文件换行符是 linux 的 `\n`
insert_final_newline = true       # 文件末尾添加一个空行
trim_trailing_whitespace = true   # 不保留行末的空格

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
max_line_length = 0               # 在指定的字符数后强制换行。off 关闭此功能

[package.json]                    # 对 package.json 生效
indent_size = 2

husky + lint-staged + commitlint 提交规范配置

git hooks 前置知识

和其它版本控制系统一样,Git 能在特定的重要动作发生时触发自定义脚本。

有两组这样的钩子:客户端的和服务器端的。 客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。

你可以随心所欲地运用这些钩子。

本文整理部分 hooks 如下:

git hooks运行时机说明工作流所属类型
pre-commit在键入提交信息前运行以非零值退出,Git 将放弃此次提交, 可以利用该钩子,来检查代码风格是否一致提交客户端
prepare-commit-msg在启动提交信息编辑器之前,默认信息被创建之后运行提交客户端
commit-msg接收一个参数, 以非零值退出,Git 将放弃提交。可以在提交通过前验证项目状态或提交信息提交客户端
post-commit在整个提交过程完成后运行一般用于通知之类的事情提交客户端
applypatch-msg可以用该脚本来确保提交信息符合格式,或直接用脚本修正格式错误。email客户端
pre-applypatch运行于应用补丁之后,产生提交之前可以用它在提交前检查快照email客户端
post-applypatch运行于提交产生之后可以用它把结果通知给一个小组或所拉取的补丁的作者email客户端
pre-rebase运行于变基之前可以使用这个钩子来禁止对已经推送的提交变基其他客户端
post-rewrite被那些会替换提交记录的命令调用用途很大程度上跟 post-checkout 和 post-merge 差不多其他客户端
post-checkout在 git checkout 成功运行后可以根据你的项目环境用它调整你的工作目录其他客户端
post-merge在 git merge 成功运行后可以用它恢复 Git 无法跟踪的工作区数据其他客户端
pre-push会在 git push 运行期间, 更新了远程引用但尚未传送对象时被调用可以在推送开始之前,用它验证对引用的更新操作其他客户端
pre-auto-gc会在垃圾回收开始之前被调用可以用它来提醒你现在要回收垃圾了,或者依情形判断是否要中断回收其他客户端
pre-receive客户端的推送操作时,最先执行可以用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所修改的所有引用和文件进行访问控制推送服务端
update会为每一个准备更新的分支各运行一次更新服务端
post-receive在整个过程完结以后运行可以用来更新其他系统服务或者通知用户完结服务端

这里,我们通常只关注提交工作流的几个 hooks,用的最多的一个就是 pre-commit 。当然,你可以利用 git commit --no-verify 来绕过这个环节。

配置 husky

Husky 就是一个创建或修改 Git hooks 的工具,它在内部封装了 git hook,允许我们在提交代码时运行一些额外的脚本。比如校验规范,格式化代码等。

初始化 husky

执行 npm i husky -D 安装 Husky 后,我们在 package.json 中添加一个 script:

{
    "scripts": {
        // ......
        "prepare": "husky install"
    }
}

执行 npm run prepare,会创建 .husky 文件夹,此时 git hook 已被启用。

创建 git hook

接着,我们使用 husky add <file> [cmd] 命令往里面添加 hook,其中:

  • <file>: 指名为 git hook 的文件,通常放在 .husky/ 目录下。比如:.husky/pre-commit ;
  • [cmd]: 指想要运行的命令。比如:npm run lint:eslint

以下是官方示例:

npx husky add .husky/pre-commit "npm run test"
git add .husky/pre-commit

此时,.husky 文件夹中会创建一个名为 pre-commit 的 shell 脚本。当你尝试 commit 提交时,pre-commit hook 会提前执行其中的 npm run test,如果检测失败,那么本次提交会被自动阻止。

当然,你可以根据自己的需求修改这个 shell 脚本。

官方推荐

typicode.github.io/husky/get-s…

我们还可以使用官方推荐的方法,一次性快速初始化 husky 。

# npm
npx husky init

# pnpm
pnpm exec husky init

该命令会自动初始化并生成 pre-commit 脚本,我们只需直接去里面修改内容即可,很方便。

配置 lint-staged

这里有个问题:npm run lint:eslint 会对你自定义的所有文件进行 ESLint 修改,成本显然很高。

所以 lint-staged 的出现允许你每次 commit 时只对当前添加到暂存区(stage)的代码进行 lint,效率大大提高。正如它的标题:不要让屎溜进你的代码库!

lint-staged.png

lint-staged 的配置方式有两大种,支持 package.json 或 JSON 和 YML 风格的 .lintstagedrc

我们以 package 为例,在执行 npm i lint-staged -D 安装依赖后,在 package.json 中添加 lint-staged 字段,格式如下:

{
  "lint-staged": {
    "<glob-pattern>": "<command>"
  }
}

其中 <command> 可以是原生的工具库命令,也可以是 Scripts 里的自定义命令。原生命令可以省略 npm run 前缀,但自定义命令必须是完整的,比如 eslint --fixnpm run lint:eslint

命令的类型可以是单个命令的字符串,也可以多个命令组成的数组。

{
    "lint-staged": {
        "packages/**/__tests__/*.ts": "npm run test",
        "packages/**/*.ts": [
            "npm run lint:eslint", // scripts 自定义的命令
            "prettier --write" // prettier 原生的命令
        ]
    }
}

上述 lint-staged 我们配了两个操作:

  • 所有 packages 下的单元测试文件在提交前,都要执行 jest 测试;
  • 所有 packages 下的 .ts 文件都要执行 eslint 校验以及 prettier 格式化。

不过目前它还不能自动生效,要在 .husky/pre-commit 脚本中修改并添加 npx --no-install lint-staged 命令,这样就生效了。

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

echo -e "\033[33m ------------------- 正在对提交的代码执行操作 -------------------- \033[0m"
npx --no-install lint-staged

配置 commitlint

commitlint 用来约束提交信息,规范提交格式。

  1. 安装 npm i @commitlint/config-conventional @commitlint/cli -D

  2. 自定义 commitlint.config.js 规范

    module.exports = {
        ignores: [commit => commit.includes('init')],
        extends: ['@commitlint/config-conventional'],
        rules: {
            'body-leading-blank': [2, 'always'], // 主体前有空行
            'footer-leading-blank': [1, 'always'], // 末行前有空行
            'header-max-length': [2, 'always', 108], // 首行最大长度
            'subject-empty': [2, 'never'], // 标题不可为空
            'type-empty': [2, 'never'], // 类型不可为空
            'type-enum': [ // 允许的类型
                2,
                'always',
                [
                    'wip', // 开发中
                    'feat', // 新增功能
                    'merge', // 代码合并
                    'fix', // bug 修复
                    'test', // 测试
                    'refactor', // 重构
                    'build', // 构造工具、外部依赖(webpack、npm)
                    'docs', // 文档
                    'perf', // 性能优化
                    'style', // 代码风格(不影响代码含义)
                    'ci', // 修改项目继续集成流程(Travis,Jenkins,GitLab CI,Circle等)
                    'chore', // 不涉及 src、test 的其他修改(构建过程或辅助工具的变更)
                    'workflow', // 流水线
                    'revert', // 回退
                    'types', // 类型声明
                    'release', // 版本发布
                ],
            ],
        },
    };
    
  3. 使用 husky 继续添加 commit-msg git hook:

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

现在,你的 .husky 目录应该长这样:

husky.png

你可以使用 git add . && git commit -m'aaa: bbb' 来测试了,正常会出现报错。

commitlint.png

总结

  1. ESLint 和 Prettier 配置;
  2. Editorconfig 的配置;
  3. Git hooks 与 Husky;
  4. lint-staged 的配置;
  5. FAQ 问题梳理;

下篇将分享如何为我们的 README 添加 badge 徽章,以及重头戏:白嫖 Github Actions 实现一个简单的 CI/CD 来部署并自动发布 NPM。

往期文章

《从零开发自己的工具库(一)配置 TS + Rollup + Jest》

《从零开发自己的工具库(三)配置 Github Actions CI 自动发布 NPM》

参考资料

ESLint 非权威配置指北(下)

EditorConfig

自定义 Git - Git 钩子

项目规范的基石——husky 与 lint-staged

husky | github

lint-staged | github