Skip to content

✍️ 필사 모드: 2026년의 포매터·린터 — Biome, dprint, Ruff, oxc, Prettier 4, ESLint v9의 진짜 위치

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

프롤로그 — 왜 또 포매터·린터 이야기인가

prettier --write . 한 줄로 끝나던 시대가 있었다. 2020년쯤이다. 그 후 5년이 지나는 동안 같은 명령이 모노레포에서 30초, CI에서 2분으로 부풀었다. eslint --max-warnings 0 은 그것보다 더 느렸고, 어느 순간 우리는 "사전 커밋 훅에서 lint를 빼야 하나"를 진지하게 토론하고 있었다.

같은 시기 Python 진영에서도 비슷한 통증이 있었다. black + flake8 + isort + pylint 4단 콤보는 안정적이지만 느렸다. 큰 저장소에서 한 번 돌리는 데 분 단위가 걸렸고, 4개 도구의 설정 파일이 따로 놀았다.

여기에 Rust 진영의 도구 작성자들이 답을 들고 왔다. 속도가 진짜 100배 빠르면 다른 모든 것이 달라진다는 답이었다. Ruff(2022)가 Python에서 그걸 증명했고, Biome(이전의 Rome)이 JS/TS에서 같은 길을 갔다. dprint, oxc, ty(타입 체커), uv(패키지 매니저)까지 — Astral과 Biome 팀, 그리고 Boshen(oxc) 같은 작은 팀들이 이끄는 Rust 재작성의 물결이다.

2026년 현재, Prettier 4가 Rust 플러그인 길을 공식적으로 받아들였고, ESLint v9의 flat config가 안정화됐으며, typescript-eslint v8는 단일 패키지로 다시 묶였다. Biome 2는 multi-file 분석과 import sorting을 정식 기능으로 갖췄고, Ruff는 사실상 Python 린팅의 기본값이 됐다.

이 글은 그 지형을 정리한다. 어느 도구가 어느 자리에서 무엇을 잘하는가, "Rust로 다시 짜면 다 좋아진다"는 명제가 어디까지 진실인가, 그리고 우리 팀에 무엇이 맞는가. 마케팅이 아니라 측정 가능한 트레이드오프로.

핵심 한 줄: 도구는 빨라졌고 통합됐다. 하지만 "Biome 한 줄로 다 해결" 같은 단순한 그림은 아직 아니다. 어떤 트레이드오프를 받아들이느냐가 선택을 결정한다.


1장 · 지형도 — 2026년의 포매터·린터 전체 그림

먼저 큰 그림을 그린다. 언어별로 어떤 선택지가 있고, 누가 어디서 잘 도느냐.

언어 / 영역전통적 도구2026년 주력차세대 후보
JS / TSPrettier + ESLintPrettier + ESLint v9, Biome 2oxc, dprint
Pythonblack + flake8 + isort + pylintRuff (format + lint)ty (타입 체커)
Rustrustfmt + clippyrustfmt + clippy— (이미 Rust)
Gogofmt + golangci-lint동일
CSS / SCSSPrettier + stylelintPrettier + stylelint, Biome 2dprint, oxc
MarkdownPrettier, remark-lintPrettier, dprint
TOML / YAML / JSONPrettierdprint, taplo, yamlfmt
다중 언어 모노레포Prettier + ESLintdprint(통합 포매터), Biome

카테고리별 한 줄 정리

  • 포매터: 코드의 모양(공백·줄바꿈·따옴표·세미콜론)을 결정한다. 의견을 갖고 강제한다. Prettier, Biome 포맷, dprint, rustfmt, gofmt, Ruff format, black이 여기에 속한다
  • 린터: 의미적 문제(미사용 변수, 잠재적 버그, 안티패턴)를 잡는다. ESLint, Biome 린트, Ruff lint, clippy, golangci-lint, stylelint, pylint
  • 타입 체커: 타입 오류를 잡는다. tsc, mypy, pyright, ty(Astral), Flow
  • 포매터·린터 통합 도구: 한 바이너리에서 둘 다 하는 흐름의 신세대. Biome, Ruff가 대표

이 글의 주인공은 세 번째 흐름 — Rust로 짜인 차세대 통합 도구들. 그 도구들이 기존 도구를 대체하는 곳, 보완하는 곳, 아직 못 가는 곳을 본다.

한 줄 요약: 언어마다 사정이 다르다. Python은 Ruff가 사실상 정답이 됐고, JS/TS는 아직 Biome vs Prettier+ESLint의 결정이 남아 있고, 모노레포는 dprint가 새로운 선택지로 들어왔다.


2장 · Rust 재작성 패턴 — 왜 10~100배 빠른가

먼저 "왜 빠른가"를 짚는다. 마케팅이 아니라 구조의 차이다.

측정 가능한 속도 차

Biome 공식 문서가 보여주는 수치 — 단일 파일 포맷에서 Biome은 Prettier보다 25배35배 빠르고, 큰 저장소에서 더 큰 격차가 난다. Ruff는 같은 규칙으로 flake8 대비 약 100배 빠른 것이 자주 보고된다(중간큰 Python 저장소에서 분 단위가 초 단위로).

