Skip to content

ESLint 与 Prettier

概述

在现代前端工程中,代码质量保障和格式统一是不可忽视的基础设施。ESLint 负责静态代码分析——发现潜在 Bug、强制编码规范;Prettier 负责代码格式化——统一代码风格、消除格式争论。两者各司其职,又需要协同工作。

代码质量保障体系

┌─────────────────────────────────────────────────┐
│                  开发者编写代码                    │
└──────────────────────┬──────────────────────────┘

        ┌──────────────┴──────────────┐
        ↓                             ↓
┌───────────────┐            ┌────────────────┐
│    ESLint     │            │    Prettier    │
│               │            │                │
│  逻辑检查      │            │  格式化         │
│  - 未使用变量   │            │  - 缩进         │
│  - 类型错误     │            │  - 引号         │
│  - 最佳实践     │            │  - 分号         │
│  - 可能的 Bug  │            │  - 换行         │
└───────┬───────┘            └───────┬────────┘
        ↓                            ↓
        └──────────────┬─────────────┘

         ┌─────────────────────────┐
         │  lint-staged + husky    │
         │  Git pre-commit 拦截    │
         └────────────┬────────────┘

         ┌─────────────────────────┐
         │   commitlint            │
         │   提交信息规范检查        │
         └────────────┬────────────┘

              代码合入仓库

本文将从底层原理到工程实践,全面深入地剖析 ESLint、Prettier 及整套前端代码规范工具链。


一、ESLint 深入

1.1 ESLint 工作原理

ESLint 的核心工作流程可以概括为四个阶段:解析(Parse)→ 遍历 AST → 规则检查 → 报告/修复

ESLint 工作流程

源代码字符串


┌──────────────────────────┐
│       Parser(解析器)     │
│                          │
│  默认: espree            │
│  TS:  @typescript-eslint │
│       /parser            │
│  Vue: vue-eslint-parser  │
│  Babel: @babel/eslint    │
│         -parser          │
└────────────┬─────────────┘
             │  生成 AST

┌──────────────────────────┐
│     AST(抽象语法树)      │
│                          │
│  遵循 ESTree 规范         │
│  TypeScript 使用          │
│  TSESTree 扩展规范        │
└────────────┬─────────────┘


┌──────────────────────────┐
│    Traverser(遍历器)     │
│                          │
│  深度优先遍历 AST 节点     │
│  对每个节点触发:           │
│    - 进入事件 (enter)     │
│    - 离开事件 (exit)      │
└────────────┬─────────────┘
             │  触发对应规则回调

┌──────────────────────────┐
│     Rules(规则集合)      │
│                          │
│  每条规则监听特定 AST 节点  │
│  检查是否违反约定          │
│  收集 lint 问题列表        │
└────────────┬─────────────┘


┌──────────────────────────┐
│    Linting Results       │
│                          │
│  - error / warning       │
│  - 定位: 文件/行/列       │
│  - 可自动修复的问题标记     │
└──────────────────────────┘

解析阶段:从源码到 AST

ESLint 默认使用 espree 作为解析器,espree 是 Acorn 的一个分支,严格遵循 ECMAScript 标准。解析器将源代码字符串转换为抽象语法树(AST),这棵树的每个节点都代表源码中的一个语法结构。

以一段简单代码为例:

js
const x = 42

解析后生成的 AST 结构(简化):

Program
└── VariableDeclaration (kind: "const")
    └── VariableDeclarator
        ├── Identifier (name: "x")
        └── Literal (value: 42, raw: "42")

对应的 AST JSON(核心字段):

