前端代码规范工具原理和最佳实践:eslint+prettier+gitHooks

1,593 阅读11分钟

前端代码规范工具主要是从三个方面提升代码质量(分别对应标题三种工具):

  1. 提高代码质量,捕获潜在的bug,比如使用未使用的变量声明(no-unused-vars)、避免精度丢失(no-loss-of-precision);
  2. 统一代码格式,避免百花齐放,比如每行代码最大长度(max-len)、关键字前后空格数(keyword-spacing);
  3. 阻止不规范代码进入代码库,规范提交信息。

本文会从依据各工具官方文档等分别对其原理和使用进行解读,并给出最佳实践,从而做到知其然知其所以然还有怎么用。

eslint

js是动态且类型宽松的语言,容易产生错误且需要在运行时发现,因此微软推出了另一个语言typescript来弥补js的天生不足(如果要进一步了解ts,可参考按照新的思路再学一遍typescript)。

而怎么能最小化解决这个问题呢?那就是使用linting工具,这里推荐的是eslint,eslint是An AST-based pattern checker for JavaScript,可以根据js代码生成的抽象语法树静态分析代码是否符合各种自定义的规则,而不需要执行便可以发现错误自动修复(可同时处理代码质量和代码格式)。

上面一句话基本概括了eslint的特点和工作原理,下面从以下几个方面做一下深入了解:

  • 使用方式
  • 配置
  • vscode集成

使用方式

eslint是一个npm包,要想使用需要先安装,然后通过执行命令行命令来对相关文件进行代码检查和修复,其中涉及到使用过程中的一些配置,可以直接在命令行上添加,也可以通过配置文件配置,这里介绍的使用原则是除了少部分需要在命令行添加的,其他配置参数都在配置文件配置。

下面介绍基本使用方式,Node.js版本要求:^10.12.0, or >=12.0.0。

  1. 安装(以yarn为例)
yarn add eslint --dev
  1. 初始化配置文件(如果全局安装可以直接使用npx,否则需要添加npx) eslint运行时会根据相关配置工作。
npx eslint --init

执行该命令后要根据选项自定义一些配置,比如

 How would you like to use ESLint? · style
√ What type of modules does your project use? · esm
√ Which framework does your project use? · none
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · browser, node
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard
√ What format do you want your config file to be in? · JSON

此时在项目根目录生成了一个.eslintrc.json的配置文件并下载了依赖的安装包。

  1. 检查并修复
    eslint使用语法为eslint [options] [file|dir|glob]*,比如
eslint file1.js file2.js
eslint lib/**

相关选项包括:

  • --fix 修复
  • -c, --config 指定另外的配置文件

配置

eslint被设计成完全可配置的,这意味着可以关掉所有规则只留下基本的语法验证,有两种主要的配置方法(不包括在命令行中配置):

  • Configuration Comments:直接在文件中使用注释作为配置
  • Configuration Files:使用 JavaScript, JSON or YAML文件作为整个目录乃至子目录的配置,整体结构可以参考schema 具体配置参考Configuring ESLint,这里对主要配置做一下介绍
  • parserOptions:解析的语言选项,包括ecma版本、代码类型(module或script)、ecma特性(比如jsx等)
  • parser:解析器,用来把js解析成AST,默认Esprima,可以使用ts或babel的解析器以兼容
  • processor:处理器,可以用来从其他类型文件中提取js进行处理
  • env:通过设置相应环境预设全局变量,比如browser或node等
  • globals:自定义的全局变量
  • plugins:插件是一系列eslint-plugin-为前缀的npm包,使用时可以不带前缀,插件可以自定义规则等,也可以提供配置方便复用
  • rules:具体规则,eslint默认的规则见Rules,可以对具体规则进行修改,可选值包括
    • "off" or 0 关闭规则
    • "warn" or 1 警告
    • "error" or 2 报错 多个参数时使用数组语法,如

配置文件

{
   "rules": {
       "eqeqeq": "off",
       "curly": "error",
       "quotes": ["error", "double"]
   }
}

注释语法/* eslint quotes: ["error", "double"], curly: 2 */
关闭规则

/* eslint-disable */

alert('foo');

