前言:记得我刚开始参与到公司项目的开发时,由于代码结构太乱被同事集体diss,而后被强烈要求打开eslint来开发,无奈之下陷入eslint的魔爪。但是当我打开eslint的时候,我真的是去他喵的,代码哪哪都给我爆红,然后我就偷偷把eslint关掉了(机智如我<( ̄︶ ̄)>)。但是,果不其然,没过多久我就又被抓住了。在经历了几次猫抓老鼠的游戏之后,我终于习惯并且喜欢上eslint了。
在这篇文章中,我不会花太多的笔墨去介绍Eslint的使用,因为那是官方文档该做的事情。我所希望做到的是,在阁下看完这篇文章之后,能够超脱于Eslint本身,去看到它所实现的需求本身,即实现js/ts的代码检查。而后,我会以实现代码检查这个需求为出发点,引出代码eslint的 四种代码检查姿势。
对于代码检查需求及其实现方案,我从个人认识出发,用脑图做了以下整理:
接下来的行文,我都会围绕这副脑图展开,如果您有兴趣继续往下看下去,我希望您能在这幅图上停留多一些时间。
好的,按照上述脑图中的逻辑,接下来我会分成以下几个部分来展开探讨本文。
- 理解代码检查
- 为项目接入Eslint实现代码检查
- 基于Eslint及其规则实现<编码后检查>
- 基于Eslint及其规则实现<编码时检查>
- 基于Eslint及其规则实现<构建前检查>
- 基于Eslint及其规则实现
好的,理清了行文思路之后,下面我们进入第一部分,理解代码检查。
一:理解代码检查
代码检查,顾名思义就是检查代码,发生于开发阶段,可有效帮助开发者 减少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 ( ) 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 ( github.com/airbnb/java…) Standard ( github.com/standard/st…) Google ( github.com/google/esli…) |
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的官方整合工具列表中的 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及其规则实现
这一个部分的讲解,我接下来会从以下四个方面循序渐进的探讨Eslint如何实现git的提交前检查:
- 1.git提交前检查原理:Git Hooks
- 2.使用husky实现:编写node代码替代shell代码
- 3.实现hook任务流:通过lint-staged来配合husky来实现
- 4.实现 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" ] }