导航
导航:0. 导论
上一章节: 2. 在 monorepo 模式下集成 Vite 和 TypeScript - 下
下一章节:(编写中)4. 定制组件库的打包体系
本章节示例代码仓:Github
引言
记得我刚参加工作不久时,前期负责的一项工作就是清理代码检查服务扫出的代码规范问题。当时公司的代码检查平台无比地难用,所有检查出的规范错误问题都显示在网页上,本地开发环境中没有任何的提示,大家需要参考规范文档"盲改"代码,提交后重新扫描,一遍一遍地减少错误。
当时我对这种情况非常痛心,一方面因为缺少 IDE 的提示,这样的修改策略效率低下;另一方面,由于缺少强硬的措施,Clean Code 的理念也难以深入人心,修改规范无非是应付一时工作的形式主义,往后大家还会本能地往里提交不规范的代码,下次到了检查窗口期,还得再花费无谓的工作量去应付检查。
于是,我开始认真研究以前轻度接触过的前端 Lint 工具,并将它们一步一步在我们的项目中落地。在 Lint 系列工具与命令行、IDE、CI 流程高度结合后,完全消灭了不符合规范的增量代码,存量代码也随着迭代一点一点地规范化。从此,我们前端组再也没有花精力在应付所谓 Clean Code 检查的工作。
许多人认为代码规范的核心在于人,而工具起不到根本作用;或者认为已有的"屎山"已经积重难返,规范工具无力回天。在我看来,也许是因为没有正确、完善地使用工具。
本期我们会给自己的组件库接入全套完善的 Lint 工具链,但是这一期的分享对于普通项目也具有极高的参考性,任何前端项目都可以按照这样的方法实现代码风格的规范化。 在实践之前,读者可以先关注下面的问题,看看有没有覆盖自己的疑惑。这些问题的答案将在下面的实践中逐步清晰:
- 如何集成
ESLint,控制代码风格? ESLint如何与Vue、TypeScript配合使用?- 如何集成
StyleLint,控制样式风格? StyleLint如何与Vue、SCSS配合使用?Prettier应该如何使用?- 如何集成
commitlint与husky,控制提交信息风格? - 如何实现增量
Lint检查?更好地应对积重难返的"屎山"项目。
ESLint
ESLint 是在
ECMAScript/JavaScript代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。
使用 ESLint 需要我们在项目的根目录下添加 .eslintrc.js 或 .eslintrc.json 文件,在其中导出配置对象。下面是从官网中摘录的一个典型的配置文件,稍作了一点修改,它结合了 typescript-eslint 实现了对 TypeScript 的支持,我们以它为例子帮助大家简单地理解重要的配置字段,并梳理 ESLint 的工作思路。
json
{
"root": true,
// 继承已有配置对象
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
// 如何理解代码
"parser": "@typescript-eslint/parser",
"parserOptions": { "project": ["./tsconfig.json"] },
// 添加哪些规则
"plugins": [
"@typescript-eslint"
],
// 已添加规则的开启 / 关闭
"rules": {
"@typescript-eslint/strict-boolean-expressions": [
2,
{
"allowString" : false,
"allowNumber" : false
}
]
},
// 对特殊文件应用特殊配置
"overrides": [
{
"files": ["*.vue"],
"rules": {
// 所有 .vue 文件除了应用上面的公共规则配置外,还需应用的独特规则配置。
},
},
],
}
ESLint如何理解代码?
parser 和 parserOptions 选项与 ESLint 如何理解我们的代码相关。这里分析器 @typescript-eslint/parser 负责解析 TypeScript 语言,将代码转化为 AST 语法树,便于进行分析。而 parserOptions 可以对解析器的能力进行详细设置。
ESLint如何判断代码是否规范?
ESLint 提供了 自定义规则 的接口,开发者需要遵照接口,根据分析器的 AST 产物,实现规范检查逻辑,再将实现的多条规范聚合为 plugin 插件的形式。plugin 字段指定了 ESLint 应用什么规则集,具有理解哪些规范的能力。
- 规则的启用与禁用
有了规则集,能够理解规范,不代表 ESLint 就要对不规范的内容做出响应,还需要进一步在 rules 字段中对这些规则进行开启或者关闭的声明,只有开启的规则才会生效。
- 继承已有配置
面对琳琅满目的规则集,我们完全在项目中配置是不可取的。因此社区逐渐演进出了许多配置预设,让我们可以一键继承,从而减少绝大多数手动配置的工作量。例如例子中的 eslint:recommended、plugin:@typescript-eslint/recommended 就代表继承了 eslint 和 typescript-eslint 的推荐配置。
- 配置的重写 如果我们希望某些文件应用一些独特的配置,可以使用
overrides字段实现。overrides的每个成员对象都需要指定目标文件,除了应用所有父级配置之外,还要应用成员对象中声明的独有配置。ESLint支持文件级别的重写。
规则集的选型
经过了多年的发展,ESLint 已经有了许多成熟的规则集,这些规则集都是成熟团队的实践。它们通过 plugin 实现了很多个性化的规则,又内置了海量的 rules 配置预设。用户只需简单的几行代码继承,就相当于拥有了这些优秀团队的优秀实践。
这里给大家推荐一些我了解过的规则集:
- eslint-config-airbnb:
Airbnb规则集 - eslint-config-alloy:腾讯 AlloyTeam 的规则集
- eslint-config-standard:
StandardJS规则集
采用什么样的规则集并不是最重要的,重要的是"正确引入,严格执行",引导团队成员同一规范,写出整洁、可维护的代码。 我个人倾向于使用 Airbnb 规则集,这套规则集在 Github 上当下拥有最多的 star 数,特点是非常严格,需要根据自己项目的情况调整或关闭一部分限制。大部分公司在制定自己内部的编码规范时,都会高度地参考这套规则,并通常沿用其中 80% 以上的内容。因此学习 Airbnb 的规则也自然就适应了公司内的大部分规则。
依赖安装
首先我们需要安装 eslint 核心工具(对 pnpm workspace 指令不熟悉的读者可以回顾:1. 基于 pnpm 搭建 monorepo 工程目录结构)。
bash
pnpm i -wD eslint
由于我们要具备解析 TypeScript 的能力,所以要安装 typescript-eslint 系列工具。同理,为了能够解析 Vue 语法,也要安装 vue-eslint-parser。
bash
pnpm i -wD @typescript-eslint/parser @typescript-eslint/eslint-plugin vue-eslint-parser
import 模块引入相关的规则、Vue 相关规则并不包含在默认规则集、typescript-eslint 规则集以及 Airbnb 规则集中,所以我们要额外安装对应的 plugin,引入这些规则集。
bash
pnpm i -wD eslint-plugin-import eslint-plugin-vue
之后安装 Airbnb 规则集,便于我们一键继承。
bash
pnpm i -wD eslint-config-airbnb-base eslint-config-airbnb-typescript
最后给大家推荐 eslint-define-config,这个库能够让在我们编写 .eslintrc.js 配置文件时,提供完善的类型支持,大幅度提升体验,强烈推荐安装!
bash
pnpm i -wD eslint-define-config
配置
我们在根目录建立 .eslintrc.js 文件,作为 ESLint 的配置文件。
js
// .eslintrc.js
const { defineConfig } = require('eslint-define-config');
const path = require('path');
module.exports = defineConfig({
// 指定此配置为根级配置,eslint 不会继续向上层寻找
root: true,
// 将浏览器 API、ES API 和 Node API 看做全局变量,不会被特定的规则(如 no-undef)限制。
env: {
browser: true,
es2022: true,
node: true,
},
// 设置自定义全局变量,不会被特定的规则(如 no-undef)限制。
globals: {
// 假如我们希望 jquery 的全局变量不被限制,就按照如下方式声明。
// $: 'readonly',
},
// 集成 Airbnb 规则集以及 vue 相关规则
extends: [
'airbnb-base',
'airbnb-typescript/base',
'plugin:vue/vue3-recommended',
],
// 指定 vue 解析器
parser: 'vue-eslint-parser',
parserOptions: {
// 配置 TypeScript 解析器
parser: '@typescript-eslint/parser',
// 通过 tsconfig 文件确定解析范围,这里需要绝对路径,否则子模块中 eslint 会出现异常
project: path.resolve(__dirname, 'tsconfig.eslint.json'),
// 支持的 ecmaVersion 版本
ecmaVersion: 13,
// 我们主要使用 esm,设置为 module
sourceType: 'module',
// TypeScript 解析器也要负责 vue 文件的 <script>
extraFileExtensions: ['.vue'],
},
// 在已有规则及基础上微调修改
rules: {
'import/no-extraneous-dependencies': 'off',
'import/prefer-default-export': 'off',
// vue 允许单单词组件名
'vue/multi-word-component-names': 'off',
'operator-linebreak': ['error', 'after'],
'class-methods-use-this': 'off',
// 允许使用 ++
'no-plusplus': 'off',
'no-spaced-func': 'off',
// 换行符不作约束
'linebreak-style': 'off',
},
// 文件级别的重写
overrides: [
// 对于 vite 和 vitest 的配置文件,不对 console.log 进行错误提示
{
files: [
'**/vite.config.*',
'**/vitest.config.*',
],
rules: {
'no-console': 'off',
},
},
],
});
结合上文中对 ESLint 主要字段的讲解以及注释内容,不难理解这个配置文件的含义。这里我们需要注意一下 parserOptions.project 字段,TypeScript 解析器需要一个 tsconfig 文件来确认解析范围。
我们希望 ESLint 检查能覆盖所有源码文件,但是 tsconfig.json 已经被占用做其他用途(IDE 语言服务),不能够迁就 ESLint,因此我们只得另外建立一个 ESLint 专用的文件 tsconfig.eslint.json,在其中包含所有希望被规范化的源码文件。这也是 typescript-eslint 官方为 monorepo 型工程推荐的一种解决方案(Monorepo Configuration)。
json
// tsconfig.eslint.json
{
// eslint 检查专用,不要包含到 tsconfig.json 中
"extends": "./tsconfig.base.json",
"compilerOptions": {
// 参考 https://typescript-eslint.io/linting/typed-linting/monorepos
"noEmit": true
},
// 只检查,不构建,因此要包含所有需要检查的文件
"include": [
"**/*",
// .xxx.js 文件需要单独声明,例如 .eslintrc.js
"**/.*.*"
],
"exclude": [
// 排除产物目录
"**/dist",
"**/node_modules"
]
}
对于一些我们不希望应用 ESLint 检查的内容,我们可以通过 .eslintignore 文件将之排除,.eslintignore 的规则与 .gitignore 的规则完全相同。我们排除 ESLint 对依赖目录与产物目录的检查。
ini
# .eslintignore
node_modules
dist
!.eslintrc.js
!.stylelintrc.js
!.prettierrc.js
!.lintstagedrc.js
!.commitlintrc.js
关于 .eslintignore 还有一点需要注意:ESLint 默认忽略对 . 开头文件的检查。 对于配置文件 .eslintrc.js 以及之后的 .stylelintrc.js 等都需要用 ! 反向声明忽略。
之后,我们在 package.json 中加入 eslint 检查的脚本,并尝试执行检查。
diff
// package.json
{
// 其他配置...
"scripts": {
+ "lint:script": "eslint --ext .js,.jsx,.ts,.tsx,.vue --fix ./",
// 其他脚本...
}
}
bash
pnpm run lint:script

