在Node.js项目中,完成Lint代码检查的自动安装与强制运行

前端团队在协作过程中,保障代码质量和代码风格的统一是非常必要的。使用 Lint 检查(如 Eslint 等)来规范代码是目前比较通用的方式。为了保证仓库中的代码都是通过 Lint 检查的,我们需要使每一个开发者在提交代码之前,强制对所提交的代码进行 Lint 检查,如果没有通过检查则代码提交失败。

本文将介绍一种在 Node.js 项目中,结合 Git 的 pre-commit 钩子和 dev 开发环境下的构建命令,完成多个 Lint 检查的自动安装和强制运行的实践方式。目录结构如下:

  • Lint 检查的介绍与配置
  • Lint 检查的钩子实现
    1. Git Hooks 介绍
    2. 检查脚本的具体实现
  • 通过 Gulp 任务完成 Lint 检查的自动安装

Lint 检查的介绍与配置

Lint 检查是指,通过配置文件来指定代码书写的规范,并通过运行相应的程序来检查代码是否符合所配置的规范。不同类型的文件有不同的主流 Lint 检查器。

本文的实践项目所用到文件类型对应的检查器如下:
*.js → ESLint
*.scss → stylelint
*.ts & *.tsx → TSLint

以 ESLint 为例,配置使用过程如下:

  • 在项目中添加所用到的检查器( ESLint 为例):
    npm install eslint --save-dev
  • 在项目中添加检查器对应的配置文件( ESLint 为例):
    详见官网说明
  • 如需对某个文件进行 ESLint 检查,在命令行运行如下命令:
    // 需要全局安装 ESLint
    eslint xxx.js
    //没有全局安装 ESLint,直接使用项目中安装的依赖)
    ${相对路径}/node_modules/.bin/eslint xxx.js
  • 如果检测出现不符合所配置的规范的问题,命令行会出现类似的错误输出:
    QQ20171009-011326@2x

Lint 检查的钩子实现

1. Git Hooks 介绍

Hook 中文一般叫做钩子,是指在特定的动作发生时触发自定义脚本。一般的版本控制工具都会提供 Hook 扩展功能,Git 也不例外。Git Hooks 分为客户端和服务端两种,客户端是指在本地发生的操作流相关的钩子,如 commit 操作流有四个钩子,pre-commit、prepare-commit-msg、commit-msg、post-commit;服务端是指发生网络请求的操作流相关的钩子,如 push 到远程的操作流相关的钩子。

Git Hook的详细介绍请见官网

Git Hooks 脚本存储在 $GIT-DIR/hooks 下,新的 git 仓库会为你生成一些移 .sample 为后缀的hooks脚本的例子,去掉 .sample 后缀即变为真正的钩子,会在特定的时候被执行。
------_20171009193211

本文用到的是 pre-commit 钩子,发生在 commit 操作完成前。如果脚本返回的是非零的值,则不会进行 commit 操作。

2. 检查脚本的具体实现

如上文所述,我们需要在项目的 /.git/hooks 下存储 pre-commit 的 hook 脚本,在用户输入 commit 命令时会被自动执行。同时还有三个 Lint 检查的 js 脚本,会在 pre-commit 脚本中被调用执行。目录结构如下:

.git
├── hooks
│   └── pre-commit
│   └── eslint-hook.js
│   └── stylelint-hook.js
│   └── tslint-hook.js

pre-commit 脚本内容如下:
/.git/hooks/pre-commit

#!/bin/sh

# $?表示前一个指令的返回码,没有前一个指令则默认为0
# 如果前一个指令的返回码为0,则继续执行下一个lint检查
# 否则用1退出,会终止commit操作

if [ $? -eq 0 ];then
node "./.git/hooks/eslint-hook.js"
else
exit 1
fi

if [ $? -eq 0 ];then
node "./.git/hooks/stylelint-hook.js"
else
exit 1
fi

if [ $? -eq 0 ];then
node "./.git/hooks/tslint-hook.js"
else
exit 1
fi

