Linter & Formatter 配置指南

3,194 阅读27分钟

我正在参加「掘金·启航计划」

这是一篇长文,提供项目规范方面的配置指南。

概述

简介

团队开发时,为了维护代码质量和可读性,可以使用两类工具:linter & formatter。

  • linter:代码检查工具,对代码进行静态分析,发现代码中的潜在错误
  • formatter: 格式化工具,用于统一编码风格

linter 也可用于检查代码格式,一定程度上可以替代 formatter,但配置起来可能没有 formatter 那么方便。

常见的 linter 工具如下:

常见的 formatter 工具如下:

  • Prettier:用于格式化文件,支持多种格式的文本文件

集成方式

当我们在项目中使用时,一般会通过如下方式进行集成:

  • scripts 脚本:在 package.json 文件中新建脚本,手动执行
  • 编辑器集成:安装相关的编辑器插件,获得最好的代码检查体验
    • 在编写代码时自动提示代码中存在的问题
    • 保存时自动格式化和修复错误
    • 更改配置文件时自动提示配置项
  • 打包工具集成:安装相关的打包工具插件,在编译和构建时执行代码检查,确保所有人在开发时都能进行代码检查
  • git 集成:利用 git hook,在提交代码时自动执行代码检查命令,确保团队成员提交的代码符合规范

配置方式

这些工具拥有类似的配置方式:

工具配置文件配置字段忽略文件
ESLint.eslintrc.*eslintConfig.eslintignore
stylelint.stylelintrc.*stylelint.stylelintignore
prettier.prettierrc.*prettier.prettierignore
commitlint.commitlintrc.*commitlint不支持

配置文件的*号需要换成后缀名。

配置文件支持以下格式:

  • json 格式的后缀名为 json,优点是支持配置项提示(需进行编辑器集成)
  • js 格式的后缀名为 js,优点是灵活,比如在不同环境下使用不同的配置
  • yaml 格式的后缀名为 yml,优点是可读性好

部分工具支持其他的配置文件名和配置文件格式,具体可参看官网文档。

配置字段指的是在 package.json 文件中使用特定字段来进行配置,这样就不需要使用单独的配置文件了。

忽略文件用于忽略特定的文件路径,书写规则同 gitignore 语法规则

Prettier

集成 Prettier

Prettier 作为格式化工具,比较方便的使用方式是与编辑器集成,让编辑器通过 Prettier 的规则进行代码格式化。集成或配置的方式参考 Prettier 官网。

配置 Prettier

Prettier 有一组可配置的规则,配置方式是在配置对象的最外层直接指定规则选项和选项值,比如:

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}

所有可用的规则可参考:www.prettier.cn/docs/option…,表格中的 API Override 就是规则在配置对象中的键名。

每个规则都有默认值,如果不进行配置,仍会使用规则的默认值进行格式化。

prettier 命令

也可以使用命令行来格式化代码。首先在项目中安装,以确保有 prettier 命令。

npm i -D prettier

使用以下命令进行代码格式化:

prettier --write .

命令中的点号代表当前目录。以上命令会格式化所有支持的文件,并将格式化的结果回写到文件中。如果只想知道是否所有文件都已格式化,可以使用如下命令:

prettier --check .

可以将命令放到 package.jsonscripts 脚本中,以便手动运行:

{
  "scripts": {
    "prettier": "prettier --write ."
  }
}

这样就能通过 npm run prettier 来执行格式化操作了。

ESLint

配置 ESLint

rules 规则

和 Prettier 不同,你必须在 rules 字段声明要使用的规则,才能让 eslint 进行代码检查。属性名为要配置的规则,值可以是这几个:

  • 字符串 "off" 或数字 0 代表不使用规则
  • 字符串 "warn" 或数字 1 代表违反时抛出警告
  • 字符串 "error" 或数字 2 代表违反时抛出错误

抛出的警告会在控制台打印,而抛出错误会导致程序或编译进程终止。