正确配置后,ESLint 能检查出不少错误,包括了 .vue、.ts 文件。
不过这样获取的错误信息并不直观,我们会在下文去探索更优的方式,增强 ESLint 的使用体验。
Stylelint
接下来,我们进入 Stylelint 的部分。
Stylelint 是一个强大的 CSS 格式化工具,可以帮助使用者避免语法错误并统一编码风格。
Stylelint 的原理与上面讲到的 ESLint 是一样的,只不过它是 CSS 样式领域的 Lint 工具。在上手了 ESLint 之后,我们理解 Stylelint 并不会有太大的困难。
首先,为项目安装 Stylelint,并结合我们的实际需求安装必要的插件:
bash
pnpm i -wD stylelint
- stylelint-config-standard-scss:一键集成完整的
sass规则集。继承了很多东西,包括sass规则实现的插件、css标准规则集 stylelint-config-standard 等。如果你使用less,stylelint-config-standard-less 也是类似的效果。 - stylelint-config-recommended-vue:使
Stylelint支持对.vue文件的<style></style>部分进行检查。 - stylelint-config-recess-order:一种推荐的
css属性排序的规则。 - stylelint-stylistic:
Stylelint升级到15.0.0大版本后,计划废弃风格相关的规则,这部分内容分离出来由社区维护,需要单独安装。
bash
pnpm i -wD stylelint-config-standard-scss stylelint-config-recommended-vue stylelint-config-recess-order stylelint-stylistic
我们在项目根目录建立 .stylelintrc.js,编写配置文件。配置文件的写法与 ESLint 几乎是完全一致的,只有 rules 中规则的开关选项有所不同,在 ESLint 中使用 'warn'(警告) | 'error'(报错) | 'off' 开关规则,在 Stylelint 中使用 any(不同的规则要求的值不同) | null(关闭规则统一) 开关规则。
js
// .stylelintrc.js
module.exports = {
// 继承的预设,这些预设包含了规则集插件
extends: [
// 代码风格规则
'stylelint-stylistic/config',
// 基本 scss 规则
'stylelint-config-standard-scss',
// scss vue 规则
'stylelint-config-recommended-vue/scss',
// 样式属性顺序规则
'stylelint-config-recess-order',
],
rules: {
// 自定义规则集的启用 / 禁用
// 'stylistic/max-line-length': null,
'stylistic/max-line-length': 100,
},
};
同样,.stylelintignore 文件也要忽略产物目录和依赖目录。
ini
# .stylelintignore
node_modules
dist
接着,我们在 button 包源码目录中建立 button.scss 文件,并且在 button.vue 中补充 <style></style> 部分,填写一些测试的 scss 样式,检查 stylelint 能否识别。
scss
/* packages/button/src/button.scss */
.test-class {
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
html
<script setup lang="ts">
// packages/button/src/button.vue
// 先前的内容。。。
</script>
<template>
<!-- 先前的内容。。。 -->
</template>
<style lang="scss">
.testClass {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 1rem;
font-weight: 500;
line-height: 1.5;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
color: #212529;
background-color: #e9ecef;
}
</style>
在 package.json 中加入 stylelint 检查的脚本,准备执行检查。
diff
// package.json
{
// 其他配置...
"scripts": {
+ "lint:style": "stylelint --fix ./**/*.{css,scss,vue,html}",
// 其他脚本...
}
}
bash
pnpm run lint:style

配置完成后,Stylelint 成功地在 .vue 与 .scss 文件中检查除了错误。
Prettier
Prettier 是一个固执己见的代码格式化工具。
它是一款只需进行简单配置,就能支持多种语言格式化的工具。由于它专注的方向是代码风格(换行、缩进),并不涉及语法检查,因此很多实践中会让 Prettier 与 Lint 系列工具互相配合------将 Prettier 以插件规则集的方式集成到 Lint 中,并关闭原本内置的功能重复的规则。
但是,在我们的组件库中,Prettier 是被边缘化的。 理由如下:
ESLint和Stylelint本身就有控制代码风格的规则。只不过它们只针对 JS / TS / CSS,不如Prettier支持的语言种类多,但我们对其他语言支持的需求度不高。Prettier与Lint结合使用时,所有的格式错误都会被标注为统一的prettier/prettier,没法进一步细分错误。Prettier比较固执己见,无法对规则进行更细粒度的控制,ESLint和Stylelint这方面的潜力更大。
总体上,我比较赞同 antfu 大佬的观点:为什么我不使用 Prettier。因此我们的组件库将使用 Prettier 完成 ESLint、Stylelint 不支持的文件类型的格式化, 例如 Markdown、json、yaml 等。
如果你还是希望详细了解如何配合使用 ESLint、Stylelint 和 Prettier,使它们之间可以互相兼容,可以阅读这篇文章: 你不能再说你不会配置ESLint和prettier了
接下来我们将执行以下操作完成 Prettier 的集成:
- 安装
Prettier:
bash
pnpm i -wD prettier
- 在根目录,创建配置文件
.prettierrc.js,创建.prettierignore忽略依赖目录与产物目录:
js
// .prettierrc.js
module.exports = {
// 一行最多字符
printWidth: 100,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// 标签闭合不换行
bracketSameLine: true,
// 箭头函数尽量简写
arrowParens: 'avoid',
// 行位换行符
endOfLine: 'lf',
};
ini
# .prettierignore
node_modules
dist
与 IDE 插件结合
接下来我们要致力于提高 Lint 工具的本地使用体验,让它们与 IDE 结合起来,在编码过程中可以实时展示错误。我们需要安装三个 VSCode 插件:ESLint、Stylelint、Prettier。



接下来,我们要对这些插件进行一些配置,将项目的格式规范要求在 IDE 的层面固定下来。这里采取修改 .vscode 目录下的项目级 IDE 配置的方式(回顾:2. 在 monorepo 模式下集成 Vite 和 TypeScript - 下)。
首先,在 extensions.json 中增加这三个插件,引导新贡献者安装:
diff
// .vscode/extensions.json
{
"recommendations": [
// ...
+ "esbenp.prettier-vscode",
+ "stylelint.vscode-stylelint",
+ "dbaeumer.vscode-eslint",
]
}
接着进行 settings.json 项目级 IDE 选项的配置:
json
// .vscode/settings.json
{
// 已有配置...
// 关闭 IDE 自带的样式验证
"css.validate": false,
"less.validate": false,
"scss.validate": false,
// 指定 stylelint 生效的文件类型(尤其是 vue 文件)。
"stylelint.validate": ["css", "scss", "postcss", "vue"],
// 启用 eslint 的格式化能力
"eslint.format.enable": true,
// eslint 会在检查出错误时,给出对应的文档链接地址
"eslint.codeAction.showDocumentation": {
"enable": true
},
// 指定 eslint 生效的文件类型(尤其是 vue 文件)。
"eslint.probe": ["javascript", "typescript", "vue"],
// 指定 eslint 的工作区,使每个子模块下的 .eslintignore 文件都能对当前目录生效。
"eslint.workingDirectories": [{"mode": "auto"}],
}
经过配置后,无论是 JS / TS / vue 文件都可以在 IDE 层面提示错误了。


我们还可以让 IDE 帮我们自动修复错误,调整格式,从而避免大量手动操作。我们继续在 settings.json 中配置:
editor.codeActionsOnSave的相关配置,让ESLint和Stylelint的自动修复功能在保存文件时触发。 当然,部分复杂的错误无法自动修复,需要人工检视。- 将默认的格式化工具设为
Prettier,但是禁用自动格式化,避免格式化与自动修复之间的冲突。自动格式化只对非ESLint和Stylelint目标的文件开启, 例如json、yaml。
json
// .vscode/settings.json
{
// 已有配置。。。
// 设置默认格式化工具为 Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 默认禁用自动格式化(手动格式化快捷键:Shift + Alt + F)
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
// 启用自动代码修复功能,保存时触发 eslint 和 stylelint 的自动修复。
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
// volar 可以处理 vue 文件的格式化
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
// json、yaml 等配置文件保存时自动格式化
"[json]": {
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.formatOnSave": true
},
"[yaml]": {
"editor.formatOnSave": true
}
}


最后,为了更好地与 Lint 插件配合,我们再补充一些 IDE 文本格式相关的配置。
json
// .vscode/settings.json
{
// 已有配置...
// 行尾默认为 LF 换行符而非 CRLF
"files.eol": "\n",
// 键入 Tab 时插入空格而非 \t
"editor.insertSpaces": true,
// 缩进大小:2
"editor.tabSize": 2,
// 自动补充闭合的 HTML 标签
"html.autoClosingTags": true,
// 更多格式相关配置...
}
自此,我们的组件库项目在本地开发环境下,就有了从命令行到 IDE 的完善的代码规范能力。接下来只需根据已敲定的团队开发规范,在 .eslintrc.js 以及 .stylelintrc.js 的 rules 字段中设置规则集即可。
commitlint
除了代码规范之外,Git 提交信息的规范也是值得关注的,相比起随意的提交信息,规范的提交信息能带来以下好处:
- 定位问题时,提供更加清晰的线索;
- 团队协作时,降低理解他人代码的成本;
- 配合一些 CI 自动化工具,能够一键生成清晰的版本更新记录
CHANGELOG。
commitlint 工具可以检查 Git 提交信息是否符合规范。通过以下命令可以安装相关依赖。
bash
pnpm i -wD @commitlint/config-conventional @commitlint/cli
之后在根目录创建 .commitlintrc.js,继承默认的 @commitlint/config-conventional 规范集:
js
// .commitlintrc.js
module.exports = {
extends: ['@commitlint/config-conventional'],
};
@commitlint/config-conventional 规定了这样的 Git 提交规范:
bash
type(scope?): subject
type:本次提交的类型,默认规范集支持以下类型。feat:添加新功能fix:Bug 修复build:构建相关的修改chore:对项目功能没有影响的修改ci:持续集成方面的修改docs:文档的修改perf:性能优化refactor:代码重构revert:代码回退style:样式相关调整test:测试相关代码
scope:本次提交涉及哪个子模块,此部分可不填。subject:本次提交的描述信息。
如此配置后的 commitlint,要求我们的提交信息只能是以下形式:
csharp
feat(button): add click event.
fix(input): fix the error of v-model.
docs: add README.md for button.
而不能再随意提交:
bash
# 不符合 type(scope?): subject 的格式,缺少 type。
add click event.
通过 husky 集成到 Git hooks 中
commitlint 是无法单独使用的。 因为提交信息发生在 git commit 阶段,而 git commit 时,控制台已经被占用,无法再容我们输入其他命令。
Git 提供了一个叫做 Git Hooks 的功能,它能让我们在特定的重要动作发生时触发自定义脚本。 按照这个说法,我们就可以使用 Git Hooks 在 commit 动作发生的时候执行 commitlint 脚本,判断所提交的信息是否符合规范。Git Hooks 中的 commit-msg 钩子就正好符合我们的需求。
如果需要详细了解 Git Hooks 可以阅读以下文章: 一文带你彻底学会 Git Hooks 配置
当然,直接使用 Git Hooks 会存在着诸多的不便,例如直接在 .git/hooks 中创建的 Git Hooks 脚本是无法上传到远程仓库的(因为 .git 目录不能入仓)。我们使用一款工具 husky 来让 Git 钩子的配置变得更加简单。首先安装依赖项,并初始化 husky。
bash
pnpm i -wD husky
npx husky install
执行完初始化命令后,我根目录下出现了 .husky 目录,所有与 Git Hooks 相关的配置后续都会在其中。husky 官方推荐我们将 husky install 命令设置为项目启动前脚本------将 husky install 放入 package.json 中的 scripts.prepare 中,使得每次完成依赖安装后都会执行 husky 的初始化。
diff
// package.json
{
// 其他配置...
scripts: {
+ "prepare": "husky install",
// 其他脚本...
}
}
接下来我们开始落实 commit-msg 钩子,先执行命令,生成 .husky/commit-msg 文件后,再将执行 commitlint 的命令写入其中:
bash
npx husky add .husky/commit-msg
diff
# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-undefined
+npx --no -- commitlint -e $HUSKY_GIT_PARAMS
到此,commitlint 的集成算是正式完成,可以看到我们的工程阻止了不符合规范的提交信息。

lint-staged 实现增量检查
到目前为止,我们所配的 ESLint、Stylelint 实现的都是全量检查。我们的组件库作为一个新的项目,可以接受全量检查,但是对于很多大项目而言,全量检查的代码规范是无法落地的,存在以下问题:
- 项目体积过大,全量检查需要扫描的文件过多,导致检查花费的时间太多。如果这样的检查集成到了 CI 门禁中,将会大大降低构建效率。
- 项目历史有太多不规范的技术债,全量检查扫描出的问题过多,若要集成到 CI 门禁中,将使团队面临巨大的修改工作量和代码变更带来的风险。
为了让 Lint 检查能够在大多数情况下平滑地集成到项目中,我们需要实现增量检查。而 lint-staged 就适用于这样的场景。
lint-staged包含一个可以执行任意shell任务的脚本,在执行脚本时以 暂存区 的文件列表作为参数,并支持按 glob 模式进行过滤。
这里的这个暂存区可能有些难以理解,我们可以暂时这样简单地理解:在 git commit 之前我们往往要先执行 git add,git add 的作用就是将工作区的文件变化推送到暂存区(stage),以便下一步通过 git commit 将暂存区的内容推送到远端版本库。lint-staged 也正如其名,它的目标就是这些暂存区的文件。
如果你想更多地理解暂存区的概念,这里推荐一些博文:
当然,如果你对 Git 也不太了解,这里也推荐一个交互式的 Git 学习网站:
首先我们安装 lint-staged:
bash
pnpm i -wD lint-staged
然后在根目录创建配置文件 .lintstagedrc.js,lint-staged 的灵活性就在于支持通过 glob 模式匹配对暂存区的文件列表进行分类过滤,以实现对不同的文件应用不同检查的效果。
js
// .lintstagedrc.js
module.exports = {
// 对于 js、ts 脚本文件,应用 eslint
'**/*.{js,jsx,tsx,ts}': [
'eslint --fix',
],
// 对于 css scss 文件,应用 stylelint
'**/*.{scss,css}': [
'stylelint --fix',
],
// Vue 文件由于同时包含模板、样式、脚本,因此 eslint、stylelint 都要使用
'**/*.vue': [
'eslint --fix',
'stylelint --fix',
],
// 对于其他类型的文件,用 prettier 修复格式
'**/*.{html,json,md}': [
'prettier --write',
],
};
由于 lint-staged 要处理的是缓冲区中的变化文件,所以我们要利用 Git Hooks 中 pre-commit 这个钩子,就能够实现在 commit 发生之前对变化(增量)的文件进行 Lint 扫描,若 Lint 抛出错误,说明此次准备提交的文件存在代码规范的问题,提交失败。这需要我们再次用到 husky。
bash
npx husky add .husky/pre-commit
diff
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-undefined
+npx --no -- lint-staged
接下来我随意修改了几个文件(脚本、样式、vue 都涉及到),这几个文件都存在 lint 错误,执行一次代码提交,看看会发生什么。


在修复了所有问题后,这些文件才能通过 pre-commit 钩子中 lint-staged 的检查,被成功提交。如此,我们本地提交代码规范的增量检查门禁便成型了。

增量检查进阶:lint-staged --diff
lint-staged 的强大之处在于它并不是只支持对缓冲区内文件变化的读取,它可以通过 --diff 指令对比工作区与远程版本库的差异,将差异文件列表作为参数执行 Lint 脚本。这一特性可以被利用起来,实现流水线门禁中的增量检查。
由于本章不涉及持续集成相关的实践,因此只是在本地环境模拟演示一下这种场景,若想进一步了解增量检查在流水线中的应用,请关注本系列后续的更新。
我们设想这样一个场景:
- 我们从
master分支拉出一个feat分支用于新特性的开发。 feat分支开发完成后,需要通过merge request/pull request合入master分支。- 在这次
merge request/pull request的门禁中,我们需要确认feat分支合入master分支后,发生变化的文件是否有不合规范的现象。
下面展示操作流程:
我们先确保 master 分支的代码已经集成了所有 Lint 工具链(若 master 分支进度落后需要及时合并最新分支) ,以 master 分支为基础创建 feat 分支:
bash
git checkout -b feat
在 feat 分支中,我们改动一部分文件(5 个文件:md、json、ts、scss、vue 文件各一个),之后提交代码。

bash
git add .
git commit -m 'chore: test lint-staged diff'
git push --set-upstream origin feat
现在我们切换回 master,准备模拟一个 merge request / pull request。
bash
git checkout master
目前,远端的 origin/feat 要领先于工作区的 master 分支,工作区 master 分支与远端的 origin/master 分支保持一致。在合入远端分支 origin/feat 后,工作区的 master 分支与远端的 origin/feat 一致,领先于远端的 origin/master。
bash
git merge origin/feat
# 结果
Updating a3c6c8d..12be1a3
Fast-forward
README.md | 7 ++++++-
packages/button/src/button.scss | 1 +
packages/button/src/button.vue | 1 +
packages/button/src/index.ts | 1 +
tsconfig.json | 5 +++--
5 files changed, 12 insertions(+), 3 deletions(-)
这个时候,对工作区的 master 和远端的 origin/master 进行 diff,就可以比较出两者之间的文件差异。这个差异正是我们之前在 feat 分支上的变更。
bash
git diff origin/master --name-only
# 结果
README.md
packages/button/src/button.scss
packages/button/src/button.vue
packages/button/src/index.ts
tsconfig.json
那么,我们借助 lint-staged -diff 指令,就可以实现对这部分变化文件的 Lint 检查。
bash
npx lint-staged --diff=origin/master

虽然这部分实践没办法立即受用,但是它为后续持续集成时的 merge request / pull request 门禁实现增量检查打下了基础, 日后我们会再次回顾。最后我们将 lint-staged 指令也加入到 package.json 中。
diff
// package.json
{
// 其他配置...
scripts: {
+ "lint-staged": "lint-staged",
// 其他脚本...
}
}
结尾与资料汇总
在文章的结尾,让我们来集中梳理一下本章的思路:
- 我们首先简单地了解了
ESLint的功用和原理。ESLint是对ECMAScript代码进行规范化的工具。它通过parser解析代码;通过plugin聚合多条检查规则的实现逻辑;通过rules开启 / 关闭 / 配置 各条检查规则;通过extends继承配置预设。 - 我们以
Airbnb规则集为基础,添加了许多周边插件使ESLint支持了对vue、TypeScript、import语法的支持,最终确定了基本的.eslintrc.js配置文件。 Stylelint与ESLint原理类似,我们也按照类似的方式确定了基本的.stylelintrc.js配置文件。我们配好的Stylelint具有识别vue、识别sass、对 css 属性自动排序的能力。- 对于
Prettier,我们分析了其优劣,不准备将其集成到ESLint、Stylelint中,只是单独负责json、md等其他文件的处理。 - 我们安装了
ESLint、Stylelint、Prettier相关的 IDE 插件,并做了许多插件相关的编辑器配置,实现了代码规范与 IDE 的高度结合,使Lint工具的使用体验得到飞跃。 commitlint和husky配合使用,可以使我们的 Git 提交信息规范化,不合规的提交信息将在Git Hooks的commit-msg钩子中被拦截。- 我们尝试使用
lint-staged工具,配合husky,在Git Hooks的pre-commit钩子中只对暂存区的代码进行Lint检查,实现了本地提交代码规范增量检查门禁。 - 进一步探索
lint-staged,发现其--diff选项可以获取任意两个分支之间的文件变化列表,由此初步确定了代码合并的场景下进行代码规范增量检查的方案。
本章涉及到的相关资料汇总如下:
官网与文档:
stylelint-config-standard-scss
stylelint-config-standard-less
stylelint-config-recommended-vue
分享博文: