如何在 eslint 中干净利落地结合 Vue 3 和 Typescript?

Eve*_*ert 7 eslint typescript-eslint vuejs3

基本上,这就是我正在尝试(到目前为止尚未成功)实现的目标:

我希望 eslint 对我的.vue文件和我的.ts文件使用相同的 TypeScript 规则(我的每个组件都使用<script lang="ts" setup>),并使用 mytsconfig.json作为两者的基本 TS 配置。

现在我发现的困难是这样的。据我了解,当你为 eslint 添加 typescript 插件时,需要明确告诉它你在哪里tsconfig.json这里展示了如何做到这一点:

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    tsconfigRootDir: __dirname,
    project: ['./tsconfig.json'],
  },
  plugins: ['@typescript-eslint'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
  ],
};
Run Code Online (Sandbox Code Playgroud)

但问题是,我无法将打字稿解析器设置为主解析器。因为vue解析器需要为主解析器。

所以我把它设置如下:

module.exports = {
  env: {
    node: true,
  },
  root: true,
  ignorePatterns: ["/.vscode/**/*", "/dist/**/*", "/public/**/*"],
  extends: [
    "eslint:recommended",
    "plugin:import/recommended",
    "plugin:import/typescript",
    "plugin:vue/vue3-recommended",
    "@vue/eslint-config-typescript",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:json/recommended",
    "plugin:prettier/recommended",
  ],
  plugins: ["@typescript-eslint"],
  parser: "vue-eslint-parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    vueFeatures: {
      filter: false,
      styleCSSVariableInjection: true,
    },
    parser: {
      // Script parser for `<script>`
      js: "espree",

      // Script parser for `<script lang="ts">`
      ts: "@typescript-eslint/parser",

      // Script parser for vue directives (e.g. `v-if=` or `:attribute=`)
      // and vue interpolations (e.g. `{{variable}}`).
      // If not specified, the parser determined by `<script lang ="...">` is used.
      // "<template>": "typescript-estree",
    },
  },
  settings: {
    "import/resolver": {
      typescript: true,
      node: true,
    },
  },
  globals: {
    $: "readonly",
    $$: "readonly",
    $ref: "readonly",
    $computed: "readonly",
    $shallowRef: "readonly",
    $customRef: "readonly",
    $toRef: "readonly",
  },
}
Run Code Online (Sandbox Code Playgroud)

但正如你所看到的,我没有地方配置tsconfig.json. 现在我遇到了一些问题。一是在我的tsconfig.jsonI 有一个路径别名配置:"paths": { "@/*": ["src/*"] },但 eslint 现在无法识别。我的系统中还有一些tsconfig.jsoneslint 现在不知道的其他配置,因此我收到的警告和错误比我应该收到的要多。

那么如何正确配置 eslint 才能干净利落地与 vue 和 typescript 一起使用呢?

Eve*_*ert 2

对于任何感兴趣的人,我最终让它发挥作用。但与此同时,我也让我的事情变得.eslintrc.js比它本身需要的复杂得多。但我现在懒得简化它,所以我将整个内容粘贴到这里:

// .eslintrc.js

/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")

const isProd = process.env.NODE_ENV === "production"
const runInProd = config => !isProd ? "off" : config

const rules = {
  eslint: {
    "no-debugger": runInProd("warn"),
    "dot-location": ["error", "property"],
    "eqeqeq": ["error", "smart"],
    "max-len": [
      "error",
      {
        code: 90,
        tabWidth: 2,
        ignoreUrls: true,
        ignoreStrings: true,
        ignoreComments: true,
        ignoreRegExpLiterals: true,
        ignoreTrailingComments: true,
        ignorePattern: 'class="|<path',
      },
    ],
    "no-tabs": "error",
    "no-throw-literal": "error",
    "no-trailing-spaces": "warn",
    "no-unused-expressions": ["error", {
      allowShortCircuit: true,
      allowTernary: true,
      allowTaggedTemplates: true,
      enforceForJSX: true,
    }],
    "no-unused-vars": ["warn", {
      args: "all",
      vars: "local",
      caughtErrors: "all",
      ignoreRestSiblings: true,
      varsIgnorePattern: "^_",
      argsIgnorePattern: "^_",
      caughtErrorsIgnorePattern: "^_",
      destructuredArrayIgnorePattern: "^_",
    }],
    "prefer-template": "warn",
  },

  vue: {
    "attribute-hyphenation": [
      "warn",
      "always",
      {
        ignore: [
          "DEF",
          "nameSpaceName",
          "mapDEFToID",
          "contentType",
          "showStat",
          "showLog",
        ],
      },
    ],
    "block-lang": [
      "warn",
      {
        script: {
          lang: "ts",
        },
      },
    ],
    "block-tag-newline": "error",
    "comma-spacing": "warn",
    "component-api-style": ["error", ["script-setup"]],
    "component-name-in-template-casing": ["error", "PascalCase"],
    "define-macros-order": "warn",
    "dot-location": ["error", "property"],
    "html-closing-bracket-newline": [
      "error",
      {
        singleline: "never",
        multiline: "always",
      },
    ],
    "html-indent": ["error", 2, { baseIndent: 0 }],
    "html-self-closing": [
      "error",
      {
        html: {
          void: "any",
        },
      },
    ],
    "max-attributes-per-line": [
      "error",
      {
        singleline: {
          max: 2,
        },
        multiline: {
          max: 2,
        },
      },
    ],
    get "max-len"() {
      return rules.eslint["max-len"]
    },
    "multi-word-component-names": "off",
    "no-setup-props-destructure": "off",
    "no-template-shadow": "off",
    "no-template-target-blank": "error",
    "no-v-html": "off",
    "object-shorthand": "warn",
    "padding-line-between-blocks": "warn",
    "prefer-template": "warn",
    "require-default-prop": "off",
    "script-indent": ["error", 2],
  },

  import: {
    "export": "error",
    "extensions": "off",
    "first": "error",
    "no-self-import": "error",
    "no-unresolved": "error",
    "no-useless-path-segments": [
      "error",
      {
        noUselessIndex: true,
      },
    ],
    "order": [
      "error",
      {
        groups: [
          "type",
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index",
          "object",
        ],
        pathGroups: [
          {
            pattern: "@/*",
            group: "internal",
          },
        ],
      },
    ],

    "no-cycle": runInProd("error"),
    "no-deprecated": runInProd("warn"),
    "no-unused-modules": runInProd("error"),
    "no-named-as-default": runInProd("error"),
  },

  typescript: {
    "ban-types": [
      "error",
      {
        types: {
          "Function": false,
          "{}": false,
        },
        extendDefaults: true,
      },
    ],
    "brace-style": "warn",
    "comma-dangle": ["warn", "always-multiline"],
    "comma-spacing": "warn",
    "consistent-type-exports": runInProd("warn"),
    "consistent-type-imports": runInProd("warn"),
    "dot-notation": "off",
    "func-call-spacing": "warn",
    "indent": "off",
    "keyword-spacing": "warn",
    "lines-between-class-members": "warn",
    "member-delimiter-style": [
      "warn",
      {
        multiline: {
          delimiter: "none",
        },
        singleline: {
          requireLast: false,
        },
      },
    ],
    "no-confusing-void-expression": "off",
    "no-empty-function": [
      "warn",
      {
        allow: ["arrowFunctions"],
      },
    ],
    "no-explicit-any": "off",
    "no-extra-parens": "warn",
    "no-floating-promises": "off",
    "no-misused-promises": [
      "error",
      {
        checksVoidReturn: false,
      },
    ],
    "no-non-null-assertion": "off",
    "no-throw-literal": "error",
    "no-redeclare": [
      "warn",
      {
        ignoreDeclarationMerge: true,
      },
    ],
    "no-redundant-type-constituents": runInProd("warn"),
    "no-unnecessary-boolean-literal-compare": runInProd([
      "warn",
      {
        allowComparingNullableBooleansToTrue: false,
      },
    ]),
    "no-unnecessary-condition": "off",
    "no-unsafe-argument": "off",
    "no-unsafe-assignment": "off",
    "no-unsafe-call": "off",
    "no-unsafe-member-access": "off",
    "no-unsafe-return": "off",
    get "no-unused-expressions"() {
      return rules.eslint["no-unused-expressions"]
    },
    get "no-unused-vars"() {
      return rules.eslint["no-unused-vars"]
    },
    "padding-line-between-statements": [
      "warn",
      { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
      { "blankLine": "any", "prev": ["singleline-const", "singleline-let", "singleline-var"], "next": ["singleline-const", "singleline-let", "singleline-var"] },
      { "blankLine": "always", "prev": "multiline-block-like", "next": "*" },
      { "blankLine": "any", "prev": "expression", "next": "expression" },
      { "blankLine": "always", "prev": "multiline-expression", "next": "*" },
      { "blankLine": "always", "prev": ["interface", "type"], "next": "*" },
      { "blankLine": "always", "prev": "break", "next": "*" },
      { "blankLine": "always", "prev": "throw", "next": "*" },
      { "blankLine": "always", "prev": "*", "next": "return" },
      { "blankLine": "always", "prev": "return", "next": "*" },
    ],
    "prefer-optional-chain": "error",
    "prefer-readonly": "off",
    "prefer-reduce-type-parameter": "off",
    "prefer-return-this-type": runInProd("warn"),
    "prefer-string-starts-ends-with": runInProd("warn"),
    "prefer-ts-expect-error": "warn",
    "promise-function-async": runInProd("warn"),
    "quotes": [
      "error",
      "double",
      {
        avoidEscape: true,
        allowTemplateLiterals: false,
      },
    ],
    "require-await": "off",
    "restrict-template-expressions": "off",
    "return-await": "warn",
    "semi": [
      "error",
      "never",
      {
        beforeStatementContinuationChars: "always",
      },
    ],
    "sort-type-union-intersection-members": "error",
    "space-before-blocks": "warn",
    "space-before-function-paren": [
      "warn",
      {
        anonymous: "never",
        named: "never",
        asyncArrow: "always",
      },
    ],
    "space-infix-ops": "warn",
    "switch-exhaustiveness-check": runInProd("error"),
    "type-annotation-spacing": "warn",
    "unbound-method": runInProd("error"),
    "unified-signatures": "warn",
  },
}

rules["import"] = Object.keys(rules["import"]).reduce(
  (obj, key) => ({
    ...obj,
    [`import/${key}`]: rules["import"][key],
  }),
  {}
)

rules["vue"] = Object.keys(rules["vue"]).reduce(
  (obj, key) => ({
    ...obj,
    [key]: "off",
    [`vue/${key}`]: rules["vue"][key],
  }),
  {}
)

rules["typescript"] = Object.keys(rules["typescript"]).reduce(
  (obj, key) => ({
    ...obj,
    [key]: "off",
    [`@typescript-eslint/${key}`]: rules["typescript"][key],
  }),
  {}
)

const eslint = {
  root: true,
  env: {
    es2021: true,
    browser: true,
  },
  extends: [
    "eslint:recommended",
  ],
  plugins: [
    "import",
    "unused-imports",
  ],
  rules: {
    ...rules["eslint"],
    ...rules["import"],
  },
  settings: {
    "import/ignore": [/\.vue$/],
    "import/extensions": [".js", ".jsx", ".ts", ".tsx", ".vue"],
    "import/parsers": {
      "espree": [".js", ".jsx"],
    },
    "import/resolvers": {
      "alias": [["@/", "./src/"]],
    }
  },
  globals: {
    $: "readonly",
    $$: "readonly",
    $ref: "readonly",
    $computed: "readonly",
    $shallowRef: "readonly",
    $customRef: "readonly",
    $toRef: "readonly",
    x3dom: "readonly",
    X3dMouseEvent: "readonly",
  },
}

const typescript = {
  files: ["*.ts", "*.tsx"],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    tsconfigRootDir: __dirname,
    project: "./tsconfig.json",
  },
  extends: [
    ...eslint.extends,
    "@vue/eslint-config-typescript/recommended",
    ...(isProd ? ["plugin:@typescript-eslint/recommended-requiring-type-checking"] : []),
    "plugin:import/typescript",
  ],
  plugins: [...eslint.plugins, "@typescript-eslint"],
  settings: {
    ...eslint.settings,
    "import/parsers": {
      ...eslint.settings["import/parsers"],
      "@typescript-eslint/parser": [".ts", ".tsx"],
    },
    "import/resolver": {
      typescript: {
        alwaysTryTypes: true,
        project: `${__dirname}/tsconfig.json`,
      },
    },
  },
  rules: {
    ...eslint.rules,
    ...rules["typescript"],
    "@typescript-eslint/no-unused-vars": "off",
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": rules["eslint"]["no-unused-vars"],
  },
}

const vue = {
  files: ["*.vue"],
  parser: "vue-eslint-parser",
  parserOptions: {
    parser: "@typescript-eslint/parser",
    tsconfigRootDir: __dirname,
    project: "./tsconfig.json",
  },
  extends: [
    "plugin:vue/vue3-recommended",
    ...typescript.extends,
  ],
  plugins: [...typescript.plugins, "vue"],
  settings: {
    ...typescript.settings,
    "import/parsers": {
      ...typescript.settings["import/parsers"],
      "vue-eslint-parser": [".vue"],
    },
    "import/resolver": {
      ...typescript.settings["import/resolver"],
      vue: {
        parserOptions: {
          ecmaVersion: "latest",
          sourceType: "module",
          vueFeatures: {
            filter: false,
            styleCSSVariableInjection: true,
          },
          parser: {
            // Script parser for `<script>`
            js: "espree",

            // Script parser for `<script lang="ts">`
            ts: "@typescript-eslint/parser",

            // Script parser for vue directives (e.g. `v-if=` or `:attribute=`)
            // and vue interpolations (e.g. `{{variable}}`).
            // If not specified, the parser determined by `<script lang ="...">` is used.
            // "<template>": "typescript-estree",
          },
        },
      },
    },
  },
  rules: {
    ...typescript.rules,
    ...rules["vue"],
    "eol-last": "off",
    "linebreak-style": "off",
    "max-lines": "off",
    "unicode-bom": "off",
  },
}

module.exports = {
  ...eslint,
  overrides: [
    { ...typescript },
    { ...vue },
  ],
}

Run Code Online (Sandbox Code Playgroud)

哦,这些是我安装的与 eslint 相关的开发依赖项。有些我什至可能不会使用。我仍在计划找出我没有使用的内容并因此可以将其删除。但无论如何,这是粘贴的整个列表:

{
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.1.4",
    "@typescript-eslint/eslint-plugin": "^5.36.1",
    "@typescript-eslint/parser": "^5.36.1",
    "@typescript-eslint/typescript-estree": "^5.36.1",
    "@volar/vue-typescript": "^0.40.5",
    "@vue/eslint-config-typescript": "^11.0.0",
    "eslint": "^8.23.0",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-import-resolver-typescript": "^3.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-unused-imports": "^2.0.0",
    "eslint-plugin-vue": "^9.3.0",
    "espree": "^9.3.3",
    "typescript": "^4.8.2",
    "vue-eslint-parser": "^9.0.3",
  }
}
Run Code Online (Sandbox Code Playgroud)

祝你好运 :-)