{
  "rules": {
    "semi": "error",
    "quotes": "error"
  }
}

ESLint 内置了一套规则,可参看:eslint.cn/docs/rules/

有些规则有额外的选项,要配置这些选项,可以使用数组传入。

{
  "rules": {
    "semi": ["error", "never"],
    "quotes": ["error", "single", { "avoidEscape": true }]
  }
}

如果只是启用规则,而不配置选项,则会使用选项的默认值。

extends 继承

一个一个配置规则非常麻烦,所以可以通过 extends 字段来继承其他的配置。extends 字段的值主要有以下几种字符串:

  • 其他配置文件的路径,以 ./ 开头
  • 共享配置名,即省略前缀的 npm 包名,比如 standard 代表 eslint-config-standard 这个包,这种包以 eslint-config- 开头,导出一个 ESLint 配置对象
  • ESLint 内置规则集,以 eslint: 开头,比如 eslint:recommended 代表启用所有推荐的内置规则
{
  "extends": "eslint:recommended"
}

如果要继承多个配置,可以使用数组。

{
  "extends": ["eslint:recommended", "standard", "./default.js"]
}

plugins 插件

ESLint 内置的规则有时候会不够用,插件可以额外的规则。ESLint 插件是以 eslint-plugin- 开头的 npm 包,主要作用就是提供自定义规则。

要使用插件提供的规则,首先在 plugins 字段中添加插件名,即省略前缀的包名,然后在 rules 中以 插件名/规则名 的形式配置。

{
  "plugins": ["plugin1"],
  "rules": {
    "plugin1/rule1": "error"
  }
}

插件除了可以提供规则,还可以提供配置。以 eslint-config- 开头的包可以提供一个配置,但一个插件可以提供多个配置。要使用插件提供的配置,在 extends 字段中以 plugin:插件名/配置名 的形式引入。

{
  "extends": ["eslint:recommended", "plugin:vue/recommended"]
}

使用插件提供的配置时,可以不用在 plugins 字段声明插件。

overrides 覆盖

overrides 字段可以为特定文件使用不一样的配置。

{
  "overrides": [
    {
      "files": ["*-test.js", "*.spec.js"],
      "rules": {
        "no-unused-expressions": "off"
      }
    }
  ]
}

envglobals 全局变量

有时你需要告诉 ESLint 当前的运行环境,以免在使用相关的全局变量时,ESLint 认为你使用了未定义的变量。ESLint 内置了很多的环境,插件也可以提供额外的运行环境。使用 env 字段来声明代码的运行环境,属性名代表环境名,值固定为 true

{
  "plugins": ["example"],
  "env": {
    "browser": true,
    "node": true,
    "example/custom": true
  }
}

你也可以直接在 globals 字段中声明全局变量。

{
  "globals": {
    "var1": "writable",
    "var2": "readonly",
    "Promise": "off"
  }
}

globals 中的值有以下几种:

  • writable 代表此全局变量可写
  • readonly 代表此全局变量只读
  • off 代表禁用此全局变量

root 停止搜索

当 ESLint 检测某个文件时,会从文件所在目录开始,一直往上级目录搜索配置文件,然后将所有找到的配置合并。将 root 字段设置为 true 可以使 ESLint 停止往上查找。所以,项目根目录中的配置最好加上这个字段。

{
  "root": true
}

parserparserOptions 解析器

ESLint 的核心是规则,而规则本质上是一个函数。ESLint 将 js 代码解析为 AST(抽象语法树),而规则函数通过分析 AST 发现代码中的问题,并可能给出修复的方法。

ESLint 将代码解析为 AST 的工具叫做 espree,但有时遇到其他格式的语法,或者过于超前的 ES 版本的语法,可能会导致解析失败,这时就需要使用其他的解析器。比如解析高版本的 ES 语法可以使用 @babel/eslint-parser,解析 vue 文件 <template> 部分的语法需要使用 vue-eslint-parser

parser 字段用来配置要使用的解析器,parserOptions 字段配置解析器的选项。