/* eslint-enable */

为指定行指定规则

alert('foo'); // eslint-disable-line no-alert, quotes, semi
  • extends 扩展其他配置文件,包括eslint自带的(eslint:recommended, or eslint:all)或者其他shareable config,或者插件带的配置
{
    "plugins": [
        "react"
    ],
    "extends": [
        "eslint:recommended",//eslint自带配置
        "plugin:react/recommended"//插件带的配置
    ],
    "rules": {
       "react/no-set-state": "off"
    }
}
  • ignorePattern 指定忽略的文件,如
{
    "ignorePatterns": ["temp.js", "**/vendor/*.js"],
    "rules": {
        //...
    }
}

也可以使用.eslintignore文件,注意用forward slashes(/)作为分隔符

# Valid
/root/src/*.js

# Invalid
\root\src\*.js
  • overrides:为一部分文件重写规则
{
  "rules": {...},
  "overrides": [
    {
      "files": ["*-test.js","*.spec.js"],
      "rules": {
        "no-unused-expressions": "off"
      }
    }
  ]
}

vscode集成

eslint可以和各种编辑器,各种工具集成,这里只介绍vscode集成,下载eslint插件进行相关配置可以实现保存时检测

    "editor.codeActionsOnSave": {
        "source.fixAll": true
    }

prettier

prettier是一个用于代码格式化的插件,可对前端大部分文件代码按照一定规则进行美化。
它的处理方式是把除了empty linesmulti-line objects两个样式以外的所有格式清除转化成AST后根据配置的规则重新生成新格式的代码.
prettier专注于对代码格式进行处理,而对代码质量无能为力。
这里还是按三部分来讲:

  • 使用方式
  • 配置
  • vscode集成

使用方式

安装

yarn add --dev --exact prettier

创建一个配置文件让编辑器知道我们在用prettier

echo {}> .prettierrc.json

可以创建一个文件 .prettierignore,指示哪些文件不需要格式化

# Ignore artifacts:
build
coverage

使用prettier进行格式化

npx prettier --write .

把--write换成--check则只检查不进行处理

配置

,而且不同于eslint的细粒度可配置,perttier提供了少量的配置选项,虽然稍微缺乏灵活,但使用起来更加简单。
配置文件和eslint很相似,想查看结构可以参考schema,其他配置参数参考Configuration File

vscode集成

下载prettier插件可设置在保存时对文件进行格式化,但是后面我们会将eslint和prettier结合使用,运行eslint时会自动格式化,因此这里不多讨论。

结合使用eslint和prettier

前面对两个工具做了基本介绍,现在将两者做一下结合,结合之前我们先解决一个问题,就像前面说的,eslint既可以解决代码质量问题也能解决格式问题,为什么还要引入prettier?
这个问题的答案是eslint对于某些格式问题的修复无能为力,比如max-len(其实涉及到这个问题的基本都拿这一个规则举例子,比如Why You Should Use ESLint, Prettier & EditorConfig),需要用户手动修改才能解决。于是才有了Prettier vs. Linters说的eslint负责代码质量,prettier负责格式的分工。

将两者结合需要解决两个问题,一者快捷执行两个工具的命令,二者关闭eslint中关于格式的规则避免两个工具中规则冲突。这里引入eslint-plugin-prettiereslint-config-prettie两个辅助包,具体区别可参考What's the difference between prettier-eslint, eslint-plugin-prettier and eslint-config-prettier?

具体用法:
eslint-plugin-prettier是eslint的一个插件,会作为插件的一部分在执行eslint时自动执行prettier,假设已经按照前面两部分的介绍下载完了eslint和prettier。
安装

yarn add eslint-plugin-prettier  --dev

在eslint配置文件上添加

{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error"
  }
}

此时便解决了两个命令同时运行的问题。
继续下载eslint-config-prettie,它提供eslint配置文件,可以使eslint中关于格式的所有规则不可用。

yarn add eslint-config-prettier --dev

在eslint配置文件中的extends数组将plugin:prettier/recommended添加为最后一个

{
  "extends": ["plugin:prettier/recommended"]
}

这样解决了格式规则冲突的问题。

从此eslint和prettier便快乐的生活在一起。

githooks

git提供了很多在特定动作发生时触发的钩子,像具体了解gitHooks乃至git可参考 git原理和命令详解:当我们操作git时发生了什么
在这里我们引入gitHooks主要是为了解决两个问题

  • 阻止不规范代码进入代码库
  • 规范提交信息 分别对应pre-commit和commit-msg两个钩子,两者解决问题的原理分别是:
  • 在执行钩子pre-commit时,为了保证进入commit的都是规范的代码,有两个思路
    • 在这个钩子检查代码规范性,如果不规范,则返回1,取消提交
    • 在这个钩子fix规范,如果已修复且符合规范则执行git add将刚修复的代码加入index,继续执行commit,否则返回1,取消提交
  • 在执行钩子commit-msg时,对提交的信息进行检查,如果不符合标准则返回1,取消提交。

下面我们对钩子进行编辑

直接编辑钩子

虽然现在对钩子已经有了比较好的第三方封装,但是我们还是先用最原始的方式进行实现,git的钩子脚本在项目的.git/hooks/目录下,其中包含不同钩子脚本的模板,编辑其中内容(比如替换成下面的脚本),将对应模板的后缀.sample去除,便可在我们对项目进行提交时自动执行。

  • pre-commit 对暂存区的文件执行eslint,如果修改完毕则执行git add重新加入index,返回返回1失败
for file in $(git diff --cached --name-only | grep -E '\.(ts|tsx)$')
do
echo ":$file"
 node_modules/.bin/eslint "$file" --fix #只lint改动的部分
  if [ $? -ne 0 ]; then
    echo "ESLint failed on staged file '$file'. Please check your code and try again. You can run ESLint manually via npm run eslint."
    exit 1 # exit with failure status\
  fi
   git add "$file" # 把lint过的代码重新添加到index
  
done
  • commi-msg 以提交信息不能小于10为例
#!/bin/sh

MSG=`awk '{printf("%s",$0)}' $1`

if [ ${#MSG} -lt 10 ]  
  then
    echo "-------------------------------------------------------------------"
    echo "当前提交的 commit message 为: $MSG"
    echo "commit message 只有${#MSG}字符"
    echo "message的长度不能小于10, 本次提交失败,请完善commit message,再提交"
    echo "-------------------------------------------------------------------"
    exit 1
fi

这样便实现了我们想要的功能,但是缺点是这里的脚本不会同步到远程仓库进行复用。

使用第三方工具

这个功能已经有了更好的实现,我们接下来引入三个包

  • husky 根据配置修改对应hook文件,实现hook的复用
  • lint-staged 配和husky,对将要被commit的文件使用Prettier, ESLint/TSLint, or stylelint等进行格式化,并对格式化的文件重新加入index,继续commit
  • commitlint 配和husky,对提交信息进行检查

开始之前,如果我们已经手动改了对应hook文件,需要将其移除,否则将影响husky的使用。

下载三个包

yarn add husky lint-staged --dev
yarn add @commitlint/{cli,config-conventional} --dev

创建commitlint配置文件,指定提交信息根据配置config-conventional来验证,其符合Angular Commit Guidelines的要求。

echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js

修改package.json相关配置,参考如下

  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.js": "eslint --fix"
  }

其中husky字段是husky的配置,其中可以包含各种gitHooks,我们这里只使用其中两个:

当pre-commit时调用lint-staged,即lint-staged的字段,可以根据实际情况进行修改,此demo意为对所有js后缀的文件执行eslint --fix

当commit-msg发生时执行commitlint -E HUSKY_GIT_PARAMS,其中-E指提交信息所在的文件,默认为.git/COMMIT_EDITMSG,HUSKY_GIT_PARAMS是husky传给commitlint关于提交信息的参数。

总结

现在我们对代码规范的三个问题有了切实可用的解决方式,虽然我们常用的脚手架,比如vue-cli或者create-react等都内置了相关工具,但是只有从原理上明白它们起作用的原因,才能对其进行进一步调整,乃至自己搭脚手架。

本文对三个功能涉及到的工具做了梳理和解读,其中某些细节处理有待改进,后期会对本内容进行进一步优化更新。

参考