Ask
88

biome or keep eslint+prettier when lint takes 4m12s on 180k lines Solved

Monorepo, 11 packages, about 180k lines of TypeScript. turbo run lint takes 4m12s on a cold cache and it is now the longest thing in the pipeline. Format check is another 40s on top.

Biome is obviously fast. My hesitation is rule coverage - we lean on a handful of type-aware rules (no-floating-promises being the main one, plus the exhaustive-deps rule for hooks and an import cycle check) and my understanding is those need type information that a non-TypeScript-based linter does not have.

So the options seem to be: migrate fully and lose some rules, run both and add complexity, or stay put and fix the speed some other way.

Anyone actually done this on a repo this size? What did you lose?

8 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @whetstone_wu · 3w ago · 3 replies

    Ran this migration on a comparable repo (~150k lines). The answer that worked was: Biome for formatting and the syntactic rules, a much smaller ESLint config for the type-aware ones only.

    What that looks like in practice:

    • Biome replaces Prettier entirely. Format check went from 40s to about 3s. This part is free and you should do it regardless of what you decide about linting.
    • Biome takes over the several hundred rules that need no type information. Our full Biome check is around 9 seconds on the whole repo.
    • ESLint stays, but with parserOptions.project and a rule list of maybe six entries - the floating promises one, the misused promises one, and a couple of internal rules. It is still the slow one, roughly 90 seconds, but it went from 4m12s to that because it is no longer parsing everything for rules that did not need types.

    biome migrate eslint --write translates a lot of your existing config automatically and tells you what it could not map, which is a useful inventory of exactly what you are giving up.

    Things we genuinely lost: a few niche plugin rules with no equivalent, and the ecosystem of one-off plugins for specific libraries. Nobody has missed them in eight months.

    76
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @petra_lindqvist · 3w ago

      "Biome for format immediately, decide about lint separately" is a much better framing than the all-or-nothing one I had in my head. The format change alone is a no-brainer.

      22
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @packlist_pen · 3w ago

      Warning from doing this: the reformat commit touches every file and destroys git blame for a while. Land it as a single isolated commit and add its SHA to .git-blame-ignore-revs the same day, or you will be answering "why did you change this line" for months.

      17
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @kraut_corner · 3w ago · 2 replies

    Before you migrate anything, find out where the 4 minutes goes, because you might be fixing the wrong problem.

    TIMING=1 eslint . --ext .ts,.tsx
    

    prints the ten slowest rules with percentages. If one type-aware rule is 60% of the time, you have a targeted fix rather than a migration.

    Then check the cheap wins:

    • --cache with the cache file in a location Turbo does not blow away between runs.
    • lint as a per-package turbo task so it caches per package and unchanged packages cost nothing. If you are running one lint task at the repo root you are re-linting 11 packages to check a change in one.
    • make sure parserOptions.project is not pointed at a tsconfig that includes test fixtures or generated files.

    We got from 3m40s to about 25s without changing tools, purely from per-package caching. Then we moved to Biome anyway, but at least we did it because we wanted to and not under duress.

    52
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @dartfit_dana · 3w ago

      The per-package caching point is the big one and it is free. Most people write one root lint script out of habit and then wonder why their build tool's caching does nothing for it. Turbo can only skip work you have described as separable.

      24
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @durable_ines · 3w ago

    Concrete list of what you cannot currently get without type information, so you can decide whether you care:

    • floating promises / unhandled rejections
    • promise passed where a void callback is expected
    • unsafe any propagation rules
    • anything that needs to know whether a type is nullable

    Everything else you probably use - unused vars, import ordering, hook dependency arrays, accessibility rules on JSX - is syntactic and has an equivalent.

    My honest read: the floating promise rule is worth keeping ESLint around for on its own. It catches real production bugs, not style opinions.

    34
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @oncall_omar · 3w ago

    One thing to decide up front is whether lint blocks the merge or just annotates it. We moved the slow type-aware pass to a scheduled run against main and kept only the fast checks on PRs. Fewer people waiting on a queue, and the rules still catch things within a few hours.

    16
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @listening_lane · 3w ago

    4m12s of linting on every push is a lot of collective staring at a spinner. Whatever you pick, that maths alone justifies an afternoon.

    7
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report