eslint 命令

eslint 命令的使用方式为:

eslint --fix [file/dir/glob]

--fix 参数表示自动修复可以修复的错误并回写到文件中,如果不加这个参数,则不会自动修复错误。

可以把这个命令添加到 npm 脚本中:

{
  "scripts": {
    "lint": "eslint --fix src/**/*.{vue,js}"
  }
}

然后只需要执行 npm run lint 即可。

ESLint 配置实例

如果你使用 Vue CLI,则可以在初始化时选择一种 ESLint 的配置方案。如果你需要手动配置 ESLint,以下将给出一些参考。

ESLint 集成 Prettier

ESLint 的规则分为两类,一类用于检测代码质量,另一类用于检测代码风格。所以 ESLint 和 Prettier 都可以用来约束代码风格。

使用 ESLint 来约束代码风格,相比 Prettier 更加灵活,体现在以下方面:

  • Prettier 配置选项比较少,很多风格是定死的,而 ESLint 几乎可以完全定制任何风格
  • ESLint 的规则一般比 Prettier 中的对应规则拥有更多的选项,可配置项更高

但是,Prettier 作为专业的格式化工具,相比 ESLint 具有以下优势:

  • 配置方便,Prettier 几乎不用配置就能直接使用,而 ESLint 需要将相关规则一条一条打开
  • 约束性更强,同样的代码不管格式化之前是什么样子,用 Prettier 格式化之后基本上都一个样,但用 ESLint 修正的代码风格可能并不会约束得很严格
  • 支持的文件格式更多,ESLint 几乎只能用于 js 代码,而 Pretter 可以用于各种格式的代码,还能通过插件支持更多格式

所以,有时候你可能会想要用 Prettier 来约束代码风格,而 ESLint 仅仅用于检测代码质量。Prettier 官网给出了几个 npm 包用于完成这件事,它们的详细用法参考:www.prettier.cn/docs/integr…

  • eslint-config-prettier 这个 ESLint 配置关闭了和 Prettier 重复的规则
  • eslint-plugin-prettier 这个 ESLint 插件将 prettier 当成了 eslint 的一条规则,想要先执行 eslint 后执行 prettier 可以使用它
  • prettier-eslint 将 prettier 格式化后的代码交给 eslint,想要先执行 prettier 后执行 eslint 可以使用它

后面两个工具相当于把两条命令合成了一条,我们也可以不使用它们,手动执行两条命令。

假设想要先执行 prettier,再执行 eslint,只需执行如下命令:

prettier --write src & eslint --fix "src/**/*.{vue,js}"

命令中的 & 代表前面的命令执行成功时,才执行后面的命令。

如果想先执行 eslint,然后再用 prettier 来格式化,会遇到一个问题,就是 prettier 格式化之后的代码可能会在 ESlint 中引起报错。所以我们需要使用 eslint-config-prettier 来禁用 ESLint 中和 Prettier 冲突的规则。

{
  "extends": ["eslint:recommended", "prettier"]
}

因为 prettier 只负责代码风格的部分,所以我们用 eslint:recommended 这个内置的配置来启用推荐的规则,再使用 eslint-config-prettier 的配置来关闭其中和 prettier 冲突的规则。此时需要注意的是:

  • pretter 配置必须放在 extends 的最后面,以保证最后会将冲突的规则关闭
  • 不能在 rules 字段重新开启那些和 prettier 冲突的规则,否则还是会冲突

接着就可以执行如下命令:

eslint --fix "src/**/*.{vue,js}" & prettier --write src

standard 规范

一个比较著名的编码规范是 standard 规范,它的详细介绍可以看这里:standardjs.com/readme-zhcn…

这套规范规定了一套代码风格,同时检查代码中的问题。standard 规范认为争论代码风格是没有意义的,因此制定了一套"标准"的代码风格,并且不推荐修改,比如:使用两个空格缩紧,字符串使用单引号,不使用分号。

如果要在 eslint 中使用 standard 规范,可以安装 eslint-config-standard,然后在配置中继承它。

