Skip to content

✍️ 필사 모드: Formatters and Linters in 2026 — The Real Place of Biome, dprint, Ruff, oxc, Prettier 4, and ESLint v9

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

Prologue — Why We Are Talking About Formatters Again

There was a time when prettier --write . was the whole story. That was around 2020. Five years later the same command had grown to 30 seconds in a monorepo and two minutes on CI. eslint --max-warnings 0 was slower still, and at some point we were seriously debating whether to remove lint from the pre-commit hook.

The Python world had a parallel pain. The classic combo of black, flake8, isort, and pylint was stable but slow. A full pass on a large repo took minutes, and four config files drifted apart.

Then a wave of Rust tooling arrived with a different answer: if the tool is genuinely 100 times faster, every other workflow assumption changes. Ruff proved it in Python in 2022, and Biome (formerly Rome) took the same path in JS and TS. dprint, oxc, ty (a Rust type checker), and uv (a Rust package manager) followed — a sustained pattern of Rust rewrites led by Astral, the Biome team, and small projects like Boshen's oxc.

In 2026, Prettier 4 officially adopts a Rust backend path, ESLint v9's flat config has stabilized, and typescript-eslint v8 collapses into a single package. Biome 2 ships multi-file analysis and import sorting as first-class features, and Ruff is effectively the Python linting default.

This post maps that terrain. Which tool wins where, how far the "rewrite in Rust and everything gets better" claim actually holds, and what your team should pick. Measured trade-offs, not marketing copy.

Headline: tools got fast and unified. But "Biome solves everything" is still not the picture. The choice comes down to which trade-offs you accept.


1. The Map — A 2026 Overview of Formatters and Linters

Start with the big picture: which tools own which language territory in 2026.

Language / areaTraditional stack2026 defaultNext-gen contender
JS / TSPrettier + ESLintPrettier + ESLint v9, Biome 2oxc, dprint
Pythonblack + flake8 + isort + pylintRuff (format + lint)ty (type checker)
Rustrustfmt + clippyrustfmt + clippyalready Rust
Gogofmt + golangci-lintunchangednone
CSS / SCSSPrettier + stylelintPrettier + stylelint, Biome 2dprint, oxc
MarkdownPrettier, remark-lintPrettier, dprintnone
TOML / YAML / JSONPrettierdprint, taplo, yamlfmtnone
Multi-language monorepoPrettier + ESLintdprint (unifying formatter), Biomenone

One-line takes by category

  • Formatter: decides shape — whitespace, line breaks, quotes, semicolons. Opinionated and enforces. Prettier, Biome format, dprint, rustfmt, gofmt, Ruff format, black
  • Linter: catches semantic issues — unused variables, likely bugs, anti-patterns. ESLint, Biome lint, Ruff lint, clippy, golangci-lint, stylelint, pylint
  • Type checker: catches type errors. tsc, mypy, pyright, ty (Astral), Flow
  • Integrated formatter + linter: a single binary that does both. Biome and Ruff are the canonical examples

The protagonist of this post is the third category — Rust-written next-gen integrated tools. Where they replace incumbents, where they only supplement, and where they cannot yet go.

Headline: situations differ by language. Python is effectively decided by Ruff. JS and TS still have a live Biome vs Prettier+ESLint debate. Monorepos opened a new lane for dprint.


2. The Rust Rewrite Pattern — Why 10 to 100 Times Faster

Start with the structural reason — not marketing.

Numbers you can measure

Biome's own benchmarks show 25 to 35 times the throughput of Prettier on single-file formatting, with a wider gap on big repositories. Ruff is regularly reported as roughly 100 times faster than flake8 on equivalent rule sets, turning minute-scale checks into single-digit seconds on medium-to-large Python repos.

Patterns from real-world reports:

  • Prettier on a monorepo, 30 seconds; Biome, 1 to 2 seconds
  • flake8 + black on a monorepo, 90 seconds; Ruff, 2 to 3 seconds
  • ESLint flat config on a monorepo, 45 seconds; Biome, 3 to 5 seconds (different rule coverage, more on this below)
  • Prettier on a single file, 300 ms; dprint or Biome, 5 to 15 ms