json
{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "x"
          },
          "init": {
            "type": "Literal",
            "value": 42,
            "raw": "42"
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

不同解析器的适用场景

┌──────────────────────┬────────────────────────────────────┐
│       解析器          │            适用场景                 │
├──────────────────────┼────────────────────────────────────┤
│ espree               │ 纯 JavaScript(ESLint 默认)        │
├──────────────────────┼────────────────────────────────────┤
│ @typescript-eslint   │ TypeScript 项目                     │
│ /parser              │ 生成 TSESTree(ESTree 超集)         │
├──────────────────────┼────────────────────────────────────┤
│ vue-eslint-parser    │ Vue SFC(.vue 文件)                │
│                      │ 内部委托 TS parser 处理 <script>    │
├──────────────────────┼────────────────────────────────────┤
│ @babel/eslint-parser │ 使用 Babel 实验性语法               │
│                      │ 如 decorators / pipeline operator  │
└──────────────────────┴────────────────────────────────────┘

规则检查阶段:访问者模式

ESLint 的规则系统基于访问者模式(Visitor Pattern)。每条规则声明它关心哪些 AST 节点类型,当遍历器访问到对应节点时,自动调用规则的回调函数。

AST 遍历与规则触发示意

           Program (enter)


     VariableDeclaration (enter)  ← no-var 规则监听此节点


       VariableDeclarator (enter)
           ┌──┴──┐
           ↓     ↓
      Identifier  Literal (enter) ← no-magic-numbers 规则监听此节点
      (name:"x") (value:42)
           ↓     ↓
      Identifier  Literal (exit)
           └──┬──┘

       VariableDeclarator (exit)

     VariableDeclaration (exit)

           Program (exit)

每条规则独立运行、互不干扰。一次遍历完成后,ESLint 汇总所有规则产出的问题列表,按严重程度(error / warning)和位置信息输出。

1.2 配置体系

Flat Config(eslint.config.js)—— ESLint v9+ 新标准

ESLint v9 将 Flat Config 设为默认配置方式。相比旧的 .eslintrc,Flat Config 使用纯 JavaScript 模块导出一个配置对象数组,每个对象描述一组文件的 lint 规则。

js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/recommended'],
  {
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {
        projectService: true,
      },
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
  {
    files: ['**/*.vue'],
    languageOptions: {
      parserOptions: {
        parser: tseslint.parser,
      },
    },
  },
  {
    ignores: ['dist/', 'node_modules/', '*.config.js'],
  },
]

Flat Config 核心设计理念:

Flat Config 配置合并策略

配置数组:[ config1, config2, config3, ... ]

对于每个被 lint 的文件:
  1. 从左到右遍历配置数组
  2. 检查 files / ignores 是否匹配当前文件
  3. 匹配的配置按顺序合并(后者覆盖前者)
  4. 最终得到该文件的有效配置

┌──────────┐  ┌──────────┐  ┌──────────┐
│ config1  │  │ config2  │  │ config3  │
│          │  │          │  │          │
│ files:   │  │ files:   │  │ files:   │
│ **/*.js  │  │ **/*.ts  │  │ **/*.ts  │
│          │  │          │  │          │
│ rules:   │  │ rules:   │  │ rules:   │
│ semi:err │  │ semi:off │  │ no-any:  │
│          │  │          │  │   warn   │
└──────────┘  └──────────┘  └──────────┘

对于 foo.ts:
  config1 不匹配(*.js)→ 跳过
  config2 匹配 → { semi: off }
  config3 匹配 → { semi: off, no-any: warn }

.eslintrc(旧配置)—— Legacy Config

旧配置支持 .eslintrc.js.eslintrc.json.eslintrc.yml 等格式,使用 extends 继承、overrides 覆盖、env 声明环境变量。

json
{
  "root": true,
  "env": {
    "browser": true,
    "es2024": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "plugins": ["@typescript-eslint", "react", "react-hooks"],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  },
  "overrides": [
    {
      "files": ["**/*.test.ts", "**/*.spec.ts"],
      "env": { "jest": true },
      "rules": {
        "@typescript-eslint/no-explicit-any": "off"
      }
    }
  ]
}

Flat Config vs Legacy Config 对比

┌────────────────────┬───────────────────────┬───────────────────────┐
│       特性          │     Flat Config        │    Legacy Config      │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 配置文件            │ eslint.config.js       │ .eslintrc.*           │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 格式                │ 纯 JS/TS ESM 模块     │ JSON/YAML/JS          │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 配置继承            │ 数组展开 (spread)      │ extends 字段          │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 插件注册            │ 配置对象中直接引入      │ plugins 字段 (字符串)  │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 级联查找            │ 无(单一配置入口)      │ 逐级向上查找到 root    │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 环境声明 (env)      │ 移除,用 globals 替代  │ env 字段              │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 全局变量            │ languageOptions.globals│ env + globals         │
├────────────────────┼───────────────────────┼───────────────────────┤
│ 文件匹配            │ files / ignores        │ overrides.files       │
├────────────────────┼───────────────────────┼───────────────────────┤
│ ESLint 版本         │ v9+ 默认               │ v8 及以下默认          │
└────────────────────┴───────────────────────┴───────────────────────┘

1.3 规则分类

ESLint 内置规则按功能分为三大类:

Possible Problems(可能的错误)

检测代码中可能存在的逻辑错误和运行时问题。

┌────────────────────────┬──────────────────────────────────────┐
│         规则            │               说明                    │
├────────────────────────┼──────────────────────────────────────┤
│ no-undef               │ 禁止使用未声明的变量                   │
├────────────────────────┼──────────────────────────────────────┤
│ no-unused-vars         │ 禁止定义未使用的变量                   │
├────────────────────────┼──────────────────────────────────────┤
│ no-dupe-keys           │ 禁止对象字面量中出现重复的 key          │
├────────────────────────┼──────────────────────────────────────┤
│ no-unreachable         │ 禁止在 return/throw 后出现不可达代码    │
├────────────────────────┼──────────────────────────────────────┤
│ no-constant-condition  │ 禁止在条件判断中使用常量表达式           │
├────────────────────────┼──────────────────────────────────────┤
│ no-duplicate-case      │ 禁止 switch 中出现重复的 case          │
├────────────────────────┼──────────────────────────────────────┤
│ use-isnan              │ 要求使用 isNaN() 检查 NaN             │
├────────────────────────┼──────────────────────────────────────┤
│ valid-typeof           │ 强制 typeof 与有效字符串比较            │
└────────────────────────┴──────────────────────────────────────┘

Suggestions(建议)

提出更好的编码方式,不一定是错误但建议改进。

┌────────────────────────┬──────────────────────────────────────┐
│         规则            │               说明                    │
├────────────────────────┼──────────────────────────────────────┤
│ eqeqeq                │ 要求使用 === 和 !==                   │
├────────────────────────┼──────────────────────────────────────┤
│ no-var                 │ 要求使用 let 或 const 代替 var        │
├────────────────────────┼──────────────────────────────────────┤
│ prefer-const           │ 对不再赋值的变量要求使用 const          │
├────────────────────────┼──────────────────────────────────────┤
│ no-eval                │ 禁止使用 eval()                      │
├────────────────────────┼──────────────────────────────────────┤
│ no-implied-eval        │ 禁止使用隐式 eval (setTimeout 字符串) │
├────────────────────────┼──────────────────────────────────────┤
│ curly                  │ 要求 if/else/for/while 使用花括号      │
├────────────────────────┼──────────────────────────────────────┤
│ no-throw-literal       │ 禁止 throw 字面量,要求 throw Error   │
├────────────────────────┼──────────────────────────────────────┤
│ prefer-template        │ 建议使用模板字符串代替字符串拼接         │
└────────────────────────┴──────────────────────────────────────┘

Layout & Formatting(排版格式)

控制代码的视觉呈现,如缩进、空格、换行等。ESLint v8.53+ 已废弃全部格式化规则,推荐交由 Prettier 处理。

已废弃的格式化规则(部分):
  indent, semi, quotes, comma-dangle,
  no-trailing-spaces, eol-last,
  max-len, object-curly-spacing ...

ESLint 官方建议:
  "格式化交给 Prettier,ESLint 专注于代码逻辑"

1.4 常用 Shareable Config

Shareable Config 是预定义好的规则集合,团队可以直接复用。

eslint-config-airbnb

Airbnb 的 JavaScript 编码规范,是最流行的 ESLint 配置之一,规则非常严格。

eslint-config-airbnb 特点

┌──────────────────────────────────────┐
│  核心规则选择                          │
│                                      │
│  ✓ 强制 const / let,禁止 var        │
│  ✓ 强制使用单引号                     │
│  ✓ 强制使用分号                       │
│  ✓ 强制使用 === / !==               │
│  ✓ 强制解构赋值                       │
│  ✓ 强制箭头函数简写                   │
│  ✓ 禁止 console(需显式关闭)         │
│  ✓ 强制 import 排序                  │
│                                      │
│  附带:                               │
│  eslint-config-airbnb-base(无 React)│
│  eslint-config-airbnb/hooks          │
└──────────────────────────────────────┘

@antfu/eslint-config

Anthony Fu(Vue/Vite 核心成员)维护的配置,特点是开箱即用、支持自动检测项目类型。

js
import antfu from '@antfu/eslint-config'

export default antfu({
  typescript: true,
  vue: true,
  jsonc: false,
  yaml: false,
  stylistic: {
    indent: 2,
    quotes: 'single',
  },
  rules: {
    'no-console': 'warn',
  },
})
@antfu/eslint-config 特点

┌──────────────────────────────────────┐
│  ✓ Flat Config 原生设计              │
│  ✓ 内置 Prettier 替代方案            │
│    (用 ESLint Stylistic 管格式)     │
│  ✓ 自动检测 TS / Vue / React        │
│  ✓ 单引号、无分号风格                 │
│  ✓ 自动排序 import / 对象 key        │
│  ✓ 支持 JSON / YAML / Markdown lint │
└──────────────────────────────────────┘

1.5 与 TypeScript 集成

TypeScript 项目需要专用的解析器和插件,因为 ESLint 默认解析器 espree 无法理解 TypeScript 语法。

TypeScript + ESLint 集成架构

TypeScript 源码 (.ts / .tsx)


┌─────────────────────────────┐
│  @typescript-eslint/parser  │
│                             │
│  内部调用 TypeScript 编译器   │
│  生成 TSESTree AST          │
│  (ESTree 的超集)           │
│                             │
│  可选:开启类型信息           │
│  parserOptions.projectService│
└──────────────┬──────────────┘


┌─────────────────────────────┐
│  TSESTree AST               │
│                             │
│  包含 TS 特有节点:          │
│  - TSTypeAnnotation         │
│  - TSInterfaceDeclaration   │
│  - TSTypeAliasDeclaration   │
│  - TSEnumDeclaration        │
│  - TSAsExpression           │
│  ...                        │
└──────────────┬──────────────┘


┌─────────────────────────────┐
│  @typescript-eslint         │
│  /eslint-plugin             │
│                             │
│  提供 TS 专用规则:          │
│  - no-unused-vars (增强)    │
│  - no-explicit-any          │
│  - consistent-type-imports  │
│  - no-floating-promises     │
│  - strict-boolean-expressions│
│  ...                        │
└─────────────────────────────┘

Flat Config 下的 TypeScript 配置:

js
import tseslint from 'typescript-eslint'

export default tseslint.config(
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    rules: {
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/no-misused-promises': 'error',
      '@typescript-eslint/strict-boolean-expressions': 'warn',
      '@typescript-eslint/consistent-type-imports': [
        'error',
        { prefer: 'type-imports' },
      ],
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
      ],
    },
  },
)

类型感知规则(Type-Aware Rules)需要 TypeScript 程序实例,能够检查更深层的问题:

普通规则 vs 类型感知规则

普通规则(仅 AST):
  no-unused-vars → 只看变量是否在当前文件被引用
  no-explicit-any → 只看是否出现 any 关键字

类型感知规则(AST + 类型信息):
  no-floating-promises → 检测 Promise 是否未被 await
    需要知道函数返回值的类型 → 需要类型信息

  no-misused-promises → 检测 Promise 是否传给了非异步上下文
    需要推断表达式的类型 → 需要类型信息

  strict-boolean-expressions → 检测条件判断中是否存在隐式转换
    需要知道变量的精确类型 → 需要类型信息

性能代价:
  类型感知规则需要加载完整的 TypeScript 程序
  会显著增加 lint 耗时(通常 2-5 倍)
  建议仅在 CI 中启用全量类型检查规则

1.6 自定义 ESLint 规则开发

ESLint 的强大之处在于其规则的可扩展性。每条规则是一个对象,包含 meta(元信息)和 create(规则逻辑)两个核心部分。

Rule 结构

ESLint Rule 结构

module.exports = {
  meta: {
    type: "problem" | "suggestion" | "layout",
    docs: {
      description: "规则描述",
      recommended: true/false,
    },
    fixable: "code" | "whitespace" | null,
    schema: [...],        ← 规则选项的 JSON Schema
    messages: {           ← 错误消息模板
      messageId: "..."
    },
  },
  create(context) {
    return {
      "AST节点类型"(node) {
        ← 检查逻辑
        ← context.report() 报告问题
      }
    }
  }
}

手写规则:禁止 Magic Number

Magic Number 指代码中直接出现的、含义不明的数字字面量。我们来实现一个规则,强制使用命名常量代替 magic number。

js
const rule = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Disallow magic numbers, require named constants',
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignore: {
            type: 'array',
            items: { type: 'number' },
          },
          ignoreArrayIndexes: {
            type: 'boolean',
          },
          ignoreDefaultValues: {
            type: 'boolean',
          },
        },
        additionalProperties: false,
      },
    ],
    messages: {
      noMagicNumber:
        "No magic number: {{value}}. Extract to a named constant.",
    },
  },

  create(context) {
    const options = context.options[0] || {}
    const ignoreSet = new Set(options.ignore || [0, 1, -1])
    const ignoreArrayIndexes = options.ignoreArrayIndexes || false
    const ignoreDefaultValues = options.ignoreDefaultValues || false

    function isNumericLiteral(node) {
      return node.type === 'Literal' && typeof node.value === 'number'
    }

    function isConstDeclaration(node) {
      let current = node.parent
      while (current) {
        if (
          current.type === 'VariableDeclaration' &&
          current.kind === 'const'
        ) {
          return true
        }
        if (current.type === 'Property' || current.type === 'AssignmentPattern') {
          current = current.parent
          continue
        }
        break
      }
      return false
    }

    function isEnumMember(node) {
      return node.parent && node.parent.type === 'TSEnumMember'
    }

    function isArrayIndex(node) {
      return (
        node.parent &&
        node.parent.type === 'MemberExpression' &&
        node.parent.computed &&
        node.parent.property === node
      )
    }

    function isDefaultValue(node) {
      return node.parent && node.parent.type === 'AssignmentPattern'
    }

    return {
      Literal(node) {
        if (!isNumericLiteral(node)) return
        if (ignoreSet.has(node.value)) return
        if (isConstDeclaration(node)) return
        if (isEnumMember(node)) return
        if (ignoreArrayIndexes && isArrayIndex(node)) return
        if (ignoreDefaultValues && isDefaultValue(node)) return

        context.report({
          node,
          messageId: 'noMagicNumber',
          data: { value: node.value },
        })
      },
    }
  },
}

使用这条规则后的效果:

js
const timeout = 3000
setTimeout(callback, timeout)

const MAX_RETRIES = 5
for (let i = 0; i < MAX_RETRIES; i++) {
  retry()
}

setTimeout(callback, 3000)

for (let i = 0; i < 5; i++) {
  retry()
}

测试自定义规则

ESLint 提供 RuleTester 工具来测试规则:

js
import { RuleTester } from 'eslint'
import rule from './no-magic-number.js'

const tester = new RuleTester({
  languageOptions: { ecmaVersion: 2024 },
})

tester.run('no-magic-number', rule, {
  valid: [
    'const MAX = 100',
    'const arr = [0, 1]',
    'if (x === 0) {}',
    'if (x === -1) {}',
  ],
  invalid: [
    {
      code: 'const timeout = x + 3000',
      errors: [{ messageId: 'noMagicNumber', data: { value: 3000 } }],
    },
    {
      code: 'if (x > 42) {}',
      errors: [{ messageId: 'noMagicNumber', data: { value: 42 } }],
    },
  ],
})

开发插件

将自定义规则打包为 ESLint 插件发布:

js
import noMagicNumber from './rules/no-magic-number.js'

const plugin = {
  meta: {
    name: 'eslint-plugin-my-team',
    version: '1.0.0',
  },
  rules: {
    'no-magic-number': noMagicNumber,
  },
  configs: {},
}

plugin.configs.recommended = [
  {
    plugins: {
      'my-team': plugin,
    },
    rules: {
      'my-team/no-magic-number': 'warn',
    },
  },
]

export default plugin

二、Prettier

2.1 设计哲学:Opinionated

Prettier 的核心理念是 Opinionated(有主见的)——它故意减少配置选项,用一套经过深思熟虑的默认规则统一所有代码风格。

Prettier 设计哲学

传统格式化工具(如 ESLint formatting rules):
┌───────────────────────────────────────┐
│  几十个格式相关选项                      │
│  indent? tab? 2? 4?                   │
│  quotes? single? double?              │
│  semicolons? always? never? ASI?      │
│  trailing commas? none? all? es5?     │
│  bracket spacing? same-line? ...      │
│                                       │
│  → 团队争论不休                         │
│  → 配置地狱                             │
│  → 每个项目风格不同                      │
└───────────────────────────────────────┘

Prettier:
┌───────────────────────────────────────┐
│  极少的配置选项(约 20 个)              │
│  大部分选项不推荐修改                    │
│  "停止争论代码风格,让工具做决定"         │
│                                       │
│  → 所有项目风格一致                      │
│  → 零配置或极少配置即可使用              │
│  → 开发者专注于逻辑而非格式              │
└───────────────────────────────────────┘

Prettier 的格式化原理:

Prettier 格式化流水线

源代码


┌──────────────┐
│    Parser    │
│  解析为 AST  │
│  (多语言支持) │
└──────┬───────┘


┌──────────────┐
│   IR (Doc)   │
│  中间表示     │
│  与原始格式   │
│  完全无关     │
└──────┬───────┘


┌──────────────┐
│   Printer    │
│  根据 IR     │
│  重新输出代码 │
│  应用格式规则 │
└──────┬───────┘


格式化后的代码

关键特性:Prettier 完全丢弃原始代码格式,从零重建输出。这意味着不管你的代码之前是什么样子,经过 Prettier 处理后,结果是完全确定的(幂等性)。

2.2 .prettierrc 配置

json
{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "jsxSingleQuote": false,
  "trailingComma": "all",
  "bracketSpacing": true,
  "bracketSameLine": false,
  "arrowParens": "always",
  "endOfLine": "lf",
  "singleAttributePerLine": false
}

配置项详解:

┌───────────────────────┬──────────┬────────────────────────────────────┐
│        选项            │  默认值   │              说明                   │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ printWidth            │   80     │ 每行最大字符宽度,超出则换行          │
│                       │          │ 不是硬限制,Prettier 尽力而为        │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ tabWidth              │   2      │ 缩进空格数                          │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ useTabs               │  false   │ 是否使用 Tab 缩进                   │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ semi                  │  true    │ 语句末尾是否加分号                   │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ singleQuote           │  false   │ 是否使用单引号(JSX 单独控制)       │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ trailingComma         │  "all"   │ 尾随逗号: "all" | "es5" | "none"  │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ bracketSpacing        │  true    │ 对象花括号内是否加空格               │
│                       │          │ true:  { foo: bar }               │
│                       │          │ false: {foo: bar}                 │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ bracketSameLine       │  false   │ HTML 标签 > 是否与属性同行           │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ arrowParens           │ "always" │ 箭头函数单参数是否加括号              │
│                       │          │ "always": (x) => x               │
│                       │          │ "avoid":   x => x                │
├───────────────────────┼──────────┼────────────────────────────────────┤
│ endOfLine             │  "lf"    │ 换行符: "lf" | "crlf" | "auto"   │
└───────────────────────┴──────────┴────────────────────────────────────┘

配置文件优先级(从高到低):

1. package.json 中的 "prettier" 字段
2. .prettierrc (JSON / YAML)
3. .prettierrc.json / .prettierrc.yml
4. .prettierrc.js / .prettierrc.cjs / .prettierrc.mjs
5. prettier.config.js / prettier.config.cjs / prettier.config.mjs
6. .prettierrc.toml

2.3 Prettier 与 ESLint 的冲突解决

当 ESLint 和 Prettier 同时使用时,某些规则会产生冲突——ESLint 要求一种格式,Prettier 要求另一种格式,导致两个工具互相"打架"。

冲突示例

ESLint (indent: 4):          Prettier (tabWidth: 2):
┌──────────────────┐         ┌──────────────────┐
│ function foo() { │         │ function foo() { │
│     return 1     │   ←→    │   return 1       │
│ }                │  冲突!   │ }                │
└──────────────────┘         └──────────────────┘

ESLint 修复后 → Prettier 又改回来 → 无限循环

解决方案有两种思路:

方案一:eslint-config-prettier(推荐)

关闭 ESLint 中与 Prettier 冲突的规则,让 Prettier 全权负责格式化。

eslint-config-prettier 原理

┌──────────────────────────────────────────────┐
│  ESLint 规则集                                │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │ 逻辑规则  │  │ 建议规则  │  │ 格式规则  │   │
│  │          │  │          │  │ xxxxxxxx │   │
│  │ 保留 ✓   │  │ 保留 ✓   │  │ 关闭 ✗   │   │
│  └──────────┘  └──────────┘  └──────────┘   │
│                                              │
│  eslint-config-prettier 做的事情:             │
│  将所有 与 Prettier 冲突的规则设为 "off"        │
│  涵盖 ESLint 核心 + 多个插件的格式规则          │
└──────────────────────────────────────────────┘

Flat Config 中使用:

js
import eslintConfigPrettier from 'eslint-config-prettier'

export default [
  ...otherConfigs,
  eslintConfigPrettier,
]

Legacy Config 中使用:

json
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ]
}

注意:prettier 必须放在 extends 数组的最后一个,这样它才能覆盖前面所有配置中的格式化规则。

方案二:eslint-plugin-prettier

将 Prettier 作为一条 ESLint 规则运行——在 ESLint 中集成 Prettier,将格式差异报告为 ESLint 错误。

eslint-plugin-prettier 原理

源代码 ──→ ESLint 处理

              ├──→ 常规 ESLint 规则检查

              └──→ prettier/prettier 规则

                      ├── 调用 Prettier 格式化
                      ├── 对比原始代码与格式化结果
                      └── 差异部分报告为 ESLint error
                          并提供 autofix
js
import eslintPluginPrettier from 'eslint-plugin-prettier/recommended'

export default [
  ...otherConfigs,
  eslintPluginPrettier,
]

两种方案对比

┌─────────────────────┬──────────────────────┬──────────────────────┐
│                     │ eslint-config-prettier│ eslint-plugin-prettier│
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 做法                │ 关闭冲突规则          │ 把 Prettier 当规则跑  │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 格式化由谁负责       │ Prettier 单独运行     │ ESLint 内集成 Prettier│
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 性能                │ ✓ 好(各司其职)      │ ✗ 较慢(双重处理)     │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 编辑器体验           │ 需分别配置            │ 统一通过 ESLint 报错  │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 官方推荐             │ ✓ 是                 │ 可用但非首推           │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 红色波浪线           │ 只有 ESLint 逻辑错误  │ 格式问题也显示红线     │
└─────────────────────┴──────────────────────┴──────────────────────┘

2.4 推荐方案:职责分离

最佳实践:Prettier 管格式,ESLint 管逻辑

┌──────────────────────────────────────────────┐
│                 代码质量                       │
│                                              │
│    ┌─── ESLint 负责 ───┐  ┌─ Prettier 负责 ─┐│
│    │                   │  │                 ││
│    │  ✓ 未使用变量      │  │  ✓ 缩进         ││
│    │  ✓ 类型安全        │  │  ✓ 引号风格     ││
│    │  ✓ 最佳实践        │  │  ✓ 分号         ││
│    │  ✓ 代码复杂度      │  │  ✓ 换行         ││
│    │  ✓ import 规范     │  │  ✓ 尾随逗号     ││
│    │  ✓ React hooks 规则│  │  ✓ 括号间距     ││
│    │  ✓ 可达性检查      │  │  ✓ 对象/数组     ││
│    │                   │  │    格式排列      ││
│    └───────────────────┘  └─────────────────┘│
│                                              │
│    执行时机:                                  │
│    - 保存时:Prettier 格式化 + ESLint 修复     │
│    - pre-commit:lint-staged 同时运行两者      │
│    - CI:两者都检查,任一失败则阻断             │
└──────────────────────────────────────────────┘

三、完整工程化配置

3.1 ESLint + Prettier + TypeScript + React

一个完整的 React + TypeScript 项目配置方案:

package.json 依赖

json
{
  "devDependencies": {
    "eslint": "^9.0.0",
    "@eslint/js": "^9.0.0",
    "typescript-eslint": "^8.0.0",
    "eslint-plugin-react": "^7.35.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.0",
    "eslint-config-prettier": "^9.1.0",
    "prettier": "^3.3.0",
    "globals": "^15.0.0"
  }
}

eslint.config.js

js
import js from '@eslint/js'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import eslintConfigPrettier from 'eslint-config-prettier'

export default tseslint.config(
  { ignores: ['dist', 'node_modules', 'coverage'] },
  js.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.es2024,
      },
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    files: ['**/*.{tsx,jsx}'],
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    settings: {
      react: { version: 'detect' },
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      'react/prop-types': 'off',
      'react/react-in-jsx-scope': 'off',
    },
  },
  {
    rules: {
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
      ],
      '@typescript-eslint/consistent-type-imports': [
        'error',
        { prefer: 'type-imports' },
      ],
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
  eslintConfigPrettier,
)

.prettierrc

json
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 80,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf"
}

.prettierignore

dist
node_modules
coverage
pnpm-lock.yaml
package-lock.json

package.json scripts

json
{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\""
  }
}

3.2 ESLint + Prettier + TypeScript + Vue

eslint.config.js

js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import eslintConfigPrettier from 'eslint-config-prettier'

export default [
  { ignores: ['dist', 'node_modules'] },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/recommended'],
  {
    files: ['**/*.vue'],
    languageOptions: {
      parserOptions: {
        parser: tseslint.parser,
        extraFileExtensions: ['.vue'],
      },
    },
  },
  {
    rules: {
      'vue/multi-word-component-names': 'warn',
      'vue/no-unused-vars': 'error',
      'vue/define-macros-order': [
        'error',
        { order: ['defineProps', 'defineEmits'] },
      ],
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_' },
      ],
    },
  },
  eslintConfigPrettier,
]

3.3 lint-staged + husky:Git pre-commit 钩子

lint-staged 只对 Git 暂存区(staged)的文件运行检查,避免全量 lint 的性能问题。husky 提供 Git hooks 管理能力。

pre-commit 工作流

git add file1.ts file2.tsx


git commit -m "feat: ..."


┌──────────────────────────┐
│  husky → pre-commit hook │
└────────────┬─────────────┘


┌──────────────────────────┐
│      lint-staged         │
│                          │
│  获取暂存区文件列表        │
│  file1.ts, file2.tsx     │
│                          │
│  按 glob 匹配执行命令:    │
│  *.{ts,tsx} →            │
│    eslint --fix          │
│    prettier --write      │
│                          │
│  检查通过?               │
│  ├── Yes → 继续 commit   │
│  └── No  → 中止 commit   │
└──────────────────────────┘

安装与配置

bash
npm install -D husky lint-staged
npx husky init

执行后会在 .husky/pre-commit 生成 hook 文件:

bash
npx lint-staged

lint-staged 配置

package.json 中:

json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml}": [
      "prettier --write"
    ],
    "*.css": [
      "prettier --write"
    ]
  }
}

也可以使用独立配置文件 .lintstagedrc.js

js
export default {
  '*.{ts,tsx,js,jsx}': (filenames) => [
    `eslint --fix ${filenames.join(' ')}`,
    `prettier --write ${filenames.join(' ')}`,
  ],
  '*.{json,md,yml,yaml,css}': (filenames) => [
    `prettier --write ${filenames.join(' ')}`,
  ],
}

3.4 commitlint:提交信息规范

commitlint 配合 Conventional Commits 规范,强制约束 Git 提交信息格式。

Conventional Commits 格式

<type>(<scope>): <subject>
   │       │         │
   │       │         └── 简短描述,不超过 72 字符
   │       └── 可选,影响范围(模块/组件名)
   └── 类型(必填)

常用 type:
┌──────────┬────────────────────────────┐
│  type    │          说明              │
├──────────┼────────────────────────────┤
│ feat     │ 新功能                     │
│ fix      │ Bug 修复                   │
│ docs     │ 文档变更                   │
│ style    │ 代码格式(不影响逻辑)       │
│ refactor │ 重构(非 feat/fix)         │
│ perf     │ 性能优化                   │
│ test     │ 添加/修改测试              │
│ build    │ 构建系统或依赖变更          │
│ ci       │ CI 配置变更                │
│ chore    │ 其他杂项                   │
│ revert   │ 回滚提交                   │
└──────────┴────────────────────────────┘

示例:
  feat(auth): add OAuth2 login support
  fix(router): resolve navigation guard loop
  refactor(utils): extract date formatting helpers
  docs(readme): update installation instructions

安装与配置

bash
npm install -D @commitlint/cli @commitlint/config-conventional

commitlint.config.js

js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat', 'fix', 'docs', 'style', 'refactor',
        'perf', 'test', 'build', 'ci', 'chore', 'revert',
      ],
    ],
    'subject-max-length': [2, 'always', 72],
    'subject-empty': [2, 'never'],
    'type-empty': [2, 'never'],
  },
}

添加 commit-msg hook:

bash
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

3.5 .editorconfig:编辑器统一配置

.editorconfig 是跨编辑器的配置文件标准,确保不同编辑器(VS Code、WebStorm、Vim 等)使用相同的基础格式设置。

ini
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
.editorconfig vs Prettier vs ESLint 的职责边界

┌─────────────────┬──────────────────────────────────┐
│  .editorconfig  │ 编辑器基础设置                     │
│                 │ 缩进方式、字符编码、换行符、         │
│                 │ 尾部空格、最后空行                  │
│                 │ → 在输入时就生效                    │
├─────────────────┼──────────────────────────────────┤
│  Prettier       │ 代码格式化                         │
│                 │ 基于 AST 的完整格式重排             │
│                 │ → 保存时 / 命令行运行              │
├─────────────────┼──────────────────────────────────┤
│  ESLint         │ 代码逻辑检查                       │
│                 │ 静态分析,发现潜在 Bug              │
│                 │ → 保存时 / 命令行运行              │
└─────────────────┴──────────────────────────────────┘

三者配合:
  .editorconfig → 保证输入一致
  Prettier      → 保证输出一致
  ESLint        → 保证逻辑正确

3.6 完整工具链集成图

完整的代码规范工具链

┌─────────── 开发时 ──────────────────────────────┐
│                                                 │
│  编辑器                                          │
│  ├── .editorconfig → 统一缩进/编码               │
│  ├── ESLint 插件   → 实时显示逻辑错误             │
│  └── Prettier 插件 → 保存时自动格式化             │
│                                                 │
└─────────────────────────────────────────────────┘


┌─────────── 提交时 ──────────────────────────────┐
│                                                 │
│  git commit                                     │
│  ├── husky pre-commit                           │
│  │   └── lint-staged                            │
│  │       ├── eslint --fix (暂存区文件)           │
│  │       └── prettier --write (暂存区文件)       │
│  │                                              │
│  └── husky commit-msg                           │
│      └── commitlint                             │
│          └── 检查提交信息是否符合规范              │
│                                                 │
└─────────────────────────────────────────────────┘


┌─────────── CI/CD ───────────────────────────────┐
│                                                 │
│  GitHub Actions / GitLab CI                     │
│  ├── eslint . (全量检查)                         │
│  ├── prettier --check . (格式检查)               │
│  ├── tsc --noEmit (类型检查)                     │
│  └── npm test (单元测试)                         │
│                                                 │
│  任一步骤失败 → 阻止合入                          │
│                                                 │
└─────────────────────────────────────────────────┘

VS Code 设置(.vscode/settings.json)

json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.useFlatConfig": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue"
  ]
}

执行顺序:保存文件时,先执行 ESLint autofix(source.fixAll.eslint),再执行 Prettier format(editor.formatOnSave)。

GitHub Actions CI 配置

yaml
name: Code Quality
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx eslint .
      - run: npx prettier --check .
      - run: npx tsc --noEmit

四、新一代工具

4.1 Biome(原 Rome)

Biome 是用 Rust 编写的 All-in-one 前端工具链,集成了 Linter、Formatter、Import Sorter 等功能于一体。

Biome 架构

