规范化
规范化是我们践行前端工程化中重要的一部分。
为什么要有规范化标准
俗话说,无规矩不成方圆,尤其是在开发行业中,更是要有严谨的工作态度,我们都知道大多数软件开发都不是一个人的工作,都是需要多人协同的,而不同的开发者有不同的编码习惯和喜好,这些个人的喜好并没有什么不好的地方,只是说同一个项目中,每一个人的喜好都不相同,那么就会导致项目的维护成本大大增加,所以说我们需要为每个项目或者团队需要明确统一的标准,让项目或团队中的成员按照统一的标准去完成工作,从而避免各种不统一而带来的麻烦。那么知道了为什么规范化标准之后,那么看一下在开发过程中,哪些地方用到规范化标准的。
- 代码、文档、甚至是提交的日志
- 开发过程中人为编写的内容
- 其中代码的标准化规范最为重要,因为代码的规范很大程度上决定着代码的质量以及可维护性,为了便于后期维护以及其他成员的阅读,一般情况下我们都对代码的风格进行统一的要求。
实现规范化的方法
我们在落实规范化的标准的时候也很简单,只需要提前约定好一个执行的标准,然后按照标准各自执行各自的开发工作。最后在Code Review环节按照之前约定的标准去检查各自相应的代码,但是按照人为约定的方式执行规范化会有很多的问题,一来人为约束不可靠,二来开发者也很难记住规则。所以我们就需要相对应的工具做保障,相比人为的检查,工具的检查更为可靠,同时配合自动化的工具进行自动化检查,这样就能得到质量化的保证。将工具检查代码规范的过程称为lint,例如前端常见的eslint、stylelint等。
常见的规范化实现方式
- ESLint工具使用
- 定制ESLint校验规则
- ESLint对TypeScript的支持
- ESLint结合自动化工具或者Webpack进行项目自动化校验
- 基于ESLint的衍生工具
- Stylelint工具的使用
ESLint介绍
EsLint是目前最为主流的JavaScript Lint工具,专门用于检测JS代码质量,通过ESLint很容易统一开发者的编码风格,例如:缩进、换行、分号以及空格之类的使用。不仅如此,EsLint还能帮助我们找到代码中不合理的地方,例如我们定义了一个从未使用的变量,或者在变量使用之后才去做声明等等,而这些不合理的操作就是代码中潜在的问题,通过EsLint能有效避免这些问题,从而提高代码的质量。
对于 JavaScript 这种动态、宽松类型的语言来说,开发者更容易犯错。由于 JavaScript 不具备先天编译流程,往往会在运行时暴露错误,而 Linter,尤其最具代表性的 ESLint 的出现,允许开发者在执行前发现代码错误或不合理的写法。
ESLint 最重要的几点哲学思想:
- 所有规则都插件化
- 所有规则都可插拔(随时开关)
- 所有设计都透明化
- 使用 Espree 进行 JavaScript 解析
- 使用 AST 分析语法
- 想要顺利执行 ESLint,还需要安装应用规则插件。
那么如何声明并应用规则呢?在根目录中打开 .eslintrc 配置文件,我们在该文件中加入:
{
"rules": {
"semi": ["error", "always"],
"quote": ["error", "double"]
}
}
semi、quote 就是 ESLint 规则的名称,其值对应的数组第一项可以为:off/0、warn/1、error/2,分别表示关闭规则、以 warning 形式打开规则、以 error 形式打开规则。
- off/0:关闭规则
- warn/1:以 warning 形式打开规则
- error/2:以 error 形式打开规则
同样我们还会在 .eslintrc 文件中发现:
{
"extends": "eslint:recommended"
}
这行表示 ESLint 默认的规则都将会被打开。当然,我们也可以选取其他规则集合,比较出名的有:
注意,上文中 .eslintrc 文件我们采用了 .eslintrc.js 的 JavaScript 文件格式,此外还可以采用 .yaml、.json、yml 等格式。如果项目中含有多种配置文件格式,优先级顺序为:
- .eslintrc.js
- .eslintrc.yaml
- .eslintrc.yml
- .eslintrc.json
- .eslintrc
- package.json
最终,我们在 package.json 中可以添加 script:
"scripts": {
"lint": "eslint --debug src/",
"lint:write": "eslint --debug src/ --fix"
},
我们对上述 npm script 进行分析,如下:
- lint 这个命令将遍历所有文件,并在每个找到错误的文件中提供详细日志,但需要开发者手动打开这些文件并更正错误。
- lint:write 与 lint 命令类似,但这个命令可以自动纠正错误。
ESLint安装
首先我们需要创建一个项目,并在项目中安装ESLint模块为开发依赖,然后初始化配置文件。
mkdir test-eslint && cd test-eslint
npm init
npm install eslint -D //安装
npm init @eslint/config //初始化配置文件
然后提示,给出了三个选项
- To check syntax only:只检查语法的错误
- To check syntax and find problems:检查语法错误并发现问题代码
- To check syntax, find problems, and enforce code style:第三个在前两个基础上校验代码风格
其中语法错误比较好理解,问题代码指的是代码不合理的地方,例如未被使用的变量或者不存在的函数。代码风格指的是在代码风格上存在的问题,例如缩进不统一。在实际开发中建议选择第三种。
然后再选择项目的模块化,根据项目的实际情况选择即可。
等等一些基础配置,包括项目用的什么框架,React、Vue还是啥也没用,是否用TypeScript,你的项目运行在什么环境是浏览器还是Node,你想要在你的项目中怎样定义代码风格,使用市面上主流的风格、通过询问问题形成风格、根据JS代码文件推断出风格? 那么我们选择市面上主流的风格后,又提示让你选择具体市面上哪个风格。 最后提示配置文件以何种方式存放,这里选择JS的方式即可,方便后续做条件判断。 之后会提示安装项目需要的插件,确定安装即可,在项目根目录下会看到.eslintrc.js文件。
然后再执行npx eslint xxx.js
可以看出校验了很多的问题。
总结来讲eslint有两个作用:一是可以找出代码的问题,包括语法错误、代码不合理、风格不统一。二是可以修复代码风格上的大多数的问题。例如npx eslint xxx.js --fix
命令来修复,例如添加如下命令:
"scripts": {
"lint": "eslint ./src/**/*.{js,jsx,vue,ts,tsx} --fix"
},
ESLint配置文件解析
项目生成的.eslintrc.js文件内容如下:
module.exports = {
env: {
browser: true,
es2021: true
},
extends: 'standard',
overrides: [
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
}
}
该配置项默认有四个配置选项
- env:JS在不同的环境中有不同的api可以调用,例如在浏览器中可以直接使用widow、document全局成员,这个env就是表明当前代码的运行环境,它会根据环境信息判断全局成员是否可用,为了避免代码中使用不存在的成员。比如browser为true表示代码运行在浏览器中,这里定义的每一个环境对应了一组全局变量,一旦开启了某个环境,这个环境的全局变量则都能被使用。env运行环境配置如下:
- browser:浏览器环境中的全局变量
- node:Node.js全局变量和Node.js作用域
- commonjs:CommonJS全局变量和CommonJS作用域(用于Browserify/Webpack打包的只在浏览器中运行的代码)
- shared-node-browser:Node.js和Browser通用全局变量
- es6:启用除了modules以外的所有ECMAScript6特性(该选项会自动设置ecmaVerison解析器选项为6)
- worker:Web Workers全局变量
- amd:将require()和define()定义为像amd一样的全局变量
- mocha:添加所有的Mocha测试全局变量
- jasmine:添加所有的Jasmine版本1.3和2.0的测试全局变量
- jest:Jest全局变量
- phantomjs:PhantomJS全局变量
- protractor:Protractor全局变量
- qunit:QUnit全局变量
- jquery:jQuery全局变量
- prototypejs:Prototype.js全局变量
- shelljs:ShellJS全局变量
- meteor:Meteor全局变量
- mongo:MongoDB全局变量
- applescript:AppleScript全局变量
- nashorn:Java 8 Nashorn全局变量
- atomtest:Atom测试全局变量
- embertest:Ember测试全局变量
- webextensions:WebExtensions全局变量
- greasemonkey:GreaseMonkey全局变量
- extends:用于继承一些共享的配置,例如我们使用的standard规范。
- parserOptions:该选项用于设置语法解析器的配置,它只检测语法,而不是检测哪个全局成员是否可用。
- rules:用于配置校验规则的开启和关闭。
- plugins:设置规则插件。
- parser:默认情况下 ESLint 使用 Espree 进行解析。
如果想校验vue规范,可以安装vue-eslint-plugin,添加如下配置即可
{
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
'plugin:vue/vue3-recommended',
// 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
],
}
常用依赖
- eslint: ESLint 的程序入口,在 lint-staged 中设定针对 JS 文件执行该程序
- eslint-config-standard: 一套通用的 ESLint 规则配置。
- eslint-config-prettier:关闭与 Prettier 可能冲突的 ESLint 规则,解决 ESLint + Prettier 时的冲突问题
- eslint-plugin-prettier:Prettier 的插件,使得 ESLint + Prettier 结合使用成为可能。让 Prettier 的格式化操作归属在 ESLint 的过程中执行,对于 Prettier 发现的格式问题,也在 ESLint 的过程中自动修复
其中 eslint-config-standard 就是一套行业通用的 ESLint 规则配置,由 JavaScript Standard Style 提供。
ESLint配置注释
ESLint配置注释指的是将配置以注释的方式写在脚本文件中,然后再去执行代码的校验。在实际开发的过程中,难免会遇到一两个违反配置规则的情况,不能因为这一两个点去推翻校验规则的配置,这个时候可以使用ESLint配置注释来解决这样的问题。
const str1 = '${name} is a coder'; //eslint-disable-line no-template-curly-in-string
console.log(str1);
例如使用eslint-disable-line选择性忽略这行代码,但是如果一行代码中有多个问题,通过这个注释都不会被检测到了,因此后面加上具体禁用的规则名称,这样就不影响其他的规则。
编辑器集成
编辑器集成主要是如何看到不符合规范的错误提示,如何按照项目中的ESLint规则要求进行格式化。
首先先去卸载或者禁用Vetur插件,然后安装vscode的eslint插件,以及volar插件。
eslint插件是安装并启用了这个插件,它就会自动查找项目中的eslint配置规范,并且给出验证提示。那如何格式化呢?ESLint提供了格式化工具,但是需要手动配置才可以。
onType是在写代码过程中实时去验证,onSave是保存的时候去验证。
ESLint结合gulp
如果我们的项目中采用自动化构建工作流,那么我们就把ESLint集成到工作流中。在gulp使用babel编译之前,通过glup-eslint插件来检查代码。使用的时候先调用eslint插件检测,然后调用eslint.format()方法在控制台打印错误信息,然后再使用eslint.failAfterError()方法,在检查到错误之后终止任务管道。
ESLint结合Webpack
在webpack中使用ESLint需要安装eslint-loader在babel-loader之前调用。
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'eslint-loader',
enfore: 'pre'
}
]
这里使用一个 enforce 属性配置pre优先处理。它的值如下:
- pre 优先处理
- normal 正常处理(默认)
- inline 其次处理
- post 最后处理
针对React特殊语法需要安装eslint-plugin-react插件,然后在.eslintrc.js文件中配置插件,编写rules规则即可。
module.exports = {
env: {
browser: true,
es2021: true
},
extends: 'standard',
overrides: [
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2
},
plugins:[
'react'
]
}
我们也可以直接继承eslint-plugin-react中编写的规则,就可以共享配置中的内容。
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'standard',
'plugin:react/recommended'
],
overrides: [
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
}
}
ESLint检查TypeScript
之前我们检查TypeScript是采用tslint工具,后来tslint官方放弃维护,推荐使用eslint配合typescript插件来做代码校验。注意这里在.eslintrc.js中配置ts的语法解析器。
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'standard',
],
parser: '@typescript-eslint/parser',
overrides: [
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
}
plugins:[
'@typescript-eslint'
]
}
StyleLint介绍
在前端项目中除了JavaScript代码需要lint之外,css代码也需要lint,css代码的lint一般使用StyleLint来帮助完成。StyleLint默认提供了代码检查规则供开发者使用,我们也可以在配置文件中选择性的开启和关闭规则。StyleLint也同样提供了CLI工具,供开发者在命令行中调用,从而检查css代码。StyleLint也支持通过插件来实现对Sass、Less、PostCSS等衍生语法的检查。最后StyleLint也支持Gulp和Webpack工具的集成。
安装stylelint
npm install stylelint -D
安装stylelint共享配置模块
npm install stylelint-config-standard
创建.stylelintrc.js配置文件
module.exports = {
extends: [
'stylelint-config-standard',
]
}
再通过命令校验css文件
npx stylelint ./index.css
如果你需要使用stylelint校验sass代码,那么就需要安装相对应的模块。
npm install stylelint-config-sass-guidelines
然后再添加配置文件
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-sass-guidelines'
]
}
常用的依赖
- stylelint-config-standard: 官网提供的 css 标准
- stylelint-config-recess-order: 属性排列顺序
- stylelint-prettier: 基于 prettier 代码风格的 stylelint 规则
- stylelint-config-prettier: 禁用所有与格式相关的 Stylelint 规则,解决 prettier 与 stylelint 规则冲突,确保将其放在 extends 队列最后,这样它将覆盖其他配置。
- stylelint-order:用于规范样式属性写作顺序的插件。
- stylelint-config-rational-order:css属性顺序的规则。配合stylelint-order使用。
Prettier的使用
Prettier是一款通用的前端代码格式化工具,它很强大,几乎能完成所有前端代码的格式化工作。它一般不会检查我们代码具体的写法,而是在“可读性”上做文章。目前支持包括 JavaScript、JSX、Angular、Vue、Flow、TypeScript、CSS(Less、SCSS)、JSON 等多种语言、数据交换格式、语法规范扩展。在日常工作中,我们可以使用它来完成日常代码的自动格式化,通过使用它可以落实前端项目的规范化标准。
总的来说,它能够将原始代码风格移除,并替换为团队统一配置的代码风格。虽然几乎所有团队都在使用这款工具,这里我们还是简单分析一下使用它的原因:
- 构建并统一代码风格
- 帮助团队新成员快速融入团队
- 开发者可以完全聚焦业务开发,不必在代码整理上花费过多心思
- 方便,低成本灵活接入,并快速发挥作用
- 清理并规范已有代码
- 减少潜在 Bug
- 丰富强大的社区支持
当然,Prettier 也可以与编辑器结合,在开发者保存后立即进行美化,也可以集成到 CI 环境中,或者 Git pre-commit 的 hook 阶段。比如使用 pretty-quick:
yarn add prettier pretty-quick husky -D
格式化文件
npx prettier style.css --write
使用通配符的方式格式化所有的文件
npx prettier . --write
并在 package.json 中配置:
{
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
}
在 husky 中,定义 pre-commit 阶段,对变化的文件运行 Prettier,--staged 参数表示 pre-commit 模式:只对 staged 的文件进行格式化。
这里我们使用了官方推荐的 pretty-quick 来实现 pre-commit 阶段的美化。这只是实现方式之一,还可以通过 lint-staged 来实现。
Git Hooks介绍
Git Hook也称之为Git钩子,每个钩子都连着一个具体的Git操作,比如commit、push等等,我们可以通过shell脚本来编写钩子任务触发时要具体执行的操作。例如我们可以通过Git Hooks的方式在提交代码之前强制做lint检查,以便后续CI的时候lint失败。
在git提交信息的规范中,一般常用的两个阶段是:pre-commit 和 commit-msg:
- pre-commit:在代码提交之前执行。可处理代码格式规范等。
- commit-msg:处理代码提交的message信息。
除此以外,git hooks还有多个阶段,可以在需要的时候启用,比如以下这些:
- pre-receive:从本地版本完成一个推送后
- prepare-commit-msg:信息准备完成后但编辑尚未启动
- pre-push:push 之前执行
- post-update:在工作区更新完成后执行
- pre-rebase:在rebase操作之前执行
我们打开git项目,在根目录下找到.git隐藏文件夹 打开hooks目录,可以看到sample钩子文件。我们可以去自定义这些文件。
由于很多前端开发者并不擅长shell,所以有开发者开发了一个npm模块Husky帮助实现Git Hooks的使用需求。使用Husky就可以在不编写shell脚本的情况下实现Git钩子的需求。
安装,使用以下命令会在项目中安装husky和lint-staged
npx mrm@2 lint-staged
安装Husky
npm install husky -D
然后再package.json中配置husky,husky下有hooks对象,里面可以配置钩子和对应的命令。
{
"husky":{
"hooks":{
"pre-commit": "npm run test"
}
}
}
如果我们想在代码格式化后提交到暂存区,那么仅使用husky是满足不了需求的,这时候就需要使用lint-staged模块帮助协作。我们先安装lint-staged模块。
npm install lint-staged -D
然后配置package.json
{
"scripts":{
"precommit": "lint-staged"
}
"husky":{
"hooks":{
"pre-commit": "npm run precommit"
}
},
"lint-staged":{
"*.js":[
"eslint",
"git add"
]
}
}
这样就能在提交代码前先使用eslint校验,然后执行git add命令。
pre-commit
上面介绍中,提到husky工具会在根目录下生成 .husky 目录,保存有husky的基本配置。要想配置 git hooks,还得在这个目录下进行操作,可以采取下面两种方式。
第一种,我们可以直接在 .husky 目录下新建文件:pre-commit,注意无后缀名。然后给该文件添加以下内容:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx eslint src
其中,npx eslint src 是需要在git提交前执行的命令,也可以换成类似 npm run lint 等 scripts 脚本。
第二种,或者使用脚本命令生成该文件,执行以下脚本:
npx husky add .husky/pre-commit "npx eslint src"
该脚本执行后,将在 .husky 目录下自动生成 pre-commit 文件,并且写入对应的脚本命令:npx eslint src,内容与上面的第一种方式一样。
完成以上操作后,当我们执行 git commit 命令时,就会自动执行eslint命令。除了eslint,我们也可以配置其他诸如 stylelint、prettier 等等。
commit-msg
对git提交信息中 message 的规范,也是我们在项目开发中必不可少的一环,这块内容对团队协作、版本日志等都能带来帮助。 提交 message 的规范有多种约定式的规范,如 Conventional Commits specification,但只靠规范约定很显然不能保证规范完整正确的被执行,所以需要辅助工具来帮助我们处理,比如 commitlint。
husky
Husky 是一款管理 git hooks 的工具,可以让我们更方便的管理 git hooks 脚本。它将在我们提交代码时触发不同的钩子,执行不同脚本,帮忙我们自动化的处理一些任务,比如执行 eslint 命令等。
首先,安装husky:
npm install husky -D
然后,在 package.json 文件的 scripts 中配置自动安装脚本:
"prepare": "husky install"
当设置了该配置脚本后,在我们执行 npm install 命令操作时,会自动执行 npm run prepare 命令,即会执行 husky install 命令,并在项目的根目录下生成 .husky 目录,如下图所示:
.husky 目录下会自动生成两个文件,其中脚本文件 husky.sh 表示 husky 的初始化基础配置。
除此外,我们当然也可以直接执行 prepare 命令,同样有效:
npm run prepare
自定义配置目录
.husky 目录是默认生成的,当然我们也可以自定义husky配置的文件目录,只需要在命令中加入参数即可,如下所示:
husky install gitHooks/husky
这样,就会在项目根目录下,生成 gitHooks 目录和 husky 子目录,而对应的初始化配置文件也会自动生成。
lint-staged
在介绍 pre-commit 的时候,我们使用了 npx eslint src 命令来处理代码规范。但它存在一个问题,就是每次git提交都会对整个项目src中的所有文件都进行检测,很多时候这是不需要的,最好的方法就是对新修改的代码进行检测。 而 lint-staged 工具就是基于此,只针对提交的代码文件进行检查处理。
安装 lint-staged:
npm install lint-staged -D
lint-staged 需要增加配置,一般可以使用两类方式。
直接在package.json文件中增加内容,如下所示:
"lint-staged": {
"*.{js,jsx,vue,ts}": [
"eslint src"
]
}
或者,增加配置文件,如 lintstagedrc.json,内容如下:
{
"*.{js,jsx,vue,ts}": ["eslint src"]
}
配置好以后,可以修改pre-commit文件,添加以下脚本:
npx lint-staged
至此,则 lint-staged 就设置完成,当再次使用 git commit 提交代码的时候,就会先执行代码检查之类的动作,并且是增量代码检查。
git commit规范
推荐参考
统一团队Git commit日志标准,便于后续代码review,版本发布以及日志自动化等。
那么就考虑使用commitlint
安装
npm install --save-dev @commitlint/config-conventional @commitlint/cli
创建commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional']
}
如果想自动生成changelog可以使用conventional-changelog
技术原理和设计
ESLint 是基于静态语法分析(AST)进行工作的,AST 已经不是一个新鲜话题。ESLint 使用 Espree 来解析 JavaScript 语句,生成 AST。
有了完整的解析树,我们就可以基于解析树,对代码进行检测和修改。ESLint 的灵魂是每一条 rule,每条规则都是独立且插件化的,我们挑一个比较简单的“禁止块级注释规则”源码来分析:
module.exports = {
meta: {
docs: {
description: '禁止块级注释',
category: 'Stylistic Issues',
recommended: true
}
},
create (context) {
const sourceCode = context.getSourceCode()
return {
Program () {
const comments = sourceCode.getAllComments()
const blockComments = comments.filter(({ type }) => type === 'Block')
blockComments.length && context.report({
message: 'No block comments'
})
}
}
}
}
从中我们看出,一条规则就是一个 node 模块,它由 meta 和 create 组成。meta 包含了该条规则的文档描述,相对简单。而 create 接受一个 context 参数,返回一个对象,如下代码:
{
meta: {
docs: {
description: '禁止块级注释',
category: 'Stylistic Issues',
recommended: true
}
},
create (context) {
// ...
return {
}
}
}
从 context 对象上我们可以取得当前执行扫描到的代码,并通过选择器获取当前需要的内容。如上代码,我们获取代码的所有 comments(sourceCode.getAllComments()),如果 blockComments 长度大于 0,则 report No block comments 信息。了解了这些,相信你也能写出 no-alert、no-debugger 的规则内容。
虽然 ESLint 背后的技术内容比较复杂,但是基于 AST 技术,它已经给开发者提供了较为成熟的 APIs。写一条自己的规则并不是很难,只需要开发者找到相关的 AST 选择器。更多的选择器可以参考:Selectors - ESLint - Pluggable JavaScript linter,熟练掌握选择器,将是我们开发插件扩展的关键。