从零入手 ESLint + Prettier + husky + Lint-staged:一统前端代码规范

1,538 阅读11分钟

每个语句的末尾是否需要分号?使用两个空格还是四个空格?

关于代码格式的讨论数不胜数,而且没有绝对的谁对谁错,每个人有自己的代码偏好。如果是单人开发那没什么问题,但涉及到多人协同开发就不可避免地产生代码风格上的冲突:

同事 A 喜欢使用两个空格并把代码提交了上去,同事 B 喜欢使用四个空格并好心地将原先的双空格改了回去,如此反复地更改不可避免地会产生无用的 Git 提交记录(并且影响同事间的关系)

以上是关于代码格式,代码规范除了代码格式还有重要的一环:代码质量。使用未定义的变量?定义变量使用到了保留字?函数返回值是否需要强制类型?是否完全禁用 any 的使用?这些涉及到代码的内部逻辑都属于代码质量。

代码质量出问题意味着程序有潜在的 Bug,代码格式出问题最多也就是看着不爽。

所以需要指定编码规范,团队中的每个人都强制执行,并使用工具对代码进行检查,并提供格式化的功能。

本文讲解使用 ESLint + Prettier 规范你的团队编码规范,读完本文你将掌握了解以下知识:

  • 什么是 ESLint、Prettier?它们解决了什么问题?
  • ESLint 的配置、使用
  • ESLint 配置包
  • ESLint 和 Prettier 的差异,如何将两者搭配使用
  • 了解 Git hooks 并能使用指定的 hook 对提交代码进行检查

ESLint 基础使用

ESLint 是一个开源的 JavaScript 的代码检查工具,使用 espree 将代码解析为抽象语法书(AST),通过 AST 进行静态代码分析,并给予代码格式和代码质量两个方面的提示:

  1. 代码格式(Formatting rules):Tab 长度、函数最大行数、尾随分号等
  2. 代码质量(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 的执行结果: image.png

配置

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 为例,此插件提供了 recommendedall 等配置:eslint-plugin-react 的导出配置,此时继承此插件提供的 recommended 配置如下:

{
  "extends": [
    "plugin:react/recommended"
  ],
}

parser 和 parserOptions

parser 指的是将 .js.ts 文件处理为 AST 使用的解析器。ESLint 默认使用 espree 作为解析器。

除了 espree,还有以下于 ESLint 兼容的 parser:

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-lenquotescurly 等规则,代码格式相关修复全部交给 prettier,代码检查都交给 ESLint:

// 先进行 prettier 格式化
npx prettier src --write
// 再进行 eslint 检查
npx eslint src

基于 husky、Lint-staged 进行代码检查

事实证明,开发过程中很少有人手动执行 ESLint、Prettier 去代码检查,所以最好是可以在 Git 提交代码前统一进行强制的代码检查,通过 git commit 前的代码检查就能避免 💩 偷偷溜进代码仓库,此时就需要 huskyLint-staged 结合使用。

husky

Git hooks 指定了 Git 操作的钩子,通过监听这些钩子就可以在指定的时机执行相应的脚本,比如在 commitpush 触发前或触发后执行相应的脚本,这样就可以对项目开发做到约束,常见的脚本有以下几种:

  • 通过 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 检查。