┌──────────────────────────────────────────┐
│              Biome (Rust)                │
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │ Formatter │  │  Linter  │  │ Import │ │
│  │          │  │          │  │ Sorter │ │
│  │ 替代     │  │ 替代     │  │        │ │
│  │ Prettier │  │ ESLint   │  │        │ │
│  └──────────┘  └──────────┘  └────────┘ │
│                                          │
│  统一的 AST 解析(一次解析,多次使用)       │
│  Rust 实现 → 极快的执行速度               │
│  零依赖 → 无 node_modules 膨胀            │
│  97%+ Prettier 兼容                      │
│  200+ lint 规则                           │
└──────────────────────────────────────────┘

Biome 配置

json
{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "complexity": {
        "noBannedTypes": "error",
        "noExtraBooleanCast": "error"
      },
      "correctness": {
        "noUnusedVariables": "warn",
        "noUnusedImports": "error",
        "useExhaustiveDependencies": "warn"
      },
      "suspicious": {
        "noExplicitAny": "warn",
        "noDoubleEquals": "error"
      },
      "style": {
        "useConst": "error",
        "noNonNullAssertion": "warn"
      }
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "asNeeded",
      "trailingCommas": "all"
    }
  }
}

Biome CLI

bash
npx @biomejs/biome check .

npx @biomejs/biome check --write .

npx @biomejs/biome format --write .

npx @biomejs/biome lint .

npx @biomejs/biome ci .

4.2 oxlint(Oxc Linter)

oxlint 是 Oxc 项目的 Linter,同样用 Rust 编写,专注于高性能的 JavaScript/TypeScript 代码检查。

oxlint 定位

┌──────────────────────────────────────────┐
│              Oxc 项目                     │
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │  oxlint  │  │ oxc-parser│ │ oxc-   │ │
│  │  Linter  │  │ 解析器    │  │ minifier│ │
│  └──────────┘  └──────────┘  └────────┘ │
│                                          │
│  特点:                                   │
│  - 比 ESLint 快 50-100 倍                │
│  - 无需配置即可使用                        │
│  - 默认开启约 100 条高质量规则              │
│  - 设计为 ESLint 的补充而非替代            │
│  - 不支持自定义规则/插件                   │
└──────────────────────────────────────────┘
bash
npx oxlint@latest

npx oxlint@latest --fix

npx oxlint@latest -D correctness -D perf

oxlint 推荐的使用策略是与 ESLint 互补:先用 oxlint 快速检查通用规则,再用 ESLint 检查项目特定规则(如 React Hooks、Vue 等框架规则)。

json
{
  "scripts": {
    "lint": "oxlint && eslint .",
    "lint:fix": "oxlint --fix && eslint . --fix"
  }
}

4.3 Biome vs ESLint + Prettier 对比

┌─────────────────────┬──────────────────────┬──────────────────────┐
│       维度           │   Biome              │  ESLint + Prettier   │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 语言实现             │ Rust                 │ JavaScript           │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 速度                │ 极快(10-100x)       │ 较慢                  │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 安装体积             │ 单一二进制文件        │ 大量 node_modules     │
│                     │ (~50MB)              │ (~200MB+)            │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 配置复杂度           │ 单一 biome.json      │ 多个配置文件           │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 规则数量             │ 200+                 │ 1000+(含插件生态)   │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 插件/自定义规则       │ 不支持               │ 丰富的插件生态         │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 框架专用规则          │ 有限                 │ 完善(React/Vue/...) │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ TypeScript 支持      │ 内置                 │ 需额外解析器+插件      │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 类型感知 Lint        │ 不支持               │ 支持                  │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ Prettier 兼容性      │ 97%+                 │ 100%(就是 Prettier) │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 社区生态             │ 快速增长中            │ 非常成熟              │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ 适用场景             │ 新项目 / 追求速度     │ 成熟项目 / 需要丰富   │
│                     │ / 简化工具链          │ 插件生态              │
└─────────────────────┴──────────────────────┴──────────────────────┘

选型建议

选型决策树

你的项目需要...

├── 框架专用规则(react-hooks / vue / angular)?
│   ├── Yes → ESLint + Prettier
│   │         (框架插件生态无可替代)
│   └── No ─┐
│            │
├── 自定义 lint 规则?
│   ├── Yes → ESLint + Prettier
│   │         (Biome 不支持自定义规则)
│   └── No ─┐
│            │
├── 类型感知 lint(如 no-floating-promises)?
│   ├── Yes → ESLint + typescript-eslint
│   │         (Biome 不支持类型感知)
│   └── No ─┐
│            │
├── 追求极致速度 / 简化工具链?
│   ├── Yes → Biome
│   └── No ─┐
│            │
└── 默认推荐:
    新项目 → 尝试 Biome,不够再加 ESLint
    存量项目 → ESLint + Prettier(迁移成本低)
    大型 Monorepo → Biome(速度优势明显)

渐进式迁移方案

从 ESLint + Prettier 迁移到 Biome 可以分步进行:

渐进式迁移路径

阶段 1:替换 Prettier
┌──────────────────────────────┐
│ Formatter: Prettier → Biome  │
│ Linter:    ESLint(保留)     │
└──────────────────────────────┘

阶段 2:补充 Biome Lint
┌──────────────────────────────┐
│ Formatter: Biome             │
│ Linter:    Biome + ESLint    │
│           (Biome 跑通用规则) │
│           (ESLint 跑框架规则)│
└──────────────────────────────┘

阶段 3:(如果可能)全面切换
┌──────────────────────────────┐
│ Formatter: Biome             │
│ Linter:    Biome             │
│ (仅当 Biome 覆盖所有需求时)  │
└──────────────────────────────┘

Biome 提供了从 ESLint/Prettier 迁移的命令:

bash
npx @biomejs/biome migrate prettier --write

npx @biomejs/biome migrate eslint --write

五、面试高频问题

问题 1:ESLint 的工作原理是什么?

回答思路

