使用 ESLint、Prettier 和 Stylelint 来规范代码

4,730 阅读12分钟

前言

无论是多人协作还是个人项目,代码规范都非常重要。遵循代码规范不仅能减少基本语法错误,同时也保证了代码的可读性。而代码风格检查则是确保代码规范一致性的重要工具之一。

ESLint

ESLint 是什么?

ESLint 是一个用于检查和修复 JavaScript 代码中问题的代码检测工具。它能够帮助你发现并修复 JavaScript 代码中的问题。

ESLint 的作用包括:

  • 语言语法检查:比如检查出字符串引号不匹配或函数调用括号缺失的问题
  • 编码错误检查:比如检查出在代码中使用了一个不存在的变量,或者定义了一个变量却没有使用的问题
  • 代码风格检查:比如检查出开发者没有使用分号的问题

安装 ESLint

使用 Vue Cli 安装

vue add eslint
# or
vue add @vue/cli-plugin-eslint

在输入完命令后,需要回答一些问题来根据你的项目需求和个人偏好进行配置。问题如下:

  • Pick an ESLint config(选择一个 ESLint 配置预设)

    • Error prevention only(只检测错误)
    • Airbnb(安彼迎)
    • Standard(标准)
    • Prettier(Prettier)
  • Pick additional lint features(选择什么时候进行代码检查)

    • Lint on save(保存时检查)

    • Lint and fix on commit(提交代码时检查)

在回答完问题后,vue-cli 会自动创建一个 ESLint 配置文件 .eslintrc.js,并自动安装以下依赖:

  • eslint:ESLint 核心模块
  • eslint-plugin-vue:ESLint 插件,该插件用于提供针对 Vue.js 代码的规则和检查
  • prettier:Prettier 核心模块
  • eslint-plugin-prettier:ESLint 插件,该插件用于将 Prettier 的格式化规则集成到 ESLint 中
  • @vue/eslint-config-prettier:ESLint 配置,它将禁用与 Prettier 格式化规则冲突的 ESLint 规则

ESLint 常用配置项

  • root:定义根配置

  • env:定义运行环境

  • rules:定义检查规则

  • plugins:规则拓展

  • extends:集成配置方案

  • parser:语法解析器配置

下面是每个配置项的详细介绍:

root

默认情况下,ESLint 会在所有父级目录中寻找配置文件,一直到根目录。这样可以让所有项目都遵循同一套规则。你也可以将 .eslintrc.js 配置文件中的 root 设置为 true,ESLint 一旦发现配置文件中有 root: true,它就会停止在父级目录中寻找。

env

这个选项用于定义项目代码运行的环境,每个环境都包含一组特定的预定义全局变量:

  • browser — 浏览器全局变量
  • node — Node.js 全局变量和 Node.js 作用域
  • es6 — 启用除模块之外的所有 ES6 功能

...

比如项目要启用浏览器环境和 Node.js 环境:

module.exports = {
  // ...
  env: {
    browser: true,
    node: true,
  },
};

rules

规则的值有两种格式:数组和字符串/数字。如果是数组,则可以设置两个内容:

第一项内容定义规则是关闭还是打开:

  • off0:关闭规则,ESLint 不对该规则进行检查
  • warn1:将规则作为警告打开(不影响退出代码)。在开发环境下,终端会输出警告信息,但不会影响功能页面的展示
  • error2:将规则作为错误打开(触发时退出代码为 1)。在开发环境下,终端输出错误信息,浏览器页面也会显示错误信息而不是正常的功能展示

第二项内容是为该规则提供的参数。

如果规则的值是字符串/数字,则只定义规则的关闭或打开。

以下是禁用 JavaScript 中的 console 对象的方法调用的示例,对应的 ESLint 规则名称是 no-console

module.exports = {
  // ...
  rules: {
    // 表示在代码中不允许直接调用 console.log/error/warn 等方法,否则将报错
    "no-console": "error",

    // 表示在代码中不推荐直接调用 console.log/error/warn 等方法,否则将发出警告
    "no-console": 1,

    // 表示在代码中可以直接调用 console.log/error/warn 等方法,因为规则被关闭了
    "no-console": "off",

    // 表示在代码中不允许直接调用 console.log 方法,但允许使用 warn 和 error 方法,否则将报错
    "no-console": ["error", { allow: ["warn", "error"] }],
  },
};

plugins

plugins 是用于扩展 ESLint 内置规则的。ESLint 主要用于对 JavaScript 代码进行语法检查,无法直接检查其他格式的文件内容。如果需要对这些文件内容进行检查,就需要使用 plugins 配置来扩展 ESLint 规则。例如:

  • eslint-plugin-vue:可以对 Vue 文件的 <template><script> 内容进行检查,具体使用方法请参考文档
module.exports = {
  // ...
  plugins: ["eslint-plugin-vue"],
  rules: {
    // ... eslint-plugin-vue 的 lint 规则
  },
};

需要注意的是, plugins 中的规则默认是未启用的,需要在 rules 中启用相应的规则。也就是说, plugins 需要与 rules 结合使用。

extends

可以看到,手动配置 rules 的工作量很大,这时候 extends 派上了用场。extends 可以理解为一份配置好的 plugins 和 rules。

比如:eslint-plugin-vue 提供了一个名为 plugin:vue/recommended 的规则包。在 extends 中引用这个规则包相当于启用了一系列的规则:

module.exports = {
  // ...
  extends: ["plugin:vue/recommended"],
  rules: {
    // 此处无需手动一项一项配置 lint 规则,因为插件已经根据 recommended 选项自动设置了预设的规则包
  },
};

eslint-plugin-vue 还提供了其他的规则包,具体可看官方文档

ESLint 本身也提供一些内置的规则包,如:eslint:recommended,在规则页面 上标记了 ✅ 的规则都属于该规则包。

parser

ESLint 默认使用 Espree 作为其解析器,它仅支持 ES5 语法,不支持实验性语法和非标准语法(如 TypeScript 类型)。但是我们可以配置解析器(parser)以使 ESLint 支持其他语法。例如:

  • babel-eslint:使 ESLint 兼容 ES6 语法
module.exports = {
  // ...
  parserOptions: {
    parser: "babel-eslint",
  },
};

ESLint 常用命令

检查文件

npx eslint [文件或目录路径]

修复文件

npx eslint --fix [文件或目录路径]

指定检查的文件扩展名

当 ESLint 检查的目标是一个目录时,默认情况下只会检查该目录下的 .js 文件。如果需要检查其他扩展名的文件,可以使用 --ext 命令来定义要检查的文件类型。

# 仅检查 Vue 文件
npx eslint --ext .vue [目录路径]

# 同时检查 JavaScript 和 Vue 文件
npx eslint --ext .js --ext .vue [目录路径]
# or
npx eslint --ext .js,.vue [目录路径]

Prettier

Prettier 是什么?

Prettier 是一个代码格式化工具,用于检查代码中的格式问题。

Prettier 和 ESLint 的区别?

在代码格式化方面,Prettier 确实与 ESLint 有一些重叠,但它们的重点不同。ESLint 主要用于检查代码质量并给出提示,它在格式化方面的功能有限;而 Prettier 在代码格式化方面更加全面,因此通常将它们结合使用。

安装 Prettier

在使用 vue-cli 安装 ESLint 时,Prettier 已经被安装了,因此无需额外安装。

Prettier 常用配置项

在项目的根目录下,创建一个名为 .prettierrc.js 的文件,用于配置 Prettier

// .prettierrc.js
module.exports = {
  printWidth: 80, // 一行的字符数,如果超过会进行换行,默认为 80
  tabWidth: 4, // 一个 tab 代表几个空格数,默认为 2
  useTabs: true, // 是否使用 tab 进行缩进,默认为 false,表示用空格进行缩进
  semi: true, // 是否在句尾添加分号,默认为 true
  singleQuote: false, // 字符串是否使用单引号,默认为 false,使用双引号
  quoteProps: "as-needed", // 对象属性名称是否添加引号,有三个可选值"<as-needed|consistent|preserve>",默认为 as-needed
  trailingComma: "es5", // 是否使用尾逗号,有三个可选值"<none|es5|all>",默认为 all
  bracketSpacing: true, // 对象括号内是否需要空格,默认为 true
  arrowParens: "avoid", // 箭头函数只有一个参数时是否使用括号,有两个可选值"<always|avoid>",默认为 always
};

更多配置可查看官方文档

集成 ESLint & Prettier

在上述安装 ESLint 的过程中,已经安装了与 Prettier 有关的依赖:

  • eslint-plugin-prettier:ESLint 插件,该插件用于将 Prettier 的格式化规则集成到 ESLint 中
  • @vue/eslint-config-prettier:ESLint 配置,它将禁用与 Prettier 格式化规则冲突的 ESLint 规则

eslint-plugin-prettier@vue/eslint-config-prettier 提供了 plugin:prettier/recommended 的规则包,我们还需要在 .eslintrc.js 添加相应的配置:

// .eslintrc.js
module.exports = {
  // ...
  extends: ["plugin:prettier/recommended"],
};

上面的配置,实际上等同于:

// .eslintrc.js
module.exports = {
  // ...
  extends: ["prettier"],
  plugins: ["prettier"],
  rules: {
    "prettier/prettier": "error",
    "arrow-body-style": "off",
    "prefer-arrow-callback": "off",
  },
};

需要注意的是,plugin:prettier/recommended 需要添加到数组的最后一个元素才能覆盖前面的规则,从而禁用与 Prettier 格式化规则冲突的 ESLint 规则

详细内容可查看官方文档

Prettier 常用命令

格式化文件

npx prettier --write [文件或目录路径]

检查文件格式

npx prettier --check [文件或目录路径]

StyleLint

StyleLint 是什么?

StyleLint 是一个用于检查和维护 CSS、SCSS 等样式表的工具。它的主要功能是确保代码风格的一致性,并检查样式表是否符合预设的规范。

安装 StyleLint

npm install --save-dev stylelint stylelint-config-standard-scss stylelint-config-prettier

以下是与 StyleLint 相关的插件和配置说明:

  • stylelint:StyleLint 的核心模块
  • stylelint-config-standard:StyleLint 官方推荐的配置规则
  • stylelint-config-prettier:该配置用于解决 StyleLint 和 Prettier 之间的规则冲突问题。将其放在 extends 数组的最后位置,可以确保覆盖 Prettier 的配置
  • stylelint-scss:该插件是 StyleLint 的 SCSS 扩展,增加了对 SCSS 语法的支持,允许检查和验证 SCSS 文件

StyleLint 配置

在项目根目录下创建名为 .stylelintrc.js 的文件来进行 StyleLint 的配置

// .stylelintrc.js
module.exports = {
  extends: ["stylelint-config-standard", "stylelint-config-prettier"],
  plugins: ["stylelint-scss"],
  // ...
};

更多规则可查看官方文档

规范 CSS 书写顺序

除了基础功能之外,StyleLint 还可以帮助规范 CSS 书写顺序。浏览器根据 CSS 样式的书写顺序按照 DOM 树的结构渲染样式。在解析过程中,如果浏览器检测到某个元素的定位发生了变化并且影响了布局,浏览器会执行回流操作。因此,通过正确的书写顺序可以使渲染引擎更高效地工作,减少不必要的回流操作,从而提升页面性能。

为了自动对 CSS 属性进行排序,我们可以使用 stylelint-order 插件。该插件提供了一套默认的属性排序规则,可以按照指定的顺序对属性进行排序。

安装 stylelint-order

npm install --save-dev stylelint-order

配置 stylelint-order

.stylelintrc.js 文件加入 stylelint-order 插件的规则:

// .stylelintrc.js
module.exports = {
  extends: ["stylelint-config-standard", "stylelint-config-prettier"],
  plugins: ["stylelint-scss", "stylelint-order"],
  // ...
};

现在可以通过使用 order/properties-order 规则来指定属性的排序规则,下面是一些建议的 CSS 书写顺序:

  1. 影响文档流的属性(例如:positionlefttoprightbottomz-indexdisplayfloatclearoverflow 等)
  2. 自身盒模型的属性(例如:marginborderpaddingwidthheight 等)
  3. 文字相关的属性(例如:fontline-heighttext-aligntext-indentvertical-align 等)
  4. 外观相关的属性(例如:colorbackground 等)
  5. CSS3 新增的属性(例如:transitiontransformanimation 等)
  6. 其他(例如:opacitybox-shadowcursor 等)

最终的 order/properties-order 的配置会在文末提供。

StyleLint 常用命令

检查文件

npx stylelint [文件或目录路径]

修复文件

npx stylelint --fix [文件或目录路径]

最终配置

ESLint

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    es6: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:vue/recommended",
    "plugin:prettier/recommended",
  ],
  parserOptions: {
    parser: "babel-eslint",
  },
  rules: {
    "vue/no-v-html": "off",
    "vue/html-indent": "off",
    "vue/html-self-closing": "off",
    "vue/max-attributes-per-line": "off",
    "vue/multi-word-component-names": "off",
    "vue/no-mutating-props": "off",
    "vue/no-unused-vars": "off",
    "no-unused-vars": "off",
    "prettier/prettier": "warn",
    "vue/no-unused-components": "warn",
    "vue/singleline-html-element-content-newline": "off",
  },
};

Prettier

// .prettierrc.js
module.exports = {
  useTabstrue,
  tabWidth4,
  arrowParens"avoid",
  htmlWhitespaceSensitivity"ignore",
  trailingComma"es5",
};

StyleLint

