前言:记得我刚开始参与到公司项目的开发时,由于代码结构太乱被同事集体 diss,而后被强烈要求打开 eslint 来开发,无奈之下陷入 eslint 的魔爪。但是当我打开 eslint 的时候,我真的是去他喵的,代码哪哪都给我爆红,然后我就偷偷把 eslint 关掉了(机智如我 <( ̄︶ ̄)>)。但是,果不其然,没过多久我就又被抓住了。在经历了几次猫抓老鼠的游戏之后,我终于习惯并且喜欢上 eslint 了。
在这篇文章中,我不会花太多的笔墨去介绍 Eslint 的使用,因为那是官方文档该做的事情。我所希望做到的是,在阁下看完这篇文章之后,能够超脱于 Eslint 本身,去看到它所实现的需求本身,即实现 js/ts 的代码检查。而后,我会以实现代码检查这个需求为出发点,引出代码 eslint 的四种代码检查姿势。
对于代码检查需求及其实现方案,我从个人认识出发,用脑图做了以下整理:
接下来的行文,我都会围绕这副脑图展开,如果您有兴趣继续往下看下去,我希望您能在这幅图上停留多一些时间。
好的,按照上述脑图中的逻辑,接下来我会分成以下几个部分来展开探讨本文。
- 理解代码检查
- 为项目接入 Eslint 实现代码检查
- 基于 Eslint 及其规则实现 <编码后检查>
- 基于 Eslint 及其规则实现 <编码时检查>
- 基于 Eslint 及其规则实现 <构建前检查>
- 基于 Eslint 及其规则实现 <git 提交前检查>
好的,理清了行文思路之后,下面我们进入第一部分,理解代码检查。
一:理解代码检查
代码检查,顾名思义就是检查代码,发生于开发阶段,可有效帮助开发者减少 JavaScript 粗心代码,如语法错误、变量未定义、变量未使用等等问题。除此之外,代码检查还可以约束统一开发人员的代码风格,利于团队协作。
经过上面这段话,我们就已经达成了对代码检查的概念理解。下面我们再从三个方面来展开分析,加深对代码检查的实践理解。这三个方面分别为以下三点:
- 代码检查的功能
- 代码检查的类型
- 代码检查的工具
好的,下面我们就先进入代码检查功能的探讨。
1. 代码检查的功能
我认为,代码检查这个切面大概可以帮助我们做以下三件事情:
- 语言语法检查:比如检查出字符串引号或者函数调用括号没有匹配等问题。
- 编码错误检查:比如检查出开发者在使用一个不存在的变量或者变量定义了却没有使用等问题。
- 代码风格检查:比如检查出开发者没有使用分号(与所选风格有关)等问题。
文外总结:切面所在层次,决定了该切面的职能上线。
理解了代码检查这个切面所可以实现的功能之后,下面我们就探讨一下代码检查的类型。
2. 代码检查的方式
以代码检查发生的不同时间和场景来划分,我把代码检查的方式分类成以下四种:
- 编码时检查:编写代码时检查,通常表现为由 IDE 自动实时检查并进行代码提示。
- 编码后检查:编写代码后检查,通常表现为手动调用检查脚本 / 工具进行代码的检查或者代码保存后由 IDE 自动检查当前文件。
- 构建前检查:构建执行前检查,通常表现为将代码检查作为构建任务的一个前置切面,构建时自动触发代码检查。
- 提交前检查:git commit 前检查,通常表现为将代码检查作为 git commit 的一个 hooks 任务,代码提交前自动触发代码检查。
如此无私分享,不值得你的点赞和关注鼓励一下吗?
理解代码检查的方式很重要,这直接反映了你对代码检查这个概念本身的掌握程度。废话不多说,下面我们进入代码检查工具的探讨。
3. 代码检查工具
代码检查的实现通常不会仅仅是字符串分析处理,这其中会大量涉及到语法分析。既然涉及到语法,那么就需要对不同的代码使用不同的代码检查工具,通常来说,我们会使用 Eslint 工具来实现对 JavaScript 和 Typescript 代码的检查,使用 stylelint 工具对样式代码进行代码检查。对于 stylelint,本文不多做叙述,接下来我们把舞台交给 Eslint,一起探讨一下如何使用 Eslint 实现 JavaScript 代码和 typescript 代码的检查。
好的,在按顺序探讨编码后检查、编码时检查、构建前检查、git 提交前检查这四种代码检查方式之前,我们先为项目接入 Eslint,并配置各种代码检查方式都需要的检查规则。
二:为项目接入 Eslint 实现代码检查
有不少朋友可能并不了解 Eslint,下面我们先对它做个简单介绍。
Eslint 是一款插件(检查规则)化的 JavaScript 代码检查工具。概念言简意赅,需要注意的是,概念中说到 eslint 是一个插件化的检查工具,其意思是指 eslint 的设计是把检查工具和检查规则之间解耦了。也就是说,在安装好 eslint 的开发依赖之后,我们还可以并且需要选择安装一个我们中意的检查规则。
好的,理论就说到这,接下来我们就从实践中得到真知,实践步骤如下:
step1:安装检查工具 eslint
- yarn add eslint --dev
step2:安装并配置检查规则
在探讨配置安装检查规则之前,我们有必要先明确一下我们的检查目标是什么。我认为,检查目标自然是构建前的代码,并且是自己 / 自己团队编写的代码(非第三方模块)。毕竟检查的最终目标是为修复服务的,我们只负责修复自己 / 自己团队编写的代码,构建后代码以及第三方代码即使检查不通过我们也不会也不应该由我们去修复。
检查规则在项目中通常有两种表现形式,即:
- 配置文件中配置的规则:主要形式,通过继承和扩展的方式声明了大量规则
- 项目代码中的魔法注释:次要形式,通常是用于作为配置文件中规则的特例
生成 eslint 配置文件:
对于配置文件,我们通常会使用 eslint --init 命令来生成这个配置文件,下表列举了一些调用这个命令之后经常出现的配置问题(不一定会是依次下面几个问题)。
注意:我觉得回答这些问题还是要慎重一些,因为问题答案会影响配置,配置会影响检查规则,检查规则会影响检查结果。当然,回答这些问题的目的也就是生成想要配置文件,如果问题回答错了或者后续对规则有改动也可以直接修改 eslint 配置文件。
序号 | 作用 | 问题 |
---|---|---|
1 | 选择使用 eslint 的用途,常选 3 | How would you like to use ESLint? (Use arrow keys) To check syntax only To check syntax and find problems To check syntax, find problems, and enforce code style |
2 | 选择项目使用的模块化规范 | What type of modules does your project use? (Use arrow keys) JavaScript modules (import/export) only CommonJS (require/exports) None of these |
3 | 选择项目使用的框架 | Which framework does your project use? (Use arrow keys) React Vue.js None of these |
4 | 是否使用 Typescript | Does your project use TypeScript? (y/N) |
5 | 选择代码的运行环境(多选) | Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection) ( ) Browser ( ) Node |
6 | 选择如何为你的项目定义风格,常选 1 | How would you like to define a style for your project? (Use arrow keys) Use a popular style guide Answer questions about your style Inspect your JavaScript file(s) |
7 | 选择使用具体的流行代码风格 | Which style guide do you want to follow? (Use arrow keys) Airbnb (https://github.com/airbnb/javascript) Standard (https://github.com/standard/standard) (https://github.com/google/eslint-config-google) |
8 | 选择配置文件格式,常选 1(更灵活) | What format do you want your config file to be in? (Use arrow keys) JavaScript YAML JSON |
代码中的魔法注释写法:
除了配置文件中配置规则,eslint 还有一个代码中通过魔法注释打规则补丁的办法,如下示例:
- // 屏蔽整行的代码检查
- const str1 = "${name} is a coder" // eslint-disable-line
- // 屏蔽某一个规则:如此行的no-template-curly-in-string规则
- const str1 = "${name} is a coder" // eslint-disable-line no-template-curly-in-string
温馨提示:规则名称可以从检查结果提示或输出信息中得到
三:基于 Eslint 及其规则实现 <编码后检查>
对于一个工作流程的解释,我还是更倾向于直接演示一个简单的 demo。在这个行文思路下,下面我们就分别演示一个 Eslint 检查 JS 代码的示例以及一个 Eslint 检查 TS 代码的示例。根据代码检查的逻辑,demo 演示和讲解时我会遵循以下思路,即:
- 目标问题代码
- 代码检查规则配置
- 代码检查操作和结果
- 修复代码操作
好的,下面我们就进入 Eslint 对 JS 代码编码后检查示例的探讨。
1.Eslint 编码后检查 JS 代码示例
1): 目标问题代码
- const noUsedVar = 1;
- function fn() {
- console.log('hello')
- cnsole.log('eslint');
- }
- fn(
- fn2();
以上短短几行代码就可以表示出 eslit 代码检查的三个部分,它们分别是:
- 语法错误
- 编码错误:未定义、未使用
- 编码风格:没有分号
2): 代码检查规则配置
通过 eslint --init,根据项目特征来回答问题之后得到的 eslint 配置文件如下:
- module.exports = {
- env: {
- browser: true,
- es6: true,
- },
- extends: [
- 'airbnb-base',
- ],
- globals: {
- Atomics: 'readonly',
- SharedArrayBuffer: 'readonly',
- },
- parserOptions: {
- ecmaVersion: 2018,
- },
- rules: {
- },
- };
3): 代码检查操作和结果
第一轮检查结果先是报了语法错误,在修复语法错误之后,第二轮检查报错了很多编码以及风格上的错误。将两次检查结果关联到问题代码可以得到如下分析:
- const noUsedVar = 1; // find program:'noUsedVar' is assigned a value but never used
- function fn() {
- console.log('hello') // enforce code style:Missing semicolon(分号)
- cnsole.log('eslint'); // find program:'cnsole' is not defined
- }
- fn( // syntax error
- fn2(); // find program:'fn2' is not defined
4): 修复代码
根据上述检查结果进行修复。对于代码风格上的不一致导致的错误,通过参数 --fix 即可以自动修复大部分的问题。而对于语法以及编码上的错误则大部分只能是开发者自己手动修复。经过手动修复以及自动修复之后,问题代码可能变为如下模样:
- import { fn2 } from './test2';
- function fn() {
- console.log('hello');
- console.log('eslint');
- }
- fn();
- fn2();
2.Eslint 编码后检查 TS 代码示例
1): 目标问题代码
- function foo(ms: string): void{
- console.log(msg);
- }
- foo("hello typescript~")
2): 代码检查规则配置
项目安装 typescript 依赖后(不先安装 typescript 依赖就执行 eslint --init 会报错),通过 eslint --init 命令,根据项目特征来回答问题(typesrcipt yes)之后得到的 eslint 配置文件如下:
- module.exports = {
- "env": {
- "browser": true,
- "es6": true
- },
- "extends": [
- "airbnb-base"
- ],
- "globals": {
- "Atomics": "readonly",
- "SharedArrayBuffer": "readonly"
- },
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "ecmaVersion": 2018
- },
- "plugins": [
- "@typescript-eslint"
- ],
- "rules": {
- }
- };
相对于 JavaScript 的代码检查,对于 typescript 的检查需要额外的一个语法解析器(即上述配置中的 parser 配置项内容)。
3): 代码检查操作和结果
eslint 以 --fix 参数检查之后,主要报了两个编码错误,映射到文件中分析如下:
- function foo(ms: string): void { // 'ms' is defined but never used
- console.log(msg); // 'msg' is not defined
- }
- foo('hello typescript~');
4): 修复代码
在本示例中,在 fix 自动修复代码风格之后,手动把 foo 函数的形参改为 msg 即可。
- function foo(msg: string): void {
- console.log(msg);
- }
- foo('hello typescript~');
四:基于 Eslint 及其规则实现 <编码时检查>
说到编码时检查就不得不提到代码提示,也不得不联系到我们所具体选择使用的 IDE。除此之外,为了保证多种方式下代码检查规则的统一配置,我们还需要做到的关键一点是 IDE 能够读取我们在项目中配置的 eslint 规则文件去实时检查代码。
由于个人主要使用 VScode 开发,所以下面做的 demo 和探讨都默认在 vscode 环境下。官方相关资料:Eslint 的官方整合工具列表中的 [1. 安装 eslint、配置 eslint 规则](https://marketplace.visualstudio.com/items?item>Visual Studio Code: ESLint Extension 文档。
说实话,这个东西我也是现学现卖,毕竟前端要学的东西实在太多了,而且这篇文章的初衷也不是对某一技术点的面面俱到,而是帮助你建立系统化认识以及引导。
由于我们需要做到在 IDE 实时代码检查时,能够读取读取项目下的 eslint 规则。所以以下两个步骤必不可少:
- 安装 eslint、配置 eslint 规则
- IDE 实时代码检查功能相关安装配置
根据以上实现步骤思路,下面我们就做一个vscode环境下,基于Eslint及其规则实现编码时检查的demo。
1.安装eslint、配置eslint规则
- yarn add eslint --dev # 安装
- eslint --init # 初始化配置
eslint的规则配置方式上面已经讨论过,这里就不再展开赘述了。
2.IDE 实时代码检查功能相关安装配置
在 eslint 工具以及检查规则准备好之后(该规则就是 IDE 代码检查的规则),剩下的具体的检查行为触发以及代码提示工作就要交给 IDE 来实现了。对于 vscode,这里我们的实现思路是安装 eslint 这个扩展插件(个人使用的版本 2.1.14,下面的配置 1.x 很可能不适用),而后注意打开 vscode 的 eslint 代码检查功能(vscode 下的 eslint 开关为右下角的 eslint 文字标识)即可实现 vscode 环境下的 eslint 实时代码检查。
配置 IDE 的实时检查功能
如果需要对 IDE 的实时检查功能做一些配置,则可以通过 打开 vscode 的 setting -> 找到 eslint -> 打开 setting.json 这几个步骤来找到相关的配置文件,以下是配置 vscode 代码检查功能示例:
- //配置eslint
- "editor.codeActionsOnSave": { // 保存时自动fix
- "source.fixAll.eslint": true
- },
- "eslint.quiet": true, // warning时不报红色下划线,可用于处理no-console规则爆的warning
实时代码检查报错后的修复
在 IDE 自动检查的过程中,报错提示如果报的是代码触犯了规则的提示,那么就可以修改项目下的检查规则文件(.eslintrc.js)。比如个人在 demo 实践中就遇到了 ESLintExpected linebreaks to be 'LF' but found 'CRLF'这个规则错误,并且它的情况还有些特殊,它在 IDE 实时检查会报,但是手动调用 eslint 命令时不报,具体原因在此不多做分析,个人是直接在规则文件(.eslintrc.js)加入以下规则得以解决的:
- rules: {
- 'linebreak-style': [0, 'error', 'windows'],
- },
五:基于 Eslint 及其规则实现 <构建前检查>
下面我们直接进入现如今较为常用的 gulp 以及 webpack 构建工具如何实现构建前检查的探讨。
1.gulp 构建前检查
通过 gulp 实现这个代码检查切面的思路如下:
- 安装 eslint 并初始化 eslint 配置文件
- 下载安装 gulp-eslint 插件
- 编写 js 文件处理的代码检查切面
1): 初始化 eslint 配置文件
- yarn add eslint --dev
- eslint --init
2): 下载安装 gulp-eslint 插件
- yarn add gulp-eslint --dev
3): 编写 js 文件处理的代码检查切面
示例如下:
- // ... 其它代码
- const eslint = require('gulp-eslint');
- const script = (0 => {
- return src(['scripts/*.js'])
- .pipe(eslint()) // 代码检查
- .pipe(eslint.format()) // 将lint结果输出到控制台。
- .pipe(eslint.failAfterError()) // lint错误,进程退出,结束构建。
- .pipe(babel({ presets: ['@babel/preset-env']}))
- .pipe(dest('temp'))
- .pipe(bs.reload( { stream: true } ))
- }
- // ... 其它代码
2.webpack 构建前检查
通过 webpack 来实现这个代码检查切面的思路如下:
- 安装 eslint 并初始化 eslint 配置文件
- 安装 eslint-loader
- 编写 webpack 配置文件,为 js 文件加上这个 eslint-loader
(1): 安装 eslint 并初始化 eslint 配置文件
- yarn add eslint --dev
- eslint --init
(2): 安装 eslint-loader
- yarn add eslint-loader --dev
(3): 编写 webpack 配置文件,为 js 文件加上这个 eslint-loader
- rules: [
- {
- test: /.js$/,
- exclude: /node_modules/,
- use: [
- 'babel-loader',
- 'eslint-loader' // 更后的先执行
- ]
- }
- ]
六:基于 Eslint 及其规则实现 <git 提交前检查>
这一个部分的讲解,我接下来会从以下四个方面循序渐进的探讨 Eslint 如何实现 git 的提交前检查:
- 1.git 提交前检查原理:Git Hooks
- 使用 husky 实现:编写 node 代码替代 shell 代码
- 实现 hook 任务流:通过 lint-staged 来配合 husky 来实现
- 实现 git 提交前检查:先执行 eslint 任务而后执行 git add 任务
下面我们进入第一点,git 提交前检查原理:Git Hooks 的探讨。
1.git 提交前检查原理:Git Hooks
Eslint 实现 git 的提交前检查是通过 Git Hooks 实现的,Git Hook 也称之为 git 钩子,每个钩子都对应一个任务,通过 shell 脚本可以编写钩子任务触发时要具体执行的操作。
本文关注实现 git 提交前的代码检查,所以我们关注 git commit 这个钩子,使用步骤如下:
- 编写 hook 任务:项目的. git/hooks 文件夹下新建一个 pre-commit 文件
- #!/bin/sh
- echo "before commit"
- 触发钩子:项目下执行 git commit 命令
git commit 命令执行后,可以发现 commit 操作不管是否成功,都可以看到输出的 before commit 信息。
上述方式可用但还在实际生产中使用还是不太合适,毕竟对于前端开发者来说,使用 shell 脚本编写 git hook 的方式还是比较难接收,下面我们介绍 husky 这个库的使用,帮助我们达成以 js 代码的方式来编写 hook 任务的目的。
2. 使用 husky 实现:编写 node 代码替代 shell 代码
实现步骤如下:
- (1): 安装 husky
- (2): 配置 husky 的 hook 任务:如下 package.json 任务
- (3): 触发钩子:git add -> git commit
1): 安装 husky
- yarn add husky --dev
在安装好这个模块后,就可以在. git/hooks 文件夹下看到如下这些新添加的文件。
2): 配置 husky 的 hook 任务:如下 package.json 任务
- "scripts": {
- "test1": "echo before commit",
- "test2": "node test2.js"
- },
- "husky": {
- "hooks": {
- "pre-commit": "yarn test2"
- }
- },
3): 触发钩子:git add -> git commit
git commit 命令执行后,就可以触发我们通过 husky 实现的由 js 代码编写的 hook 任务。
下面我们增强一下,通过 lint-staged 这个库,让 hook 不但支持单任务还支持任务流的触发。
3. 实现 hook 任务流:通过 lint-staged 来配合 husky 来实现
实现步骤如下:
- (1): 安装 husky 和 lint-staged 模块
- (2): 配置 husky 的 hook 任务流:如下 package.json 任务
- (3): 触发任务流:git add -> git commit
1): 安装 husky 和 lint-staged 模块
- yarn add husky --dev
- yarn add lint-staged --dev
2): 配置 husky 的 hook 任务流:如下 package.json 任务
- "scripts": {
- "precommit": "lint-staged"
- },
- "husky": {
- "hooks": {
- "pre-commit": "yarn precommit"
- }
- },
- "lint-staged": {
- "*.js":[
- "echo task1",
- "echo task2",
- "echo task3"
- ]
- }
3): 触发任务流:git add -> git commit
实践发现,与单独的 husky 模块实现单任务相比而言,使用 lint-staged 之后,git commit 命令只有成功执行(有 add 资源并且有提交信息)才会触发 lint stage 中的操作,且这些操作只会对 js 文件有效。
好的,经过以上铺垫,下面我们就可以进入我们所要实现的最终需求,即实现 git 提交前检查的探讨了。
4. 实现 git 提交前检查:先执行 eslint 任务而后执行 git add 任务
实现步骤如下:
- (1): 安装 husky 和 lint-staged 模块
- (2): 配置 husky 的 hook 任务流:如下 package.json 任务
- (3): 触发任务流:git add -> git commit
1): 安装 husky 和 lint-staged 模块
- yarn add husky --dev
- yarn add lint-staged --dev
2): 配置 husky 的 hook 任务流:如下 package.json 任务
- "scripts": {
- "precommit": "lint-staged"
- },
- "husky": {
- "hooks": {
- "pre-commit": "yarn precommit"
- }
- },
- "lint-staged": {
- "*.js":[
- "eslint --fix",
- "git add"
- ]
- }
3): 触发任务流:git add -> git commit
经过这些开发包的下载以及配置,在我们执行 git commit 之后,就会触发 husky 配置的 pre-commit 的 hook 任务,而这个 hook 任务又把任务交给了 lint-staged 处理,进而通过 lint-staged 实现对 js 文件的代码检查以及自动风格修复后(还是错误则会中断提交)重新 add,而后再执行 commit 任务,保证了代码在提交前一定经过了代码检查。