前端使用工具强制实行代码规范(Vue)

本文最后更新于:2019年9月26日 凌晨

前端使用工具强制实行代码规范(Vue)

场景

GitHub Demo

非强制性的规范都不会成为真正的规范。在之前,我们也会在团队内编写一些开发规范文档,却总是不能真正有效的执行下去,原因何在?

  • 其一,人是健忘的,就算真正看完了规范文档,也不可能记住每一条规范,并且在代码中正确实践。
  • 其二,人是懒惰的,即便有着规范,但只要写代码的时候没有强制性,那总是不会在意的。

那么,使用自动化的工具是为首选,而前端恰好有了一些可用的工具来帮助我们。

ESLint

ESLint 是一个对 JS 低级错误进行修复的工具,甚至于,现在连 TypeScript 官方也抛弃 TSLint 转向这个工具了,因为它的生态更大,实现的规则数量远远多于 TSLint,能把 JS 中的大部分低级错误检测出来,并能自动修复其中一部分。

安装依赖

npm i -D eslint babel-eslint eslint-plugin-vue @vue/cli-plugin-eslint

依赖解释

  • eslint: 本体包
  • babel-eslint: eslint 与 babel 整合包
  • eslint-plugin-vue @vue/cli-plugin-eslint: eslint 与 vue 整合包

进行配置

// .eslintrc.js
module.exports = {
  root: true,
  //环境
  env: {
    browser: true,
    commonjs: true,
    es6: true,
    node: true,
  },
  extends: [
    //继承 vue 的标准特性
    'plugin:vue/essential',
    'eslint:recommended',
  ],
  rules: {
    /**
     * 禁止不需要的括号,例如 const i = (1 + 1),但该规则存在的问题是会认为类两侧的圆括号也是不合法的
     * 例如: billId => (StringValidator.isBlank(billId) ? '否' : '是')
     */
    // 'no-extra-parens': 'error',
    /**
     * 禁止魔法值,该规则的主要问题是很多误报
     * 例如: offset / size + (offset % size === 0 ? 0 : 1)
     */
    'no-magic-numbers': 'off',

    //禁止使用 var,强制要求使用 const/let
    'no-var': 'error',
    //不使用未定义的变量
    'no-use-before-define': 'error',
    //不允许在循环中使用 await,请使用 Promise.all
    'no-await-in-loop': 'error',
    //不允许使用 return await,直接返回 Promise 就好
    'no-return-await': 'error',
    //不允许使用 console 对象,因为会打印到控制台上
    'no-console': 'error',
    //使用 class 中的方法必须使用 this. 前缀
    // 'class-methods-use-this': 'error',
    //禁止使用 alert, confirm, prompt,该 API 会阻断所有其他操作,但该规则存在的问题是有可能需要之后用上方便统一调用呢?
    'no-alert': 'error',
    //禁止使用 eval,该操作是危险的
    'no-eval': 'error',
    'no-implied-eval': 'error',
    //禁止使用 new Function 创建函数
    'no-new-func': 'error',
    //禁止使用包装类 String, Number, Boolean
    'no-new-wrappers': 'error',
    //禁止把语句作为计算结果返回,请使用两条语句
    'no-return-assign': 'error',
    //禁止使用不应该的 concat 连接,字符串请使用 + 连接,数组则可以使用 [...arr1, ...arr2]
    'no-useless-concat': 'error',
    //禁止 yoda 比较,不要用 1 === i 而是用 i === 1 更加自然
    yoda: 'error',
    //禁止没有用的三元运算符,就算是 ⑨ 也知道这样做有问题 answer === 1 ? true : false
    'no-unneeded-ternary': 'error',
    //禁止无用的计算属性 const obj = { ['1']: 1 }
    'no-useless-computed-key': 'error',
    //如果可以使用解构,那就进行警告,例如 const name = user.name 就应该被替换为 const { name } = user,避免了重复声明,也能进行默认赋值等操作
    'prefer-destructuring': 'warn',
    //使用 rest 不定参数代替全局变量 arguments
    'prefer-rest-params': 'error',
    //使用扩展运算符代替 apply 调用
    'prefer-spread': 'error',
    //使用 Symbol 必须使用描述说明它要做什么
    'symbol-description': 'error',
    //如果可以使用反射,那就是用反射调用,Reflect 代替 delete 关键字删除对象属性
    'prefer-reflect': 'warn',
  },
  parserOptions: {
    //使用 babel 解析语法
    parser: 'babel-eslint',
    //使用 es2017 的语法
    ecmaVersion: 2017,
  },
}

如果有需要忽略的文件也可以在 .eslintignore 文件中进行配置

// .eslintignore
`// 忽略掉 TypeScript 类型定义文件
*/**/*.d.ts`

添加脚本

// package.json
{
  // 其他配置。。。
  "scripts": {
    "lint:js": "vue-cli-service lint",
    "fix:js": "vue-cli-service lint --fix"
  }
  // 其他配置。。。
}

运行脚本

npm run lint:js

现在,你可以检测到代码中的问题,并修复它了。

相关链接

WebStorm 配置 ESLint 即时检查:
WebStorm 使用 ESLint
参考: https://stackoverflow.com/questions/28808857

StyleLint

StyleLint 是一个用来对 CSS 进行校验/修复的工具,和 ESLint 类似,但却针对 CSS 方面。我们使用它用来避免一些不好的 CSS 写法,也能避免 code review 时被其他人吐槽。。。

安装依赖

npm i -D stylelint stylelint-config-standard stylelint-order stylelint-scss

依赖解释

  • stylelint: 本体包
  • stylelint-config-standard: stylelint 标准配置
  • stylelint-scss: stylelint scss 支持插件包
  • stylelint-order: stylelint 属性排序插件

进行配置

// stylelint.config.js
module.exports = {
  plugins: ['stylelint-scss', 'stylelint-order'],
  extends: ['stylelint-config-standard'],
  rules: {
    /**
     * 关键问题在于是否要忽略空检查
     * 下面两种都会被判断为错误
     * <style lang="scss" scoped></style>
     * a {}
     * 上面一种是很常见的
     */
    // 'no-empty-source': null,
    //禁止注释两侧出现空白(IDEA 默认就没有空白)
    'comment-whitespace-inside': 'never',
    //scss 的特定符号将被 css 检查时忽略而在 scss 检查时才会生效
    'at-rule-no-unknown': null,
    'scss/at-rule-no-unknown': true,
    //CSS 声明定义顺序,自定义 class 放在默认元素上面
    'order/order': ['custom-properties', 'declarations'],
    /**
     * CSS 属性顺序
     * 1. 控制外部属性
     * 2. 盒模型
     * 3. 视觉
     * 4. 其他
     * 5. 未定义
     */
    'order/properties-order': [
      [
        //指令
        'composes',
        '@import',
        '@extend',
        '@mixin',
        '@at-root',
        //盒模型相关
        'display',
        'flex',
        'flex-basis',
        'flex-direction',
        'flex-flow',
        'flex-grow',
        'flex-shrink',
        'flex-wrap',
        'grid',
        'grid-area',
        'grid-auto-rows',
        'grid-auto-columns',
        'grid-auto-flow',
        'grid-gap',
        'grid-row',
        'grid-row-start',
        'grid-row-end',
        'grid-row-gap',
        'grid-column',
        'grid-column-start',
        'grid-column-end',
        'grid-column-gap',
        'grid-template',
        'grid-template-areas',
        'grid-template-rows',
        'grid-template-columns',
        'gap',
        'align-content',
        'align-items',
        'align-self',
        'justify-content',
        'justify-items',
        'justify-self',
        'order',
        'float',
        'clear',
        'box-sizing',
        'width',
        'min-width',
        'max-width',
        'height',
        'min-height',
        'max-height',
        'margin',
        'margin-top',
        'margin-right',
        'margin-bottom',
        'margin-left',
        'padding',
        'padding-top',
        'padding-right',
        'padding-bottom',
        'padding-left',
        //定位相关
        'position',
        'top',
        'right',
        'bottom',
        'left',
        'z-index',
        //边框
        'border',
        'border-color',
        'border-style',
        'border-width',
        'border-top',
        'border-top-color',
        'border-top-width',
        'border-top-style',
        'border-right',
        'border-right-color',
        'border-right-width',
        'border-right-style',
        'border-bottom',
        'border-bottom-color',
        'border-bottom-width',
        'border-bottom-style',
        'border-left',
        'border-left-color',
        'border-left-width',
        'border-left-style',
        'border-radius',
        'border-top-left-radius',
        'border-top-right-radius',
        'border-bottom-right-radius',
        'border-bottom-left-radius',
        'border-image',
        'border-image-source',
        'border-image-slice',
        'border-image-width',
        'border-image-outset',
        'border-image-repeat',
        'border-collapse',
        'border-spacing',
        //溢出
        'object-fit',
        'object-position',
        'overflow',
        'overflow-x',
        'overflow-y',
        //版式
        'color',
        'font',
        'font-weight',
        'font-size',
        'font-family',
        'font-style',
        'font-variant',
        'font-size-adjust',
        'font-stretch',
        'font-effect',
        'font-emphasize',
        'font-emphasize-position',
        'font-emphasize-style',
        'font-smooth',
        'line-height',
        'direction',
        'letter-spacing',
        'white-space',
        'text-align',
        'text-align-last',
        'text-transform',
        'text-decoration',
        'text-emphasis',
        'text-emphasis-color',
        'text-emphasis-style',
        'text-emphasis-position',
        'text-indent',
        'text-justify',
        'text-outline',
        'text-wrap',
        'text-overflow',
        'text-overflow-ellipsis',
        'text-overflow-mode',
        'text-orientation',
        'text-shadow',
        'vertical-align',
        'word-wrap',
        'word-break',
        'word-spacing',
        'overflow-wrap',
        'tab-size',
        'hyphens',
        'unicode-bidi',
        'columns',
        'column-count',
        'column-fill',
        'column-gap',
        'column-rule',
        'column-rule-color',
        'column-rule-style',
        'column-rule-width',
        'column-span',
        'column-width',
        'page-break-after',
        'page-break-before',
        'page-break-inside',
        'src',
        //视觉
        'list-style',
        'list-style-position',
        'list-style-type',
        'list-style-image',
        'table-layout',
        'empty-cells',
        'caption-side',
        'background',
        'background-color',
        'background-image',
        'background-repeat',
        'background-position',
        'background-position-x',
        'background-position-y',
        'background-size',
        'background-clip',
        'background-origin',
        'background-attachment',
        'background-blend-mode',
        //动画
        'transition',
        'transition-delay',
        'transition-timing-function',
        'transition-duration',
        'transition-property',
        'animation',
        'animation-name',
        'animation-duration',
        'animation-play-state',
        'animation-timing-function',
        'animation-delay',
        'animation-iteration-count',
        'animation-direction',
        'animation-fill-mode',
        //其他
        'appearance',
        'content',
        'clip',
        'clip-path',
        'counter-reset',
        'counter-increment',
        'resize',
        'user-select',
        'nav-index',
        'nav-up',
        'nav-right',
        'nav-down',
        'nav-left',
        'pointer-events',
        'quotes',
        'touch-action',
        'will-change',
        'zoom',
        'fill',
        'fill-rule',
        'clip-rule',
        'stroke',
      ],
      {
        unspecified: 'bottom',
      },
    ],
    //CSS 属性值顺序
    // 'order/properties-alphabetical-order': [],
  },
}

添加脚本

// package.json
{
  // 其他配置。。。
  "scripts": {
    "lint:css": "stylelint src/**/*.{vue,html,css,scss,sass}",
    "fix:css": "stylelint --fix src/**/*.{vue,html,css,scss,sass}"
  }
  // 其他配置。。。
}

运行脚本

npm run lint:css

相关链接

WebStorm 使用
WebStorm 配置 StyleLint 即时检查:
WebStorm 配置即时检查
参考 https://stackoverflow.com/questions/54304313/

添加外部工具以进行快速修复
添加外部工具以进行快速修复
然后添加一个快捷键即可

Prettier

Prettier 是一个代码格式化工具,但并非针对一种语言,对 HTML/CSS/JavaScript/Vue/SCSS 都有效果。可以通过配置文件在不同项目间统一代码格式化,以修正不同编辑器/IDE 之间格式化不同的问题。

安装依赖

npm i -D prettier eslint-plugin-prettier eslint-config-prettier prettier-eslint-cli stylelint-config-prettier stylelint-prettier

依赖解释

  • prettier: 本体包
  • eslint-plugin-prettier eslint-config-prettier prettier-eslint-cli: prettier 与 eslint 整合包
  • stylelint-config-prettier stylelint-prettier: prettier 与 stylelint 整合包