실제 사용 보고에서 자주 보이는 수치:

  • Prettier 모노레포 포맷 30초 → Biome 1~2초
  • flake8 + black 모노레포 점검 90초 → Ruff 2~3초
  • ESLint flat config 모노레포 lint 45초 → Biome 3~5초 (규칙 커버리지는 다름, 후술)
  • Prettier 단일 파일 포맷 300ms → dprint/Biome 5~15ms

왜 이 정도 차이가 나나

  1. 언어 자체의 차 — JS 도구는 V8 JIT 위에서 도는 인터프리터 코드, Rust 도구는 네이티브 바이너리. 이것만으로도 3~10배
  2. 단일 패스 파싱 — 전통 도구는 lex→parse→AST 변환을 여러 번 한다. Biome/oxc/Ruff는 한 번의 패스로 lossless concrete syntax tree(CST)를 만들고 모든 분석에 재사용
  3. 병렬화 — Rust의 데이터 동시성을 살려 파일을 진짜 병렬로 처리. JS 도구는 단일 스레드, Worker 풀을 쓰더라도 직렬화 비용
  4. 메모리 레이아웃 — CST 노드를 cache-friendly한 배열에 저장(rowan/oxc-resolver 같은 라이브러리). Allocation이 적음
  5. 재시작 비용 없음 — Rust 바이너리는 시작이 15ms, Node는 50200ms. CLI 반복 호출에서 큰 차이

빠름의 2차 효과

  • 사전 커밋 훅에서 다시 돌릴 수 있게 됨 — 30초였던 게 1초가 되면 훅에 다시 넣어도 됨
  • 에디터 통합이 라이브가 됨 — 타이핑할 때마다 린트가 가능
  • CI에서 따로 캐싱할 필요가 줄어듦 — 어차피 빠르니까
  • monorepo 전체 lint가 일상이 됨 — 분 단위면 CI에서만, 초 단위면 로컬에서도

한 줄 요약: 빠름 자체보다 빠름이 가능하게 하는 워크플로 변화가 본질이다. "어차피 느려서 못 함"이 풀린다.


3장 · Biome 2 — 포매터·린터 통합의 선두

Biome은 가장 야심적인 통합 도구다. 원래 이름은 Rome(2020, Sebastian McKenzie — Babel 만든 사람), 자금 부족으로 정지, 2023년 커뮤니티 포크로 Biome 출범, 2024년 Biome 1.0, 2025년 Biome 2.0.

Biome 2의 핵심 약속

  • JS / TS / JSX / TSX / JSON / CSS / GraphQL 포맷 + 린트 한 바이너리
  • 단일 설정 파일 biome.json — Prettier+ESLint 두 개를 통합
  • 타입 인식 린팅 — typescript-eslint와 비슷한 type-aware 규칙 일부 (V2에서 multi-file 분석 추가)
  • 임포트 정렬 — eslint-plugin-import 일부를 대체
  • diff format — 변경된 부분만 포맷 가능 (CI에서 큰 PR에 유용)
  • 마이그레이션 도구 biome migrate prettier, biome migrate eslint

설정 예시

{
  "$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" } }
  }
}

Biome가 잘하는 곳

  • JS/TS 위주, 단순한 규칙 세트 — 90% 규칙 커버리지면 충분한 팀
  • 단일 설정의 가치를 알아보는 팀 — Prettier+ESLint 설정의 복잡성에 지친 팀
  • CI 속도가 진짜 문제인 팀 — 모노레포 lint가 분 단위 걸리는 경우
  • 로컬 IDE 통합 속도 — VS Code 익스텐션이 즉시 반응

Biome가 못 가는 곳 / 아직 부족한 곳

  • ESLint 플러그인 생태계 부재 — eslint-plugin-react-hooks, eslint-plugin-jest, eslint-plugin-tailwindcss, eslint-plugin-import 같은 외부 규칙을 직접 못 씀. Biome가 일부를 내장으로 흡수 중이지만 ESLint 플러그인 1:1 대체는 어려움
  • Vue, Svelte 등 비표준 파일 포맷 미지원.vue, .svelte는 아직 (커뮤니티 플러그인 일부 있으나 공식 미지원)
  • Markdown / YAML / TOML 포맷 미지원 — Prettier가 커버하던 영역을 못 함
  • 타입 인식 린팅의 한계 — typescript-eslint의 전체 규칙 세트를 따라잡지 못함
  • 마이그레이션 비용 — 큰 저장소에서 ESLint 규칙 차이를 손으로 메우는 데 며칠

결정 기준

팀 상황Biome 적합도
새 JS/TS 프로젝트, 단순 규칙매우 높음
모노레포, CI 속도 중요, Vue/Svelte 없음높음
기존 Prettier+ESLint, 만족 중옮길 동기 약함
eslint-plugin-* 깊이 활용아직 어려움
Vue / Svelte / Astro / MDX 비중 큼추천 안 함

한 줄 요약: Biome는 "JS/TS만 보면" 가장 매력적인 통합 도구다. 단, 플러그인 생태계와 비표준 파일 지원에서 Prettier+ESLint를 완전히 대체하기에는 아직 빈자리가 있다.


