跪拜 Guibai
← Back to the summary

ESLint Flat Config from Zero: One File to End Team Style Wars

Opening

One of the most draining parts of team development is repeatedly arguing about formatting during Code Review:

These issues don't affect how the code runs, but they seriously hurt readability and collaboration efficiency. ESLint is the standard answer to this kind of problem — it's a cornerstone of frontend engineering that forces teams to write code with consistent style and controllable quality.

As ESLint enters the 9.x+ era, the official recommendation is now Flat Config, the eslint.config.mjs format, replacing the traditional .eslintrc.js. This article, based on the latest Flat Config writing style, breaks down a production-ready ESLint configuration from 0 to 1.

1. Why do you need ESLint?

Before diving into the configuration, let's clarify its core value:

  1. Unify code style: The whole team writes the same way; anyone's code looks like it was written by one person.
  2. Catch errors early: Undefined variables, deprecated syntax, scope issues — intercepted during development.
  3. Improve review efficiency: No more arguing about formatting; focus purely on business logic.
  4. Lower maintenance costs: Standardized code is more readable, and newcomers onboard faster.

2. Differences between the new Flat Config and the old version

Many people first encountered ESLint in the .eslintrc.js format. The now-official Flat Config brings several core changes:

In short: the new config is more like writing a normal JS module, with more unified syntax, less nesting, and better TypeScript friendliness.

3. Building an ESLint config from scratch

3.1 Install dependencies

We use pnpm as the package manager to install the core dependencies:

# Install ESLint core
pnpm i -D eslint

# New official JS rule set
pnpm i -D @eslint/js

# Global variable definitions
pnpm i -D globals

# TypeScript support
pnpm i -D typescript-eslint

You can also generate it interactively with the init command:

npx eslint --init

3.2 Complete config file

Create eslint.config.mjs in the project root with the following content:

import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: {
      globals: globals.browser
    },
    rules: {
      // 0 = off
      // 1 = warn
      // 2 = error
      "no-var": 2,
      "no-console": 1,
      "quotes": ["error", "double"],
      "semi": ["error", "always"],
      "indent": ["error", 2]
    }
  },
  ...tseslint.configs.recommended,
]);

3.3 Line-by-line breakdown of the config

Let's explain each section clearly.

① Import dependencies

import js from "@eslint/js";          // Official JS recommended rule set
import globals from "globals";         // Common environment global variable definitions
import tseslint from "typescript-eslint"; // TypeScript rule set
import { defineConfig } from "eslint/config"; // Config definition helper function

② defineConfig and the config array

export default defineConfig([
  // Config block 1: JS general rules
  // Config block 2: TS rules
])

defineConfig provides type hints, helping you get completions when writing the config. The export is an array of config objects; each item corresponds to a set of matching rules, and later configs override earlier ones.

③ Basic JS config block

{
  // Which files this applies to
  files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
  // Register plugins
  plugins: { js },
  // Inherit official recommended rules
  extends: ["js/recommended"],
  // Language options: global variables
  languageOptions: {
    globals: globals.browser
  }
}

④ TypeScript rule inheritance

...tseslint.configs.recommended

Uses the spread operator to expand the TS recommended rule set into the config array. It automatically adapts to .ts files, providing TS syntax checks and type-related rules.

⑤ Custom rules

This is the core of the config; we can override preset rules and customize our own code standards.

rules: {
  "no-var": 2,
  "no-console": 1,
  "quotes": ["error", "double"],
  "semi": ["error", "always"],
  "indent": ["error", 2]
}

4. The three rule levels: 0 / 1 / 2

Each rule can be set to one of three levels, a fundamental ESLint concept:

Value Corresponding word Effect
0 off Turns the rule off, no checking at all
1 warn Warning level, alerts but doesn't error, doesn't affect compilation
2 error Error level, reports an error directly, terminal shows red error

In team standards, mandatory rules should always be set to error, advisory ones to warn.

