每个语句的末尾是否需要分号?使用两个空格还是四个空格?
关于代码格式的讨论数不胜数,而且没有绝对的谁对谁错,每个人有自己的代码偏好。如果是单人开发那没什么问题,但涉及到多人协同开发就不可避免地产生代码风格上的冲突:
同事 A 喜欢使用两个空格并把代码提交了上去,同事 B 喜欢使用四个空格并好心地将原先的双空格改了回去,如此反复地更改不可避免地会产生无用的 Git 提交记录(并且影响同事间的关系)
以上是关于代码格式,代码规范除了代码格式还有重要的一环:代码质量。使用未定义的变量?定义变量使用到了保留字?函数返回值是否需要强制类型?是否完全禁用 any 的使用?这些涉及到代码的内部逻辑都属于代码质量。
代码质量出问题意味着程序有潜在的 Bug,代码格式出问题最多也就是看着不爽。
所以需要指定编码规范,团队中的每个人都强制执行,并使用工具对代码进行检查,并提供格式化的功能。
本文讲解使用 ESLint + Prettier 规范你的团队编码规范,读完本文你将掌握了解以下知识:
- 什么是 ESLint、Prettier?它们解决了什么问题?
- ESLint 的配置、使用
- ESLint 配置包
- ESLint 和 Prettier 的差异,如何将两者搭配使用
- 了解 Git hooks 并能使用指定的 hook 对提交代码进行检查
ESLint 基础使用
ESLint 是一个开源的 JavaScript 的代码检查工具,使用 espree 将代码解析为抽象语法书(AST),通过 AST 进行静态代码分析,并给予代码格式和代码质量两个方面的提示:
- 代码格式(Formatting rules):Tab 长度、函数最大行数、尾随分号等
- 代码质量(Code-quality rules):未使用的变量、使用到了保留字、全局变量的污染等问题。
此外 ESLint 是完全插件化的,每一个规则就是一个插件。为了方便使用 ESLint 已经内置了一些默认规则,开发者可以自定义规则并在运行时添加更多的规则。
安装使用
在 npm inite -y
初始化一个项目后,执行:
npm install -D eslint
npx eslint --init
一路回车后会创建一个默认的 .eslintrc
配置文件,新建一个 index.js 文件:
const data = 'semi';
然后执行 npx eslint index.js
会看到 ESLint 的执行结果:
配置
ESLint 主要提供两种配置方式:
- 在代码的源文件中使用 JavaScript 注释
- 使用一个独立的
.eslintrc.*
配置文件,或者直接在package.json
中使用eslintConfig
字段指定配置。ESLint 会自动寻找查找并读取它们。
在同一个目录下有多个配置文件只会使用一个,优先级从高到低为:
.eslintrc.js
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
.eslintrc
package.json
主要的配置参数如下:
env
:指定脚本的运行环境,每一个环境定义了一组预定义的全局变量。globals
:脚本在执行期间访问的额外的全局变量。rules
:修改使用的规则。extends
:继承其它配置文件中开启的规则。plugins
:使用第三方插件。parser
:解析器。parserOptions
:解析器配置。
rules
rules
是具体的代码检查选项,要改变一个规则设置,必须将规则 ID 设置为下列值之一:
"off"
或0
- 关闭规则"warn"
或1
- 开启规则,使用警告级别的错误:warn
(不会导致程序退出)"error"
或2
- 开启规则,使用错误级别的错误:error
(当被触发的时候,程序会退出)
{
"rules": {
"eqeqeq": "off",
// 如果一个规则有额外的选项,可以使用数组字面量指定
"quotes": ["error", "double"],
// 配置定义在插件中的一个规则的时候,你必须使用 插件名/规则ID 的形式
"plugin1/rule1": "error"
}
}
所有的规则默认都是禁用的,在配置文件中,可以开启 "extends": "eslint:recommended"
来开启 ESLint 推荐的规则,更多规则可以查看:rules
plugins
ESLint 支持使用第三方插件。在使用插件之前,你必须使用安装它。
在配置文件里配置插件时,可以使用 plugins
关键字来存放插件名字的列表。插件名称可以省略 eslint-plugin-
前缀,以 eslint-plugin-react
为例:
{
"plugins": ["react"]
}
extends
extends
提供了继承其它配置文件的选项,属性可以是一个第三方包、配置文件的路径,也可以是字符串数组继承多个(每个配置继承它前面的配置)。
extends 属性值可以省略包名的前缀 eslint-config-
,比如 eslint-config-standard
:
{
"extends": "standard"
}
一些插件也可以输出一个或多个命名的配置,此时 extends
由以下属性组成:
plugin:
- 包名 (省略了前缀
eslint-plugin-
,比如,react
) /
- 配置名称 (比如
recommended
)
以 eslint-plugin-react
为例,此插件提供了 recommended
、all
等配置:eslint-plugin-react 的导出配置,此时继承此插件提供的 recommended
配置如下:
{
"extends": [
"plugin:react/recommended"
],
}
parser 和 parserOptions
parser
指的是将 .js
、.ts
文件处理为 AST 使用的解析器。ESLint 默认使用 espree 作为解析器。
除了 espree
,还有以下于 ESLint 兼容的 parser:
- esprima
- @babel/eslint-parser -- 一个对 Babel 解析器的包装,使其能够与 ESLint 兼容。
- @typescript-eslint/parser -- 将 TypeScript 转换成与 estree 兼容的形式,以便在ESLint中使用。
parser
解析器的选项可以由 parserOptions
配置,解析器会被传入 parserOptions
。默认情况下,ESLint 支持 ECMAScript 5 语法。
一个配置例子如下:
{
"parserOptions": {
// 使用的 ECMAScript 版本
"ecmaVersion": 6,
// 设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。
"sourceType": "module",
}
}
env
指定脚本的运行环境,每一个环境定义了一组预定义的全局变量,可以定义多个环境。
比如配置了 browser
就可以使用浏览器中的全局变量 window
:
{
"env": {
"browser": true,
},
};
常见的环境有以下:
- browser - 浏览器环境中的全局变量。
- node - Node.js 全局变量和 Node.js 作用域。
- commonjs - CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
- shared-node-browser - Node.js 和 Browser 通用全局变量。
- es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
globals
当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。如果想要在源文件中使用额外的全局变量,需要在 ESLint 中定义这些变量:
{
"globals": {
// var1 变量允许重写
"var1": "writable",
// var2 只读,不允许重写
"var2": "readonly"
}
}
VSCode 提示
每次都需要命令行执行 npx eslint
也太麻烦了,能否在开发时 IDE 对代码进行检查并提示错误?打开 VSCode Extensions 搜索 ESlint 并安装扩展,会发现在 VSCode 中开发源文件,相关错误能直接在 IDE 中给予提示。
并且 npx eslint --fix
还可以对部分规则进行修复,同时配置 .vscode/settings.json
可以实现在 IDE 中格式化代码:
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
}
ESLint 配置包
OK~目前你已经掌握了 ESLint 的基础使用,同时你们团队经过讨论后指定了一系列的编码规范,此时 .eslintrc
配置文件已经上百行,每个开发者都需要在本地配置这么多规则十分麻烦,同时规范的后续更新也很难做到同步,所以你创建了一个 npm 包 eslint-config-mine
作为团队的 ESLint 配置包:
module.exports = {
"extends": "eslint:recommended",
"rules": {
// enable additional rules
"indent": ["error", 4],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
// override default options for rules from base configurations
"comma-dangle": ["error", "always"],
"no-cond-assign": ["error", "always"],
// disable rules from base configurations
"no-console": "off",
}
}
团队中的开发者只需要安装 eslint-config-mine
依赖,并在 .eslintrc
中引入即可:
{
"extends": "mine"
}
只不过这么多规则,每个都需要经过讨论后制定是不是太麻烦了?有没有现成的配置包提供使用?你会发现很多大公司都发布了自己的标准,其中 Airbnb 做的是最好的,可以直接安装 eslint-config-airbnb
使用:
{
"extends": "airbnb"
}
Prettier
似乎上面提到的 ESLint 可以同时解决代码格式和代码质量,Prettier 没有使用的必要了?但其实 ESLint 主要解决的是代码质量的问题,代码格式这部分 ESLint 并没有全部做完。Prettier 就是接管了两类问题中的代码格式,并进行自动修复。
在项目中执行 npm install -D prettier
安装 prettier 依赖,目前的 index.js 如下:
const data = 'semi'
console.log(data)
执行 npx prettier index.js --write
会发现文件被格式化为:
const data = "semi";
console.log(data);
当然,如果你想要使用单引号,新建一个 .prettierrc.js
配置如下即可:
module.exports = {
singleQuote: true
};
你会发现格式化后的代码使用的是单引号。
ESlint + Prettier 搭配使用
通过上面了解你知道了 ESLint、Prettier 都有格式化的功能,ESLint 校验包含了代码格式和代码质量,不过主要针对 代码质量 而Prettier只针对 代码格式。对于代码校验来说,代码格式是需要进行一键修复的,而代码质量一般都需要手动修复(函数返回值未指定类型、如果 any)。
而代码格式既然 ESLint、Prettier 都可以进行修复,那必然会出现冲突的情况,所以最好的处理方式是 Prettier 只负责代码格式的检测并修复,ESLint 只负责代码质量的检测,各自负责自己专项的领域就好了。
配置
eslint-config-prettier 是 ESLint 的一个配置包,其作用就是关闭 Prettier 已经负责处理的代码格式相关的规则,首先安装依赖:
npm i -D prettier eslint-config-prettier
然后在 eslintrc
中引入这个配置包:
{
"extends": [
"prettier",
"some-other-config-you-use"
]
}
这样 ESLint 就关闭了 max-len
、quotes
、curly
等规则,代码格式相关修复全部交给 prettier,代码检查都交给 ESLint:
// 先进行 prettier 格式化
npx prettier src --write
// 再进行 eslint 检查
npx eslint src
基于 husky、Lint-staged 进行代码检查
事实证明,开发过程中很少有人手动执行 ESLint、Prettier 去代码检查,所以最好是可以在 Git 提交代码前统一进行强制的代码检查,通过 git commit
前的代码检查就能避免 💩 偷偷溜进代码仓库,此时就需要 husky、Lint-staged 结合使用。
husky
Git hooks 指定了 Git 操作的钩子,通过监听这些钩子就可以在指定的时机执行相应的脚本,比如在 commit
、push
触发前或触发后执行相应的脚本,这样就可以对项目开发做到约束,常见的脚本有以下几种:
- 通过 ESLint 对代码质量进行检查
- 通过 Prettier 对代码格式问题进行自动化修复
- 对
Commit Message
做规范化校验 - 对项目代码进行自动化测试
值得注意的是,在一个 Git 仓库中 .git/hooks
下已经自动生成了一些钩子脚本并提供定制,不过直接去修改这些脚本是十分不友好的,所以 husky 提供了简单上手的 Git hooks 配置,这里我们使用推荐的 husky@7
,与 husky@4
差异可查看: Migrate from v4 to v7
首先,安装依赖:
npm i -D husky
然后启动 Git hooks:
npx husky install
为了在安装后自动启动 Git hooks,package.json
需要添加 prepare 脚本:
{
"scripts": {
"prepare": "husky install"
}
}
做完以上工作,就可以使用 husky 创建一个 hook 了:
npx husky add .husky/pre-commit "npm test"
打开 ./husky
就可以看到已经生成了 pre-commit
脚本,此脚本会在 git commit
前执行 npm test
:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test
Lint-staged
此时我们已经实现了监听 Git hooks,接下来我们需要在 pre-commit
这个 hook 使用 Lint-staged 对代码进行 prettier 的自动化修复和 ESLint 的检查,如果发现不符合代码规范的文件则直接退出 commit。
并且 Lint-staged 只会对 Git 暂存区(git add 的代码)内的代码进行检查而不是全量代码,且会自动将 prettier 格式化后的代码添加到此次 commit 中。
首先安装依赖:
npm i -D lint-staged
然后在 package.json
中配置:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint"],
"*.{css,less,scss}": "prettier --write",
}
}
上面的配置是指对 .js
、.jsx
、.ts
、.tsx
文件进行 prettier 格式化,然后执行 eslint。对样式文件只需要 prettier 就足够了。
别忘了在 .husky/pre-commit
中添加 lint-staged
的调用:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
总结
看完此篇文章后你应该对项目的整体代码规范和约束有了一定的初步理解,并对一下几个方面能够实践:
- 了解到 ESLint、Prettier 的简单技术原理并能够上手进行 ESLint、prettier 的配置,并可以将团队讨论的统一规范集合成配置包使用。
- 理解 ESLint、prettier 两者的差异点并将两者搭配使用。
- 初步了解 Git hooks 的作用,并能使用 husky 监听执行的 hook。
- 在执行的 Git hook 中对代码进行 prettier、ESLint 检查。