前端工程化工具系列(一)—— ESLint(v9.4.0):代码质量守护者 基础篇

1,648 阅读5分钟

ESLint 作为前端工程化中的重要工具,主要用于检查和修复 JavaScript/TypeScript 代码中的语法或格式错误。目的是为了统一代码风格,并确保代码的一致性和可维护性。

1. 环境要求

v9 以上的 ESLint,支持 v18.18.0+,v20.9.0+ 以及 v21.1.0+ 的 Node.js,不支持 v19 及之前的版本。 在命令行中输入以下内容来查看当前系统中已安装的 node 版本。

node -v

Node.js 推荐使用 v18.20.3+ 或者 v20.13.1+。

包管理器推荐使用 PNPM(v9.2.0),而不是 NPM 或者 Yarn。

2. 安装

这里以使用 ESLint + Airbnb 风格为例。

2.1 安装 ESLint

在对应的项目下打开命令行工具(如:Windows 中的 cmd.exe、PowerShell,MacOS 中的 Bash、Zsh),输入以下内容后回车:

pnpm i -D eslint @eslint/js

如使用 NPM 或者 Yarn 作为包管理器,直接将上方内容中的 pnpm 替换成对应的包管理器名即可,例:npm i -D eslint @eslint/js

2.2 针对 JavaScript

安装对应的 Airbnb 配置和插件。

pnpm i -D eslint-config-airbnb-base eslint-plugin-import

2.3 针对 TypeScript

在 JavaScript 的基础上多了规则、插件和解析器。

pnpm i -D eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
  • eslint-config-airbnb-typescript Airbnb 风格的 TypeScript 支持。它将一些常见配置都加了进去,省下了好多工作量。 该插件包含了@typescript-eslint/parser(TypeScript 解析器),它调用 @typescript-eslint/typescript-estree(通过在给定的源代码上调用 TypeScript 编译器,就是 npm i typescript -D 安装的那个,以产生 TypeScript AST,然后将该 AST 转换为 ESLint 期望的格式)。ESlint 默认的解析器叫 espree。

这是一个匈牙利布达佩斯技术和经济大学的学生做的,想想自己的大学生活都在做啥...

  • typescript-eslint 针对 TypeScript 运行分析规则,用于替代之前的 @typescript-eslint/eslint-plugin 与 @typescript-eslint/parser

2.4 兼容之前的配置

从 ESLint v9.0.0 开始,默认采用新的扁平化配置系统。绝大部份扩展和插件没有做适配,因此需要通过以下官方包来兼容旧的配置。

pnpm i -D @eslint/compat @eslint/eslintrc

2.5 支持 Vue

pnpm add -D vue-eslint-parser eslint-plugin-vue

3 配置

在项目的根目录下创建 ESLint 的配置文件:eslint.config.js。

3.1 针对 JavaScript

在 eslint.config.js 中加入以下配置:

/* eslint-disable no-underscore-dangle */
/* eslint-disable import/no-extraneous-dependencies */
// eslint-disable-next-line import/no-unresolved
import { fixupConfigRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import path from 'path';
import { fileURLToPath } from 'url';

// 模仿 CommonJS 变量 -- 如果使用 CommonJS 则不需要
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const flatCompat = new FlatCompat({
  baseDirectory: __dirname, // 可选;默认值: process.cwd()
  resolvePluginsRelativeTo: __dirname, // 可选
  recommendedConfig: js.configs.recommended, // 默认使用 "eslint:recommended"
});

export default [{
  // 配置需要被忽略的文件,替代之前的 .eslintignore 文件
  ignores: [
      '.idea',
      '.vscode',
      '**/dist/',
    ],
  },{
    // eslint-plugin-import 目前还没有兼容 flat,因此增加以下配置
    languageOptions: {
      parserOptions: {
        // Eslint doesn't supply ecmaVersion in `parser.js` `context.parserOptions`
        // This is required to avoid ecmaVersion < 2015 error or 'import' / 'export' error
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
    settings: {
      // This will do the trick
      'import/parsers': {
        espree: ['.js', '.cjs', '.mjs', '.jsx'],
      },
    },
  },
  ...fixupConfigRules(
    // 因为v9变化较大,为了兼容之前的 config,官方提供了转换整个旧的 config 的方法
    flatCompat.config({
      extends: ['airbnb-base'],
      rules: {
        'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
        'max-len': ['error', 200],
      },
    }),
  ),
  {
    languageOptions: {
      ecmaVersion: 'latest', // 指示正在检查的代码的 ECMAScript 版本,确定语法和可用的全局变量
      sourceType: 'module', // 模块化方式,其他可选 script和commonjs 
    },
  },
];

正像注释中所说 eslint-plugin-import 尚未支持 flat config,因此添加了一些配置,以后支持了会更新。

3.2 针对 TypeScript

/* eslint-disable no-underscore-dangle */
import { fixupConfigRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import eslint from '@eslint/js';
// import eslintImport from 'eslint-plugin-import';
import globals from 'globals';
import path from 'path';
import tseslint from 'typescript-eslint';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const flatCompat = new FlatCompat({
  baseDirectory: __dirname,
});

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  ...fixupConfigRules(flatCompat.extends('airbnb-base')),
  ...flatCompat.extends('airbnb-typescript/base'),
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    files: ['**/*.{js,jsx,cjs,mjs}'],
    ...tseslint.configs.disableTypeChecked,
  },
  {
    // 配置需要被忽略的文件,替代之前的 .eslintignore 文件
    ignores: [
      '.idea',
      '.vscode',
      '**/dist/',
    ],
    files: ['**/*.{js,jsx,mjs,cjs,ts,ts,vue}'],
  },
  {
    // plugins: {
    //   import: eslintImport, // 添加了 airbnb-base 就不用再加这个了
    // },
    settings: {
      'import/resolver': {
        typescript: {},
      },
    },
    languageOptions: {
      globals: {
        ...globals.node, // 'process' is not defined.
      },
    },
  },
  {
    files: ['eslint.config.js', 'vite.config.js', 'stylelint.config.js'],
    rules: {
      'import/no-extraneous-dependencies': 'off',
    },
  },
  {
    rules: {
      'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'max-len': ['error', 200],
    },
  },
);

3.3 支持 Vue

  ...fixupConfigRules(
    // 因为v9变化较大,为了兼容之前的 config,官方提供了转换整个旧的 config 的方法
    flatCompat.config({
      extends: [
        'airbnb-base',
        'airbnb-typescript/base',
        'plugin:vue/vue3-essential', // 增加这行
        'plugin:@typescript-eslint/eslint-recommended',
        'plugin:@typescript-eslint/recommended',
      ],
      parser: 'vue-eslint-parser', // 增加这行
      parserOptions: {
        parser: '@typescript-eslint/parser',
        project: './tsconfig.json',
        extraFileExtensions: ['.vue'], // 在这里添加.vue文件扩展名
      },

4 结合 Husky

利用 Husky 在 git commit 时自动校验文件中的代码内容,如不符合规范,则不能被 commit。详细操作见《前端工程化工具系列(五)—— Husky(v9.0.11)&lint-staged(v15.2.5):代码提交前的自动审查利器》中的 2.1节。

5 结合 VSCode

配合 VSCode 插件,可在做文件保存时自动修复错误。

6 问题解决

问题1:

Error: Could not find config file.

解决: ESLint9 的配置文件名必须为 eslint.config.js,之前的 eslintrc.(可为 空,js, yaml, yml, json)都被废弃了。

问题2:

SyntaxError: Cannot use import statement outside a module

解决: 两种方式:

  1. 在 package.json 中添加 "type": "module",形如:
{
	"type": "module",
	"devDependencies": {
		"@eslint/js": "^9.4.0",
		"eslint": "^9.4.0",
		"eslint-config-airbnb-base": "^15.0.0",
		"eslint-plugin-import": "^2.29.1"
	}
}
  1. 将配置内容修改为 CommonJS 格式:
// eslint.config.js
module.exports = [
    {
        rules: {
            semi: "error",
            "prefer-const": "error"
        }
    }
];

问题3:

.eslintignore 中定义的忽略文件不起作用。 解决: ESLint9 中需要在 eslint.config.js 中进行配置,如:

 {
    ignores: [
      '/dist',
      '.idea',
      '.vscode',
    ],
  },

node_modules 和 .git 这两个文件夹会默认被添加进来,不需要专门配置。

问题4:

Unable to resolve signature of class decorator when called as an expression. 需要在 tsconfig.json 中的 compilerOptions 节点下添加:

 "experimentalDecorators": true

问题5:

TypeError: context.getAncestors is not a function. 如上文提到的,有些旧的配置在转换成 flat config 时会有错误,需要通过安装和配置以下内容来解决:

pnpm add @eslint/compat -D
import { fixupConfigRules } from '@eslint/compat';

...fixupConfigRules(flatCompat(旧的配置)) 

详情请参看以上的 3 配置。

问题6:

TypeError: Key "rules": Key "import/extensions": Could not find plugin "import". 先安装:

pnpm add eslint-plugin-import -D

然后在 eslint.config.js 中增加配置:

import eslintImport from 'eslint-plugin-import';

    plugins: {
      import: eslintImport, // 添加了 airbnb-base 就不用再加这个了
    },

详情请参看以上的 3.2 针对 TypeScript,被注释掉的部分。

问题7:

Unable to resolve path to module 'typescript-eslint'.eslintimport/no-unresolved. 需要在 eslint.config.js 中增加配置:

    settings: {
      'import/resolver': {
        typescript: {},
      },
    },

详情请参看以上的 3.2 针对 TypeScript。