{
  "extends": "standard"
}

airbnb 规范

另一套著名的规范是 Airbnb 规范,它的具体规则和使用每个规则的原因可以参看这里:github.com/lin-123/jav…

Airbnb 规范的特点是非常的严格,比如:

  • 不应该使用一元自增 ++ 或自减 -- 运算符,因为自动添加分号时可能会出现意料之外的结果
  • 不应该使用 for-infor-of,而应该使用数组方法进行遍历,因为处理起来更加容易
  • 使用分号,以避免执行时在错误的地方自动添加分号
  • 在数组字面量和对象字面量的最后一项后面加逗号,这样在版本对比时更加清晰

引入 airbnb 比引入 standard 复杂得多。首先 eslint-config-airbnb 是为 react 设计的,所以不能使用这个,而应该使用 eslint-config-airbnb-base

npm i -D eslint-config-airbnb-base

这个包有一个 peerDependency 是 eslint-plugin-import,你可以手动安装它,也可以使用 install-peerdeps 来安装。

npx install-peerdeps --dev eslint-config-airbnb-base

Airbnb 会使用这个依赖来检查 import 语句,确保导入的文件存在。但它不能识别路径别名,比如使用 @ 来代替 src 时是会报错的。这时可以使用额外的解析器来解析别名路径,比如 eslint-import-resolver-alias

{
  "extends": "airbnb-base",
  "settings": {
    "import/resolver": {
      "alias": [
        ["@", "./src"],
        ["util", "./src/util"]
      ]
    }
  }
}

eslint-plugin-vue

在 vue 项目中,一般使用 eslint-plugin-vue 来检查 vue 代码,这个插件提供了许多规则来检查代码是否符合 vue 官网的风格指南。你也可以用这些规则来定制 vue 文件 <template> 部分的代码风格。

这个插件还提供了一些配置,来对应风格指南中的特定优先级的规则,比如要启用 优先级C:推荐 的规则,可以使用配置 plugin:vue/recommended

{
  "extends": ["plugin:vue/recommended"],
  "rules": {
    "vue/require-default-prop": "off",
    "vue/max-attributes-per-line": [
      "error",
      {
        "singleline": {
          "max": 3,
          "allowFirstLine": true
        },
        "multiline": {
          "max": 1,
          "allowFirstLine": true
        }
      }
    ]
  }
}

三个实用的配置文件

.editorconfig 统一编辑器配置

虽然可以使用 ESLint 和 Prettier 来约束代码风格,但这只在执行校验时生效,在编辑过程中,编辑器可能并不遵守你设置的规则。比如虽然在 ESLint 中设置了 2 个空格缩进,但由于编辑器的设置是 4 个空格缩进,所以按回车时仍会缩进 4 个空格。

EditorConfig可以用来覆盖编辑器的默认设置,为文件设置缩进方式和编码方式等。只需为编辑器安装这个插件,然后在项目根目录添加配置文件 .editorconfig 即可。

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
charset = utf-8

.gitattributes 处理换行符转换

不同的操作系统默认的换行符是不一样的:

  • Windows 系统的默认换行符是 CRLF (即 \r\n)
  • Linux 系统的默认换行符是 LF (即 \n)
  • MacOS 系统的默认换行符是 LF 或者 CR (即 \r)

如果团队成员使用不同的操作系统来编写代码,可能会导致问题,比如:

  • 文件中混用了不同形式的换行符
  • 格式化之后把每一行的换行符都替换了,导致版本对比时不清晰

为了应对这种情况,git 在代码检出和提交时可能会进行换行符转换。比如 windows 上安装 git 时,有一页是让用户选择换行符的转换方式,默认的转换方式是:

  • 代码检出时将换行符换成 CRLF
  • 代码提交时将换行符换成 LF