Why the gap is this large

  1. Language baseline — JS tools run as interpreted code on V8's JIT; Rust tools are native binaries. That alone buys 3 to 10 times
  2. Single-pass parsing — legacy tools do lex, parse, AST transform in multiple passes. Biome, oxc, and Ruff build one lossless concrete syntax tree (CST) and reuse it for every analysis
  3. Real parallelism — Rust leans into data parallelism so files actually run on multiple cores. JS tools are single-threaded; worker pools pay serialization overhead
  4. Memory layout — CST nodes are stored in cache-friendly arrays (rowan, oxc-resolver). Fewer allocations
  5. Startup cost — a Rust binary starts in 1 to 5 ms; Node in 50 to 200 ms. Repeated CLI invocations compound this

Second-order effects of being fast

  • Pre-commit hooks become viable again — 30 seconds was forbidden, 1 second is invisible
  • Editor integration becomes live — lint on every keystroke is realistic
  • CI caching matters less — already fast enough not to need an optimization tier
  • Full-monorepo lint becomes routine — minutes meant CI-only, seconds means local-too

Headline: the second-order workflow shift matters more than the raw speed. "Too slow to be worth it" was an unspoken constraint that just got removed.


3. Biome 2 — The Frontrunner of Formatter and Linter Fusion

Biome is the most ambitious integration. The name began as Rome (2020, Sebastian McKenzie — the author of Babel), the project paused due to funding, and the community fork shipped as Biome in 2023. Biome 1.0 in 2024, 2.0 in 2025.

What Biome 2 promises

  • JS / TS / JSX / TSX / JSON / CSS / GraphQL formatting + linting in one binary
  • A single config file biome.json — Prettier and ESLint configs collapsed
  • Type-aware linting — overlaps with typescript-eslint's type-aware rules (v2 adds multi-file analysis)
  • Import sorting — supersedes parts of eslint-plugin-import
  • Diff format — format only the changed portion, useful for large PRs in CI
  • Migration toolingbiome migrate prettier, biome migrate eslint

Sample config

{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
  "files": { "ignoreUnknown": true },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "javascript": {
    "formatter": { "quoteStyle": "single", "semicolons": "asNeeded" }
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": { "noUnusedVariables": "error" },
      "style": { "useImportType": "warn" },
      "suspicious": { "noExplicitAny": "warn" }
    }
  },
  "assist": {
    "enabled": true,
    "actions": { "source": { "organizeImports": "on" } }
  }
}

Where Biome shines

  • JS/TS focused, simple rule sets — teams that find 90% rule coverage sufficient
  • Teams that value a single config — anyone tired of the Prettier+ESLint configuration surface
  • CI speed is a real bottleneck — monorepo lint that ran in minutes
  • Local editor integration speed — the VS Code extension responds instantly

Where Biome falls short

  • No ESLint plugin ecosystem — eslint-plugin-react-hooks, eslint-plugin-jest, eslint-plugin-tailwindcss, eslint-plugin-import are not directly usable. Biome absorbs some of them into the core, but 1:1 parity is hard
  • Non-standard file formats are missing.vue and .svelte are not officially supported (some community plugins exist)
  • No Markdown / YAML / TOML formatting — territory Prettier covers
  • Limited type-aware lint — does not fully match typescript-eslint's rule depth
  • Migration cost — papering over rule differences in a large repo takes days

Decision matrix

Team situationBiome fit
Greenfield JS/TS, simple rulesvery high
Monorepo, CI speed matters, no Vue/Sveltehigh
Existing Prettier+ESLint, satisfiedweak motivation to switch
Heavy eslint-plugin-* usagenot yet
Big share of Vue / Svelte / Astro / MDXnot recommended

Headline: Biome is the most compelling unified JS/TS tool today. Plugin ecosystem and non-standard file support are still the gaps before it can fully replace Prettier+ESLint.


4. dprint — The Plugin-Driven Multi-Language Formatter

dprint is a different shape. Tiny core, language-specific plugins. It formats JS/TS, JSON, Markdown, TOML, Dockerfile, and SQL out of one tool.

What sets dprint apart

  • A WASM plugin system — language support lives in plugins; the core stays small and fast
  • Designed for multi-language monorepos — covers files Prettier never handled (TOML, Dockerfile)
  • Rust core with a Rust / WASM plugin SDK
  • Pure formatter — no linting; does one thing well

Sample config

