前言
在一个团队项目中,代码风格不统一,会大大的降低可读性,也会给后期的维护增加时间成本。如果代码风格统一、代码提交信息也简单明了,那么在后期协作以及处理bug时可以达到事半功倍的效果,所以就需要在我们使用git提交时校验eslint和提交信息的格式问题。需要用到的插件 husky、lint-staged、commitlint,prettier+eslint实现代码格式代码规范统一
一、prettier
作用
格式化工具,可以在各种编辑器找到实现它的插件,如vscode,atom,webstom等,用它统一代码格式化的规则,在代码提交前做代码格式化,防止不同的人开发由于编辑器格式化的规则不同导致代码冲突
使用
安装 prettier
yarn add prettier --save-dev
安装 eslint-plugin-prettier
yarn add eslint-plugin-prettier --save-dev
在项目下创建文件.prettierrc.js
module.exports = {
// 代码结尾是否加分号
semi: false,
// 是否使用单引号
singleQuote: true,
// 对象大括号内两边是否加空格 { a:0 }
bracketSpacing: true,
// 单个参数的箭头函数不加括号 x => x
arrowParens: 'avoid',
// 超过多少字符强制换行
printWidth: 200,
// 文件换行格式 LF/CRLF
endOfLine: 'auto',
// 使用 4、个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// Vue 文件脚本和样式标签缩进
vueIndentScriptAndStyle: true
}
二、Eslint
作用
代码检查工具,eslint也可以负责一部分代码格式检查的工作,但是有prettier检查代码格式就可以了,所以只让eslint对代码错误进行检查即可
使用
安装 eslint
yarn add eslint --save-dev
安装 ts 解析器以及 ts 规则补充
yarn add @typescript-eslint/parser --save-dev
yarn add @typescript-eslint/eslint-plugin --save-dev
支持 tsx
yarn add eslint-plugin-react --save-dev
4.在项目根目录创建 .eslintrc.js
当运行 ESLint 的时候检查一个文件的时候,它会首先尝试读取该文件的目录下的配置文件,然后再一级一级往上查找,将所找到的配置合并起来,作为当前被检查文件的配置。
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['prettier', 'react', 'react-hooks', '@typescript-eslint/eslint-plugin'],
parserOptions: {
ecmaVersion: 8,
ecmaFeatures: {
experimentalObjectRestSpread: true,
impliedStrict: true,
requireConfigFile: false,
classes: true
}
},
env: {
browser: true,
node: true,
jest: true
},
globals: {
__DEV__: false,
__dirname: false,
window: true,
define: true,
history: true,
location: true,
wxjs: true,
$: true,
WeixinJSBridge: true,
wx: true,
process: true,
qq: true
},
settings: {
react: {
version: 'detect'
}
},
rules: {
'prettier/prettier': 'error',
// '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], // 优先使用 interface 而不是 type
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用该 any 类型 quchu
'@typescript-eslint/no-var-requires': 'off', // 不允许使用 require 语句,除了在 import 语句中
'@typescript-eslint/no-empty-function': 'off', // 禁止空函数
'@typescript-eslint/no-use-before-define': 'off', // 在定义之前禁止使用变量
'@typescript-eslint/ban-ts-comment': 'off', // 禁止 @ts-<directive> 使用评论或在指令后要求描述
'@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
'@typescript-eslint/no-non-null-assertion': 'off', // '!'不允许使用后缀运算符的非空断言
'@typescript-eslint/explicit-module-boundary-types': 'off', // 需要导出函数和类的公共类方法的显式返回和参数类型
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], // 禁止未使用的变量
'@typescript-eslint/no-empty-interface': ['error', { allowSingleExtends: false }], // 禁止声明空接口
'react/sort-comp': [2], // 强制组件方法顺序
'react/jsx-boolean-value': 2, // 在JSX中强制布尔属性符号
'react/jsx-curly-spacing': [2, { when: 'never', children: true }], // 在JSX属性和表达式中加强或禁止大括号内的空格。
'react/jsx-key': 2, // 在数组或迭代器中验证JSX具有key属性
'react/jsx-max-props-per-line': [1, { maximum: 5 }], // 限制JSX中单行上的props的最大数量
'react/jsx-no-duplicate-props': 2, // 防止在JSX中重复的props
'react/jsx-no-undef': 2, // 在JSX中禁止未声明的变量
'react/jsx-pascal-case': 0, // 为用户定义的JSX组件强制使用PascalCase
'react/jsx-uses-react': 2, // 防止反应被错误地标记为未使用
'react/jsx-uses-vars': 2, // 防止在JSX中使用的变量被错误地标记为未使用
'react/no-did-mount-set-state': 2, // 防止在componentDidMount中使用setState
'react/no-did-update-set-state': 2, // 防止在componentDidUpdate中使用setState
'react/no-unknown-property': 2, // 防止使用未知的DOM属性
'react/prefer-es6-class': 2, // 为React组件强制执行ES5或ES6类
// 'react/react-in-jsx-scope': 2, // 使用JSX时防止丢失React 就是在每个页面要倒入react
'react/self-closing-comp': [2, { component: true, html: false }], // 结束标签,组件省略闭合标签,html不省略闭合标签
'react/no-deprecated': 2, // 不使用弃用的方法
'react/jsx-equals-spacing': 2, // 在JSX属性中强制或禁止等号周围的空格
'react/jsx-filename-extension': [2, { extensions: ['.ts', '.tsx'] }],
'react-hooks/exhaustive-deps': [2], // 检查 effect 的依赖
'react-hooks/rules-of-hooks': [2], // 检查 Hook 的规则,不允许在if for里面使用
'no-dupe-keys': 2, // 禁止对象字面量中出现重复的 key
'no-duplicate-case': 2, // 禁止重复的 case 标签
'no-empty': 2, // 禁止空语句块
'no-ex-assign': 2, // 禁止对 catch 子句的参数重新赋值
'no-extra-boolean-cast': 2, // 禁止不必要的布尔转换
curly: [2, 'all'], // 强制所有控制语句使用一致的括号风格
'no-catch-shadow': 0, // 禁止 catch 子句的参数与外层作用域中的变量同名
'no-undef-init': 2, // 禁止将变量初始化为 undefined
'array-bracket-spacing': [2, 'never'], // 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
'computed-property-spacing': [2, 'never'], // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
'func-call-spacing': 2, // 要求或禁止在函数标识符和其调用之间有空格
'lines-around-comment': [2, { beforeBlockComment: true }], // 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
'max-nested-callbacks': [2, 5], // 强制回调函数最大嵌套深度 5层
'jsx-quotes': 0, // 强制在 JSX 属性中一致地使用双引号或单引号
'max-len': [2, 200, { ignoreUrls: true }], // 强制一行的最大长度
'max-params': [1, 5], // 强制 function 定义中最多允许的参数数量
'max-statements': [1, 200], // 强制 function 块最多允许的的语句数量
'newline-per-chained-call': 1, // 要求方法链中每个调用都有一个换行符
'no-whitespace-before-property': 2, // 禁止属性前有空白
'no-case-declarations': ['warn'], // 禁止词法声明 (let、const、function 和 class) 出现在 case或default 子句中
'arrow-body-style': 2, // 要求箭头函数体使用大括号
'arrow-parens': 2, // 要求箭头函数的参数使用圆括号
'no-class-assign': 2, // 禁止修改类声明的变量
'no-const-assign': 2, // 禁止修改 const 声明的变量
'no-dupe-class-members': 2, // 禁止类成员中出现重复的名称
'no-this-before-super': 2, // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
'object-curly-spacing': ['error', 'always'], // 对象前后要加空格 { a: 1 }
'arrow-spacing': [2, { before: true, after: true }], // 强制箭头函数的箭头前后使用一致的空格
'no-use-before-define': 'off', // 禁止在变量定义之前使用它们
'space-before-function-paren': 'off', // 强制在 function的左括号之前使用一致的空格
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'accessor-pairs': 2, // 强制 getter 和 setter 在对象中成对出现
'block-spacing': [2, 'always'], // 禁止或强制在代码块中开括号前和闭括号后有空格 (要求使用一个或多个空格)
'brace-style': [2, '1tbs', { allowSingleLine: true }], // if while function 后面的{必须与if在同一行,java风格。
camelcase: [2, { properties: 'always' }], // 强制驼峰法命名
'comma-spacing': [2, { before: false, after: true }], // 控制逗号前后的空格
'comma-style': [2, 'last'], // 控制逗号在行尾出现还是在行首出现
'dot-location': [2, 'property'],
'eol-last': 2, // 文件末尾强制换行
eqeqeq: [2, 'allow-null'], // 使用 === 替代 ==
'key-spacing': [2, { beforeColon: false, afterColon: true }], // 对象字面量中冒号的前后空格
'keyword-spacing': [2, { before: true, after: true }], // 强制在关键字前后使用一致的空格
'new-cap': [2, { newIsCap: true, capIsNew: false }], // 函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用
'init-declarations': 0, // 声明时必须赋初值
'max-depth': [0, 4], // 嵌套块深度
'no-dupe-args': 2, // 函数参数不能重复
'no-func-assign': 2, // 禁止重复的函数声明
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'default-case': 2, // switch语句最后必须有default
'no-var': 0, // 禁用var,用let和const代替
'new-parens': 2, // new时必须加小括号
'no-array-constructor': 2, // 禁止使用数组构造器
'no-caller': 2, // 禁止使用arguments.caller或arguments.callee
'no-empty-character-class': 0, // 正则表达式中的[]内容不能为空
'no-eval': 0, // 禁止使用eval
'no-extend-native': 2, // 禁止扩展native对象
'no-extra-bind': 2, // 禁止不必要的函数绑定
'no-extra-parens': [2, 'functions'], // 禁止非必要的括号
'no-floating-decimal': 2, // 禁止省略浮点数中的0 .5 3.
'no-implied-eval': 2, // 禁止使用隐式eval
'no-label-var': 2, // label名不能与var声明的变量名相同
'no-lone-blocks': 2, // 禁止不必要的嵌套块
'no-multi-spaces': 2, // 不能用多余的空格
'no-multi-str': 2, // 禁止使用多行字符串
'no-multiple-empty-lines': [2, { max: 2 }], // 空行最多不能超过2行
'no-new-object': 2, // 禁止使用new Object()
'no-new-require': 2, // 禁止使用new require
'no-new-wrappers': 2, // 禁止对 String,Number 和 Boolean 使用 new 操作符
'no-return-assign': [2, 'except-parens'], // return 语句中不能有赋值表达式
'no-sequences': 2, // 禁止使用逗号运算符
'no-spaced-func': 2, // 函数调用时 函数名与()之间不能有空格
'no-sparse-arrays': 0, // 禁止稀疏数组, [1,,2]
'no-trailing-spaces': 2, // 一行结束后面不要有空格
'no-unexpected-multiline': 0, // 避免多行表达式
'no-unmodified-loop-condition': 0, // 检查引用是否在循环中被修改
'no-unneeded-ternary': 2, // 禁止不必要的三元表达式 var isYes = answer === 1 ? true : false;
'no-unsafe-finally': 0, // 禁止在 finally 语句块中出现控制流语句
'no-useless-call': 2, // 禁止不必要的call和apply
'no-useless-computed-key': 0, // 禁止在对象中使用不必要的计算属性
'no-useless-constructor': 2, // 可以在不改变类的工作方式的情况下安全地移除的类构造函数
'one-var': 0, // 禁止连续声明
'operator-linebreak': [2, 'after', { overrides: { '?': 'before', ':': 'before' } }], // 换行时运算符在行尾还是行首
'padded-blocks': 0, // 块语句内行首行尾是否要空行
quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], // 强制使用一致的反勾号、双引号或单引号
'semi-spacing': [2, { before: false, after: true }], // 分号前后空格
'space-before-blocks': [2, 'always'], // 不以新行开始的块{前面要不要有空格
'space-in-parens': [2, 'never'], // 小括号里面要不要有空格
'space-infix-ops': 2, // 中缀操作符周围要不要有空格
'space-unary-ops': [2, { words: true, nonwords: false }], // 一元运算符的前/后要不要加空格
'spaced-comment': [2, 'always'], // 强制在注释中 // 或 /* 使用一致的空格
'template-curly-spacing': [2, 'never'], // 要求或禁止模板字符串中的嵌入表达式周围空格的使用
yoda: [2, 'never'], // 禁止尤达条件
'no-extra-semi': 'off'
}
}
三、husky
作用
GitHook工具(下一篇会详细介绍GitHook)husky会在你提交前,调用pre-commit钩子,执行lint-staged,如果代码不符合prettier配置的规则,会进行格式化;然后再用eslint的规则进行检查,如果有不符合规则的且无法自动修复的,就会停止提交。
使用
先安装最新的husky
npm install husky --save-dev
启动git钩子。启用后就可以看到项目根目录下生成了.husky文件夹。
npx husky install
然后依次生成我们需要的 git hook。引号中的内容是需要执行的脚本,可以在对应的官网中找到,下面会给出地址。
npx husky add .husky/pre-commit 'lint-staged'
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
生成后就可以在.husky/下看到我们生成的两个git hook,commit-msg和 pre-commit 。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
至此husky就算安装完成了,两种方式都可以,看自己的需求,旧版是将全部的git hook都生成,新版的只取自己需要的。
四、 lint-staged
作用
lint-staged能够让lint只检测暂存区的文件,即当前提交的文件,这样可以加快速度
使用
每次检查代码我们不需要检查全项目的文件,那样只会增加时间成本,这时候就需要用到 lint-staged,一个仅仅过滤出 Git 代码暂存区文件(被 git add 的文件)的工具。依旧是先安装。
npm install lint-staged
package.json中配置:
{
"lint-staged": {
"**/*.less": "stylelint --syntax less",
"**/*.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx",
"**/*.{js,jsx,tsx,ts,less,md,json}": [
"prettier --write",
"git add"
]
}
}
五、 commitlint
作用
在代码提交前,对commit提交的信息说明进行校验,如果不符合规范则不给提交
使用
正如官方文档中所言,commitlint帮助你的团队遵守commit约定,检查你的提交消息是否符合传统的提交格式。
npm install --save-dev @commitlint/config-conventional @commitlint/cli
随后要添加配置文件,文件的格式可以是 commitlint.config.js、.commitlintrc.js、.commitlintrc、 .commitlintrc.json、 .commitlintrc.yml文件,或 package.json中的 commitlint字段中定义配置。可以自己创建,也可以按照官网的方式使用下面这行代码生成,这需要在项目的git命令行中操作(通常是在项目目录中使用右键选择Git Bash Here)。
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'start', // 开始做某事,比如创建分支等
'end', // 结束做某事,比如删除分支等
'bump', // 修改某个版本号
'del', // 删除功能
'feat', // 新功能、新特性
'fix', // 修改 bug
'perf', // 更改代码,性能优化
'refactor', // 代码重构,没有加新功能或者修复bug(在不影响代码内部行为、功能下的代码修改)
'docs', // 文档修改
'style', // 代码格式修改, 注意不是 css 修改(例如分号修改)
'test', // 测试用例新增、修改
'conf', // 配置文件修改
'revert', // 恢复上一次提交
'ci', // 持续集成相关文件修改
'chore', // 改变构建流程、或者增加依赖库、工具等
'release', // 发布新版本
'workflow' // 工作流相关文件修改
]
],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never']
}
}
配置package.json
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,tsx,ts,less,scss}": [
"npm run lint",
"git add"
]
}
提交示列 git commit -m 'fix: 修改xxxbug'