然而这可能会导致问题,比如 ESLint 中校验了换行符必须为 LF 时,就会产生警告或错误。可以使用命令来改变默认设置,禁用这种自动转换行为,但你无法为每一个项目成员都设置一遍。这时可以使用 gitattributes来改变 git 的行为,只需要在项目根目录下创建文件 .gitattributes

* text=auto eol=lf

* 表示该行配置针对所有文件,text=auto 表示在换行符转换时自动判断哪些文件是文本文件,eolend of line 的缩写,eol=lf 表示代码检出时将换行符转换为 LF。你也可以这样配置:

* -text

这表示在换行符转换时,所有文件都不当成文本文件,那么自然就不会对任何文件进行换行符转换了。

jsconfig.json 让 VSCode 识别路径别名

当使用 import 引入模块时,编辑器可能会在你输入路径时自动补全,点击路径或引入的内容时可以跳转过去。但如果使用了路径别名,编辑器可能就识别不了了。如果你使用的是 VSCode,可以通过在项目根目录创建 jsconfig.json 来让编辑器识别路径别名,这个文件和 TypeScript 的配置文件 tsconfig.json 是一样的。如果你使用的是 typescript 项目,可以直接用 tsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

这样就能使 VSCode 识别路径别名了。这个文件还有许多其他的用法,比如 Vite 使用它来引入类型定义文件,从而使编辑器拥有更智能的代码提示。关于这个文件的更多介绍可以看这里:www.tslang.cn/docs/handbo…

stylelint

ESLint 只能检测 js 代码,而 stylelint 是用来检测 css 代码的。

配置 stylelint

stylelint 和 ESLint 一样,默认不进行任何校验,你需要在 rules 字段中指定要校验的规则。

{
  "rules": {
    "block-no-empty": null,
    "indentation": 2,
    "max-empty-lines": 2,
    "unit-allowed-list": [
      ["px", "em"],
      {
        "ignoreProperties": {
          "rem": ["line-height", "/^border/"],
          "%": ["width"]
        }
      }
    ]
  }
}

内置的规则名是很有讲究的,可以参考官网。规则的值有以下 3 种:

  • 设置为 null 代表关闭规则
  • 直接设置为规则的选项值
  • 有些规则有额外的选项,可以使用数组的第 2 项传入

数组第二项的选项一定是一个对象,并且可以通过这个对象传入一些共有的选项:

  • message 违反规则时的提示信息
  • severity 错误级别,可选值为 warningerror,默认是 error
{
  "indentation": [
    2,
    {
      "except": ["block"],
      "message": "Please use 2 spaces for indentation.",
      "severity": "warning"
    }
  ]
}

stylelint 同样支持使用 extends 字段来继承配置,但是对共享配置的包名并没有规定,所以并不能像 ESLint 那样省略前缀。stylelint 也没有内置可继承的配置,比较著名的配置是 stylelint-config-standard,它启用了一些错误检查和风格相关的规则。

{
  "extends": ["stylelint-config-standard", "./my-stylelint-config"]
}

另一个比较著名的配置是 stylelint-config-primer,这个配置对 css 代码的限制更加严格。

styelint 也同样支持插件,插件可以提供规则和配置,一个比较著名的插件是 stylelint-order,它提供了规则来规定 css 中各种属性的顺序。

{
  "plugins": ["stylelint-order"],
  "rules": {
    "order/properties-order": ["width", "height"]
  }
}

更多的 stylelint 共享配置和插件可以查找 awesome-stylelint

stylelint 命令

stylelint 命令和 eslint 命令类似,直接接上要检查的文件或者路径即可,使用 --fix 参数可以修复那些支持自动修复的错误。

{
  "scripts": {
    "lint:style": "stylelint --fix src/**/*.{vue,css}"
  }
}

stylelint 注意事项

stylelint 不仅支持原生的 css 语法,也支持一些 css 预处理器的语法,比如 scss 和 less。但是,如果你使用 PostCSS 来定制了 css 的语法,可能就识别不了了。

还有一些语法本身不被 stylelint 支持,但可以通过插件来支持。比如 stylus 语法可以使用插件 stylelint-plugin-stylus 来支持。