{
  "lineWidth": 100,
  "indentWidth": 2,
  "useTabs": false,
  "typescript": {
    "quoteStyle": "preferSingle",
    "semiColons": "asi"
  },
  "json": {
    "lineWidth": 200
  },
  "markdown": {
    "textWrap": "always"
  },
  "toml": {},
  "dockerfile": {},
  "plugins": [
    "https://plugins.dprint.dev/typescript-0.93.0.wasm",
    "https://plugins.dprint.dev/json-0.20.0.wasm",
    "https://plugins.dprint.dev/markdown-0.18.0.wasm",
    "https://plugins.dprint.dev/toml-0.7.1.wasm",
    "https://plugins.dprint.dev/dockerfile-0.3.4.wasm"
  ],
  "includes": ["**/*.{ts,tsx,js,jsx,json,md,toml,Dockerfile}"],
  "excludes": ["**/node_modules", "**/dist"]
}

Where dprint shines

  • Multi-language monorepos — TS + JSON + Markdown + TOML + Dockerfile in one tool
  • Pure-formatter value — no need to bundle linting, just a faster Prettier replacement
  • Plugin isolation — each language is a WASM module; no dependency conflicts
  • Version pinning conveniencedprint config update refreshes plugin versions in one step

Where dprint falls short

  • No linting — pair it with ESLint, Biome lint, or Ruff
  • Rule coverage for JS/TS — slightly thinner than Prettier; opinions are stronger
  • Editor maturity — the VS Code extension works but is not as smooth as Prettier's
  • CSS and GraphQL plugins — some are beta or community-maintained

Who actually uses it

  • Deno-style codebases (dprint plays well with Deno)
  • Infrastructure repos (IaC, Docker, Kubernetes, Terraform)
  • Polyglot monorepos (TS + Python + infra code together)

Headline: dprint aims to be the one formatter for every kind of text. No linting, formatting only, and it shines in a truly polyglot monorepo.


5. Ruff — The Rust Tool That Took Python

Ruff is the most overwhelming success story. Charlie Marsh (Astral) shipped the first release in 2022; by 2026 it is effectively number one on PyPI download stats for Python developer tooling. Django, Pandas, FastAPI, Pydantic, Apache Airflow, scikit-learn — all major projects adopt Ruff.

What Ruff promises

  • Replacement for flake8, isort, pydocstyle, pyupgrade, bandit, autoflake, black — 7 to 10 tools collapse to 1
  • Over 800 lint rules — most of flake8 plus its major plugins
  • Built-in formatterruff format is black-compatible (it preserves black's strong opinions almost identically)
  • Import sorting — supersedes isort
  • Auto-fixruff check --fix applies safe corrections automatically
  • Single config — one [tool.ruff] section in pyproject.toml

Sample config

[tool.ruff]
line-length = 100
target-version = "py312"
extend-exclude = ["migrations", ".venv", "build"]

[tool.ruff.lint]
select = [
  "E", "W",      # pycodestyle
  "F",           # pyflakes
  "I",           # isort
  "N",           # pep8-naming
  "UP",          # pyupgrade
  "B",           # flake8-bugbear
  "C4",          # flake8-comprehensions
  "DTZ",         # flake8-datetimez
  "S",           # flake8-bandit (security)
  "RUF",         # ruff-specific
]
ignore = ["E501"]  # line-too-long handled by formatter
fixable = ["ALL"]
unfixable = []

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]  # allow assert in tests
"__init__.py" = ["F401"]  # allow unused imports

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true

Where Ruff shines — nearly everywhere

  • Speed — flake8+pylint at 90 seconds collapse to Ruff at 2 seconds
  • Single tool — pre-commit configuration shrinks from 7 entries to 1
  • Auto-fix breadth — what flake8 only reported, Ruff also fixes
  • No CI caching needed — already fast
  • flake8-rule-selector compatible — low migration cost

Where Ruff has limits and differences

  • Not a mypy replacement — type checking is separate. Astral's ty (Rust type checker) is in alpha / beta in 2026
  • Not 100% identical to black — 95% match; edge cases (long strings, complex expressions) differ. Running ruff format once will produce a diff — plan for a one-shot reformatting PR
  • Some deep pylint analyses are weaktoo-many-locals, cyclomatic complexity rules are limited
  • No plugin system — every rule lives in the core; custom rules are hard to add

