unit | system |
---|---|
专为团队设计的 lint 工具
目录:
- ELint
在使用 ELint 之前,需要先了解几个核心概念。
elint 是一款代码校验工具,通过插件系统可以将 ESLint、Stylelint、commitlint、Prettier 等工具整合使用。Elint 本身不包含任何校验规则,而是通过 preset 定义各个 lint 工具的配置。elint 的主要功能包括:
- 支持对 js,css 的校验 (eslint, stylelint)。
- 支持对 git commit message 的校验 (husky, commitlint)。
- 支持对代码进行格式化 (prettier)。
- 编写定制化、场景化的 preset,preset 包含所有验证规则,保证团队内部校验规则的一致性和可复用。
ELint 3.0 后不再内部依赖 ESLint、Stylelint 等 lint 工具,对于这些工具的支持将由 elint 插件提供。
名称 | 依赖 | 描述 |
---|---|---|
@elint/plugin-commitlint |
"@commitlint/core": ">=16.0.0" |
commitlint 插件,支持校验 git commit message |
@elint/plugin-eslint |
"eslint": ">=8.0.0" |
eslint 插件 |
@elint/plugin-prettier |
"prettier": ">=2.0.0" |
prettier 插件,检查代码格式 |
@elint/plugin-stylelint |
"stylelint": ">=14.0.0" |
stylelint 插件 |
简单来说,preset 就是一个 npm package,可以理解为”规则集“。
只有 elint 是不够的,因为它不知道具体的校验规则,而 preset 就是用来定义规则的。preset 大概长这样:
elint-preset-<name>
├── .husky
├── configs # 存放真正规则文件的地方
├── node_modules # 定义 git hooks
├── .commitlintrc.js # 定义 commitlint 规则,用于 git commit message 的校验
├── .eslintignore # 定义 eslint 忽略规则
├── .eslintrc.js # 定义 eslint 规则,用于 js 的校验
├── index.js # preset 配置
├── package.json
├── package-lock.json
├── .prettierignore # 定义 prettier 忽略规则
├── .prettierrc.js # 定义 prettier 的规则
├── .stylelintignore # 定义 stylelint 忽略规则
└── .stylelintrc.js # 定义 stylelint 的规则,用于 css 的校验
要求:
- npm package name 需要以
elint-preset-
开头,如:elint-preset-<name>
或@team/elint-preset-<name>
。 - 入口文件需要默认导出 preset 配置
- preset 配置中需要配置启用的插件列表,并将插件和插件的依赖定义在
dependencies
中 - lint 工具配置的依赖(例如 eslint-plugin-react),必须明确定义在
package.json
的dependencies
中。
满足以上要求的 npm package 就是一个合法的 elint preset。
一般来说,不建议把
.eslintignore
,.stylelintignore
,.prettierignore
添加到 preset 中,因为 preset 应该对所有项目都是适用的(除非你的 preset 限定在一个很小的范围内使用,各个项目的目录结构都是一致的,此时可以使用它们来定义需要忽略校验的文件和文件夹)。
在
package.json
中添加关键字elint-preset
会方便大家找到。这里可以查找现有的 preset。
eslint, stylelint, commitlint, husky 等配置文件的语法规则,请阅读参考一节提供的文档。
如果仅为了测试或不方便发布 npm 包,可以参考 本地 preset 的方法。
开始使用 elint 之前,请先检查下你使用 node 和 npm 版本。运行 elint 需要:
- node:
^14.13.1 || >=16.0.0
,建议尽量使用新版本。
下面我们一起从零开始,一步一步的看看如何将 elint 集成到项目中。
首先要安装 elint:
# 假设项目是 test-project
cd test-project
# 安装 elint
npm install elint --save-dev
强烈不建议全局安装(具体原因,请阅读常见问题)。
安装完 elint 后,我们需要安装 preset,因为启用插件和校验规则(rules)都定义在 preset 中。一般来说,一个团队应该有一套编码规范,根据编码规范编写好 preset 后,团队中的各个项目直接安装使用即可,不必为每个项目都新建一个 preset。
在一个团队里,可能一个 preset 并不能适应全部场景,那么我们可以根据不同的场景定义多个 preset。例如
@team/elint-preset-h5
,@team/elint-preset-node
等。要覆盖所有项目,并不需要多少 preset。
这里我们假设团队还没有任何 preset,一起看下如何新建它:
上文已经讲过了,preset 本质上还是一个 npm package,所以新建 preset 的第一步就是新建 npm package:
# 假设叫 elint-preset-test
mkdir elint-preset-test
cd elint-preset-test
# 为了方便演示,全部使用默认值,实际项目中酌情修改
npm init -y
默认入口文件为 index.js
配置其内容为:
module.exports = {
plugins: [
// ...插件列表
]
}
这步是可选的,如果你不需要校验 js 文件,可以直接跳过(下同)
首先安装 @elint/plugin-eslint
和插件的依赖 eslint
:
npm i @elint/plugin-eslint eslint
之后在入口文件 index.js
中添加插件名称:
module.exports = {
plugins: ['@elint/plugin-eslint']
}
新建 eslint 配置文件,下文使用 .eslintrc.js
touch .eslintrc.js
.eslintrc.js
写入如下内容:
module.exports = {
rules: {
// 禁止 console
'no-console': 2
}
}
使用 eslint 插件或预设配置
由于不同包管理器依赖安装和解析方式不同,如果配置中需要继承预设配置,直接写插件名可能出现在运行时无法找到对应依赖的错误。
推荐将 eslint
的实际配置写在 preset 中并导出,并在复制到外层的 linter 配置文件中引入 preset 内的配置,可以参考 elint-preset-self
中 eslint
配置的处理方式。
由于 eslint
一直没有支持以继承的配置文件为根目录引入插件 Support having plugins as dependencies in shareable config,如果我们将 eslint
所需的插件依赖都放在 preset
内时,可能会出现找不到插件的报错提示。目前可以选择的一种方案是使用 @rushstack/eslint-patch,在对应的 eslintrc
文件顶部调用:
require('@rushstack/eslint-patch/modern-module-resolution')
为什么
elint
项目本身没有使用@rushstack/eslint-patch
却没有问题呢,因为pnpm
会默认将eslint
相关的依赖都自动提升,可以参考 public-hoist-pattern
首先安装 @elint/plugin-stylelint
和插件的依赖 stylelint
:
npm i @elint/plugin-stylelint stylelint
之后在入口文件 index.js
中添加插件名称:
module.exports = {
plugins: ['@elint/plugin-stylelint']
}
添加对应的 stylelint 配置
首先安装 @elint/plugin-prettier
和插件的依赖 prettier
:
npm i @elint/plugin-prettier prettier
之后在入口文件 index.js
中添加插件名称:
module.exports = {
plugins: ['@elint/plugin-prettier']
}
添加对应的 prettier 配置
首先安装 @elint/plugin-commitlint
和插件的依赖 @commitlint/core
:
npm i @elint/plugin-commitlint @commitlint/core
之后在入口文件 index.js
中添加插件名称:
module.exports = {
plugins: ['@elint/plugin-commitlint']
}
新建配置文件 .commitlintrc.js
:
touch .commitlintrc.js
.commitlintrc.js
写入如下内容:
module.exports = {
rules: {
// type 不能为空
'type-empty': [2, 'never'],
// type 必须是 build 或 ci
'type-enum': [2, 'always', ['build', 'ci']]
}
}
通过命令添加 commit 信息校验 hook
npx husky add .husky/commit-msg "npm run beforecommit"
更多使用方式可以参考
husky
官方文档 Create a hook,创建对应的 hooks
从 1.10.0 版本开始,elint 支持更新检测功能,提示用户更新到新版本的 preset。
要开启此功能,只需在 preset 的 package.json 文件中添加如下配置:
{
"elint": {
"updateCheckInterval": "3d"
}
}
上述配置会让 elint 每三天检测一次是否有新版本 preset。
如果 preset 中使用了非官方支持的插件,需要将其配置文件移动到项目目录,可以配置入口文件:
module.exports = {
plugins: [
/* ... */
],
configFiles: ['xxx.js']
}
这样在安装 preset 时就会将 xxx.js
移动到项目目录
elint 各个插件内部有一个默认的扩展名支持列表,但是这个列表不一定能满足各种场景(例如使用 stylelint
检测 JS 文件中的内联样式)。这种情况下可以配置 overridePluginConfig
,用以覆盖某个插件的具体配置
module.exports = {
plugins: ['@elint/plugin-stylelint'],
overridePluginConfig: {
'@elint/plugin-stylelint': {
activateConfig: {
extensions: ['.js']
}
}
}
}
发布 npm package,执行:
npm publish
如果使用
pnpm
来发布包,需要将husky
配置文件列表添加到publishConfig.executableFiles
列表中,详情参考 package.json | pnpm
如果希望更多人发现你的 preset,可以添加关键字
elint-preset
,点击此处可以查看现有可用的 preset。
刚刚我们已经编写并发布了 preset,现在安装到项目中就好了:
cd test-project
# 安装我们刚才新建的 elint-preset-test
npm install elint-preset-test --save-dev
preset 安装完成后,还有一个重要的步骤,就是将 preset 内的各种配置文件复制到项目目录里,这是为了兼容各种 IDE 和 build 工具(如 webpack)。 同时,我们也需要将 git hooks 进行初始化。
在项目的 package.json
中添加 prepare
定义:
{
"scripts": {
"prepare": "elint prepare"
}
}
如果你用过旧版本的
elint
,可以知道以前配置文件的复制过程是由 preset 配置的postinstall
命令完成的,但是从 v3 版本开始,elint 不再推荐使用这种方法,而是推荐在项目中主动配置安装命令。为什么要做这样的改动呢?
postinstall
不总是能够成功运行,且其输出会被包管理工具隐藏,难以发现一些问题;- 新版本
husky
移除了自动安装功能,也需要主动配置安装命令。可以参考 Why husky doesn't autoinstall anymore因此 elint 也推荐主动配置安装命令来完成 preset 初始化的流程。
注意:Yarn 2
不支持 prepare
生命周期,可以参考 Yarn 2 | husky 进行修改
按照常规套路,需要在 package.json
中定义 npm test
,如果项目只有 lint,可以这样写:
{
"scripts": {
"test": "elint lint 'src/**/*.js' 'src/**/*.css'"
}
}
如果除了 lint 还有其他测试,可以这样写:
{
"scripts": {
"test": "npm run test:lint && npm run test:other",
"test:lint": "elint lint 'src/**/*.js' 'src/**/*.css'",
"test:other": "..."
}
}
注意: glob 最好加上引号,详见 ELint CLI
刚才编写 preset 的时候,定义了在 commit 前执行 npm run beforecommit
,所以我们必须定义好 beforecommit,否则会报错:
{
"scripts": {
"beforecommit": "npm run test:lint && elint lint commit",
"test": "npm run test:lint && npm run test:other",
"test:lint": "elint lint 'src/**/*.js' 'src/**/*.css'",
"test:other": "..."
}
}
按照上面的写法,commit 之前会执行校验代码和 commit message。至此,elint 已经成功添加到项目中,执行 npm test
会校验代码,在 commit 前会校验代码和 commit message
当 lint 运行在 git hooks 中时,文件的范围限定在 git 暂存区,也就是你将要提交的文件(详见 ELint CLI)。
如果为了测试 elint 或迁移之前没有使用 elint 的项目,可以采用本地 preset 的方案。
在项目中创建一个 JS 文件作为 preset 配置,参考 2.2.3 - 2.2.8 完成对应的操作。在执行 lint 操作时,传入 --preset
参数指定本地的 preset 配置文件即可。
lint
命令用来执行代码校验和 git commit message 校验。当 lint 运行在 git hooks 中时,文件的搜索范围限定在 git 暂存区,也就是只从你将要 commit 的文件中,找到需要校验的文件,执行校验。
elint lint [options] [type] [files...]
type 可选的值:
- file: 检测文件
- commit: 检测提交信息
- common: 检测所有 common 类型的插件
如果不指定 type,默认执行检测文件
options 可选的值:
- -f, --fix: 自动修复错误
- --cache: 使用缓存
- --cache-location: 指定缓存位置
- --preset: 指定 preset 位置
- --no-ignore: 忽略 elint 遍历文件时的默认忽略规则
- --no-notifier: 忽略 preset 更新检查
- --force-notifier: 强制检查 preset 更新
当添加 --fix
时,会尝试自动修复问题,无法自动修复的问题依旧会输出出来。
例子:
# 校验 js 和 scss
$ elint lint "**/*.js" "**/*.scss"
# 校验 js 和 scss,执行自动修复
$ elint lint "**/*.js" "**/*.scss" --fix
# 校验 js
$ elint lint "**/*.js"
# 校验 less
$ elint lint "**/*.less"
# 校验 commit message
$ elint lint commit
注意:
当你在 Terminal(或者 npm scripts) 中运行 elint lint **/*.js
的时候,glob 会被 Terminal 解析,然后输入给 elint。glob 语法解析的差异可能会导致文件匹配的差异。所以建议,glob 使用引号包裹,防止在输入到 elint 前,被意外解析。
hooks
命令用来安装 & 卸载 git hooks:
elint hooks [action]
支持的 action:
- install: 安装
- uninstall: 卸载
例子:
$ elint hooks install
$ elint hooks uninstall
输出版本信息
$ elint -v
> elint version
elint : 3.0.0
husky(builtIn) : 8.0.1
Preset:
elint-preset-self : unknown
Plugins:
@elint/plugin-eslint : 3.0.0
eslint : 8.16.0
@elint/plugin-stylelint : 3.0.0
stylelint : 14.8.5
@elint/plugin-prettier : 3.0.0
prettier : 2.6.2
@elint/plugin-commitlint : 3.0.0
@commitlint/core : 17.0.0
reset
用于重置缓存,执行此命令将会清空 elint 缓存,同时如果插件配置了 reset
方法,也会同时执行。
插件分为三种类型:
formatter
格式化工具,如prettier
linter
lint 检查,如eslint
、stylelint
common
公共检查工具,如commitlint
其中 formatter
和 linter
是基于文件粒度,每个文件均会经过插件检查;而 common
是全局粒度,一次检查仅会执行一次
formatter
的特殊行为:格式化工具只能将代码格式化,自身没有检查的能力,因此当 preset
中使用了任意 formatter
时,会自动在所有 formatter
和 linter
执行结束后执行 elint 内部的插件 builtIn:format-checker
插件,判断代码是否被格式化
如果官方插件无法满足需求,例如需要支持旧版本 eslint
或添加其他文件的 lint 规则,可以自行编写一个插件。
type Result = string
const elintPluginExample: ElintPlugin<Result> = {
/**
* plugin 名称(唯一)
*/
id: 'elint-plugin-example',
/**
* 可读名称,用于控制台输出
*/
name: '插件示例',
/**
* elint 插件类型
*
* - `linter` lint 检查工具
* - `formatter` 格式化工具
* - `common` 公共检查工具
**/
type: 'linter',
/**
* 插件激活配置
**/
activateConfig: {
/**
* 使用扩展名确定是否激活插件
**/
extensions: ['.js'],
/**
* 判断插件激活函数,如果传入了则会忽略上面的 extensions
*/
activate: (options) => {
return true
}
},
execute: (text, options) => {
return {
source: '',
output: '',
errorCount: 0,
warningCount: 0,
message: '命令行输出文本',
result: 'lint 工具执行结果'
}
},
/**
* 可选实现,用于重置插件的一些缓存
*/
reset: () => {}
}
作为一个项目的维护者,当你将 elint 集成到你的项目中时,了解一些细节会非常有帮助。
如果你编写好了用于自己团队的 preset,并且按照前面介绍的安装方式安装完成,你会发现,elint 将所有的配置文件从 preset 复制到了项目的根目录。这是通过定义在 postinstall 中的 elint-helpers install
命令完成的。
这么做的目的是为了兼容在 IDE、build 工具中使用 lint。所以使用 elint 的同时,你仍然可以按照原来的方式,配置你的 IDE,webpack 等,他们与 elint 完全不冲突。
安装(并初始化)完成后,可以根据你的项目的实际情况,添加 npm scripts,例如 test 时执行 elint lint '**/*.js' '**/*.less'
执行过程比较简单,对代码的 lint 的过程可以概括为一句话:“elint 根据你输入的 glob,收集并整理文件,交由各个插件执行,然后将结果输出至命令行展示”。
对 git commit 信息的 lint,主要借助于 husky 和 commitlint。安装过程中,会自动添加 git hooks,当 hooks 触发时,执行 husky 配置中的相应命令,就这么简单。
执行 commit lint 时,git hook 可以选择
commit-msg
。
elint 在逻辑上和 husky
并没有绑定,因此如果想使用旧版本 husky
,可以直接在项目中安装和配置。
注意:由于 husky
的特殊性,无法在 preset
中指定其版本
如果你能想到这个问题,那么说明你真的理解了 elint 的运作方式。忽略配置文件,防止意外被修改,所有团队成员使用 preset 定义的配置,听起来非常不错。那么从 preset 复制到项目中的各种配置文件,是否可以添加到 .gitignore
呢?这要看你的使用场景:
- 如果代码提交到 git 仓库,执行 ci 的过程中会安装依赖。
- 如果部署项目时,build 过程中不再执行 lint,或者先安装依赖,然后再执行 build。
总之,只要执行 npm install
安装依赖,配置文件就会自动添加到项目里;只要你能保证需要用到配置文件的时候它存在(例如你在 webpack 里用了 eslint-loader),就可以忽略它。
再次强调,elint 的设计原则是保证团队规范的严格执行,如果你觉得规则有问题,那么应该提出并推动修改 preset,而不是直接改项目里的配置。
不可以。安装过程中,如果两个 preset 存在相同的配置文件,后安装会覆盖之前的。在 elint 执行时,如果发现有多个 preset,会抛出错误
- 检查你的 glob 写法是否有问题。
- 可能是 glob 被传入 elint 之前就被意外解析了,参考 lint 命令。
- windows 7 + git bash 下测试时发现,npm scripts 里,glob 必须使用双引号包裹
{
"scripts": {
"test:lint": "elint lint \"src/**/*.js\""
}
}
- elint 在遍历文件时,会应用一些默认的忽略规则,如果你的文件刚好命中这些规则,可以使用
--no-ignore
选项。
const defaultIgnore = [
'**/node_modules/**',
'**/bower_components/**',
'**/flow-typed/**',
'**/.nyc_output/**',
'**/coverage/**',
'**/.git',
'**/*.min.js',
'**/*.min.css'
]
// 除此之外还有 .gitignore 定义的忽略规则
并不是所有规则都支持自动修复,具体可以查看 eslint rules 和 stylelint rules,可以自动修复的规则都有标识。
设置环境变量 FORCE_COLOR
为 0
即可,例如:
$ FORCE_COLOR=0 elint lint "src/**/*.js"
代码经过 fix 以后,有可能会在修复了现有问题的同时引入新的问题。
eslint 为此会对 fix 后的代码重新执行 fix,以确保最终代码里的错误尽可能被修复,可以参考 Applying Fixes。
但是 stylelint 一直没有实现这个功能,因此可能出现 fix 后的代码依然存在问题。可以通过这个 issue 追踪此问题解决状态。
如果在开启缓存后执行了 fix 命令,但是代码中依然有问题且无法被 lint 出来,可以通过 elint reset
来重置缓存。
v2.x 到 v3 有很多 BREAKING CHANGE,可以按照以下流程进行升级
- 修改
package.json
中的peerDependencies
elint
为^3.0.0
- 移除依赖
elint-helpers
- 创建入口文件
index.js
,并将package.json
中的main
指向该文件 - 按需安装插件
@elint/plugin-eslint
,@elint/plugin-stylelint
,@elint/plugin-prettier
,@elint/plugin-commitlint
并安装插件对应的依赖(即eslint
,stylelint
,prettier
,@commitlint/core
) - 参考 2.2.2 - 2.2.10 添加对应配置
- 调整由于升级 lint 库而失效或者新增的规则
- 调整
husky
配置,可以参考 Migrate from v4 to v8
- 不再支持
elint lint es
和elint lint style
命令,如果在 lint 时需要区分文件类型,将自行通过 glob 语法匹配。 - 参考 安装 preset 和 git hooks 在
package.json
的script
里添加"prepare": "elint install && elint hooks install"
命令。 - lint 命令不再支持
--prettier
参数,是否检查格式化的行为将由 preset 决定,如果 preset 内部包含 formatter,格式化检查就会被执行。 - 根据需求,可以添加
--cache
参数配置缓存,用来提升 lint 速度
参考 CONTRIBUTING。