4장 · dprint — 플러그인 기반 다중 언어 포매터

dprint는 다른 접근이다. 자체적으로는 작은 코어, 언어별 플러그인으로 확장한다. 단일 도구로 JS/TS, JSON, Markdown, TOML, Dockerfile, SQL을 한 번에 포맷한다.

dprint의 차별점

  • WASM 플러그인 시스템 — 언어 지원이 플러그인. 코어는 빠르고 작음
  • 다중 언어 모노레포 최적 — Prettier 안 되는 TOML, Dockerfile까지 한 도구로
  • Rust 작성 + Rust/WASM 플러그인 SDK
  • 순수 포매터 — 린팅은 안 함. 한 가지만 잘함

설정 예시

{
  "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"]
}

dprint가 잘하는 곳

  • 다중 언어 모노레포 — TS + JSON + Markdown + TOML + Dockerfile 등을 한 도구로
  • 순수 포매팅 가치 — 린팅까지 안 통합해도 됨, 그냥 빠른 Prettier 대체
  • 플러그인 격리 — 각 언어가 WASM 모듈로 격리되어 의존성 충돌 없음
  • 버전 고정의 편의dprint config update로 플러그인 버전 한 번에 갱신

dprint가 못 가는 곳

  • 린팅 없음 — 린터는 별도 (ESLint, Biome 린트, Ruff 등)
  • JS/TS 규칙 커버리지 — Prettier보다는 살짝 적음. 옵션이 더 의견 있음
  • 에디터 통합 성숙도 — VS Code 확장은 있으나 Prettier만큼 부드럽지는 않음
  • CSS / GraphQL 플러그인 상태 — 일부 플러그인은 베타 또는 커뮤니티 유지

누가 쓰나

  • Deno 표준 코드베이스(dprint 자체가 Deno 친화적)
  • 인프라 저장소(IaC, Docker, K8s, Terraform 옆에 두기 좋음)
  • 다국어 모노레포(TS + Python + 인프라 코드 함께 사는 곳)

한 줄 요약: dprint는 "한 도구로 모든 텍스트 포맷"을 노린다. 린팅 없이 포맷만, 다중 언어가 진짜로 섞인 모노레포에서 빛난다.


5장 · Ruff — Python을 평정한 Rust 도구

Ruff는 가장 압도적인 성공 사례다. Charlie Marsh(Astral)가 2022년 첫 릴리스, 2026년 현재 PyPI Python 도구 다운로드 통계에서 사실상 1위. Django, Pandas, FastAPI, Pydantic, Apache Airflow, scikit-learn 같은 메이저 프로젝트가 Ruff를 채택했다.

Ruff의 약속

  • flake8 + isort + pydocstyle + pyupgrade + bandit + autoflake + black 대체 — 7~10개 도구를 1개로
  • 800개 이상의 린트 규칙 — flake8 + 주요 플러그인 규칙 대부분 이식
  • 포매터 내장ruff format이 black 호환 (black의 고집스러운 의견을 거의 그대로 따름)
  • import 정렬 — isort 대체
  • 자동 수정ruff check --fix로 안전한 수정 자동화
  • 단일 설정pyproject.toml[tool.ruff] 섹션 하나에

설정 예시