Astral's bigger picture — a Rust rewrite of the Python stack

Charlie Marsh's ambition does not stop at Ruff. Astral is rewriting the entire Python developer tooling chain in Rust.

ToolReplacesStatus (2026)
Ruffflake8, isort, black, pydocstyle, ...stable, broadly adopted
uvpip, virtualenv, pip-tools, pipx, parts of poetrystable, rapidly spreading
tymypy, parts of pyrightalpha / beta
ryepoetry, hatch conceptuallybeing absorbed into uv

The pattern: once one tool gets fast, the whole workflow reorganizes around it. The same pattern is repeated by Biome and oxc in JS.

Headline: Ruff is the de facto default of the Python developer tooling chain. The reasons are simple — fast, integrated, low migration cost.


6. oxc — The Rust JavaScript Toolchain Family

oxc takes a different shape of ambition. Led by Boshen, the project rewrites the entire JavaScript toolchain in Rust — parser, resolver, transformer, linter, formatter, minifier. Where Biome is a "single-user tool," oxc is closer to "a component library other tools consume."

What oxc ships

ComponentRoleCompares to
oxc-parserJS/TS parseracorn, esprima, swc parser
oxc-resolvermodule resolutionenhanced-resolve
oxc-transformerJS/TS transformBabel, swc
oxlintlinterESLint
oxc-prettierformatter (Prettier Rust port)Prettier
oxc-minifierminifierterser, swc, esbuild

oxlint — the Rust linter aimed at ESLint

oxlint reimplements ESLint core rules and parts of major plugins in Rust. As of 2026, oxlint supports parts of an ESLint v9 compatibility mode and is positioned as a fast pre-flight check.

# Fast lint with oxlint, deep lint with ESLint
oxlint --deny-warnings src/
# ESLint-compatible reporting
oxlint --rules-include=react-hooks src/

Where oxc shines

  • Build tool / framework authors — Rspack, Rolldown, and others consume oxc internally. Fast component library
  • Pre-flight lint — Lefthook or Husky pre-commit hooks run oxlint for 70% rule coverage in 0.5 seconds, leaving ESLint for the deep pass
  • CI first stage — fail fast. Pass oxlint, then run ESLint deeper checks
  • Prettier-compatible formatter — part of the path Prettier 4's Rust backend treads

Where oxc has limits

  • Plugin system immature — ESLint plugins cannot be reused directly
  • Lint rule coverage partial — no 1:1 mapping to the full ESLint rule set
  • Hard to use standalone — not "this is all you need" like Biome, more of a complement
  • The formatter is closer to a Prettier 4 integration path — standalone use cases are few

Biome vs oxc — what differs

AxisBiomeoxc
Goalunified user toolcomponent library + tool family
Single configyes (biome.json)per-tool
User experience"replace Prettier+ESLint""speed up the ESLint workflow"
Pluginsmostly built-in(planned) WASM plugins
Who adoptsapplication teamsbuild tool / framework authors

Headline: Biome targets users, oxc targets tool builders. From a developer's seat Biome is intuitive; from a build-tool author's seat oxc is the practical pick. They live on different layers, not in direct competition.


7. Prettier 4 — How the Reigning Tool Responded

Prettier has been the de facto JS/TS formatter since 2017. Strong opinions, almost no configuration — that is the core philosophy. As a result, nearly every project adopted it.

But the speed problem accumulated. Through version 3, Prettier was pure JS on Node. As the gap to native tools widened, the Prettier team made a Rust backend path official in 2025.

