前端codeLint-- 为项目集成ESLint、StyleLint、commitLint实战和原理

3,367 阅读15分钟

导读

Code Lint是前端工程化中的一个重要环节(what is code lint?),它可以帮助我们在部署代码到生产环境之前及时发现错误并纠正它们,也可以规范我们的编码习惯,让团队的代码风格保持统一。

Code Lint的工作原理是借助一些lint工具对代码进行静态分析,并在合适的时机触发校验,提示错误。

Note:本文可能无法覆盖所有知识点,若有知识盲区请主动查阅补齐,也可以在文章下方留言交流。

一、都有哪些Lint?

随着nodejs和前端工程化的发展,前端圈内产出了很多成熟的Lint工具,主要包括:

  • eslint 规范并校验 ECMAScript/JavaScript code的编写
  • tslint 规范并校验 TypeScript code的编写
  • stylelint 规范并校验css/scss/less code的编写
  • commitlint 负责校验commit msg是否符合规范
  • prettierbeautifyjs 统一代码排版格式

除此之外,我们还需要一些辅助的工具:

  • husky 能够监听git hooks的nodejs包,让nodejs开发者处理git hooks任务变得更加容易
  • lint-staged 可以将git“已暂存(staged)”的文件作为参数传入你要执行的shell script之中

读到此处,你可能对这些工具的作用和用法仍然心存疑惑,不过没关系,大家暂且只需要知道我们将用这些工具对我们的代码进行各环节的严格的校验即可,接下来让我们深入了解它们。

二、集成commit lint

相信大家对Git并不陌生,不知大家是否有过这种经历,自己提交git commit msg自己却看不懂。在小的团队里,可能我们更注重的是业务产出,并不在乎这些细节。随着团队的壮大,commit msg规范化至关重要,他意味着我们是否清楚自己和同事对代码干了什么,在代码排错、回滚甚至是甩锅时起到了关键性作用。

本着“工具比人更可靠”的原则,我们期望通过在项目中集成一些工具,从而实现在执行git commit -m 'msg'时能够自动的对msg内容进行校验,无需额外执行其他命令,huskycommitlint恰好能解决我们这个痛点。

2.1、理解git hooks 和 husky

在正式集成commitlint之前,先要介绍一下git hooks, 顾名思义hooks为“钩子”之意,它是“发布订阅模式”的一种实现,和前端中的DOM事件(click、hover等)相似,Git也预先定义了一些“事件钩子”如“commit-msg”、“pre-commit”等,当我们执行对应的Git操作时会触发它们,从而通知订阅该事件的shell script文件处理我们要进行的任务,这些shell脚本文件存放在项目根目录下的.git/hooks 目录中,如图所示:

image

我们可以通过编写这些shell script文件定制我们的校验任务,但前端工程师大多对linux/windows shell并不擅长,这和我们平时的工作重心有关, 因此我们通过编写git hooks脚本来优化前端工作流的这条道路十分艰难。Nodejs改变了这一切,它让JavaScript拥有了控制“操作系统”的能力,你只需要安装nodejs包husky,它会帮我们自动生成.git/hooks目录下的shell script,我们便可以很轻松的使用更熟悉的Nodejs处理git hooks任务,而无需关注shell script的实现细节。

执行下面命令, 在开发环境中安装husky,如下所示:

npm install husky --save-dev

在项目根目录下package.json文件中添加如下配置,并在hooks字段下添加git hooks监听任务配置, 如下:

// 这是NPM原生支持的脚本执行定义,当执行“npm run 脚本名”时执行
"scripts": {
    "test": "node test.js"
},
// 这是husky扩展的脚本执行的定义方式,当对应git hooks触发时执行
"husky": {
    "hooks": {
      // 可以执行一个js文件,将控制权转移给我们更熟悉的nodejs
      "pre-commit": "node heihei.js", 
      // 也可以调用其他脚本或者执行一段原生shell命令
      "commit-msg": "npm run test && echo succeed" 
    }
}

上面的配置只作为测试例子之用,无需真正集成到项目中,大家可以先写一个小demo尝试一下,加深一下对git hookshusky的理解。

2.2、安装配置commitlint

当然不要忘记我们今天的主角commitlint,我们需要利用它的cli(command-line interface)能力,配置一套属于我们自己的git commit msg 校验规则, 首先安装 @commitlint/cli@commitlint/config-conventional ,如下:

npm install @commitlint/cli --save-dev  
npm install @commitlint/config-conventional --save-dev
  • @commitlint/cli 是commitlint提供的命令行工具,安装后会将cli脚本放置在./node_modules/.bin/目录下
  • @commitlint/config-conventional是社区中一些共享的配置,我们可以扩展这些配置,也可以不安装这个包自定义配置

接下来是初始化@commitlint/cli的配置文件,在项目根目录创建名为commitlint.config.js的文件,代码如下:

/**
* feature:新功能
* update:更新某功能
* fixbug:修补某功能的bug
* refactor:重构某个功能
* optimize: 优化构建工具或运行时性能
* style:仅样式改动
* docs:仅文档新增/改动
* chore:构建过程或辅助工具的变动
*/
module.exports = {
  extends: [
    '@commitlint/config-conventional'
  ],
  rules: {
    'type-enum': [2, 'always', [
      'feature', 'update', 'fixbug', 'refactor', 'optimize', 'style', 'docs', 'chore'
    ]],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72]
  }
};
// 这些配置是什么意思?请自行查阅commitlint文档

最后我们需要在package.json中配置上我们的husky的选项,代码片段如下:

"scripts": {
    // 忽略
    "build": "node build/build.js" 
},
"husky": {
    "hooks": {
      // 重点
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 
    }
}

这段配置告诉了git hooks ,当我们在当前项目中执行 git commit -m '测试提交' 时将触发commit-msg事件钩子并通知husky,从而执行 commitlint -E HUSKY_GIT_PARAMS命令,也就是我们刚开始安装的./node_modules/.bin/commitlint,它将读取commitlint.config.js配置规则并对我们刚刚提交的测试提交这串文字进行校验,若校验不通过,则在终端输出错误,commit终止。

2.3、commitlint工作原理

简单阅读了一遍huskycommitlint的源码,大概的执行过程是这样的,如图所示:

image

如果上面这张图看不太懂,这没关系,但你至少要看懂下面这张图:

image

上面我们阐述了git hooks, huskycommitlint的工作流程, 理解husky的工作原理非常重要, 后面还会用到它,请大家务必理解。

三、集成ESLint

大家知道JavaScript语法存在一些设计缺陷,编码风格也可以各不相同,举一个最简单的例子,在定义一个string时小A同学喜欢用双引号,小B同学习惯用单引号,我们不讨论谁的风格更加合理,但我们至少应该统一它,这将减少我们代码的管理成本。

在我们的项目中集成eslint已经成为解决这个问题的成熟方案,可以说任何中大型项目都应该使用ESlint来约束我们的JavaScript代码,接下来看一下我们如何在webpack构建的vue项目中集成eslint

3.1、安装配置eslint-cli

首先在开发环境下安装eslint, 代码如下所示:

npm install --save-dev eslint

commitlint一样, 这会在./node_modules/.bin/目录下创建一个名为eslint.cmd的执行脚本(在windows系统下生成的是cmd脚本,在linux下就是sh脚本咯,这由nodejs自动帮你处理),eslint.cmd是eslint包提供的cli工具。

在项目根目录创建名为.eslintrc.js的文件,eslint.cmd执行时会默认读取该文件中的配置,初始配置如下:

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true
  },
  plugin:[],
  extends: [],
  rules:[]
}  

完成上面配置,我们就可以使用eslint的cli功能对我们的js代码进行校验咯,在根目录终端输入下面命令,校验当前目录下所有js文件,并修其中复可自动修复的错误,代码如下:

./node_modules/.bin/eslint ./** --fix

如果你的npm版本在5.2版本以上,可以使用npx来执行./node_modules/.bin下面的脚本更,这样加方便,如下:

npx eslint ./** --fix

从下图可以看出,eslint已经生效:

image

3.2、为eslint扩展配置和规则

当然仅仅是这些还不够,我们需要个性化的集成一些其他辅助的包或者自己配置一套属于自己的eslint环境,让它更符合我们项目的需要。

npm上有不少社区提供的共享配置,我们可以选择一种适合我们的配置,直接集成过来,比如我们在Github中搜索“eslint-config-” 则会发现有很多这种开源的共享配置可选,如图:

image

大家可以根据项目的需要,选择适合的配置,此处我们采用eslint-config-standard这个共享配置,执行安装:

npm install --save-dev eslint-config-standard

修改配置文件如下:

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true
  },
  extends: [
    'standard'
  ]
}  

让我们深入 eslint-config-standard包内部一探究竟,看它到底做了什么,下面为它内部的部分代码段:

image

通过上图,我们可以发现,eslint-config-standard 内部同时扩展了eslint-plugin-importeslint-plugin-nodeeslint-plugin-promise等插件,它们的作用分别是:

  • eslint-plugin-import 使eslint支持对import/export 的识别和校验
  • eslint-plugin-node 使eslin支持对nodejs原生模块的识别和校验
  • eslint-plugin-promise 使支持对promise最佳实践的校验

Note: 如果大家没有使用 eslint-config-standard, 但还想支持上面的这些功能,请自己安装集成, 此处就不再展示自定义集成的代码。

3.3、让eslint支持vue的识别和校验

如果你是vue项目,还需要安装下面这个包:

npm install --save-dev eslint-plugin-vue

官网对eslint-plugin-vue的介绍“ This plugin allows us to check the <template> and <script> of .vue files with ESLint.”集成这个插件,将使eslint拥有处理.vue文件的能力。

此外我们还需要安装 babel-eslint 包,安装命令如下:

npm install --save-dev babel-eslint

babel-eslint是一款es语法解析器,使用它解析代码可以提供一些es实验阶段语法的校验支持, 最后我们完整的eslint配置如下:

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true
  },
  parserOptions: {
    parser: 'babel-eslint'
  },
  extends: [
    'plugin:vue/essential',// 识别vue语法,并提供vue默认校验规则
    'standard'
  ],
  rules:[]// 你要覆盖的一些规则
}  

如果大家还需要修改配置或者需要其他包的集成,请自己去完成扩展哦,这是对大家动手能力的小小考验。

四、集成StyleLint

stylelint和eslint有相似的作用和原理,只不过校验对象不同,它主要用来校验你的样式代码,如css、scss、less等,这里就不做过多介绍,相信聪明的你可以从刚刚eslint的文章中做到举一反三,直接上代码:

npm install --save-dev stylelint
npm install --save-dev stylelint-config-standard

在本地创建名为.stylelintrc.js的配置文件,配置如下:

"use strict";
module.exports = {
  ignoreFiles: [
    "./**",
    "!./client/views/**/*.vue",
    "!./client/views/**/*.scss",
    "!./client/styles/**/*.scss",
    "!./client/plugin/**/*.vue",
    "!./client/plugin/**/*.scss"
  ],
  extends: ["stylelint-config-standard"],
  rules: {
      // 这里可以覆盖一些配置
  }
};

测试一下校验能力,项目根目录输入代码:

npx stylelint ./ --fix

上面代码会默认读取.stylelintrc.js的配置,忽略你不需要校验的目录。

五、格式化代码是否要加hard Lint?

我们知道eslint可以约束JavaScript的用法,但是它约束代码排版格式的能力却非常有限,同时它不能约束css、scss、html等,若想为我们的代码格式化加上强约束,我们还需要像类似prettierbeautify这样的工具, 特别是prettier它可以配合eslint使用。

prettierreactjsx支持的很好,但遗憾的是prettier格式化vue的能力并没有那么强大,可配置项也是少的可怜,阿三没有采用,并且考虑到工具链的调整需要渐进完善,阿三我也不太想马上就做格式化的强校验,感兴趣的同学可以自己研究一下。

外部链接:prettier对vue的格式化能力有限

六、执行Lint的时机有哪些?

上面我们讲了,如何为我们的项目集成commitlinteslintstylelint, 最终实现了,当我们执行git commit -m '测试提交'时,会对commit-msg的规范性进行校验校验,也可以在我们的项目根目录执行npx eslint ./ --fixnpx stylelint ./ --fix来校验和修复我们的代码。

但实际上除了commit-msg的校验, 已经满足了我们真实的场景,后面的cli手动校验还不能满足我们项目的需求,我们期望的是自动化的完成这一切。

那什么时机进行自动lint更适合呢?这取决于你的需求,下面是我总结的一些常见的校验时机。