stylelint 同样可以和 Prettier 集成,方法和 ESLint 类似。

browserslist

很多时候,我们需要考虑浏览器兼容性问题,browserslist 是一个 npm 包,用来设置项目支持的浏览器列表。它本身没有太多的作用,你甚至不需要在项目依赖中安装它,但它可以和其他工具配合使用,比如可以结合 babel 插件来决定 babel 编译的结果代码。

官方文档中列举了许多可以和它配合使用的工具,有些可以根据目标浏览器来编译代码或引入 Polyfill,有些可以检测和目标浏览器不兼容的代码。

接下来介绍如何用它和 ESLint 以及 stylelint 配合,来避免写出和目标浏览器不兼容的代码。

配置 browserslist

首先需要配置需要兼容的目标浏览器,方法是在项目根目录下创建配置文件 .browserslistrc,在文件中一行一行列出想要兼容的浏览器。支持的配置方式有许多种,比如:

  • >5% in CN 在中国的使用比例大于 5%
  • last 2 versions 每种浏览器最新的 2 个版本
  • supports es6-module 支持特定的语法特性的浏览器
  • not IE <= 10 排除部分浏览器版本
  • not dead 排除"已死"的版本,即官方两年以上没有更新和维护的版本
  • defaults 默认规则,即 > 0.5%, last 2 versions, Firefox ESR, not dead

可以使用 and 来连接多个条件,表示同时满足多个条件。所有支持的写法可以查看官方文档。另外,VSCode 上可以安装插件来支持这个文件的语法高亮。

最后,可以通过 npx browserslist 命令查看最终要兼容的浏览器列表。

eslint-plugin-compat

使用 eslint-plugin-compat 来检查编写的 js 代码是否被目标浏览器支持。这个插件还支持指定引入的 Polyfills,这样在检测到这些 API 时就不会报错了。

{
  "extends": ["plugin:compat/recommended"],
  "env": {
    "browser": true
  },
  "settings": {
    "polyfills": [
      "Promise",
      "WebAssembly.compile",
      "fetch",
      "Array.prototype.push"
    ]
  }
}

其实,这个插件的意义可能并不太大,因为项目中一般会使用 babel 进行语法转换,所以即使写出一些不被目标浏览器兼容的语法或 API,也不会出现兼容性问题。

stylelint-no-unsupported-browser-features

真正容易出现兼容性问题的是 css 的部分,所以可以通过 stylelint-no-unsupported-browser-features 这个 stylelint 插件来检测 css 代码中的兼容性问题。

{
  "plugins": ["stylelint-no-unsupported-browser-features"],
  "rules": {
    "plugin/no-unsupported-browser-features": [
      true,
      {
        "severity": "warning"
      }
    ]
  }
}

当出现存在兼容性问题的代码时,会提示你哪个特性在哪些浏览器上有兼容性问题。这时你可以使用它给出的特性名称去 CanIUse 上查询,看看具体会有什么兼容问题。

git 集成

执行代码检测的时机有三个:

  • 编辑器保存代码时检测,可以自动修复代码中的问题
  • 打包工具编译代码时检测,可以提示代码中发现的问题
  • 使用 git 提交代码时检测,可以确保提交的代码符合规范

接下来讲讲如何将检测工具和 git 集成,在 git 提交时自动执行代码检测。

husky

git 提供了一些钩子来在各个阶段执行特定代码,这些钩子叫做 git hook。我们可以使用 husky 这个 npm 包来帮我们管理 git hook。

官方推荐的安装方式是使用以下命令:

npx husky-init && npm install

这条命令会配置 git,指定 git hook 脚本所在目录为 .husky,并且默认安装了一个 hook 叫做 pre-commit,对应的脚本文件是 .husky/pre-commit,里面会执行一条 npm test 命令。

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test

这个 hook 正好指的是在 git 提交前执行,也就是说在 git 提交代码的时候会自动执行 npm test 命令。所以我们直接把它替换成我们想要的命令即可。

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx eslint --fix "src/**/*.{vue,js}"
npx stylelint --fix "src/**/*.{vue,css}"