Headline changes in Prettier 4

  • Rust-backed parser and formatter path — the JS/TS core gets an adapter that can run on top of oxc or swc based Rust backends
  • Compatibility first — bit-identical output to Prettier 3 is the goal (95%+ match). Reaching 100% is incremental
  • Plugin API stabilization — Markdown, YAML, GraphQL, Tailwind plugins keep working
  • Speed gains — 5 to 15 times faster with the Rust backend enabled (not yet matching Biome's raw throughput but meaningful)
  • Same installation modelnpm install prettier and you are done

Sample config

{
  "experimentalRustBackend": true,
  "printWidth": 100,
  "singleQuote": true,
  "semi": false,
  "trailingComma": "all",
  "plugins": [
    "prettier-plugin-tailwindcss",
    "prettier-plugin-organize-imports"
  ]
}

Why Prettier survives

  • Ecosystem inertia — hundreds of plugins (Tailwind ordering, import sorting, Astro / Svelte / Vue support)
  • Editor integration maturity — "format on save" in VS Code remains the most reliable
  • Opinion of opinions — Prettier style is the style — even Biome promises Prettier-compatible output
  • Broad file format support — JSON, YAML, Markdown, GraphQL, MDX

Prettier vs Biome — choosing in 2026

AxisPrettier 4Biome 2
JS/TS format speed5 to 15 times (Rust backend)25 to 35 times
Lint integrationnone (ESLint separate)integrated
Plugin ecosystemhugesmall
Non-standard file supportbroadnarrow
Single configpartial (.prettierrc + .eslintrc still)full (biome.json)
Migration costnone (already in use)medium (biome migrate)

Headline: Prettier is not going away — the ecosystem is too deep. The Rust backend narrows the speed gap, and pairing with ESLint keeps it competitive. Biome shines when "integration value" matters on a new project.


8. ESLint v9 and typescript-eslint v8 — The Flat Config Era

The ESLint camp did not stand still.

What ESLint v9 changes

  • flat config is the standardeslint.config.js (or .ts) replaces .eslintrc.* (legacy). Legacy goes away in v10
  • First-class TypeScript ESM — flat config maps naturally to ESM
  • --inspect-config — visualize which rule is enabled where and why
  • CLI stabilizationeslint . is the default (no arguments required from v9)
  • Deprecated formatting rules — every formatting-related rule is deprecated and delegated to Prettier or another formatter

Sample flat config

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

export default tseslint.config(
  { ignores: ['dist', 'build', '.next'] },
  js.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  {
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': 'warn',
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  }
)

typescript-eslint v8 — a single package

Before v7 you installed @typescript-eslint/parser and @typescript-eslint/eslint-plugin separately. v8 collapses to a single typescript-eslint package. Config gets shorter, too.

npm i -D typescript-eslint

projectService — cutting the cost of type-aware lint

Type-aware lint (@typescript-eslint/recommended-type-checked) is precise but slow. v8's projectService works like the TypeScript LSP — discovers tsconfig automatically and lazy-loads type information per file. Lint speed improves 2 to 5 times on monorepos.

Why ESLint survives

  • Plugin ecosystem — eslint-plugin-react-hooks, eslint-plugin-jest, eslint-plugin-jsx-a11y, eslint-plugin-import, eslint-plugin-tailwindcss, eslint-plugin-storybook, hundreds more
  • Depth of type-aware rulesno-floating-promises, await-thenable, no-misused-promises need the type checker
  • Ease of authoring custom rules — write a plugin in plain JS
  • Legacy compatibility — flat config still accepts most legacy plugins

The pattern of coexisting Biome and ESLint

Many teams arrive at this:

Format:           Prettier (or Biome format)
Fast lint:        oxlint / Biome lint  (pre-commit, immediate feedback)
Deep lint:        ESLint                (CI, type-aware + plugins)

ESLint is not being replaced — the workflow is split into a fast first pass and a deep second pass. Biome and ESLint running side by side may look odd, but it works in practice.

Headline: ESLint v9 settled with flat config, and typescript-eslint v8's projectService closes the speed gap. The plugin ecosystem keeps it alive for the foreseeable future.


9. stylelint and the CSS Tooling Picture

CSS follows a similar pattern.

stylelint v16

  • Remains the default for linting CSS / SCSS / Less
  • v16 brings flat-config-like simplification
  • Biome 2's CSS lint absorbs some rules, but does not match stylelint's coverage
  • Tailwind, CSS Modules, and PostCSS integration are still stronger in stylelint

Who formats CSS well

ToolCSSSCSSLessTailwind class sorting
Prettierstablestablestablevia plugin
Biomestablepartial (2.x)unsupportedunsupported (directly)
dprintbeta pluginbetaunsupportedunsupported

Decision guide

  • Heavy Tailwind, CSS Modules — Prettier + prettier-plugin-tailwindcss + stylelint (the most battle-tested)
  • Plain CSS, monorepo speed matters — Biome 2 CSS format + lint, stylelint as a complement
  • Heavy SCSS usage — Prettier + stylelint (Biome's SCSS is still partial)

Headline: Prettier + stylelint remains the safest combination for CSS. Biome 2 is catching up fast, but SCSS and Tailwind integration need more time.


10. The Comparison Matrix — One View of 2026

AxisPrettier 4Biome 2dprintRuffoxlintESLint v9stylelint v16
LanguagesJS/TS/CSS/MD/YAML/JSON/Vue/Svelte/AstroJS/TS/JSX/JSON/CSS/GraphQLJS/TS/JSON/MD/TOML/DockerfilePythonJS/TSJS/TSCSS/SCSS/Less
Formatteryesyesyes (core)yesnono (deprecated)no
Linternoyesnoyesyesyesyes
ImplementationJS + Rust (v4)RustRust + WASMRustRustJSJS
Relative speed1x (v4 Rust 5 to 15x)25 to 35x10 to 30x50 to 100x50 to 80x1x1x
Single configpartialfullfull (format only)fullpartialpartialpartial
Plugin ecosystemhugesmallmediumnone (built-in)smallhugehuge
Auto-fixformat onlyformat + some lintformat onlybroadpartialbroadpartial
Editor integrationbestgoodgoodbestgoodbestgood
Adoption cost (existing team)none (already in use)mediummediumlowlow (complement)none (already in use)none
Recommended useall JS/TS, broad file formatsJS/TS integrated, speed firstmulti-language monorepoalmost all Pythonfast first-pass lintdeep lint, plugin ecosystemall CSS

One-line summaries

  • Prettier 4 — stability and ecosystem champion, Rust backend narrows the speed gap
  • Biome 2 — the integrated JS/TS frontrunner, the value of one config
  • dprint — multi-language formatter, shines in monorepos
  • Ruff — de facto standard for the Python toolchain
  • oxlint — fast first-pass for ESLint, component piece for build tools
  • ESLint v9 — flat config tidy-up, plugin depth keeps it relevant
  • stylelint v16 — CSS lint default, Biome catching up

11. Migrations — Three Real Scenarios

Scenario A · Prettier + ESLint to Biome 2 (Next.js monorepo)

# 1. Install and initialize Biome
npm i -D --save-exact @biomejs/biome
npx biome init

# 2. Migrate Prettier config
npx biome migrate prettier --write
# 3. Migrate ESLint config (where mappable)
npx biome migrate eslint --write

# 4. First formatting pass (one large diff)
npx biome format --write .

# 5. Lint pass
npx biome lint .

# 6. Update CI / package.json scripts
# "lint": "biome lint .",
# "format": "biome format --write ."

Field report:

  • 50-package monorepo: CI lint stage dropped from 90 seconds to 6 seconds
  • Migration effort: ~1.5 days (papering over rule differences)
  • Did not drop ESLint entirely; some type-aware rules stayed as a complement
  • Minor differences between Prettier and Biome opinions (long expression wrapping) produced a diff that was handled in one PR

Signs you should not migrate:

  • Deep dependence on eslint-plugin-jest, eslint-plugin-storybook, and similar external plugins
  • Significant share of Vue / Svelte / Astro files
  • Team is happy with Prettier; no real speed complaint

Scenario B · black + flake8 + isort to Ruff (Django monorepo)

# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
extend-select = ["E", "W", "F", "I", "B", "UP", "DTZ", "S", "RUF"]
ignore = ["E501"]

[tool.ruff.format]
quote-style = "double"
# 1. Install
uv add --dev ruff

# 2. Configure pyproject.toml (as above)

# 3. First format pass (95% match with black, one diff)
ruff format .

# 4. Lint + auto-fix
ruff check --fix --unsafe-fixes .

# 5. Simplified pre-commit
# - id: ruff
#   args: [--fix]
# - id: ruff-format
# (previously: black, flake8, isort, pyupgrade, autoflake — 5 entries)

Field report:

  • 300k-line Django codebase: pre-commit dropped from 90 seconds to 3 seconds
  • Migration effort: ~0.5 days (one black-style diff, rule-selector mapping)
  • mypy was kept (Ruff is not a type checker)
  • High team satisfaction; nearly every Python project converges on this same pattern

Signs you should not migrate: very few. Ruff is effectively the Python default.

Scenario C · Polyglot monorepo with dprint + Biome + Ruff (split workflow)

A big-company monorepo lives with TS, Python, Markdown, TOML, Dockerfile, and Kubernetes YAML side by side. No single tool covers all of that, so split the work.

Format:
  TS / JS / JSON / CSS    -> Biome format
  Python                   -> Ruff format
  Markdown / TOML / Docker -> dprint

Lint:
  TS / JS                 -> Biome lint (fast) + ESLint (deep, CI)
  Python                  -> Ruff
  CSS                     -> stylelint

CI stages:
  Stage 1: dprint check + biome check + ruff check  (all 5 to 10 seconds)
  Stage 2: ESLint --max-warnings 0                   (CI only, 1 to 2 minutes)
  Stage 3: type checking (tsc, mypy / ty)

Field report: three tools instead of one, but each tool is placed where it shines. Pre-commit runs only the fast tools; the deep checks live on CI.

Headline: migrations do not have to be all-or-nothing. Adopt the fast tool where it wins and keep the incumbent for deep verification. This staged path is the safest.


12. Anti-Patterns and Pitfalls

Common traps when adopting fast tooling.

"Biome alone will solve everything"

  • Symptom: ESLint is removed entirely, plugin rules dropped overnight
  • Result: type-aware rules (no-floating-promises) and plugin rules disappear. Latent bugs slip through
  • Instead: split into fast lint (Biome) and deep lint (ESLint). Migrate gradually

"Ruff format equals black 100%"

  • Symptom: assume zero diff on the first ruff format of a big PR
  • Result: 95% match, edge-case differences. The PR gets noisy
  • Instead: do a dedicated reformat PR up front

"Editing gets worse without Prettier"

  • Symptom: VS Code stops formatting on save after switching to Biome
  • Result: settings missed. Different formats land on commits
  • Instead: set "editor.defaultFormatter": "biomejs.biome" in .vscode/settings.json and require the Biome extension

"Rust tools are fast — no caching needed"

  • Symptom: CI ignores cargo cache and plugin cache; downloads on every build
  • Result: tool itself is fast, but 30 seconds to 1 minute of download overhead
  • Instead: cache Biome / Ruff binaries and dprint plugins

"Linter is fast — turn every rule on"

  • Symptom: Biome correctness.all, Ruff select = ["ALL"]
  • Result: false positives explode, the team starts ignoring lint
  • Instead: start from recommended, add only what your codebase needs. Every rule is a policy

"Pre-commit should run everything"

  • Symptom: husky runs biome + eslint + ruff + tsc + jest
  • Result: 20 seconds to 1 minute per commit. Developers route around with --no-verify
  • Instead: pre-commit runs only fast checks; deep checks belong on CI

"Biome migrate will be perfect"

  • Symptom: run biome migrate eslint once and call it done
  • Result: rules that did not map silently disappeared. Latent bugs missed
  • Instead: review the diff post-migration; record which rules dropped

"Single tool equals single source of truth"

  • Symptom: the team declares "Biome is the standard" but Prettier output still differs slightly
  • Result: some team members install Prettier, others Biome, every commit produces a diff
  • Instead: agree on a tool, then enforce via .editorconfig, lockfile, and required editor extension

"Rust backend means uniformly faster"

  • Symptom: micro-benchmarks of single small files where Biome is sometimes slower than Prettier (startup cost)
  • Result: decisions based on the wrong measurement
  • Instead: measure the workflow that matters — full monorepo format times

"Integrating formatter + linter avoids conflicts"

  • Symptom: Biome format and ESLint stylistic rules (brace placement, etc.) both enabled
  • Result: infinite loop — format, then lint flags, then auto-fix changes format again
  • Instead: pick one tool as the formatter; remove stylistic rules from the linter

13. The Decision Tree — What Should Your Team Pick

Start:
|
+- Language is Python?
|   +- Yes -> Ruff (almost always). Keep mypy / ty separate.
|
+- Primarily JS/TS?
|   |
|   +- Greenfield, simple rules, speed-first
|   |   +- Biome 2 standalone
|   |
|   +- Existing Prettier+ESLint, satisfied, no speed pain
|   |   +- Keep it (consider enabling Prettier 4 Rust backend)
|   |
|   +- Existing Prettier+ESLint, real speed pain
|   |   +- Heavy plugin usage -> Prettier + Biome lint (fast first) + ESLint (deep)
|   |   +- Light plugin usage -> Migrate to Biome 2
|   |
|   +- Heavy Vue / Svelte / Astro
|   |   +- Stay on Prettier (Biome cannot cover)
|   |
|   +- Need a faster pre-commit
|       +- Add oxlint to the husky pre-commit
|
+- Polyglot monorepo (TS + Python + infra)
|   +- Split tools:
|      - TS -> Biome or Prettier
|      - Python -> Ruff
|      - infra (TOML, Dockerfile, MD) -> dprint
|
+- CSS / Tailwind heavy
|   +- Prettier + prettier-plugin-tailwindcss + stylelint
|
+- Monorepo-wide lint takes minutes
    +- Add a fast first pass:
       - Biome / Ruff / oxlint as the husky pre-commit and CI stage 1
       - Keep the existing tool for the deep CI pass

Simple rules

  • Python -> Ruff
  • Greenfield JS/TS, simple -> Biome 2
  • Prettier+ESLint already working -> keep it
  • Real monorepo lint pain -> add a fast first pass
  • Polyglot -> split tools
  • Heavy Tailwind / Vue / Svelte / MDX -> stay on Prettier

Epilogue — Checklist, Anti-Patterns, Next Post

The 2026 picture is different. The Rust rewrite wave bought 10 to 100 times speed, and the "formatter absorbs the linter" pattern finally materialized. Biome 2 and Ruff sit at near-standard status in their domains, Prettier 4 replied with a Rust backend, and ESLint v9 cleaned house with flat config.

But the picture is still not "one tool does it all." Plugin ecosystems, non-standard file formats, and type-aware analysis keep incumbents alive. The right pick for your team starts from measurement — what is the real bottleneck, and which trade-offs you accept.

Tool selection checklist

  1. Is your lint and format time actually a problem? — measure what changes when 30 seconds becomes 1 second
  2. Are the per-language stories different? — Python is mostly settled; JS/TS is not
  3. Do you depend deeply on plugins? — heavy eslint-plugin-* use keeps ESLint
  4. Monorepo, polyglot, non-standard files? — consider dprint or split tools
  5. Migrate all at once or incrementally? — large migrations belong in a dedicated PR
  6. Pre-commit versus CI separation — fast tools belong on pre-commit, deep ones on CI
  7. Do you need type-aware lint? — keep typescript-eslint for no-floating-promises and friends
  8. Does editor integration actually work? — verify VS Code / JetBrains extension maturity
  9. Do you cache tools on CI? — even fast tools have a download step worth caching
  10. Is there team agreement? — partial adoption produces conflicting commits

Anti-pattern recap

Anti-patternWhy it bitesInstead
Dropping ESLint entirely for Biometype-aware rules and plugins vanishsplit fast lint and deep lint
Assuming Ruff format equals black 100%95% match, edge-case diffsdedicated reformat PR
Enabling every rule (ALL)false-positive explosion, team ignoresstart from recommended, add what you need
Pre-commit runs everythingminute-scale commits, --no-verify workaroundsfast on pre-commit, deep on CI
Skipping caching for Rust tools30 to 60 seconds of download costcache binaries and plugins
No diff review post-migrationlost rules unnoticedhuman-review the migration diff
Single tool without team buy-inpartial adoption, diff conflictsagreement first, enforce via tooling
Optimizing for micro-benchmarksnot your actual workflowmeasure full monorepo timings
Format and lint stylistic clashinfinite loopone formatter, drop stylistic lint rules
Treating Prettier 4 Rust backend as production-ready immediatelybeta volatilityconfirm stability before adopting

Next post

Next: "Build Tools in 2026 — The Real Place of Vite, Turbopack, Rspack, Rolldown, esbuild, swc, and oxc". If this post was about how we tidy code, the next is about how we bundle it. esbuild's Go, swc and Turbopack's Rust, Vite's transition to esbuild plus Rolldown, Rspack's Rust rewrite, and oxc's componentization — the same Rust rewrite pattern, applied to bundlers.


References

현재 단락 (1/483)

There was a time when `prettier --write .` was the whole story. That was around 2020. Five years lat...

작성 글자: 0원문 글자: 30,870작성 단락: 0/483