从零构建前端 Lint 工作流
eslint + prettier + husky + lint-staged + typescript + eslint-config-alloy
为构建项目代码质量保驾护航- 传承 ESLint 推崇的插件化、配置化的理念,满足个性化需求(即让专业的工具做擅长的事)
选择性阅读
- 新手建议从头开始,都是手把手,按步骤配置一遍
- 有 ESLint 使用经验的,直接上 AlloyTeam 配置
- 让 ESLint 检查 TypeScript
- 想集成 VSCode 插件,自动提示/修复错误-->VSCode 集成 ESLint 检查
- 本地 Lint 已构建,缺少 Git 提交代码预检
什么是代码检查
- 代码检查主要是用来发现代码错误、统一代码风格。
- 在 JavaScript 项目中,我们一般使用 ESLint 来进行代码检查,它通过插件化的特性极大的丰富了适用范围,搭配 typescript-eslint 之后,甚至可以用来检查 TypeScript 代码。
配置 ESLint
小试牛刀
- 新建一个文件夹,打开命令行,
npm init -y
创建package.json
- 安装依赖
npm install --save-dev eslint babel-eslint eslint-config-alloy
- 在项目根目录下创建一个
.eslintrc.js
或.eslintrc.json
的配置文件:
// .eslintrc.jsmodule.exports = { extends: [ 'alloy', ],};
- 在项目根目录下创建一个
index.js
,复制下面内容:
var myName = 'Tom';console.log(`My name is ${myNane}`);
- 在命令行输入
npx eslint index.js
// eslint 报错信息:✖ 2 problems (2 errors, 0 warnings)error Unexpected var, use let or const instead no-varerror 'myNane' is not defined no-undef
- 使用
npx eslint index.js --fix
自动修复某些规则
// 这时 var 变成了 let// 还剩下一个无法自动修复的错误 ✖ 1 problem (1 error, 0 warnings)error 'myNane' is not defined no-undef
配合 TypeScript
- 由于 ESLint 默认使用 Espree 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser,替代掉默认的解析器,别忘了同时安装 typescript:
npm install --save-dev typescript @typescript-eslint/parser
- 接下来需要安装对应的插件 @typescript-eslint/eslint-plugin 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。
npm install --save-dev @typescript-eslint/eslint-plugin
- 修改配置文件
module.exports = { extends: [ 'alloy', ], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], rules: { // 禁止使用 var 'no-var': "error", // 优先使用 interface 而不是 type '@typescript-eslint/consistent-type-definitions': [ "error", "interface" ] }}
- 以上配置中,我们自定义了两个规则,其中
no-var
是 ESLint 原生的规则(我们刚刚已经用到了这个规则,它被包含在alloy
中,此处会覆盖),@typescript-eslint/consistent-type-definitions 是 @typescript-eslint/eslint-plugin 新增的规则 - 规则的取值一般是一个数组(上例中的 @typescript-eslint/consistent-type-definitions),其中第一项是 off、warn 或 error 中的一个,表示关闭、警告和报错。后面的项都是该规则的其他配置。
- 如果没有其他配置的话,则可以将规则的取值简写为数组中的第一项(上例中的 no-var)。
关闭、警告和报错的含义如下:
- 关闭:禁用此规则
- 警告:代码检查时输出错误信息,但是不会影响到 exit code
- 报错:发现错误时,不仅会输出错误信息,而且 exit code 将被设为 1(一般 exit code 不为 0 则表示执行出现错误)
- 新建
index.ts
文件:
var myName = 'Tom';console.log(`My name is ${myNane}`);console.log(`My name is ${myName.toStrng()}`);type Foo = {};
- 在命令行输入
npx eslint index.ts
,如下可以看到报错信息以及可修复项
1:1 error Unexpected var, use let or const instead no-var 2:27 error 'myNane' is not defined no-undef 4:6 error Use an `interface` instead of a `type` @typescript-eslint/consistent-type-definitions✖ 3 problems (3 errors, 0 warnings) 2 errors and 0 warnings potentially fixable with the `--fix` option.
脚本命令检查整个项目
- 根目录新建一个src文件夹,将我们的
index.js
和index.ts
放进去 - 在
package.json
中的scripts
新增:
{ "scripts": { // 因为eslint不是全局安装的,所以要使用npx "eslint": "npx eslint src --ext .js,.ts,tsx" // eslint 默认不会检查 .ts 后缀的文件,所以需要加上参数 --ext .ts }}
- 然后
npm run lint
就可以看到src下所有指定后缀文件的报错信息
推荐使用 AlloyTeam 的配置
- 上面手把手完成了
ESLint
的配置过程 - 有一定经验的推荐直接使用
AlloyTeam
实现可自定义拓展的ESLint
规则 - AlloyTeam/eslint-config-alloy已经帮我们集成了各种技术栈
- 安装技术栈相关依赖
// Eslintnpm install --save-dev eslint babel-eslint eslint-config-alloy// Reactnpm install --save-dev eslint babel-eslint eslint-plugin-react eslint-config-alloy// Vuenpm install --save-dev eslint babel-eslint vue-eslint-parser eslint-plugin-vue eslint-config-alloy// TypeScriptnpm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy// TypeScript Reactnpm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy
- 配置
.eslintrc.js
文件
/* .eslintrc.js */module.exports = { extends: [ 'alloy', // 都需要 'alloy/vue', //vue项目需要 'alloy/react', //react项目需要 'alloy/typescript', //ts项目需要 ], env: { // 你的环境变量(包含多个预定义的全局变量) // // browser: true, // node: true, // mocha: true, // jest: true, // jquery: true }, globals: { // 你的全局变量(设置为 false 表示它不允许被重新赋值) // // myGlobal: false }, rules: { // 自定义你的规则 }};
- 接下来就可以直接用
eslint
命令检查文件了 - 这样就引入了alloy团队的lint规则了,然后可以用rules覆盖你不爽的规则,直接采用开源规则是为了避免重复造轮子,你也可以选择别的团队,或者自己定义一套
VSCode 集成 ESLint 检查
在编辑器中集成 ESLint 检查,可以在开发过程中就发现错误,甚至可以在保存时自动修复错误,极大的增加了开发效率
- 先安装 ESLint 插件,打开 VSCode 点击「扩展」按钮,搜索 ESLint,然后安装即可
- 在「文件 => 首选项 => 设置 => 工作区」中(也可以在项目根目录下创建一个配置文件
.vscode/settings.json
),添加以下配置:
{ // VSCode 中的 ESLint 插件默认是不会检查 `.vue`、`.ts` 或 `.tsx` 后缀的 "eslint.validate": [ "javascript", "javascriptreact", "vue", "typescript", "typescriptreact" ], // 开启保存时自动修复 "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, // 指定VSCode用于IntelliSense(智能感知)的ts版本,将内置版本更换为工作区版本 "typescript.tsdk": "node_modules/typescript/lib"}
结合 Prettier 使用
Prettier 是一个代码格式化工具,相比于 ESLint 中的代码格式规则,它提供了更少的选项,但是却更加专业。
AlloyTeam 推荐用 Prettier 管理格式化相关的规则,用 ESLint 来检查它更擅长的逻辑错误。
配置 Prettier
- 安装 Prettier
npm install --save-dev prettier
- 配置
.prettierrc.js
仅供参考:
// .prettierrc.jsmodule.exports = { // 一行最多 100 字符 printWidth: 100, // 使用 4 个空格缩进 tabWidth: 4, // 不使用缩进符,而使用空格 useTabs: false, // 行尾需要有分号 semi: true, // 使用单引号 singleQuote: true, // 对象的 key 仅在必要时用引号 quoteProps: 'as-needed', // jsx 不使用单引号,而使用双引号 jsxSingleQuote: false, // 末尾不需要逗号 trailingComma: 'none', // 大括号内的首尾需要空格 bracketSpacing: true, // jsx 标签的反尖括号需要换行 jsxBracketSameLine: false, // 箭头函数,只有一个参数的时候,也需要括号 arrowParens: 'always', // 每个文件格式化的范围是文件的全部内容 rangeStart: 0, rangeEnd: Infinity, // 不需要写文件开头的 @prettier requirePragma: false, // 不需要自动在文件开头插入 @prettier insertPragma: false, // 使用默认的折行标准 proseWrap: 'preserve', // 根据显示样式决定 html 要不要折行 htmlWhitespaceSensitivity: 'css', // 换行符使用 lf endOfLine: 'lf'};
VSCode 集成 Prettier
- 在
.vscode/settings.json
中添加配置:
{ // 保存时自动格式化所有支持文件 javascript/javascriptreact/typescript/typescriptreact/json/graphql "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode",}
- 这时我们保存文件的时候,已经可以自动格式化了
- 也可以指定格式化文件类型:
{ // Set the default "editor.formatOnSave": false, // Enable per-language "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }}
Git 代码预检
- 上面我们配置了
ESLint、Prettier
集成了VSCode插件,实现了错误提示和保存自动修复 - 然而自动修复的只是小部分,如果团队成员不按规范,依然可以将不规范的代码推送至远程代码库
- 我们通过Git 代码预检,一定程度防止不规范的代码被提交
实现过程
- 待提交的代码
- git add 添加到暂存区
- 执行 git commit(这时进行代码预检)
- husky注册在git pre-commit的钩子调起 lint-staged
- lint-staged 取得所有被提交的文件依次执行写好的任务
- 如果有错误(没通过ESlint检查)则停止任务,等待下次commit,同时打印错误信息
- 成功提交后,git push推送到远程库
什么是 git hook
git hook
就是.git文件夹的hooks下的一些钩子函数,特定时机他们将被调用- 查看所有 git 钩子函数:
cd .git/hooksls -l// 打印如下:total 96-rwxr-xr-x 1 zzc staff 478 10 21 2019 applypatch-msg.sample-rwxr-xr-x 1 zzc staff 896 10 21 2019 commit-msg.sample-rwxr-xr-x 1 zzc staff 3327 10 21 2019 fsmonitor-watchman.sample-rwxr-xr-x 1 zzc staff 189 10 21 2019 post-update.sample-rwxr-xr-x 1 zzc staff 424 10 21 2019 pre-applypatch.sample-rwxr-xr-x 1 zzc staff 1638 10 21 2019 pre-commit.sample-rwxr-xr-x 1 zzc staff 1348 10 21 2019 pre-push.sample-rwxr-xr-x 1 zzc staff 4898 10 21 2019 pre-rebase.sample-rwxr-xr-x 1 zzc staff 544 10 21 2019 pre-receive.sample-rwxr-xr-x 1 zzc staff 1492 10 21 2019 prepare-commit-msg.sample-rwxr-xr-x 1 zzc staff 3610 10 21 2019 update.sample
.sample
为各个钩子的案例脚本,可以把sample去掉,直接编写shell脚本来执行。- 而前端可以用插件husky与pre-commit,来使钩子生效。
husky 注册 git hook
Requires Node >= 10 and Git >= 2.13.0.
husky
新老版本的配置方式和使用变化较大,老版本请自行升级,详见 husky
- 安装 husky
npm install husky --save-dev
- 编辑
package.json
文件:
{ "husky": { "hooks": { "pre-commit": "eslint src/**/*.js" } },}
- 尝试
git commit
提交,就会先执行eslint src/**/*.js
,代码没有问题才会被真正提交 - 这样每次提交代码,
eslint
都会检查所有文件,如果报错过多,一定会崩溃
lint-staged 只 Lint 改动代码
lint-staged requires Node.js version 10.13.0 or later.
- v10.0.0 以后对原始暂存文件的任何新修改都将自动添加到提交中。如果您的任务以前包含一个git add步骤,请删除此步骤,同时运行多个git操作通常会导致错误,详见 lint-staged
- 安装 lint-staged
npm install lint-staged --save-dev
- 新增
package.json
配置:
{ "lint-staged": { "src/**/*.js": "eslint" }}
- 如此
husky
只负责注册git hook
,后续操作交给lint-staged
,只对改动的文件执行任务,而且可以很方便
地配置多条命令:
{ "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "src/**/*.js": ["eslint --fix", "prettier --write"] }}
- 如上,我们提交代码之前,程序会自动修复
eslint
配置,格式化prettier
配置
几点建议
- 建议代码提交只做检查和测试,拦截问题代码比较好,还是在保存时,自动修复
eslint、prettier
配置,而且大部分还需要手动修复才行 - 实在紧急,也可通过
git commit -m -n "跳过代码代码预检"
跳过检查,慎用 - 和构建有关的包建议使用
--save-dev
安装在项目内部 - 老版本
husky lint-staged
配置都放在package.json
中,现在eslint prettier husky lint-staged
都支持多种后缀配置文件,建议采用.js
统一格式,也方便拓展:
统一配置文件格式
// .eslintrc.jsmodule.exports = { extends: [ 'alloy', ],};// .prettierrc.jsmodule.exports = { // 一行最多 100 字符 printWidth: 100, // 使用 4 个空格缩进 tabWidth: 4, // ...};// .huskyrc.jsmodule.exports = { 'hooks': { 'pre-commit': "lint-staged" }}// .lintstagedrc.jsmodule.exports = { "src/**/*.{js,ts}": "eslint"}
拓展示例
.huskyrc.js
// 数组方式配置多条命令const tasks = arr => arr.join(' && ')module.exports = { 'hooks': { 'pre-commit': tasks([ 'npm run lint', 'npm test' ]) }}
.lintstagedrc.js
module.exports = { // 如果超过10个暂存文件,则在整个存储库上运行eslint '**/*.js?(x)': (filenames) => filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`, "*.css": "stylelint", "*.scss": "stylelint --syntax=scss", // 对ts文件运行tsc,但不传递任何参数 '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit'}