Skip to content

Investigate migrating formatter from Prettier to oxfmt (oxc formatter) #11350

Description

@timotheeguerin

Summary

Proof-of-concept investigation into replacing Prettier with oxfmt (the oxc formatter) for the monorepo. Result: large speed win with low churn for the JS/TS/JSON bulk. oxfmt can't format a few file types, so the proposal is a hybrid where oxfmt owns the bulk, tsp format owns .tsp, and Prettier is kept only for .astro + shell.

Tested with oxfmt 0.59.0 (npm hybrid build).

Key results

  • ~30× faster on the JS/TS/JSON bulk: oxfmt --check = ~2.7s on 2947 files vs Prettier format:check = ~80–100s.
  • Low churn: only 55 / 2947 files (1.9%) differ — genuine formatting deltas (union-type line-breaking, arrow-arg wrapping), not import reordering.
  • No JSON / YAML / CSS churn — byte-identical to Prettier.
  • .prettierrc migrated cleanly via oxfmt --migrate=prettier (all core options + every .prettierignore pattern). Only the 4 plugins are dropped (expected).

Coverage

oxfmt natively handles: JS/TS/TSX, JSON, YAML, Markdown, CSS, GraphQL, TOML.

Gaps (must live elsewhere):

File type Count Plan
.tsp 356 tsp format (compiler's own formatter; the prettier plugin just wraps it)
.astro 47 Unsupported in 0.59 → residual Prettier
.sh 1 (packages/standalone/install.sh) No shell support → residual Prettier

Benchmarks (M-series mac, 10 threads)

Step Files Wall time
Prettier format:check (current) ~all ~80–100s
oxfmt --check . 2947 ~2.7s
tsp format --check 324 ~1.8s
Prettier residual (*.{astro,sh}) 48 ~4.1s
Proposed combined pipeline ~9s

Churn detail (the 55 files)

26 .ts, 25 .tsx, 4 .md. Representative diffs:

  • Union types that fit within printWidth are broken to one member per line with a leading | by oxfmt, where Prettier 3.9.5 keeps them on one wrapped line.
  • arr.map((x): T => ({...})) — oxfmt wraps the callback argument differently.

One-time reformats; the resulting style is stable and self-consistent.

⚠️ Do NOT enable sortImports: it re-sorts with the perfectionist algorithm (≠ prettier-plugin-organize-imports order) and churn jumps to 1337 files (45%).

Import organizing — behavioral gap

  • Current prettier-plugin-organize-imports sorts + removes unused imports.
  • oxfmt sortImports is off by default; when on it only sorts (different order), never removes unused.
  • oxlint already has no-unused-vars: error, which flags unused imports (blocks CI) but does not auto-remove them on --fix.
  • Recommendation: keep sortImports off. Either (a) drop import-organizing and rely on oxlint, or (b) keep prettier-plugin-organize-imports as a residual TS/JS step. (a) is cleaner; (b) preserves exact current behavior.

Proposed end-state

  • oxfmt owns: JS/TS/TSX/MJS, JSON, YAML, Markdown, CSS, GraphQL, TOML.
  • tsp format owns: .tsp.
  • Prettier (residual) owns only: .astro + .sh (slim config, astro+sh plugins only).

Editor / CI follow-ups (not yet applied)

  • VS Code: install the oxc extension (oxc.oxc-vscode, oxfmt via LSP); set per-language editor.defaultFormatter (oxfmt for JS/TS/JSON/CSS/MD/YAML, TypeSpec ext for .tsp, Prettier for .astro/shell). Current shared .vscode/settings.json hardcodes esbenp.prettier-vscode.
  • CI (.github/workflows/consistency.yml): replace the prettier-plugin build + pnpm run format:check with the oxfmt/tsp/residual pipeline (oxfmt needs no build; tsp format still needs the compiler built).

Risks / open items

  1. One-time 55-file reformat lands a slightly different (but stable) JS/TS style vs Prettier.
  2. Loss of automatic unused-import removal (mitigated by oxlint no-unused-vars).
  3. .astro unsupported today — track oxc Tier-4 progress to eventually drop the Prettier residual.
  4. oxfmt is Beta (Feb 2026); pin the version and re-verify churn on upgrades.
  5. tsp format requires the compiler to be built before running (same as today's prettier-plugin build step).

Findings from a hands-on PoC that wired oxfmt/tsp format/residual-Prettier scripts side-by-side with the existing Prettier scripts (no repo-wide reformat performed).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions