Vue项目使用 Eslint9 配置总结

4,285 阅读6分钟

Eslint 是一个用于识别和报告在 ECMAScript/JavaScript 代码中发现的模式的工具,其目标是使代码更加一致并避免错误。

主要特点

  • 性能优化:ESLint 9 对代码分析的性能进行了优化,使得运行速度更快。
  • 扩展支持:改进了对第三方扩展和插件的支持,使得开发者可以更容易地自定义 ESLint 的功能。
  • 配置简化:引入了更简洁的配置选项,减少了配置文件的复杂度,让新手和老手都能更方便地使用。
  • 新规则:增加了多项新的代码检查规则,帮助开发者更全面地检查代码质量。
  • 改进的自动修复:增强了自动修复功能,可以自动修复更多类型的代码问题,节省开发者的时间。

先决条件

要使用 ESLint,你必须安装并构建 Node.js^18.18.0^20.9.0 或 >=21.1.0)并支持 SSL。(如果你使用的是官方 Node.js 发行版,则始终内置 SSL。)

配置

eslint9 之后配置就扁平化了,若想升级可参阅迁移指南

// eslint.config.js
export default [
    {
        rules: {
            "no-unused-vars": "error",
            "no-undef": "error"
        }
    }
];

插件推荐

  • eslint-plugin-vue Vue.js 的官方 ESLint 插件介绍
  • vue-eslint-parser 专门用于解析Vue.js单文件组件(.vue文件)的ESLint插件介绍
  • @typescript-eslint/parser 专门为 TypeScript 语法设计的解析器,能够解析 TypeScript 中的所有语法介绍
  • @stylistic/eslint-plugin ESLint 的风格规则集合介绍
  • eslint-plugin-prettier 将Prettier作为Eslint的规则运行介绍

自定义规则

当现有规则或插件无法满足时,eslint也提供了自定义功能;
详情可以查看完整配置中的eslint-rules/eslint-plugin-vue部分

  • 对外暴露一个对象,其中必须要包含metarules
  • meta中规则的元信息,例如规则类型、文档和可修复性
  • 添加规则访问者方法 【重点】
    • 定义规则的 create 函数,它接受一个 context 对象并返回一个对象,该对象具有你要处理的每个语法节点类型的属性。在这种情况下,你想要处理 VariableDeclarator 个节点。你可以选择任何 ESTree 节点类型 或 选择器
    • 通过获取到的数据通过正则或者其他插件里面的规则方法进行匹配,并处理成想要的数据
    • context.report() 向 ESLint 报告错误。错误报告包含有关错误及其修复方法的信息

完整配置

实现了对vue模板ts、以及vue模板中style模块使用prettier格式化等功能,开箱即用

// eslint.config.js
const pluginVue = require("eslint-plugin-vue")
const vueParser = require("vue-eslint-parser")
const typescriptParser = require("@typescript-eslint/parser")
const stylistic = require("@stylistic/eslint-plugin")
const eslintPluginVue = require("./eslint-rules/eslint-plugin-vue") // 自定义规则