进行配置

// .prettierrc.js
module.exports = {
  // 缩进宽度
  tabWidth: 4,
  // 单行最大宽度
  printWidth: 120,
  // 去掉代码结尾的分号
  semi: false,
  // 使用单引号替代双引号
  singleQuote: true,
  // 尽量在所有地方都添加尾逗号
  trailingComma: 'all',
  // 换行符
  endOfLine: 'crlf',
}

还需要修改 eslint 与 stylelint 的一些配置

// .eslintrc.js
module.exports = {
  // 其他配置。。。
  extends: [
    //继承 vue 的标准特性
    'plugin:vue/essential',
    'eslint:recommended',
    //避免与 prettier 冲突
    'plugin:prettier/recommended',
  ],
  // 其他配置。。。
}
// stylelint.config.js
module.exports = {
  // 其他配置。。。
  extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
  // 其他配置。。。
}

添加脚本

// package.json
{
  // 其他配置。。。
  "scripts": {
    "format": "prettier-eslint --write \"src/**/*.{js,vue,html,scss,css}\""
  }
  // 其他配置。。。
}

运行脚本

npm run format

相关链接

WebStorm 配置使用 Prettier 快速格式化:
WebStorm 配置使用 Prettier 快速格式化
建议修改为全局使用 Prettier 格式化,避免记忆两个格式化快捷键

husky 与 lint-staged

强制使用 linter 检查代码,不通过检查则无法提交代码,以使 linter 真正得到有效执行。

安装依赖

npm i -D husky lint-staged

依赖解释

  • husky: 在项目中添加 git 钩子,在 git 各个生命周期(姑且这样称呼吧)中执行一些自定义操作。我们这里主要是用在 git 提交之前执行 linter 操作,不通过则提交无效。
  • lint-staged: 简而言之,就是只针对 git 提交的文件进行一些操作,而非整个项目的所有文件。我们这里主要是用在 git 提交之前进行 linter 时只针对提交的文件,以进行渐进式的重构。

进行配置

// .huskyrc.js
module.exports = {
  hooks: {
    // git commit 前的钩子
    'pre-commit': 'lint-staged',
    // 修复 IDEA 的一些奇怪问题 <https://youtrack.jetbrains.com/issue/IDEA-135454>
    'post-commit': 'git update-index --again',
  },
}
// lint-staged.config.js
module.exports = {
  'src/**/*.{js,vue}': ['eslint --fix', 'git add'],
  'src/**/*.{vue,html,css,scss,sass}': ['stylelint --fix', 'git add'],
  'src/**/*.{js,vue,html,css,scss,sass}': [
    'prettier-eslint --write',
    'git add',
  ],
}

有人说 lint-staged 并行运行多个命令可能会有问题,因为 nodejs 本身在写入文件时不会加锁,导致多线程下可能存在问题(吾辈目前还没遇到过.JPG)

之后,在我们使用 git commit 时就会触发 lint 操作了!

相关链接

总结

基本上,这些工具初次配置起来还是非常麻烦的,但这是一件一劳永逸的事情,所以还是值得花时间去做的。

注:
目前还存在的问题是:
ESLint 检测出来的部分错误能使用 A-Enter 修复
Prettier 与 WebStorm 自身格式化不能共存(自动切换)
Prettier 在 WebStorm 中无法直接配置导致上面问题存在的必要性
StyleLint 不能使用 A-Enter 修复且不能与 WebStorm 共存
可能的解决方案是找一下是否有一种方式能够让 WebStorm 一个快捷键执行多条命令,或者,写一个可用的插件。

参考