Rule-by-rule explanation of the example

  1. "no-var": 2

    • Meaning: Forbids using var to declare variables.
    • Reason: var has issues like hoisting and no block scope; uniformly use let/const.
    • Level: error, mandatory prohibition.
  2. "no-console": 1

    • Meaning: Discourages using console.log.
    • Reason: Used for debugging during development, usually should be cleaned up in production.
    • Level: warn, allowed during development, reminds to remove before going live.
  3. "quotes": ["error", "double"]

    • Meaning: Strings must use double quotes.
    • Second parameter options: "double" double quotes / "single" single quotes / "backtick" backticks.
    • Level: error, mandatory uniformity.
  4. "semi": ["error", "always"]

    • Meaning: Semicolons must be added at the end of statements.
    • Second parameter options: "always" always add / "never" never add.
    • Level: error, mandatory uniformity.
  5. "indent": ["error", 2]

    • Meaning: Indentation must be 2 spaces.
    • Second parameter is the number of spaces, can also be set to "tab".
    • Level: error, mandatory uniform indentation.

5. Common ways to run

5.1 Run directly from the command line

# Check all files
npx eslint .

# Check a specific directory
npx eslint ./src/app

# Auto-fix all fixable formatting issues
npx eslint . --fix

--fix can automatically fix formatting issues like indentation, quotes, semicolons, and var-to-const; but logic issues (like unused variables) need manual handling.

5.2 Configure scripts in package.json

Add scripts in package.json so the team uses unified commands:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

Then run directly:

pnpm lint      # Check
pnpm lint:fix  # Auto-fix

6. VSCode integration: auto-fix on save

The most comfortable usage is integrating with the editor, auto-fixing formatting on save.

  1. Install the VSCode plugin: ESLint (official Microsoft release).
  2. Open settings settings.json and add the config:
{
  // Enable new Flat Config support
  "eslint.useFlatConfig": true,
  // Auto-execute ESLint fix on save
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  // Enable ESLint by default
  "eslint.enable": true
}

Once configured, just saving a file will automatically fix indentation, quotes, semicolons, var, and other issues — a top-tier development experience.

⚠️ Key point: "eslint.useFlatConfig": true must be enabled, otherwise VSCode won't recognize the new eslint.config.mjs config.

7. Common pitfalls and notes

1. The filename must be strictly correct

The new config filename must be eslint.config.mjs; missing a letter or getting the suffix wrong means it won't take effect.

2. Cannot use module.exports

.mjs files are ESM modules; you must use export default, cannot write module.exports, or it will error immediately.

3. TypeScript config spread syntax

The spread operator ... before ...tseslint.configs.recommended cannot be omitted; it expands the rule array and merges it into the main config.

4. Don't forget globals

If you don't configure globals: globals.browser, writing window or document in your code will trigger no-undef errors because ESLint doesn't know by default that these are browser global variables.

5. Don't be too strict with no-console

It's recommended to turn off console in production, but you need it for debugging during development. Recommended approach:

8. Team adoption suggestions

  1. Unified config maintenance: One config reused by the whole team; don't let individuals change rules on their own.
  2. Combine with Prettier: ESLint handles code quality + some formatting, Prettier specializes in formatting; the two work best together.
  3. CI integration as a gate: Add pnpm lint checks in Git commits or pipelines; don't allow merges if it fails, enforcing standards from the process.
  4. Gradual adoption: When introducing to legacy projects, first set rule levels to warn and gradually fix; don't set everything to error right away causing a flood of errors.

Closing

ESLint was never about restricting developers; it's about using a recognized standard to free the team from meaningless formatting debates and focus energy on truly valuable business logic.

Flat Config, as the new official standard, has clearer configuration and more unified syntax — it's the first choice for new projects now. The config above can be copied directly into your project; adjust a few rules to match your team's habits, and you can quickly implement code standards.