module.exports = [
    {
       ignores: [
          "**/node_modules/**/*",
          "**/dist/**/*",
          "**/lib/**/*",
          "**/types/**/*.d.ts",
       ],
       files: [
          "**/*.ts",
          "**/*.tsx",
          "**/*.vue",
          "**/*.js",
          "**/*.jsx",
          "**/*.cjs",
       ],
    },
    {
       plugins: { typescriptParser },
       languageOptions: {
          parser: typescriptParser,
          parserOptions: {
             ecmaVersion: "latest",
             sourceType: "module",
             extraFileExtensions: [".vue", ".ts", ".tsx", "css"],
          },
       },
    },
    stylistic.configs.customize({
       indent: "tab",
       quotes: "double",
       semi: false,
       jsx: true,
       commaDangle: "always-multiline",
    }),
    ...pluginVue.configs["flat/recommended"],
    {
       files: ["**/*.vue"],
       languageOptions: {
          parser: vueParser,
          parserOptions: {
             parser: typescriptParser,
             sourceType: "module",
             jsx: true,
          },
       },
    },
    {
       files: ["**/*.vue"],
       plugins: { "eslint-rules": eslintPluginVue },
       rules: {
          "eslint-rules/vue-style-prettier": "error", // vue模板中style使用prettier
       },
    },
    {
       rules: {
          "linebreak-style": ["off", "lf"], // 换行符
          "quotes": ["error", "double"], // 使用双引号
          "eqeqeq": ["error", "smart"], // 比较的时候使用严格等于
          "semi": ["error", "never"], // 不使用分号结尾
          "comma-dangle": ["error", "always-multiline"], // 要求末尾逗号
          "no-unused-vars": "error", // 禁止出现未使用过的变量
          "default-case": "error", // 要求 switch 语句中有 default 分支
          "brace-style": ["error", "stroustrup", { allowSingleLine: true }], // 大括号风格 ["error", "stroustrup"]
          "no-dupe-keys": "error", // 对象中不允许出现重复的键
          "no-sparse-arrays": "error", // 禁止稀疏数组, [1,,2]
          "no-empty": "error", // 不允许出现空的代码块
          "@typescript-eslint/no-explicit-any": "off", // 允许any类型
          "block-scoped-var": "error", // 将变量声明放在合适的代码块里
          "curly": ["error", "all"], // 强制使用花括号的风格
          "no-self-compare": "error", // 不允许自身比较
          "no-multiple-empty-lines": ["error", { max: 2 }], // 空行最多不能超过两行
          "no-const-assign": "error", // 禁止修改const声明的变量
          "no-redeclare": "error", // 禁止重复声明变量
          "no-func-assign": "error", // 禁止重复的函数声明
          "no-shadow": "error", // 外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
          // 空格
          "space-infix-ops": ["error", { int32Hint: true }], // 操作符周围的空格
          "space-before-function-paren": [
             "error",
             { anonymous: "never", named: "never", asyncArrow: "always" },
          ], // 函数定义时括号前的空格
          "space-before-blocks": ["error", "always"], // 在块语句之前始终有一个空格
          "template-curly-spacing": ["off", "never"], // 要求模板字符串中的嵌入表达式周围空格的使用
          "key-spacing": ["error", { beforeColon: false, afterColon: true }], // 对象字面量中冒号的前后空格
          "array-bracket-spacing": ["off", "always"], // 数组内前后要求有空格
          "object-curly-spacing": ["error", "always"], // 对象内前后要求有空格
          "arrow-spacing": ["error", { before: true, after: true }], // 前头=> 前后都有空格
          "comma-spacing": ["error", { before: false, after: true }], // 要求同一行内逗号后面有空格
          "keyword-spacing": "error", // 关键字前后的空格
          "no-trailing-spaces": "error", // 一行最后不允许有空格
          "switch-colon-spacing": ["error", { before: false, after: true }], // switch 冒号后要有空格
          "no-multi-spaces": "error", // 不允许出现多余的空格

          /* vue相关 - https://eslint.vuejs.org */
          "vue/max-attributes-per-line": [
             "error",
             {
                singleline: { max: 3 },
                multiline: { max: 1 },
             },
          ],
          "vue/html-indent": ["error", "tab"],
          "vue/multi-word-component-names": "off",
          "vue/html-closing-bracket-spacing": [
             "error",
             {
                startTag: "never",
                endTag: "never",
                selfClosingTag: "always",
             },
          ],
          "vue/no-async-in-computed-properties": "error",
          "vue/space-infix-ops": "error",
          "vue/key-spacing": [
             "error",
             {
                beforeColon: false,
                afterColon: true,
             },
          ],
          "vue/no-extra-parens": ["error", "all"],
          "vue/multiline-ternary": ["error", "always-multiline"],

          /* ts相关 - https://eslint.style */
          "@typescript-eslint/indent": "off", // 禁用ts的缩进规则

          /* stylistic相关 - https://eslint.style/ */
          "@stylistic/multiline-ternary": ["error", "always-multiline"],
          "@stylistic/indent": [
             "error",
             "tab",
             {
                SwitchCase: 1,
                offsetTernaryExpressions: true,
                flatTernaryExpressions: true,
             },
          ],
          "@stylistic/member-delimiter-style": "off", // 配置对象属性的分隔符风格
          "@stylistic/max-statements-per-line": ["error", { max: 2 }], // 每行最多2个语句
          "@stylistic/newline-per-chained-call": [
             "error",
             { ignoreChainWithDepth: 2 },
          ], // 多行链式调用时,每行前面必须换行
          "@stylistic/arrow-parens": ["error", "as-needed"], // 箭头函数的括号能省略就省略
          "@stylistic/no-tabs": ["off", { allowIndentationTabs: false }],
          "@stylistic/no-mixed-spaces-and-tabs": "off",
       },
    },
]
// eslint-rules/eslint-plugin-vue
const { name, version } = require("../package.json")
const vueStylePrettier = require("./vue-style-prettier")

const plugin = {
    meta: {
       name,
       version,
    },
    rules: {
       "vue-style-prettier": vueStylePrettier, // vue模板中style使用prettier
    },
}

module.exports = plugin
// eslint-rules/vue-style-prettier
/* vue模板中style使用prettier */
const synchronizedPrettier = require("@prettier/sync")
const { parse } = require("@vue/compiler-sfc")
module.exports = {
    meta: {
       type: "layout",
       fixable: "whitespace",
       messages: {
          checkMessage: "样式不符合规范,应使用Prettier进行格式化",
       },
       schema: [], // no options
    },
    create(context) {
       return {
          Program() {
             if (context.getFilename().endsWith(".vue")) {
                const content = context.getSourceCode().text
                const parsed = parse(content)
                const styles = parsed.descriptor.styles
                if (styles.length > 0) {
                   styles.forEach(styleBlock => {
                      const formatted = "\n" + synchronizedPrettier.format(
                         styleBlock.content,
                         {
                            parser: "scss",
                            singleQuote: false,
                            trailingComma: "all",
                            tabWidth: 4,
                            endOfLine: "auto",
                            arrowParens: "avoid",
                            printWidth: 80,
                            vueIndentScriptAndStyle: false,
                            embeddedLanguageFormatting: "off",
                         },
                      )
                      if (styleBlock.content !== formatted) {
                         context.report({
                            loc: {
                               start: styleBlock.loc.start,
                               end: styleBlock.loc.end,
                            },
                            messageId: "checkMessage",
                            fix(fixer) {
                               return fixer.replaceTextRange(
                                  [
                                     styleBlock.loc.start.offset,
                                     styleBlock.loc.end.offset,
                                  ],
                                  formatted,
                               )
                            },
                         })
                      }
                   })
                }
             }
          },
       }
    },
}

总结

配置可能有重复覆盖的情况,根据项目情况自行调整

规则中大致包含:

  • 缩进为1个tab(等于4个空格)
  • 结尾,不可缩略,但;省略
  • 正常空格都为一个
  • 禁止重复声明(变量,函数等)
  • if else中else另起一行
  • vue中元素超过3个属性默认全部换行
  • vue中的style使用prettier格式化