6.1、在编辑器中进行 soft lint

Soft lint是阿三我自己起的名字,也就是 “软校验”的意思,顾名思义它并不是必须的,但它是非常必要的,它的使命是在开发环境下提醒开发者哪里出现了错误,请提前修正它们,是一种工具加持,有利于提升你排错的效率。

Soft lint一般是指在开发者的编辑器上完成的校验,属于“本地校验” ,同时它也是 “增量校验”,因为大多数情况下编辑器都只会校验你已经打开过的文件,当然也可以配置为“全量”的,但是这将会十分消耗电脑性能。

让我们以vscode为例,看一下如何利用vscdoe的插件实现对项目代码的soft lint。

首先打开vscode的插件选项,搜索并安装ESLint插件和stylelint插件,安装完成后,重启你vscode, 打开你项目中的.vue或.js文件,发现已经可以进行校验了,就这么简单,我们不需要再进行多余的配置。

为什么可以无需配置?我们以vscode plugin Eslint为例,打开该插件的官方介绍,可以看到这样一段话,“If you have installed ESLint locally then run .\node_modules.bin\eslint --init under Windows and ./node_modules/.bin/eslint --init under Linux and Mac.”, 这看起来似乎很熟悉,它正是执行了我们上面通过npm安装的eslintstylelint这两个包的cli脚本,你刚刚已经配置好了,因此可以安装即用,是不是很简单。

6.2、在本地 git commit 时进行 hard lint

虽说我们已经在本地编辑器中安装了 vscode plugin Eslintvscode plugin stylelint,但是这仍然不太到位,它只是一种辅助加持,并不能做到真正的“硬限制”,我们希望通过一种方式,新人下载项目代码后即可自带校验功能(npm install一下开箱即用),无需再手动配置任何东西。我们尝试和commitlint一样在commit环节同时完成eslintstylelint的校验,若校验不通过,则无法提交代码,阿三称之为“hard lint”。

我们仍然期望它是一种“增量校验”,因为如果每次执行git commit -m 都要执行npx eslint ./** --fix对项目中所有文件做一次校验,老板都要等破产了,实在是太慢了,我们只需对Git中处于 staged状态(git add的文件)的文件进行校验即可。

前面我们讲过,想要在执行 git commit时进行一些自定义的操作, 我们可以借助husky监听git hooks的能力,此时,我们可以监听pre-commit钩子,也就是说它会比commit-msg钩子先触发,优先进行eslintstylelint的校验,校验通过后再进行commitlint的校验。

前面我们以前安装了husky, 那么我们如何实现只对staged的文件进行“增量校验”呢?此时需要借助另外一个包lint-staged ,执行下面命令:

npm install --save-dev lint-staged

然后在package.json文件中,增加下面配置(注意这里只是增加配置,别傻乎乎的把自己的原有配置给覆盖了):

{
  "scripts": {
    "eslint:fix": "eslint --fix --ext \".js,.vue\"",
    "eslint:lint": "eslint --ext \".js,.vue\"",
    "stylelint:fix": "stylelint \"./**\" --fix"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "linters": {
      "*.{scss,css}":[
        "npm run stylelint:fix",
        "git add"
      ],
      "*.vue": [
        "npm run stylelint:fix",
        "npm run eslint:fix",
        "git add"
      ],
      "*.{js}": [
        "npm run eslint:fix",
        "git add"
      ]
    },
    "ignore": [
      "**/test/**"
      // 你要忽略的其他目录...
    ]
  }
}

注意"lint-staged"这个配置,它会匹配Git中处于staged状态的文件名,并针对这些匹配到的文件执行相对应的脚本,以vue文件为例,它会你在执行git commit -m 'test'时,依次执行npm run stylelint:fixnpm run eslint:fixgit add,如果没有错误或者错误能被自动修复(--fix), 则会将改动自动add 将修复后的代码加入 git staged, 继续进行commitlint的校验,若全部通过,才会生成一个新的commit,它的流程如下图:

image

6.3、其他lint时机

除了上面讲的在编辑器中进行“soft lint” 和在commit阶段进行“hard lint”之外,我们还可以在“webpack的loader阶段”或“CI持续集成远端”进行校验,但这不一定是必须的,需要看你们公司的是否需要这种校验,感兴趣的同学可以自己研究一下。