你也可以使用 husky add 命令来添加其他的钩子脚本。

lint-staged

虽然利用 git hook 可以在提交时自动执行 eslint 和 stylelint 命令,但是却检测了 src 文件夹下所有的文件。但其实并不应该检测所有的代码,而应该只检测我们要提交的代码。所以我们可以借助一个叫做 lint-staged 的工具,这也是一个 npm 包。

从名字可以看出,这个工具用于检查 git 暂存区的代码,也就是准备提交的代码。你可以使用官方推荐的安装方式:

npx mrm lint-staged

这个命令会帮你安装并配置 husky 和 lint-staged,并根据你安装的代码检测工具自动配置,在提交时运行它们。确切的说,它会使用 husky 创建 pre-commit 钩子,在提交代码前运行 lint-staged

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

lint-staged 会根据 package.json 中的 lint-staged 字段的配置执行相关的 lint 工具。你可以查看 package.json 中的配置,如果不满意的话,可以手动更改配置。

{
  "lint-staged": {
    "*.{vue,js}": "eslint --fix",
    "*.{vue,css}": "stylelint --fix"
  }
}

属性名代表要检查的文件,但只会对 git 暂存区的文件生效。属性值代表要执行的命令,要检查的文件路径将会被拼接在命令的最后。如果要执行多条命令,使用数组即可。

{
  "lint-staged": {
    "*.{vue,js}": ["prettier --write", "eslint --fix"]
  }
}

提交日志

好的 git 提交信息如同好的代码注释一样赏心悦目,让人快速了解这次提交改动了什么。但好的提交信息如同好的变量命名一样让人纠结,写起来颇为费劲。在我看来,好的提交需要满足以下条件:

  • 一次提交只做一件事,这会让你更容易描述这次提交做了什么,因此更容易写出好的提交信息
  • 提交信息具有一定的格式,这样看起来会更加清晰

比如,如果有几个不相关的 bug 需要修复,即使都是很小的 bug,也应该分成多次提交。如果改动代码前需要格式化整个文件,那么也应该分成两次提交,这样可以在对比代码时看清代码的改动。

虽然分成多次提交会降低你的效率,认真写提交信息会消耗你的脑力,但查看提交日志时却会非常清晰,更容易看清代码改动的历史,这在我看来是值得的。

提交规范 Conventional Commits

一个比较规范的提交方式叫做约定式提交(Conventional Commits),它的介绍文档在这里:www.conventionalcommits.org/zh-hans/v1.…

约定式提交的提交信息格式如下:

<type>(<scope>): <description>
// 空一行
<body>
// 空一行
<footer>

只有类型和描述是必需的,所以一个最简单的示例为:feat: 添加中文语言支持

常见的**类型(type)**如下:

  • feat 添加功能,引入新特性
  • fix 错误修复,修复 bug
  • docs 文档变更,添加或者更新文档
  • style 格式调整,不会影响代码含义的更改(空格,格式,缺少分号等)
  • refactor 代码重构,既不是修复 bug 也不是添加特性的代码更改
  • perf 性能优化,更改代码以提高性能
  • test 更新测试,添加或者更新测试
  • build 依赖调整,影响构建系统或外部依赖项的更改(示例作用域:gulp, broccoli, npm)
  • ci 脚本变更,对 CI 配置文件和脚本的更改(示例作用域:Travis, Circle, BrowserStack, SauceLabs)
  • chore 杂务处理,其他不会修改源文件或测试文件的更改
  • revert 恢复版本,恢复到上一个版本

featfix 分别对应语义化版本中的 MINORPATCH,它们会出现在自动生成的更新日志中。其他的类型不是必需的,你可以配置在项目中允许使用哪些类型。

**作用域(scope)**代表影响的范围,比如改动了哪一个功能模块。

**描述(description)**就是对本次提交内容的简短描述,结尾不要加句号。