该脚本执行了三段相似的代码块,内容都是先判断上一个代码块的 lint 检查是否返回了非零值,如果是则执行exit 1退出脚本进程并会终止 commit 操作;否则将执行node "./git/hooks/xxx.js",也就是同样放在 .git/hooks 下的 lint 检查 js 脚本。
因为是 Node.js 项目,默认开发环境都是有 Node.js 环境的,因此通过 node 执行 js 脚本,来完成三种文件类型的 Lint 检查。

Lint 检查的 js 脚本如下(三个 lint 检查的 js 脚本内容都是相似的,这里用 eslint-hook.js 举例):
/.git/hooks/eslint-hook.js

#!/usr/bin/node

const childProcess = require('child_process');
const path = require('path');

// 通过diff指令获得所有改动过(不包括删除)的js文件路径
const cmdStr = 'git diff-index --cached HEAD --name-only --diff-filter=ACMR -- *.js';
const files = childProcess.execSync(cmdStr).toString().replace(/[\r|\n]/g, ' ');

// 如果没有改动过的文件,以0退出
if (!files) {
    process.exit(0);
}

// windows下cmd长度不能超过8000
// 因此将files切分成小于7900的字符串数组
const MAX_FILE_LENGTH = 7900;
const fileChunks = [];
let tmp = files;
while (tmp.length > MAX_FILE_LENGTH) {
    let spliceIndex = MAX_FILE_LENGTH;
    while (spliceIndex >= 0) {
        if (tmp[spliceIndex] === ' ') {
            break;
        }
        spliceIndex--;
    }
    if (spliceIndex === 0) {
        console.log('eslint error: file name too long');
        process.exit(1);
    }
    fileChunks.push(tmp.slice(0, spliceIndex));
    tmp = tmp.slice(spliceIndex + 1);
}
fileChunks.push(tmp);

const promises = fileChunks.map((fileChunk) => {
    return new Promise((resolve, reject) => {
        // 找到项目中的node_modules下的eslint二进制文件
        // 对改动过的文件路径执行eslint检查
        const cmd = `${path.normalize('${相对路径}/node_modules/.bin/eslint')} ${fileChunk} --color `;

        childProcess.exec(cmd, {maxBuffer: 100 * 1024 * 1024}, (err, stdio, stderr) => {
            if (err) {
                // 打印错误
                console.log('eslint error:');
                console.log(stdio || err);

                reject(stdio || err);
            }
            resolve();
        });
    });
});

Promise.all(promises).then(() => {
    // 如果没有错误,以0退出
    process.exit(0);
}, (error) => {
    // 有错误, 以1退出
    process.exit(1);
});

主要思路就是,通过 git 的 diff 命令,获得当前改动的某一类型的文件路径列表;再使用项目内 node_modules 里的对应的 Lint 检查执行文件,对文件路径执行 Lint 检查命令。如果通过检查则以 0 退出,否则以 1 退出。

到此为止,只要 node.js 项目安装了相关 Lint 检查的依赖,且在 .git/hooks/ 路径下正确存储了上述 pre-commit 脚本和 lint 检查的 js 脚本,就已经完成了针对开发者的 Lint 检查的强制执行。

通过 Gulp 任务完成 Lint 检查的自动安装

因为 .git 文件夹是不包含在 git 仓库中的,因此无法通过同步仓库的方式使项目所有开发者的本地都装有 pre-commit 的检查脚本。显然让每一位开发者手动复制脚本到本地对应的目录下并不是一个很科学的方式。那么如何完成Lint检查脚本的自动安装呢?

Node.js 项目在本地的 dev 环境进行开发时,通常会执行一些构建任务或者启动本地服务器等(例如结合 Webpack 或 Gulp 等工程化工具)。在本文所述项目中,我们使用了 Gulp 构建工具,因此可以在本地开发的构建过程中新增一个安装 Lint 检查脚本的任务,来完成 Lint 检查的自动安装。

所谓自动安装,就是将上文所述的 Lint 检查脚本放在代码仓库内,再通过安装脚本将它们拷贝到项目的 .git/hooks/ 目录下,实现起来比较简单,这里就不贴具体的代码了。这样就实现了在多人协同的项目开发中,结合代码仓库同步和本地开发流程,自动的使每个开发者的本地环境都装有 Lint 检查的执行脚本,进而在每个开发者进行提交的时候,强制完成 Lint 检查。

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。