背景
你是否会遇到提交代码时,没有改到同事业务模块的任何一行代码,却被提示冲突?提交注释凌乱看不懂的情况?等等。
那么为了项目代码风格统一,代码格式化规范统一,避免代码冲突,提高代码的规范性,提高CodeReview效率等等。因此结合Eslint + Prettier + Husky + Commitlint+ Lint-staged的前端工程化规范应运而生,最终提升了我们开发效率、项目质量。
配置eslint
执行安装命令
执行eslint初始化命令
依次初始化选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| (1) How would you like to use ESLint? 选择:To check syntax and find problems
(2) What type of modules does your project use? 选择:JavaScript modules (import/export)
(3) Which framework does your project use? 选择:Vue.js
(4) Does your project use TypeScript? 选择:Yes
(5) Where does your code run? 选择:Browser
(6) What format do you want your config file to be in? 选择:JavaScript
(7) Would you like to install them now? 选择:Yes
(8) Which package manager do you want to use? 选择:npm
|
依赖安装完成后,会生成.eslintrc.js配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:vue/vue3-essential", "plugin:@typescript-eslint/recommended" ], "parserOptions": { "ecmaVersion": "latest", "parser": "@typescript-eslint/parser", "sourceType": "module" }, "plugins": [ "vue", "@typescript-eslint" ], "rules": { } }
|
此时打开.eslintrc.js配置文件会出现一个报错,需要再env字段中增加node: true配置以解决eslint找不到module的报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| module.exports = { "env": { "browser": true, "es2021": true, // 新增 + "node": true }, "extends": [ "eslint:recommended", "plugin:vue/vue3-essential", "plugin:@typescript-eslint/recommended" ], "parserOptions": { "ecmaVersion": "latest", "parser": "@typescript-eslint/parser", "sourceType": "module" }, "plugins": [ "vue", "@typescript-eslint" ], "rules": { } }
|
在package.json文件中的script中添加lint命令
1 2 3 4 5 6 7 8
| { "scripts": { // eslint . 为指定lint当前项目中的文件 // --ext 为指定lint哪些后缀的文件 // --fix 开启自动修复 "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix" } }
|
执行lint命令
这时候命令行中会出现报错,意思就是在解析.vue后缀的文件时候出现解析错误parsing error。
查阅资料后发现,eslint默认是不会解析.vue后缀文件的。因此,需要一个额外的解析器来解析.vue后缀文件。
但是我们查看.eslintrc.js文件中的extends会发现已经有继承”plugin:vue/vue3-essential”的配置。然后在node_modules中可以找到eslint-plugin-vue/lib/cinfigs/essential,里面配置了extends是继承于同级目录下的base.js,在里面会发现parser: require.resolve(‘vue-eslint-parser’)这个配置。因此,按道理来说应该是会解析.vue后缀文件的。
继续往下看.eslintrc.js文件中的extends会发现,extends中还有一个”plugin:@typescript-eslint/recommended”,它是来自于/node_modules/@typescript-eslint/eslint-plugin/dist/configs/recommended.js,查看该文件会发现最终继承于同级目录下的base.js文件。从该文件中可以发现parser: ‘@typescript-eslint/parser’,配置。
按照.eslintrc.js文件中的extends配置的顺序可知,最终导致报错的原因就是@typescript-eslint/parser把vue-eslint-parser覆盖了。
1 2 3 4 5 6 7
| { "extends": [ "eslint:recommended", "plugin:vue/vue3-essential", "plugin:@typescript-eslint/recommended" ], }
|
查看eslint-plugin-vue官方文档可知。如果已经使用了另外的解析器(例如”parser”: “@typescript-eslint/parser”),则需要将其移至parseOptions,这样才不会与vue-eslint-parser冲突。
修改.eslintrc.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| module.exports = { "env": { "browser": true, "es2021": true, "node": true }, "extends": [ "eslint:recommended", "plugin:vue/vue3-essential", "plugin:@typescript-eslint/recommended" ], "parser": "vue-eslint-parser", "parserOptions": { "ecmaVersion": "latest", "parser": "@typescript-eslint/parser", "sourceType": "module" }, "plugins": [ "vue", "@typescript-eslint" ], "rules": { } }
|
两个parser的区别在于,外面的parser用来解析.vue后缀文件,使得eslint能解析template标签中的内容,而parserOptions中的parser,即@typescript-eslint/parser用来解析vue文件中script标签中的代码。
此时,再执行npm lint,就会发现校验通过了。
安装vscode插件ESLint
如果写一行代码就要执行一遍lint命令,这效率就太低了。所以我们可以配合vscode的ESLint插件,实现每次保存代码时,自动执行lint命令来修复代码的错误。
在项目中新建.vscode/settings.json文件,然后在其中加入以下配置。
1 2 3 4 5 6 7
| { // 开启自动修复 "editor.codeActionsOnSave": { "source.fixAll": false, "source.fixAll.eslint": true } }
|
安装依赖说明
- eslint: JavaScript 和 JSX 检查工具
- eslint-plugin-vue: 使用 ESLint 检查 .vue文件 的 template和script,以及.js文件中的Vue代码
配置prettier
执行安装命令
在根目录下新建.prettierrc.js
添加以下配置,更多配置可查看官方文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| module.exports = { // 一行的字符数,如果超过会进行换行,默认为80 printWidth: 80, // 一个tab代表几个空格数,默认为80 tabWidth: 2, // 是否使用tab进行缩进,默认为false,表示用空格进行缩减 useTabs: false, // 字符串是否使用单引号,默认为false,使用双引号 singleQuote: true, // 行位是否使用分号,默认为true semi: false, // 是否使用尾逗号,有三个可选值"<none|es5|all>" trailingComma: "none", // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar } bracketSpacing: true }
|
在package.json中的script中添加以下命令
1 2 3 4 5
| { "scripts": { "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"", } }
|
运行该命令,会将我们项目中的文件都格式化一遍,后续如果添加其他格式的文件,可在该命令中添加,例如:.less后缀的文件
安装该插件的目的是,让该插件在我们保存的时候自动完成格式化
在.vscode/settings.json中添加一下规则
1 2 3 4 5 6
| { // 保存的时候自动格式化 "editor.formatOnSave": true, // 默认格式化工具选择prettier "editor.defaultFormatter": "esbenp.prettier-vscode" }
|
解决eslint与prettier的冲突
在理想的状态下,eslint与prettier应该各司其职。eslint负责我们的代码质量,prettier负责我们的代码格式。但是在使用的过程中会发现,由于我们开启了自动化的eslint修复与自动化的根据prettier来格式化代码。所以我们已保存代码,会出现屏幕闪一起后又恢复到了报错的状态。
这其中的根本原因就是eslint有部分规则与prettier冲突了,所以保存的时候显示运行了eslint的修复命令,然后再运行prettier格式化,所以就会出现屏幕闪一下然后又恢复到报错的现象。这时候你可以检查一下是否存在冲突的规则。
查阅资料会发现,社区已经为我们提供了一个非常成熟的方案,即eslint-config-prettier + eslint-plugin-prettier。
- eslint-plugin-prettier: 基于 prettier 代码风格的 eslint 规则,即使eslint使用pretter规则来格式化代码。
- eslint-config-prettier: 禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突,确保将其放在 extends 队列最后,这样它将覆盖其他配置
安装依赖
1
| npm add eslint-config-prettier eslint-plugin-prettier -D
|
在 .eslintrc.json中extends的最后添加一个配置
1 2 3 4 5 6 7 8 9
| { extends: [ 'eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended', + // 新增,必须放在最后面 + 'plugin:prettier/recommended' ], }
|
最后重启vscode,你会发现冲突消失了,eslint与prettier也按照我们预想的各司其职了。
配置stylelint
stylelint为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等…
安装依赖
由于我的项目使用的less预处理器,因此配置的为less相关的,项目中使用其他预处理器的可以按照该配置方法改一下就好
stylelint v13版本将css, parse CSS(如SCSS,SASS),html内的css(如*.vue中的style)等编译工具都包含在内。但是v14版本没有包含在内,所以需要安装需要的工具
1
| npm add stylelint postcss postcss-less postcss-html stylelint-config-prettier stylelint-config-recommended-less stylelint-config-standard stylelint-config-standard-vue stylelint-less stylelint-order -D
|
依赖说明:
- stylelint: css样式lint工具
- postcss: 转换css代码工具
- postcss-less: 识别less语法
- postcss-html: 识别html/vue 中的style标签中的样式
- stylelint-config-standard: Stylelint的标准可共享配置规则,详细可查看官方文档
- stylelint-config-prettier: 关闭所有不必要或可能与Prettier冲突的规则
- stylelint-config-recommended-less: less的推荐可共享配置规则,详细可查看官方文档
- stylelint-config-standard-vue: lint.vue文件的样式配置
- stylelint-less: stylelint-config-recommended-less的依赖,less的stylelint规则集合
- stylelint-order: 指定样式书写的顺序,在.stylelintrc.js中order/properties-order指定顺序
增加.stylelintrc.js配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| module.exports = { extends: [ 'stylelint-config-standard', 'stylelint-config-prettier', 'stylelint-config-recommended-less', 'stylelint-config-standard-vue' ], plugins: ['stylelint-order'], // 不同格式的文件指定自定义语法 overrides: [ { files: ['**/*.(less|css|vue|html)'], customSyntax: 'postcss-less' }, { files: ['**/*.(html|vue)'], customSyntax: 'postcss-html' } ], ignoreFiles: [ '**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml' ], rules: { 'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器 'selector-pseudo-element-no-unknown': [ true, { ignorePseudoElements: ['v-deep'] } ], 'selector-pseudo-class-no-unknown': [ true, { ignorePseudoClasses: ['deep'] } ], // 指定样式的排序 'order/properties-order': [ 'position', 'top', 'right', 'bottom', 'left', 'z-index', 'display', 'justify-content', 'align-items', 'float', 'clear', 'overflow', 'overflow-x', 'overflow-y', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height', 'font-size', 'font-family', 'text-align', 'text-justify', 'text-indent', 'text-overflow', 'text-decoration', 'white-space', 'color', 'background', 'background-position', 'background-repeat', 'background-size', 'background-color', 'background-clip', 'border', 'border-style', 'border-width', 'border-color', 'border-top-style', 'border-top-width', 'border-top-color', 'border-right-style', 'border-right-width', 'border-right-color', 'border-bottom-style', 'border-bottom-width', 'border-bottom-color', 'border-left-style', 'border-left-width', 'border-left-color', 'border-radius', 'opacity', 'filter', 'list-style', 'outline', 'visibility', 'box-shadow', 'text-shadow', 'resize', 'transition' ] } }
|
package.json增加命令
1 2 3 4 5 6 7 8 9
| "scripts": { "prepare": "husky install", "dev": "vite", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview", "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix", "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"", + "lint:style": "stylelint \"./**/*.{css,less,vue,html}\" --fix" },
|
安装vscode的Stylelint插件
安装该插件可在我们保存代码时自动执行stylelint
在.vscode/settings.json中添加一下规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { // 开启自动修复 "editor.codeActionsOnSave": { "source.fixAll": false, "source.fixAll.eslint": true, + "source.fixAll.stylelint": true }, // 保存的时候自动格式化 "editor.formatOnSave": true, // 默认格式化工具选择prettier "editor.defaultFormatter": "esbenp.prettier-vscode", // 配置该项,新建文件时默认就是space:2 "editor.tabSize": 2, // stylelint校验的文件格式 + "stylelint.validate": ["css", "less", "vue", "html"] }
|
配置husky
虽然上面已经配置好了eslint、preitter与stylelint,但是还是存在以下问题。
对于不使用vscode的,或者没有安装eslint、preitter与stylelint插件的同学来说,就不能实现在保存的时候自动的去修复与和格式化代码。
这样提交到git仓库的代码还是不符合要求的。因此需要引入强制的手段来保证提交到git仓库的代码时符合我们的要求的。
husky是一个用来管理git hook的工具,git hook即在我们使用git提交代码的过程中会触发的钩子。
安装依赖
在package.json中的script中添加一条脚本命令
1 2 3 4 5
| { "scripts": { "prepare": "husky install" }, }
|
该命令会在npm install之后运行,这样其他克隆该项目的同学就在装包的时候就会自动执行该命令来安装husky。这里我们就不重新执行npm install了,直接执行npm prepare,这个时候你会发现多了一个.husky目录。
然后使用husky命令添加pre-commit钩子,运行
1
| npm husky add .husky/pre-commit "npm lint && npm format && npm lint:style"
|
执行完上面的命令后,会在.husky目录下生成一个pre-commit文件
1 2 3 4
| #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh"
npm lint && npm format
|
现在当我们执行git commit的时候就会执行npm lint与npm format,当这两条命令出现报错,就不会提交成功。以此来保证提交代码的质量和格式。
配置Commitlint
为什么要用Commitlint
在使用Git提交代码时,通常都需要填写提交说明,也就是Commit Message。在前面的文章中,已经介绍了如何使用Commitizen或可视化工具编写符合规范的Commit Message。然而有些同学可能还是会使用git commit方式提交一些不符合规范的Commit Message。为了禁止不符合规范的Commit Message的提交,我们就需要采用一些工具,只有当开发者编写了符合规范的Commit Message才能够进行commit。而 Commitlint就是这样一种工具,通过结合husky一起使用,可以在开发者进行commit前就对Commit Message进行检查,只有符合规范,才能够进行commit。
安装Commitlint
使用npm安装Commitlint相关依赖包。
1
| npm install @commitlint/cli @commitlint/config-conventional --save-dev
|
配置Commitlint
安装好Commitlint之后,就需要配置Commitlint,可以在根目录创建commitlint.config.js文件进行配置。
在comminlint.config.js中加入以下代码,表示使用config-conventional规范对提交说明进行检查。具体的规范配置可以查看: https://github.com/conventional-changelog/commitlint
1
| module.exports = { extends: ['@commitlint/config-conventional'] };
|
接下来,需要在package.json中加入commit-msg钩子。
1 2 3 4 5
| "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }
|
配置好了之后,当我们进行git commit时,就会触发commit-msg钩子,执行commintlint命令,并且读取commitlint.config.js中的规则对我们的提交说明进行检查,如果校验不通过,将不能提交。
配置Lint-staged
什么是Lint-staged
Lint-staged可以在git staged阶段的文件上执行Linters,简单说就是当我们运行ESlint或Stylelint命令时,可以通过设置指定只检查我们通过git add添加到暂存区的文件,可以避免我们每次检查都把整个项目的代码都检查一遍,从而提高效率。
其次,Lint-staged允许指定不同类型后缀文件执行不同指令的操作,并且可以按步骤再额外执行一些其它shell指令。
安装Lint-staged
安装Lint-staged,可以使用npm进行安装。
1
| npm install lint-staged --save-dev
|
配置Lint-staged
安装好了Lint-staged之后,就需要配置Lint-staged。我们可以在package.json中加入以下代码,这里需要先安装配置好husky,ESLint和Stylelint。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| "husky": { "hooks": { "pre-commit": "lint-staged", } }, "lint-staged": { "*.vue": [ "eslint --fix", "stylelint --fix", "git add" ], "*.{js,jsx,ts,tsx}": [ "eslint --fix", "git add" ], "*.{htm,html,css,sss,less,scss,sass}": [ "stylelint --fix", "git add" ] }
|
总结
接下来,我们就可以将这几个工具结合起来,打造完整的Git检查工作流。下面给出了一份示例代码,其中,该项目采用了Vue-cli进行构建,下面是该项目对应的package.json文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| { "name": "test", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", }, "dependencies": { "core-js": "^3.6.5", "vue": "^2.6.11", "vue-router": "^3.2.0", "vuex": "^3.4.0" }, "devDependencies": { "@commitlint/cli": "^12.1.4", "@commitlint/config-conventional": "^12.1.4","@vue/cli-plugin-babel": "^4.5.0", "@vue/cli-plugin-eslint": "^4.5.0", "@vue/cli-service": "^4.5.0", "@vue/eslint-config-prettier": "^6.0.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-vue": "^6.2.2", "husky": "^4.3.8", "less": "^3.0.4", "less-loader": "^5.0.0", "lint-staged": "^11.0.0", "prettier": "^2.2.1", "stylelint": "^13.13.1", "stylelint-config-prettier": "^8.0.2","stylelint-config-standard": "^22.0.0", "stylelint-order": "^4.1.0", "stylelint-webpack-plugin": "^2.2.2", "vue-template-compiler": "^2.6.11" }, "husky": { "hooks": { "pre-commit": "lint-staged", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, "lint-staged": { "*.vue": [ "vue-cli-service lint", "stylelint --fix", "git add" ], "*.{js,jsx,ts,tsx}": [ "vue-cli-service lint" "git add" ], "*.{htm,html,css,sss,less,scss,saas}":[ "stylelint --fix" "git add" ] } }
|
配置好package.json之后,当我们进行git commit提交时,首先将会触发pre-commit钩子,调用lint-staged命令,并且会对不同后缀的文件执行不同的检查。接着,还将会触发commit-msg钩子,调用commitlint对我们的提交说明进行检查。如果其中一个无法通过检查,将无法提交。
当校验通过时,就可以放心的将代码提交到代码仓库里。