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

本文最后更新于:2021年8月4日 晚上

场景

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 即时检查:

StyleLint

不建议使用,本身只是对 css 代码的排序,而且很多时候只提出问题但不负责解决问题(那么只有解决提出问题的工具了 xd),或许使用 postcss 是个更好的选择。

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 一个快捷键执行多条命令,或者,写一个可用的插件。

参考