ESLint 的工作流程分为四个阶段。首先,解析阶段:ESLint 通过解析器(默认 espree,TypeScript 用 @typescript-eslint/parser)将源代码转换为 AST(抽象语法树),AST 遵循 ESTree 规范。其次,遍历阶段:ESLint 使用深度优先遍历 AST 的每一个节点,在进入和离开每个节点时触发相应事件。接着,规则检查阶段:每条规则通过访问者模式注册对特定 AST 节点类型的监听,当遍历到对应节点时执行检查逻辑,违反规则则调用 context.report() 记录问题。最后,输出/修复阶段:汇总所有规则报告的问题,输出 error/warning 信息;如果规则提供了 fixer 函数,可以通过 --fix 自动修复。

追问:ESLint 的 --fix 是怎么实现的?

修复机制是通过规则在 context.report() 时提供一个 fix 函数,该函数返回一个描述文本替换操作的对象(包含替换范围和替换文本)。ESLint 收集所有 fix 操作后,检查是否存在范围冲突,无冲突的修复直接应用到源码文本上。如果存在冲突,ESLint 会多轮迭代(最多 10 次)直到没有新的修复产出。

问题 2:Flat Config 与 .eslintrc 有什么区别?为什么要迁移?

回答思路

Flat Config 是 ESLint v9+ 的新配置标准。核心区别在于:第一,配置格式——Flat Config 使用 eslint.config.js 导出一个配置数组,每个元素是一个配置对象;旧配置使用 .eslintrc.* 文件加 extends/overrides 嵌套结构。第二,配置合并——Flat Config 采用扁平化的数组合并策略,配置对象按数组顺序合并,后者覆盖前者,逻辑简单可预测;旧配置依赖级联查找(从文件目录向上找到 root:true),extends 的深层嵌套容易造成配置优先级混乱。第三,插件注册——Flat Config 直接在配置对象中 import 插件对象,类型安全、可追溯;旧配置通过字符串名称引用,依赖 ESLint 内部解析机制。第四,环境变量——Flat Config 移除了 env 字段,改用 languageOptions.globals 显式声明全局变量。迁移的原因是 Flat Config 更简单、更透明、更利于工具链集成。

问题 3:ESLint 和 Prettier 冲突怎么解决?

回答思路

冲突的根本原因是 ESLint 的格式化规则(如 indent、semi、quotes)和 Prettier 的格式化产出不一致。推荐解决方案是使用 eslint-config-prettier,它将所有与 Prettier 冲突的 ESLint 规则设为 off,让 ESLint 专注于逻辑检查,Prettier 专注于格式化。配置时需要将 eslint-config-prettier 放在配置数组的最后(或 extends 的最后),确保它能覆盖前面所有配置中的格式化规则。另一种方案是 eslint-plugin-prettier,它将 Prettier 作为一条 ESLint 规则运行,但这种方式性能较差(ESLint 和 Prettier 同时处理同一份代码),且会在编辑器中将格式问题显示为红色波浪线,干扰开发体验,目前不推荐。

问题 4:如何自定义一个 ESLint 规则?

回答思路

一个 ESLint 规则由两部分组成:meta 对象描述规则的元信息(类型、文档、是否可修复、选项 schema、消息模板),create 函数接收 context 参数并返回一个访问者对象。访问者对象的 key 是 AST 节点类型(如 LiteralCallExpressionVariableDeclaration),value 是回调函数。当 ESLint 遍历 AST 遇到对应节点时调用回调,在回调中通过 context.report({ node, messageId, data, fix }) 报告问题。开发时可以使用 AST Explorer(astexplorer.net)查看代码对应的 AST 结构,用 RuleTester 编写单元测试,最终打包为 eslint-plugin 发布。

问题 5:Prettier 的 printWidth 是硬限制吗?

回答思路

不是硬限制。printWidth 是 Prettier 的目标行宽,Prettier 会尽力将代码控制在这个宽度内,但在某些情况下会超出。例如,一个很长的字符串字面量、一个不可拆分的 URL、一个长链式调用如果拆行后反而降低可读性,Prettier 可能选择保持单行。Prettier 的算法本质上是:先尝试把整个表达式放在一行,如果超出 printWidth 则按照预定义的换行策略拆分。这不同于 ESLint 的 max-len 规则那种硬性限制。

问题 6:lint-staged 的工作原理是什么?为什么不直接 lint 整个项目?

回答思路

lint-staged 通过 git diff --staged --name-only 获取暂存区文件列表,然后仅对这些文件运行配置的命令(如 eslint、prettier)。核心原因是性能——大型项目可能有成千上万个文件,全量 lint 可能需要几分钟甚至更长时间,而每次提交通常只修改几个文件。通过只检查变更文件,可以将 pre-commit 检查时间控制在秒级,不影响开发体验。同时 lint-staged 还会在命令执行后自动将修改后的文件重新 git add,保证 stage 区的一致性。全量 lint 则放在 CI 环节执行,作为最终兜底。

问题 7:Biome 能完全替代 ESLint + Prettier 吗?

回答思路

目前还不能完全替代,但在很多场景下已经可以。Biome 的优势是速度极快(Rust 实现,比 ESLint 快 10-100 倍)、配置简单(单一 biome.json)、零 node_modules 依赖。但它也有局限:不支持自定义规则和插件、不支持类型感知的 lint 规则(如 no-floating-promises)、框架专用规则不如 ESLint 生态丰富(React Hooks 规则已支持,但 Vue/Angular 等支持有限)。实际选型建议:新项目如果不需要框架专用规则可以优先考虑 Biome;存量项目可以渐进迁移,先用 Biome 替代 Prettier,再逐步替代 ESLint 的通用规则;大型 Monorepo 中 Biome 的速度优势非常明显。

问题 8:eslint.config.js 中配置数组的合并顺序是怎样的?如果两个配置对象都匹配同一个文件,规则如何合并?

回答思路

Flat Config 的合并策略是线性扁平合并:对于每个被 lint 的文件,ESLint 从数组第一个元素开始,依次检查每个配置对象的 files/ignores 是否匹配当前文件。匹配的配置对象会按出现顺序合并,后面的覆盖前面的。具体来说:rules 字段是浅合并——同名规则后者覆盖前者;plugins 是合并而非覆盖;languageOptions 会深度合并 parserOptionsglobals 等子字段。没有 files 字段的配置对象视为匹配所有文件。这种扁平化设计避免了旧配置中 extends 层层嵌套导致的优先级混乱问题。


六、延伸阅读

用心学习,用代码说话 💻