// .stylelintrc.js
module.exports = {
  extends: ["stylelint-config-standard""stylelint-config-prettier"],
  plugins: ["stylelint-scss""stylelint-order"],
  rules: {
    "order/properties-alphabetical-order"null,
    "order/properties-order": [
      [
        {
          properties: ["position""top""bottom""right""left""z-index"],
        },
        {
          properties: [
            "display",
            "align-items",
            "justify-content",
            "visibility",
            "float",
            "clear",
            "overflow",
            "overflow-x",
            "overflow-y",
            "zoom",
          ],
        },
        {
          properties: [
            "list-style",
            "list-style-position",
            "list-style-type",
            "list-style-image",
          ],
        },
        {
          properties: [
            "margin",
            "margin-top",
            "margin-right",
            "margin-bogttom",
            "margin-left",
            "box-sizing",
            "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",
            "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",
            "padding",
            "padding-top",
            "padding-right",
            "padding-bottom",
            "padding-left",
            "width",
            "min-width",
            "max-width",
            "height",
            "min-height",
            "max-height",
          ],
        },
        {
          properties: [
            "font",
            "font-family",
            "font-size",
            "font-weight",
            "font-style",
            "font-stretch",
            "line-height",
            "text-align",
            "vertical-align",
            "white-space",
            "text-decoration",
            "text-indent",
            "text-justify",
            "letter-spacing",
            "word-spacing",
            "text-transform",
            "text-overflow",
            "word-wrap",
            "word-break",
          ],
        },
        {
          properties: [
            "color",
            "background",
            "background-color",
            "background-image",
            "background-repeat",
            "background-attachment",
            "background-position",
            "background-position-x",
            "background-position-y",
            "background-clip",
            "background-origin",
            "background-size",
          ],
        },
        {
          properties: [
            "transition",
            "transition-delay",
            "transition-timing-function",
            "transition-duration",
            "transition-property",
            "transform",
            "transform-origin",
            "animation",
            "animation-name",
            "animation-duration",
            "animation-play-state",
            "animation-timing-function",
            "animation-delay",
            "animation-iteration-count",
            "animation-direction",
          ],
        },
        {
          properties: [
            "outline",
            "outline-width",
            "outline-style",
            "outline-color",
            "outline-offset",
            "opacity",
            "filter",
            "box-shadow",
            "text-shadow",
          ],
        },
        {
          properties: [
            "content",
            "resize",
            "cursor",
            "user-select",
            "pointer-events",
          ],
        },
      ],
      {
        emptyLineBeforeUnspecified"never",
      },
    ],
    "color-hex-case""lower",
    "color-hex-length""short",
    "color-named""never",
    "font-weight-notation""numeric",
    "number-leading-zero""always",
    "string-quotes""double",
    "value-no-vendor-prefix": [
      true,
      {
        ignoreValues: [
          "placeholder",
          "input-placeholder",
          "text-fill-color",
          "line-clamp",
          "box-orient",
          "box",
        ],
      },
    ],
    "value-list-comma-newline-before""never-multi-line",
    "value-list-comma-space-before""never",
    "declaration-colon-newline-after"null,
    "declaration-bang-space-before""always",
    "declaration-bang-space-after""never",
    "declaration-colon-space-before""never",
    "declaration-colon-space-after""always",
    "declaration-empty-line-before"null,
    "declaration-block-semicolon-newline-before""never-multi-line",
    "at-rule-no-unknown"null,
    "block-closing-brace-empty-line-before""never"// fix:@keyframes Expected empty line before closing brace
    "block-closing-brace-newline-after""always",
    "block-closing-brace-newline-before""always",
    "block-opening-brace-newline-after""always",
    "block-opening-brace-space-before""always",
    "rule-empty-line-before": [
      "always",
      {
        ignore: ["after-comment""first-nested"],
      },
    ],
    "block-no-empty"null,
    "no-empty-source"null,
    "no-descending-specificity"null,
    "selector-pseudo-class-no-unknown"null,
    "property-no-unknown"null,
    "font-family-no-missing-generic-family-keyword"null,
    "font-family-name-quotes"null,
    "selector-pseudo-element-no-unknown"null,
  },
};

拓展

与 VSCode 集成

安装 ESLint 插件

use_eslint_prettier_stylelint_img_1.jpg

安装 Prettier 插件

use_eslint_prettier_stylelint_img_2.jpg

安装 Stylelint 插件

use_eslint_prettier_stylelint_img_3.jpg

在 VSCode 中安装 ESLint、Prettier 和 Stylelint 插件可以实现在编写代码时自动进行代码检查和格式化。

安装完后打开 VSCode 配置文件 settings.json 添加以下配置:

{
    // 设置全部语言在保存时自动格式化
    "editor.formatOnSave": true,
    // 设置全部语言的默认格式化程序为prettier
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    // 保存时使用 StyleLint 修复可修复错误
    "editor.codeActionsOnSave": {
        "source.fixAll.stylelint": true
    }
}

与 Webpack 集成

我们可以通过安装 eslint-loader 插件,在构建过程中启用 ESLint 的自动修复功能

安装 eslint-loader

npm install --save-dev eslint-loader

vue.config.js 文件中添加以下配置:

// vue.config.js
module.exports = {
  // ...
  chainWebpack(config) {
    // 启用 ESLint 自动修复
    config.module
      .rule("eslint")
      .use("eslint-loader")
      .loader("eslint-loader")
      .tap((options) => Object.assign(options, { fix: true }));
  },
};