**正文(body)**是对本次提交内容的详细描述,可以多行,可以有空行。

**脚注(footer)**用于两种情况:

  1. 不兼容变更(BREAKING CHANGE)

    代表这个版本的代码和上一个版本不兼容,对应语义化版本中的 MAJOR。一般指 npm 包的用法与先前版本产生了不一致,直接升级可能会导致错误。

    这种情况下的 footer 需要以 BREAKING CHANGE: 开头,后面接变动的描述、理由和迁移方法,可以有多行。

  2. 关闭 issue

    fix 类型的提交,如果有关闭 issue,可以在 footer 中记录关闭的 issue 编号,比如:closes issue #12, #15

revert 类型是一种特殊的类型,它的描述是目标版本提交信息的第一行,正文格式固定为:This reverts commit <hash>,其中 hash 是被撤销的版本的 SHA 标识符,可以用 git log 查看。

revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

提交检查 commitlimt

懒惰是程序员和人类的天性,总有人不愿意每次都编写符合规范的提交信息。所以,我们可以利用 husky 在提交时检查编写的提交信息,只有符合规范的提交才能成功。

检查提交信息的工具是 commitlint,它对应的 npm 包是 @commitlint/cli,提供了 commitlint 命令。它的配置方式和 ESLint 以及 stylelint 类似。

rules 字段配置要检测的规则,属性名是规则名,属性值是一个数组,包含 3 项:

  • 错误级别,数字 0 1 2 分别代表关闭规则、警告和错误
  • 字符串 always never 分别代表必须满足规则和必须不满足规则
  • 规则的具体配置值

如果使用 js 格式的配置文件,属性值可以是一个函数,返回一个数组或 Promise<Array>

extends 字段列出要继承的配置,官方提供了符合约定式提交的配置,我们可以安装并继承它:

{
  "extends": ["@commitlint/config-conventional"]
}

其实这样配置就已经足够了,但你也可以通过 rules 去更改其中一些规则,比如规定项目中可以使用的所有 scope。

然后用 husky 添加 git hook,由于需要拿到提交信息,所以使用的钩子是 commit-msg

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'

提交工具 commitizen

对于新手来说,手动编写符合约定式提交的提交信息可能有点困难,我们可以使用一些工具来帮助编写提交信息。

首先是一款 VSCode 插件,它的名字叫做 Conventional Commits,它可以引导你写提交信息的每一个部分。

但不是所有人都用 VSCode,所以再推荐一个命令行工具,是个 npm 包,叫做 commitizen。commitizen 是一个专门帮助生成提交信息的工具,安装后可以使用 git-cz 命令替代 git commit 命令,就会引导你编写提交信息了。如果是在项目中安装,而不是全局安装,可以把这条命令写在 npm scripts 中:

{
  "scripts": {
    "commit": "git-cz"
  }
}

以后提交时就执行 npm run commit 即可。

你还需要一个适配器来告诉这个工具提交信息的编写规则,commitlint 官网上有一些官方适配器,比如 @commitlint/cz-commitlint。你需要在 package.json 文件的 config 字段中指定适配器的路径:

{
  "scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "@commitlint/cz-commitlint"
    }
  }
}

接着再执行 npm run commit 提交时,commitizen 就会按照 commitlint 的配置文件,来引导编写符合规范的提交信息了。

日志生成 standard-version

当我们使用约定式提交时,可以根据提交记录生成更新日志文件。有许多工具可以做到这一点,其中一个比较傻瓜操作的是 standard-version。直接在 npm scripts 中配置脚本:

{
  "scripts": {
    "release": "npx standard-version"
  }
}

当准备好发布的时候,执行 npm run release 命令,将会根据 git 提交记录生成一个用于发布的版本:

  • 提取类型为 featfix 的提交记录,生成 CHANGELOG.md 文件
  • 根据提交记录中的提交类型更新 package.json 中的版本号
  • 提交一个新的版本并用新的版本号打上 git 标签