> 文章列表 > Eslint、Stylelint、Prettier、lint-staged、husky、commitlint【前端代码校验规则】

Eslint、Stylelint、Prettier、lint-staged、husky、commitlint【前端代码校验规则】

Eslint、Stylelint、Prettier、lint-staged、husky、commitlint【前端代码校验规则】

一、Eslint

yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-config-standard-with-typescript eslint-plugin-import eslint-plugin-n eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks -D

.eslintrc.js

module.exports = {env: {browser: true,es2021: true,node: true,},extends: ["plugin:react/recommended", "standard-with-typescript", "prettier"],overrides: [],parser: "@typescript-eslint/parser",parserOptions: {ecmaFeatures: {impliedStrict: true,jsx: true,},ecmaVersion: "latest",sourceType: "module",project: "tsconfig.json",},plugins: ["react", "prettier"],/*** off或者0 => 关闭规则* warn或者1 => 开启规则,警告(不影响代码执行)* error或者2 => 开启规则,报错(代码不能执行,界面报错)*/rules: {// @fixable 必须使用 === 或 !==,禁止使用 == 或 !=,与 null 比较时除外eqeqeq: ["error","always",{null: "ignore",},],semi: [2, "always"],// 引号风格,使用单引号,需要转义时忽略,反斜号忽略// quotes: [//     2,//     'single',//     {//         avoidEscape: true,//         allowTemplateLiterals: true,//     },// ],"@typescript-eslint/strict-boolean-expressions": 0,"@typescript-eslint/prefer-optional-chain": 0,"@typescript-eslint/restrict-template-expressions": [2,{allowNumber: true,allowBoolean: true,allowAny: true,allowNullish: true,allowRegExp: true,},],"@typescript-eslint/semi": [2, "always"],"@typescript-eslint/no-useless-constructor": 2,"@typescript-eslint/no-empty-function": 1, // 禁止空函数"@typescript-eslint/no-var-requires": 0, // 不允许在 import 语句中使用 require 语句"@typescript-eslint/explicit-function-return-type": 0, // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明"@typescript-eslint/explicit-module-boundary-types": 0, // 要求导出函数和类的公共类方法的显式返回和参数类型"@typescript-eslint/no-explicit-any": 0, // 禁止使用 any 类型"@typescript-eslint/no-use-before-define": 2,"@typescript-eslint/no-unused-vars": 1, // 禁止定义未使用的变量"@typescript-eslint/naming-convention": [2,{selector: "variable",format: ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],leadingUnderscore: "forbid",filter: {// 忽略部分无法控制的命名regex: "^(__html|zh_CN|en_US)$",match: false,},},{selector: "property",format: ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],leadingUnderscore: "forbid",filter: {// 忽略部分无法控制的命名regex: "^(__html|zh_CN|en_US)$",match: false,},},{selector: "class",format: ["PascalCase"],leadingUnderscore: "forbid",},{selector: "interface",format: ["PascalCase"],leadingUnderscore: "forbid",},{selector: "parameter",format: ["camelCase", "UPPER_CASE", "snake_case"],leadingUnderscore: "allow",filter: {regex: "^(Child|WrappedComponent)$",match: false,},},{selector: "memberLike",format: ["PascalCase", "camelCase", "snake_case"],leadingUnderscore: "forbid",filter: {// 忽略部分无法控制的命名regex: "^(__html|zh_TW|zh_CN|en_US|_ga|SignUp_.*|Crm_.*|UNSAFE_.*)$",match: false,},},{selector: "typeLike",format: ["PascalCase", "camelCase", "snake_case"],leadingUnderscore: "forbid",},],// 类和接口的命名必须遵守帕斯卡命名法,比如 PersianCat"@typescript-eslint/class-name-casing": 0,"@typescript-eslint/camelcase": 0,"@typescript-eslint/ban-types": [// 禁止使用特定类型0,{extendDefaults: true,types: {"{}": false,Function: false,},},],"n/no-callback-literal": 0,"linebreak-style": [2, "unix"],"no-unused-expressions": [2, { allowShortCircuit: true, allowTernary: true }],"no-plusplus": 0,"no-console": 1, //console.log"no-debugger": 2,"linkbreak-style": ["off", "windows"],"no-fallthrough": [2,{commentPattern: "fallthrough",},],"no-const-assign": 2, //禁止修改const声明的变量"no-unused-vars": 1,"class-methods-use-this": 0,camelcase: 0,"global-require": 0,"no-use-before-define": 0,"no-restricted-syntax": 0,"no-continue": 0,"consistent-return": 0,"prefer-const": [2,{destructuring: "all",},],"key-spacing": [0, { beforeColon: false, afterColon: true }], // 对象字面量中冒号的前后空格"object-curly-spacing": [0, "never"], // 大括号内是否允许不必要的空格"generator-star-spacing": 0, // 生成器函数*的前后空格"comma-spacing": 0, // 逗号前后的空格"array-bracket-spacing": [2, "never"], // 是否允许非空数组里面有多余的空格"no-trailing-spaces": 1, // 一行结束后面不要有空格"no-multiple-empty-lines": 1, // 删除多余的空行"no-spaced-func": 2, // 函数调用时 函数名与()之间不能有空格"no-regex-spaces": 2, // 禁止在正则表达式字面量中使用多个空格 /foo bar/"no-multi-spaces": 1, // 不能用多余的空格"no-mixed-spaces-and-tabs": [2, false], // 禁止混用tab和空格"no-irregular-whitespac": 0, // 不能有不规则的空格"require-render-return": 0, // 确保在 render 方法中存在返回值"react/react-in-jsx-scope": 0, // 可用空标签<></>// 自闭合的标签前要加一个空格"no-multi-spaces": 1,"react/jsx-tag-spacing": 1,"react/self-closing-comp": 2, // 没有子元素的标签请自闭合"react/jsx-closing-bracket-location": 1, // 如果组件包含多行属性,在新的一行闭合标签"react/no-array-index-key": 0, // 避免使用数组的索引作为 key 属性值, 建议使用稳定的ID"react/jsx-boolean-value": 1, // 当属性值为true时可以省略"react/jsx-curly-spacing": 1, // 不要在 JSX 的花括号里边加空格"react/jsx-closing-bracket-location": 1, // 对齐:遵循以下JSX语法的对齐风格"react/no-multi-comp": 2, // 每个文件只包含一个 React 类组件,但是多个函数式组件可以放到一个文件中"react/jsx-pascal-case": 2, // 引用命名:React 组件使用 大驼峰,组件实例使用 小驼峰"react/jsx-filename-extension": [2, { extensions: [".tsx", "ts", ".jsx", "js"] }],"react/jsx-indent-props": [2, 2],"react/jsx-indent": [2, 2],"react/jsx-one-expression-per-line": 0,"react/destructuring-assignment": 0,"react/state-in-constructor": 0,"react/require-default-props": 0,"react/jsx-props-no-spreading": 0,"react/prop-types": 0,"jsx-quotes": [2, "prefer-single"],},
};

忽略检查 .eslintignore

# 忽略不必要检查的文件
*.sh
*.md
*.woff
*.ttf
.vscode
.idea
.husky
.local
.nyc_output
/mock/*
/dist/*
/static
/public
/docs
/node_modules
/bin
/test
/unittest
/**/build
/**/dist
/**/static
.eslintrc.js
.prettierrc.js

vscode 安装eslint

二、Stylelint

yarn add stylelint stylelint-config-prettier stylelint-config-rational-order stylelint-config-recess-order stylelint-config-standard stylelint-declaration-block-no-ignored-properties stylelint-less stylelint-prettier -D

.stylelintrc.js

module.exports = {extends: ["stylelint-config-standard", // 配置stylelint拓展插件"stylelint-config-recess-order", // 配置stylelint css属性书写顺序插件,"stylelint-config-rational-order","stylelint-config-prettier", // 配置stylelint和prettier兼容],plugins: ["stylelint-order", //指定事物的排序,例如声明块中的属性(插件包)。"stylelint-declaration-block-no-ignored-properties", //禁止由于同一规则中的另一个属性值而被忽略的属性值。"stylelint-less", // 配置stylelint less拓展插件"stylelint-prettier", //将 Prettier 作为 stylelint 规则运行。],rules: {"function-calc-no-unspaced-operator": true, //禁止在 calc 函数中使用没有间隔的运算符。"function-linear-gradient-no-nonstandard-direction": null, //禁止在 linear-gradient() 中调用不符合标准语法的无效方"plugin/declaration-block-no-ignored-properties": true,"comment-empty-line-before": null,"declaration-empty-line-before": null,"function-name-case": "lower","no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器"function-url-quotes": "always", // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)""string-quotes": "double", // 指定字符串使用单引号或双引号"unit-case": null, // 指定单位的大小写 "lower(全小写)"|"upper(全大写)""color-hex-case": "lower", // 指定 16 进制颜色的大小写 "lower(全小写)"|"upper(全大写)""color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简写)"|"long(16进制扩写)""no-invalid-double-slash-comments": null,"block-no-empty": true, //禁止空块"comment-no-empty": true, // 禁止空注释"no-extra-semicolons": true, //禁止额外的分号(可自动修复)"value-keyword-case": null,"rule-empty-line-before": ["always", { except: ["after-single-line-comment", "first-nested"] }], // 要求或禁止在规则之前的空行 "always(规则之前必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有一个空行)"|"never-multi-line(多行规则之前绝不能有空行。)""font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表中缺少通用字体族关键字"block-opening-brace-space-before": "always", // 要求在块的开大括号之前必须有一个空格或不能有空白符 "always(大括号前必须始终有一个空格)"|"never(左大括号之前绝不能有空格)"|"always-single-line(在单行块中的左大括号之前必须始终有一个空格)"|"never-single-line(在单行块中的左大括号之前绝不能有空格)"|"always-multi-line(在多行块中,左大括号之前必须始终有一个空格)"|"never-multi-line(多行块中的左大括号之前绝不能有空格)""property-no-unknown": null, // 禁止未知的属性(true 为不允许)"property-no-unknown": [true,{ignoreProperties: ["box-flex"], // 忽略某些未知属性的检测},],"selector-pseudo-element-no-unknown": [true,{ignorePseudoElements: ["ng-deep", "input-placeholder"], // 忽略ng-deep这种合法的伪元素选择器报警},],"declaration-colon-newline-after": null, //一个属性过长的话可以写成多行"media-feature-name-no-unknown": null, // 关闭禁止未知的媒体功能名"no-empty-source": null, // 禁止空源码"declaration-block-trailing-semicolon": null, // 要求或不允许在声明块中使用尾随分号 string:"always(必须始终有一个尾随分号)"|"never(不得有尾随分号)""at-rule-no-unknown": null, //禁止未知的@规则"property-no-vendor-prefix": null, // 保留各大浏览器不兼容的样式属性名前缀, 如 -moz-user-select: auto"value-no-vendor-prefix": null, // 保留各大浏览器不兼容的样式属性值前缀,display: -webkit-box;"selector-no-vendor-prefix": null, // 保留各大浏览器不兼容的选择器前缀,如input::-webkit-input-placeholder"color-function-notation": "legacy", // 屏蔽background-color: rgba(0, 0, 0, 0.5);这种写法引起的警告"alpha-value-notation": "number", // 屏蔽background-color: rgba(0, 0, 0, 0.5);中0.5引起的警告"block-opening-brace-newline-after": ["always"], //禁用单行代码块"block-closing-brace-newline-before": ["always"],"number-max-precision": 10, // css属性值中小数点之后数字的最大位数indentation: 2, // 指定缩进空格"at-rule-no-vendor-prefix": null, // 动画名称前,可以加浏览器前缀  如@-webkit-keyframes bounce// 动画名称命名规则// html不区分大小写,推荐 kebab-case(短横线)风格命名// todo:现阶段仅提示,后续强制为 kebab-case 风格命名// 短横线命名(kebab-case): ^([a-z][a-z0-9]*)(-[a-z0-9]+)*$// 小驼峰命名(lowerCamelCase): ^[a-z][a-zA-Z0-9]+$// 蛇形命名(snake_case): ^([a-z][a-z0-9]*)(_[a-z0-9]+)*$// 大驼峰命名(UpperCamelCase): ^[A-Z][a-zA-Z0-9]+$"keyframes-name-pattern": ["^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",{message: "Expected keyframe name to be kebab-case",severity: "warning",},],// 小驼峰命名类名选择器命名规则"selector-class-pattern": ["^([a-z][a-zA-Z0-9]+)*$",{message: "Expected class selector to be kebab-case",severity: "error",},],// 小驼峰命名id选择器命名规则"selector-id-pattern": ["^([a-z][a-zA-Z0-9]+)*$",{message: "Expected id selector to be kebab-case",severity: "error",},],"selector-pseudo-class-no-unknown": [true,{ignorePseudoClasses: ["global", "v-deep", "deep"],},],"no-duplicate-selectors": true, //  选择器不允许重复写"no-duplicate-at-import-rules": true, // 禁止在样式表中使用重复的 @import 规则。"declaration-block-no-duplicate-properties": true, // 禁止声明块的重复属性。"declaration-block-no-shorthand-property-overrides": true, //禁止简写属性覆盖相关的扩写属性。"no-descending-specificity": true, //禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器。// 如果是小程序允许rpx// 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],// 'selector-type-no-unknown': [true, { ignoreTypes: ['page'] }],// "prettier/prettier": ["error", {"singleQuote": true, "parser": "flow"}] 这里的配置会覆盖.prettierrc.js的配置// "prettier/prettier": ["error", {}, {//		"usePrettierrc": true//	}] // 开启这个配置,可以指定使用.prettierrc.js配置,不会和其他配置冲突},ignoreFiles: ["node_modules/**/*", "./public/**/*"],customSyntax: "postcss-less",
};

忽略文件.stylelintignore

# 忽略不需要检查的文件或目录
/dist/*
/public/*
public/*
/node_modules
/test
/unittest
/**/build
/**/dist
/**/static
global.css
global.less

vscode 安装stylelint

 三、Prettier

npm install prettier -D

 .prettierrc.js

/*** always 强制* never 不检查*/
module.exports = {// 不使用缩进符,而使用空格useTabs: false,// 是否在代码语句结尾添加分号semi: true,// 'all':尽可能在结尾处加上尾逗号trailingComma: 'es5',// 使用 4 个空格缩进// tabWidth: 4, // 用eslint的// js css 是否使用单引号,不包含JSXsingleQuote: false,// 是否在JSX中使用单引号jsxSingleQuote: true,// 代码行的宽度,通用建议每行最大长度建议为100/120,但最好不超过这两个数printWidth: 120,// 'preserve':使用默认的折行标准proseWrap: 'never',// 大括号内的首尾需要空格bracketSpacing: true,// 箭头函数,只有一个参数的时候,也需要括号arrowParens: 'always',// 设置换行风格,避免不同操作系统造成的大量代码diff// lf / crlf / cr / autoendOfLine: 'lf',// 对象的 key 仅在必要时用引号quoteProps: 'as-needed',// jsx 标签的反尖括号需要换行jsxBracketSameLine: false,// 是否格式化一些文件中被嵌入的代码片段的风格,如果插件可以识别。// off / autoembeddedLanguageFormatting: 'off',// 为 HTML,Vue,Angular 和 Suppherbars 指定全局空白的灵敏度// “css” - 尊重 CSS display属性的默认值// “strict” - 所有空格都被认为是重要的// “ignore” - 所有空格都被认为是微不足道的htmlWhitespaceSensitivity: 'ignore',"properties": "always", // (默认)为属性名称强制执行 小驼峰 样式// 每个文件格式化的范围是文件的全部内容rangeStart: 0,rangeEnd: Infinity,// 不需要写文件开头的 @prettierrequirePragma: false,// 不需要自动在文件开头插入 @prettierinsertPragma: false,
};

忽略.prettierignore

# 忽略不需要的检查的代码
/node_modules
/test
/unittest
/**/build
/**/dist
/**/static

vscoe 安装 prettier

 四、lint-staged、husky

npm install lint-staged husky -D
 
npm install -g npx
 
npx husky install
 
npx husky add .husky/pre-commit "yarn lint-staged --allow-empty"

npx husky add .husky/pre-commit "npx lint-staged"
 

五、package.json配置

 


"scripts": {"lint:eslint": "eslint --config .eslintrc.js --cache --fix --ext .ts,.tsx,.js ./src","lint:prettier": "prettier --config .prettierrc.js --write ./src/**/*.{js,ts,tsx,less,css,md,json}","lint:stylelint": "stylelint --allow-empty-input --config .stylelintrc.js --cache --fix ./src/**/*.{less,css}"
},
"husky": {"hooks": {"pre-commit": "lint-staged","commit-msg": "commitlint --config .commitlintrc.js -E HUSKY_GIT_PARAMS"}
},
"lint-staged": {"./src/**/*.{js,jsx,ts,tsx}": ["eslint --quiet --config .eslintrc.js --fix","prettier --config .prettierrc.js --write"],"./src/**/*.{css,less,scss,postcss}": ["stylelint --quiet --config .stylelintrc.js --fix","prettier --config .prettierrc.js --write"],"./src/**/*.{js,jsx,ts,tsx,json,html,md}": ["prettier --config .prettierrc.js --write"]
},

六、commitlint

npm install @commitlint/cli @commitlint/config-conventional -D
 
npm install -g yarn
 
yarn husky add .husky/commit-msg 'yarn commitlint --edit "$1"'

npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

.commitlintrc.js 

const checkType = (header) => {header = `${header}`;const enumType = [ 'feat', 'fix', 'style', 'ci', 'chore', 'build', 'docs', 'test', 'merge', 'revert', 'refactor', 'reformat' ];const realType = header.split(':')[ 0 ];return enumType.includes(realType);
};const checkSubject = (header) => {header = `${header}`;const realSubject = header.split(':')[ 1 ];if (!realSubject) {return false;}return realSubject.length > 0;
};/** @Description: commit-msg提交信息格式规范** commit-msg格式: <type>: <subject>*     - feat: 新增功能*     - fix: 修改、修复bug*     - style: 代码格式修改,不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)*     - ci: 更新项目持续集成配置(ci/cd)*     - chore: 其他修改, 比如改变构建流程、或者增加依赖库、工具等*     - build: 更新项目构建配置(webpack)*     - docs: 文档修改*     - test: 新增或者修改测试用例*     - merge: 合并分支*     - revert: 回滚某个更早之前的提交*     - refactor: 重构代码(既没有新增功能,也没有修复bug)*     - perf: 优化相关,比如提升性能、体验*/
module.exports = {extends: [ '@commitlint/config-conventional' ],rules: {'type-enum-rule': [ 2, 'never' ],'subject-enum-rule': [ 2, 'never' ],'type-enum': [ 0, 'never' ],'type-empty': [ 0, 'always' ],'subject-empty': [ 0, 'always' ],},plugins: [{rules: {'type-enum-rule': ({ header }) => {return [checkType(header),`       
commit-msg 提交信息格式,需要包含提交类型,类型后面紧跟英文冒号分隔主题信息
格式: <type>: <subject>- feat: 新增功能- fix: 修改、修复bug- style: 代码格式修改,不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)- ci: 更新项目持续集成配置(ci/cd)- chore: 其他修改, 比如改变构建流程、或者增加依赖库、工具等- build: 更新项目构建配置(webpack)- docs: 文档修改- test: 新增或者修改测试用例- merge: 合并分支- revert: 回滚某个更早之前的提交- refactor: 重构代码(既没有新增功能,也没有修复bug)- perf: 优化相关,比如提升性能、体验`];},'subject-enum-rule': ({ header }) => {return [ checkSubject(header), '需要包含提交主题, 格式如: "feat: 开发新功能" 中的 开发新功能' ];},},},],
};

七、vscode配置保存时格式化代码(eslint、stylelint)

打开vscode设置,找到 settings.json

 

{"workbench.colorTheme": "One Dark Pro","eslint.enable": true,"eslint.options": {"extensions": [".js", ".vue", ".ts", ".tsx"]},//关闭VSCode在Save时候自动格式化,因为VSCode自带的格式化和ESlint规范并不兼容"editor.formatOnSave": true,//代码保存时,自动执行ESlint格式化代码"editor.codeActionsOnSave": {"source.fixAll": true,"source.organizeImports": true, //vscode 保存时自动去掉无效引用import"source.fixAll.eslint": true, //eslint修复检测"eslint.autoFixOnSave": true //eslint保存时格式化},// 配置 ESLint 检查的文件类型"eslint.validate": ["vue","html","javascript","typescript","javascriptreact","typescriptreact"],//保存自动格式化css"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],"vetur.format.defaultFormatter.html": "prettier","files.autoSave": "onFocusChange","emmet.includeLanguages": {"editor.formatOnType": "true","editor.formatOnSave": "true"},"[typescriptreact]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"editor.formatOnType": true,"editor.formatOnPaste": true,"eslint.codeActionsOnSave.rules": null,"color-highlight.sass.includePaths": [],"css.enabledLanguages": ["html"],"editor.tabSize": 2
}

 注意:此处要与eslint、stylelint一致

 代码提交

git add .
git commit -m "测试提交"