[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

Ruff가 잘하는 곳 — 거의 모든 곳

  • 속도 — flake8+pylint 90초 → Ruff 2초
  • 단일 도구 — pre-commit 설정이 7줄에서 1줄로
  • 자동 수정의 범위 — flake8은 보고만 하던 것을 Ruff는 고침
  • CI 캐싱 불필요 — 어차피 빠름
  • flake8 규칙 셀렉터 호환 — 마이그레이션 비용이 낮음

Ruff의 한계 / 차이

  • mypy 대체는 아님 — 타입 체킹은 별도. Astral이 ty(타입 체커, Rust)를 개발 중이지만 2026년 현재 알파/베타 단계
  • black과 100% 동일하지는 않음 — 95%는 같지만 엣지 케이스(긴 문자열, 복잡한 표현식) 일부 차이. 한 번 ruff format을 돌리면 diff가 발생할 수 있음 — 대규모 PR이 한 번 필요
  • pylint의 깊은 분석 일부 미흡too-many-locals, cyclomatic-complexity 같은 규칙은 제한적
  • 플러그인 시스템 없음 — 모든 규칙이 코어에 내장. 커스텀 규칙 추가가 어려움

Astral의 큰 그림 — Rust 재작성 전체 스택

Charlie Marsh의 야심은 Ruff에 그치지 않는다. Astral은 Python 도구 체인 전체를 Rust로 다시 쓰고 있다.

도구대체 대상상태(2026)
Ruffflake8, isort, black, pydocstyle, ...안정, 광범위 채택
uvpip, virtualenv, pip-tools, pipx, poetry(일부)안정, 빠르게 확산
tymypy, pyright(부분)알파/베타
ryepoetry, hatch(개념적)uv에 흡수되는 중

전체 패턴: "도구 하나가 빠르면 그 도구 위주로 모든 워크플로가 재편된다." 같은 패턴이 Biome(JS), oxc(JS)에서 반복된다.

한 줄 요약: Ruff는 Python 도구 체인에서 사실상 디폴트가 됐다. 이유는 단순하다 — 빠르고, 통합돼 있고, 마이그레이션 비용이 낮다.


6장 · oxc — Rust JavaScript 도구 체인 가족

oxc는 다른 야심이다. Boshen이 이끄는 프로젝트로, JavaScript 도구 체인 전체를 Rust로 만든다. 파서, 리졸버, 트랜스포머, 린터, 포매터, 미니파이어. Biome는 "단일 사용자 도구"라면, oxc는 "다른 도구가 쓰는 컴포넌트 라이브러리"에 가깝다.

oxc의 구성 요소

컴포넌트무엇비교 대상
oxc-parserJS/TS 파서acorn, esprima, swc 파서
oxc-resolver모듈 해석enhanced-resolve
oxc-transformerJS/TS 변환Babel, swc
oxlint린터ESLint
oxc-prettier포매터(Prettier Rust 포트)Prettier
oxc-minifier미니파이어terser, swc, esbuild

oxlint — ESLint를 노린 Rust 린터

oxlint는 ESLint 코어 규칙과 플러그인 일부를 Rust로 다시 쓴다. 2026년 현재 oxlint는 ESLint v9 호환 모드를 일부 지원하며, 빠른 사전 점검(pre-flight check) 용도로 권장된다.

# 빠른 lint (oxlint), 깊은 lint (ESLint)
oxlint --deny-warnings src/
# ESLint 호환 보고
oxlint --rules-include=react-hooks src/

oxc가 잘하는 곳

  • 빌드 도구 작성자 / 프레임워크 작성자 — Rspack, Rolldown 같은 도구가 oxc를 내부적으로 채용. 빠른 컴포넌트 라이브러리
  • 사전 점검 린트 — Lefthook/Husky 사전 커밋 훅에서 ESLint 전체 대신 oxlint로 70% 규칙을 0.5초에 점검
  • CI 첫 단계 — 빠른 실패. oxlint 통과 후 ESLint 깊은 점검
  • Prettier 호환 포매터 — Prettier 4의 Rust 플러그인 경로의 한 축

oxc의 한계

  • 플러그인 시스템 미성숙 — ESLint 플러그인을 직접 못 씀
  • 린트 규칙 커버리지 부분적 — ESLint의 전체 규칙 1:1 매핑 아직
  • 단독 도구로 쓰기 어려움 — Biome처럼 "이것만 쓰면 끝"이 아니라 보완재
  • 포매터는 Prettier 4 통합 경로에 더 가까움 — 독립 사용 사례 적음

Biome vs oxc — 무엇이 다른가

비교Biomeoxc
목표통합 사용자 도구컴포넌트 라이브러리 + 도구 가족
단일 설정그렇다 (biome.json)도구마다 다름
사용자 경험"Prettier+ESLint 대체""ESLint 빠른 보완"
플러그인내장 위주(계획) WASM 플러그인
누가 채용애플리케이션 팀빌드 도구 작성자, 프레임워크

한 줄 요약: Biome가 사용자 도구, oxc는 컴포넌트 가족. 사용자 입장에서는 Biome가 직관적, 도구 작성자 입장에서는 oxc가 유용. 둘은 경쟁이 아니라 다른 층위에서 일한다.


7장 · Prettier 4 — 황제는 어떻게 응답했는가

Prettier는 2017년 이후 사실상 JS/TS 포매터의 디폴트였다. 의견을 가지고 강제하고, 설정이 거의 없는 것이 핵심 철학. 그 결과 거의 모든 프로젝트가 채택했다.

하지만 속도 문제가 누적됐다. Prettier 3까지는 순수 JS, Node 위에서 도는 도구. 빠른 도구들과의 격차가 커지자, Prettier 팀은 2025년부터 Rust 플러그인 경로를 공식화했다.

Prettier 4의 핵심 변화

  • Rust 기반 파서·포매터 경로 — JS/TS 코어는 oxc 또는 swc 기반 Rust 백엔드를 채용할 수 있는 어댑터 추가
  • 호환성 우선 — Prettier 3의 출력과 비트 단위 일치가 목표 (95%+ 일치). 100% 가는 길은 점진적
  • 플러그인 API 안정화 — Markdown, YAML, GraphQL, Tailwind 같은 플러그인은 그대로
  • 속도 개선 — Rust 백엔드 활성 시 5~15배 가속 (Biome만큼은 아니지만 의미 있음)
  • 단일 설치 모델 유지npm install prettier 한 줄로 끝나는 사용자 경험은 유지

설정 예시 (Prettier 4)

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

Prettier가 살아남는 이유

  • 생태계의 관성 — Tailwind 정렬, import 정렬, Astro/Svelte/Vue 플러그인 등 수백 개의 플러그인
  • 에디터 통합의 완성도 — VS Code "format on save"가 가장 안정적
  • 의견의 의견 — Prettier 스타일이 표준이 된 상태 — Biome도 Prettier 호환 출력을 약속할 정도
  • 풍부한 파일 포맷 지원 — JSON, YAML, Markdown, GraphQL, MDX 등 Prettier가 가장 넓음

Prettier vs Biome — 2026년의 결정

기준Prettier 4Biome 2
JS/TS 포맷 속도5~15배 가속(Rust 백엔드)25~35배 가속
린터 통합없음 (ESLint 별도)통합
플러그인 생태계거대작음
비표준 파일 지원넓음좁음
단일 설정부분(여전히 .prettierrc + .eslintrc)완전(biome.json)
마이그레이션 비용없음(이미 쓰고 있음)중간 (biome migrate)

한 줄 요약: Prettier는 죽지 않는다 — 생태계가 너무 깊다. Rust 백엔드로 속도 격차를 좁히고, ESLint와의 조합으로 살아남는다. Biome는 "통합의 가치"가 큰 새 프로젝트에서 강하다.


8장 · ESLint v9와 typescript-eslint v8 — flat config 시대

ESLint 진영도 가만히 있지 않았다.

ESLint v9의 변화

  • flat config 표준화.eslintrc.*(legacy) 대신 eslint.config.js(또는 .ts)가 기본. v10에서 legacy는 제거 예정
  • 타입스크립트 ESM 우선 — flat config가 ESM에 자연스러움
  • --inspect-config — 어떤 규칙이 어디서 왜 켜졌는지 시각화
  • CLI 안정화eslint .이 디폴트 동작 (v9부터 인자 없이도 동작)
  • deprecated 규칙 정리 — formatting 관련 규칙은 모두 deprecated (Prettier로 위임)

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 — 단일 패키지

v7 이전에는 @typescript-eslint/parser@typescript-eslint/eslint-plugin을 따로 설치해야 했다. v8에서는 typescript-eslint 단일 패키지로 통합. 설정도 더 짧아짐.

npm i -D typescript-eslint

projectService 기능 — 타입 인식 lint의 비용 절감

타입 인식 lint(@typescript-eslint/recommended-type-checked)는 정확하지만 느렸다. v8의 projectService는 TypeScript LSP처럼 동작 — tsconfig를 자동 발견하고, 파일 단위로 타입 정보를 lazy로 로드. 모노레포에서 lint 속도를 2~5배 개선.

ESLint가 살아남는 이유

  • 플러그인 생태계 — eslint-plugin-react-hooks, eslint-plugin-jest, eslint-plugin-jsx-a11y, eslint-plugin-import, eslint-plugin-tailwindcss, eslint-plugin-storybook 등 수백 개
  • 타입 인식 규칙의 깊이no-floating-promises, await-thenable, no-misused-promises처럼 타입 체커 없이 못 잡는 규칙
  • 사용자 정의 규칙의 용이성 — JS로 짠 플러그인을 쉽게 추가
  • 레거시 호환 — flat config로 옮겨도 옛 플러그인을 대부분 수용

ESLint와 Biome의 공존 패턴

많은 팀이 이렇게 한다:

포맷:  Prettier (또는 Biome format)
빠른 린트: oxlint / Biome lint (사전 커밋, 즉시 피드백)
깊은 린트: ESLint (CI, 타입 인식 + 플러그인)

ESLint를 버리는 게 아니라 "빠른 1차 통과 + 깊은 2차 통과"로 분리. Biome+ESLint 같이 도는 게 어색해 보이지만 실제로 잘 작동한다.

한 줄 요약: ESLint는 v9 flat config로 정리됐고, typescript-eslint v8의 projectService로 속도도 개선됐다. 플러그인 생태계 덕에 가까운 시일 안에 죽지 않는다.


9장 · stylelint와 CSS 도구들

CSS 진영도 비슷한 흐름이다.

stylelint v16

  • 여전히 CSS / SCSS / Less 린팅의 디폴트
  • v16에서 flat-config 비슷한 단순화
  • Biome 2의 CSS 린트가 일부 규칙을 흡수 중이지만 stylelint의 전체 커버리지에는 못 미침
  • Tailwind / CSS Modules / postcss 통합은 여전히 stylelint가 강함

CSS 포맷 — 누가 잘하나

도구CSSSCSSLessTailwind 클래스 정렬
Prettier안정안정안정플러그인으로
Biome안정부분(2.x)미지원미지원(직접)
dprint베타 플러그인베타미지원미지원

결정 가이드

  • Tailwind 위주, CSS Modules — Prettier + prettier-plugin-tailwindcss + stylelint (가장 검증)
  • 순수 CSS, 모노레포 속도 중요 — Biome 2 CSS 포맷 + lint, stylelint 보조
  • SCSS 깊게 사용 — Prettier + stylelint (Biome SCSS는 아직 부분)

한 줄 요약: CSS는 아직 Prettier + stylelint 조합이 가장 안전하다. Biome 2가 빠르게 따라잡지만 SCSS·Tailwind 통합은 시간이 더 필요하다.


10장 · 비교 매트릭스 — 한눈에 보는 2026

항목Prettier 4Biome 2dprintRuffoxlintESLint v9stylelint v16
언어JS/TS/CSS/MD/YAML/JSON/Vue/Svelte/AstroJS/TS/JSX/JSON/CSS/GraphQLJS/TS/JSON/MD/TOML/DockerfilePythonJS/TSJS/TSCSS/SCSS/Less
포매터있음있음있음(코어)있음없음없음(deprecated)없음
린터없음있음없음있음있음있음있음
구현 언어JS + Rust(v4)RustRust + WASMRustRustJSJS
속도(상대)1배 (v4 Rust 5~15배)25~35배10~30배50~100배50~80배1배1배
단일 설정부분완전완전(포맷만)완전부분부분부분
플러그인 생태계거대작음중간없음(내장)작음거대거대
자동 수정포맷만포맷 + 일부 lint포맷만광범위일부광범위일부
에디터 통합최고좋음좋음최고좋음최고좋음
도입 비용(기존팀)없음(이미 씀)중간중간낮음낮음(보완)없음(이미 씀)없음
추천 사용처모든 JS/TS, 폭넓은 파일 포맷JS/TS 통합, 속도 우선다중 언어 모노레포Python 거의 모든 곳빠른 1차 lint깊은 lint, 플러그인 활용CSS 모든 곳

한 줄 정리

  • Prettier 4 — 안정성과 생태계의 챔피언, Rust 백엔드로 속도 개선
  • Biome 2 — JS/TS 통합 도구의 선두, 단일 설정의 가치
  • dprint — 다중 언어 포매터, 모노레포에서 강함
  • Ruff — Python 도구 체인의 사실상 표준
  • oxlint — ESLint 빠른 1차 통과, 빌드 도구의 컴포넌트
  • ESLint v9 — flat config로 정리, 플러그인 생태계의 깊이
  • stylelint v16 — CSS 린팅의 디폴트, Biome가 따라잡는 중

11장 · 마이그레이션 — 실제 시나리오 3개

시나리오 A · Prettier + ESLint → Biome 2 (Next.js 모노레포)

# 1. Biome 설치 및 초기화
npm i -D --save-exact @biomejs/biome
npx biome init

# 2. Prettier 설정 마이그레이션
npx biome migrate prettier --write
# 3. ESLint 설정 마이그레이션 (가능한 부분만)
npx biome migrate eslint --write

# 4. 첫 포맷 (큰 diff 한 번)
npx biome format --write .

# 5. 린트 점검
npx biome lint .

# 6. CI / package.json 스크립트 변경
# "lint": "biome lint .",
# "format": "biome format --write ."

현장 보고:

  • 50개 패키지 모노레포에서 CI lint 단계 90초 → 6초
  • 마이그레이션 작업 1.5일 (규칙 차이 메우기)
  • ESLint를 완전히 버리지 않고 type-aware 규칙 일부는 ESLint 보조로 유지
  • Prettier 의견과 Biome 의견의 미세한 차이(긴 표현식 줄바꿈) 때문에 코드 diff 발생, 한 번에 처리

옮기지 말아야 할 신호:

  • eslint-plugin-jest, eslint-plugin-storybook 같은 외부 플러그인 의존 깊음
  • Vue / Svelte / Astro 파일 비중 큼
  • 팀이 Prettier 설정에 만족, 속도 불만 없음

시나리오 B · black + flake8 + isort → Ruff (Django 모노레포)

# 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. 설치
uv add --dev ruff

# 2. pyproject.toml 설정
# (위와 같이 작성)

# 3. 첫 포맷 (black과 95% 일치, diff 한 번)
ruff format .

# 4. 린트 + 자동 수정
ruff check --fix --unsafe-fixes .

# 5. pre-commit 훅 단순화
# - id: ruff
#   args: [--fix]
# - id: ruff-format
# (이전에는 black, flake8, isort, pyupgrade, autoflake 5개)

현장 보고:

  • 30만 줄 Django 코드베이스에서 pre-commit 90초 → 3초
  • 마이그레이션 0.5일 (black diff 한 번, 규칙 셀렉터 매핑)
  • mypy는 유지(Ruff는 타입 체커 아님)
  • 팀이 만족도 높음, 거의 모든 Python 프로젝트가 동일 패턴 채용

옮기지 말아야 할 신호: 거의 없다. Python에서는 Ruff가 사실상 디폴트.

시나리오 C · 다중 언어 모노레포 → dprint + Biome + Ruff (혼합)

큰 회사의 모노레포에는 TS, Python, Markdown, TOML, Dockerfile, K8s YAML이 같이 산다. 한 도구로 다 못 하면 도구를 분업.

포맷:
  TS / JS / JSON / CSS  → Biome format
  Python                  → Ruff format
  Markdown / TOML / Dockerfile → dprint

린트:
  TS / JS               → Biome lint (빠른) + ESLint (CI, 깊음)
  Python                → Ruff
  CSS                   → stylelint

CI 통합:
  단계 1: dprint check + biome check + ruff check (모두 5~10초)
  단계 2: ESLint --max-warnings 0 (CI에서만, 1~2분)
  단계 3: 타입 체크 (tsc, mypy/ty)

현장 보고: 도구가 3개가 되지만 각 도구가 잘하는 곳에 둠. 사전 커밋 훅은 빠른 도구들만, 깊은 점검은 CI.

한 줄 요약: 마이그레이션은 한 번에 다 옮기지 않아도 된다. 빠른 도구가 잘하는 곳부터 점진적으로, 기존 도구는 깊은 검증으로 남기는 패턴이 가장 안전하다.


12장 · 안티패턴과 함정

빠른 도구를 도입할 때 자주 만나는 함정들.

"Biome 한 줄로 다 해결될 거다"

  • 증상: ESLint 완전 제거, 플러그인 규칙도 한 번에 버림
  • 결과: type-aware 규칙(no-floating-promises)과 플러그인 규칙 부재. 잠재 버그가 검출 안 됨
  • 대신: 빠른 lint = Biome, 깊은 lint = ESLint 보조 유지. 점진적으로 흡수

"Ruff format이 black과 100% 동일"

  • 증상: 큰 PR에서 ruff format을 처음 돌렸을 때 diff 0을 기대
  • 결과: 95% 일치, 엣지 케이스에서 diff 발생. PR이 더러워짐
  • 대신: 마이그레이션 전용 PR 하나를 만들어 한 번에 포맷 적용

"Prettier를 빼면 에디터가 불편해진다"

  • 증상: Biome로 옮긴 후 VS Code에서 format on save가 안 됨
  • 결과: 설정 빠뜨림. 팀원마다 다른 포맷이 commit됨
  • 대신: .vscode/settings.json"editor.defaultFormatter": "biomejs.biome" + Biome 익스텐션 권장

"Rust 도구는 항상 빠르니까 캐싱 필요 없다"

  • 증상: CI에서 cargo cache, plugin cache 무시. 매 빌드마다 다운로드
  • 결과: 도구 자체는 빠르지만 다운로드 단계가 30초~1분 추가
  • 대신: Biome / Ruff 바이너리 캐싱, dprint plugin cache 활용

"린터가 빠르니까 규칙을 다 켜자"

  • 증상: Biome correctness.all, Ruff select = ["ALL"]
  • 결과: false positive 폭발, 팀이 무시하기 시작
  • 대신: recommended에서 시작, 진짜 필요한 규칙만 추가. 모든 규칙은 정책

"사전 커밋 훅에 모든 걸 다 넣자"

  • 증상: husky에 biome + eslint + ruff + tsc + jest 다 넣음
  • 결과: 커밋 1회당 20초~1분. 개발자가 --no-verify로 우회
  • 대신: 사전 커밋은 빠른 것만(포맷, 빠른 lint). 깊은 점검은 CI

"Biome migrate가 완벽할 거다"

  • 증상: biome migrate eslint 한 번 돌리고 끝
  • 결과: 매핑 안 되는 규칙은 무시됨. 잠재 버그 검출 누락
  • 대신: 마이그레이션 후 diff를 사람이 검토, 어떤 규칙이 떨어졌는지 확인

"단일 도구 = 단일 진실"

  • 증상: "Biome가 표준" 하나로 미고, Prettier 출력과 약간 다른 부분에서 팀 충돌
  • 결과: 일부 팀원은 Prettier 인스톨, 다른 팀원은 Biome 인스톨, commit마다 diff
  • 대신: 도구 선택은 팀 합의, .editorconfig + lockfile + 익스텐션 권장으로 강제

"Rust 백엔드 = 무조건 빠름"

  • 증상: 작은 단일 파일에서 Biome 호출이 Prettier보다 느릴 때가 있음(시작 비용)
  • 결과: 마이크로 벤치마크 결과만 보고 결정
  • 대신: 실제 워크플로(모노레포 전체 포맷)에서 측정

"포매터·린터를 통합하면 의견이 충돌하지 않는다"

  • 증상: Biome format과 ESLint stylistic 규칙(중괄호 위치 등)을 동시에 활성
  • 결과: 무한 루프(포맷 → 린트 에러 → 자동 수정 → 포맷 다시)
  • 대신: 포맷 도구를 단일로 정하고, 린트에서는 stylistic 규칙 제거

13장 · 의사결정 트리 — 우리 팀은 무엇을 골라야 하나

시작:
├─ 언어가 Python인가?
│   └─ 예 → Ruff (거의 모든 경우). mypy/ty는 별도
├─ JS/TS 위주인가?
│   │
│   ├─ 새 프로젝트, 단순 규칙, 속도 우선
│   │   └─ Biome 2 단독
│   │
│   ├─ 기존 Prettier+ESLint, 만족, 속도 불만 없음
│   │   └─ 그대로 유지 (Prettier 4 Rust 백엔드 옵션 활성)
│   │
│   ├─ 기존 Prettier+ESLint, 속도 진짜 문제
│   │   ├─ 플러그인 깊게 활용 → Prettier + Biome lint(빠른 1차) + ESLint(깊은)
│   │   └─ 플러그인 적음 → Biome 2로 마이그레이션
│   │
│   ├─ Vue / Svelte / Astro 비중 큼
│   │   └─ Prettier 유지 (Biome 미지원 영역)
│   │
│   └─ 빠른 사전 점검 추가
│       └─ oxlint를 husky 사전 커밋에
├─ 다중 언어 모노레포(TS + Python + 인프라)
│   └─ 도구 분업:
│      - TS → Biome 또는 Prettier
│      - Python → Ruff
│      - 인프라(TOML, Dockerfile, MD) → dprint
├─ CSS / Tailwind 중심
│   └─ Prettier + prettier-plugin-tailwindcss + stylelint
└─ 모노레포 전체 lint가 분 단위
    └─ 1차 빠른 통과 도입:
       - Biome / Ruff / oxlint를 husky 사전 커밋과 CI 첫 단계로
       - 기존 도구는 CI 후반에 깊은 점검 용도로

단순 규칙

  • Python → Ruff
  • 새 JS/TS 프로젝트, 단순 → Biome 2
  • 기존 Prettier+ESLint 잘 돌고 있음 → 유지
  • 모노레포 lint가 진짜 느림 → 빠른 1차 도입
  • 다중 언어 → 도구 분업
  • Tailwind / Vue / Svelte / MDX 비중 큼 → Prettier 유지

에필로그 — 체크리스트, 안티패턴, 다음 글 예고

2026년의 포매터·린터 지형은 5년 전과 다르다. Rust 재작성의 물결로 도구가 10~100배 빨라졌고, "포매터가 린터를 흡수한다"는 통합 흐름이 본격화됐다. Biome 2와 Ruff가 각자의 영역에서 사실상 표준에 가까워졌고, Prettier 4는 Rust 백엔드로 응답했고, ESLint v9는 flat config로 자기 자리를 정리했다.

하지만 "한 도구로 모든 게 끝"이라는 그림은 아직 아니다. 플러그인 생태계, 비표준 파일 지원, type-aware 분석 같은 영역에서 기존 도구가 살아남는다. 우리 팀에 맞는 선택은 — 무엇이 진짜 병목이고, 어떤 트레이드오프를 받아들일지 — 측정에서 시작한다.

도구 선택 체크리스트

  1. 현재 lint·포맷 시간이 진짜로 문제인가? — 30초가 1초가 되어 무엇이 달라지나 측정
  2. 언어별 사정은 다른가? — Python은 Ruff가 거의 결정, JS/TS는 더 복잡
  3. 플러그인 의존이 깊은가? — eslint-plugin-* 깊게 쓰면 ESLint 유지가 안전
  4. 모노레포 / 다중 언어 / 비표준 파일? — dprint 또는 도구 분업 검토
  5. 마이그레이션 한 번에 vs 점진? — 큰 PR은 별도 마이그레이션 PR로
  6. 사전 커밋 vs CI 분리 — 빠른 도구만 사전 커밋에, 깊은 도구는 CI에
  7. type-aware 린트가 필요한가?no-floating-promises 류는 typescript-eslint 유지
  8. 에디터 통합이 되는가? — VS Code / JetBrains 익스텐션 성숙도 확인
  9. CI에서 도구 캐싱이 되는가? — 빠른 도구도 다운로드 단계 캐싱 필요
  10. 팀 합의가 있는가? — 일부만 Biome 쓰면 commit 충돌

안티패턴

안티패턴왜 나쁜가대신
Biome 한 줄로 ESLint 완전 제거type-aware 규칙, 플러그인 부재빠른 lint + 깊은 lint 분리
Ruff format이 black과 100% 같다고 가정95% 일치, 엣지 케이스 diff마이그레이션 PR 별도
모든 규칙을 켜기 (ALL)false positive 폭발, 무시 시작recommended에서 시작, 필요한 것만
사전 커밋에 모든 검사커밋 1분, --no-verify 우회빠른 것만 사전 커밋, 깊은 건 CI
Rust 도구는 캐싱 불필요다운로드 단계가 30초~1분바이너리 / 플러그인 캐싱
Biome migrate 후 검토 없음매핑 안 된 규칙 손실diff 사람 리뷰
단일 도구 강제, 팀 합의 없음일부만 도입, diff 충돌합의 후 lockfile / 익스텐션 권장
마이크로 벤치마크만 보고 결정실제 워크플로와 다름모노레포 전체 시간 측정
포맷·린트 stylistic 충돌무한 루프포맷 단일화, 린트 stylistic 제거
Prettier 4 Rust 백엔드를 즉시 의존베타 단계의 변동성안정화 확인 후 도입

다음 글 예고

다음 글은 **"빌드 도구 2026 — Vite, Turbopack, Rspack, Rolldown, esbuild, swc, oxc의 진짜 위치"**다. 이번 글이 "코드를 어떻게 정돈하는가"의 글이라면, 다음 글은 "코드를 어떻게 묶는가"의 글이다. esbuild의 Go, swc/Turbopack의 Rust, Vite의 esbuild+Rolldown 전환, Rspack의 Rust 재작성, oxc의 컴포넌트화 — 같은 Rust 재작성 패턴이 빌드 도구에서 어떻게 반복되는지, 그리고 무엇을 골라야 하는지를 정리한다.


참고 / References

현재 단락 (1/483)

`prettier --write .` 한 줄로 끝나던 시대가 있었다. 2020년쯤이다. 그 후 5년이 지나는 동안 같은 명령이 모노레포에서 30초, CI에서 2분으로 부풀었다. `...

작성 글자: 0원문 글자: 20,740작성 단락: 0/483