Claw back time with Biome
It's about time you switched your JS/TS linter and formatter to Biome.
The Status Quo before Biome
For almost a decade, there have been two dominant tools in the JS world when it comes to linting (checking your code adheres to language standards) and formatting (reprinting your code with consistent spacing and styling): ESLint and Prettier, respectively.
ESLint was created in 2013 and shot up in popularity thanks to its ability to set project-level, pluggable rules that could govern every imaginable nuance of your Javascript codebase. Then 4 years later, James Long released Prettier (heavily influenced by js-beautify) which soon became the preferred formatter among the JS community, in large part thanks to its opinionated and low-config philosophy that helped to minimise the distraction of styling issues on PRs like tabs vs spaces, semicolons vs no semicolons etc.
The first drawback to this status quo has been long-standing incompatibility. Although in theory ESLint (a linter) and Prettier (a formatter) should have separate concerns, unfortunately in reality their compatibility is only assured as far as you, the developer, are willing and able to delicately configure your IDE to ensure they don't fight each other. Even with the eslint-plugin-prettier
plugin (designed to deconflict ESLint and Prettier), you actually can't avoid hitting some issues as it tries to co-ordinate the two systems in each source code file.
The second drawback is that they're both rather slow with large codebases. To understand why, let's consider what both ESLint and Prettier actually do. Both tools ingest your source code, attempt to parse it into sets of abstract syntax trees (AST) and detect unhappy patterns in those trees (linters) or reprint those trees back to your editor with a consistent style (formatters). The most expensive work happens in the first step: parsing your source code into AST, but in particular, when parsing your Typescript source code.
It's slow because the TypeScript parser for ESLint @typescript-eslint/parser
must first compile the source to TypeScript AST in order to scrutinize TypeScript-only language features that it wouldn't have in the transpiled JS, allowing type-aware rules, e.g., no-redundant-type-constituents
. Then, using ESTree it copies and converts (node by node) the TypeScript AST to its own AST so that it can be analysed by ESLint-compatible rules. It's either two hops, or no type linting at all. Prettier doesn't have the same TypeScript issue, because it doesn't need to understand types, but it also doesn't generate its own AST especially efficiently either. Lastly, both tools are written in Javascript, which makes sense considering their stated goals of being extensible with plugins written by the wider JS community, but means their performance gains are that much harder to win.
Biome
This status quo was disrupted in August 2023 by the v1 release of the Biome project, a highly opinionated, blazing fast formatter-and-linter for Javascript (including JSX), Typescript and JSON. Biome actually started out life as Rome, a project spun out from Meta by its creator Sebastian McKenzie under Meta's OSS initiative. But Rome was forked and given a new OSS home as Biome when that commercial venture faltered. If you haven't tried it yet, here are three reasons I would recommend sampling Biome on a branch of your JS/TS project.
Scalable
Biome zips through code at the speed of thought. Lint checking the entire MergeSimple codebase is now ~16 times faster (4 minutes -> 15 seconds) on CI than with ESLint. Biome's formatting speed benchmarks are even snappier: up to ~25 times faster than Prettier. How does it do this? As mentioned, most of the speed penalties incurred by ESLint and Prettier happen in creating, copying and converting large ASTs. Biome is written in Rust and its parser, biome_rowan
, takes full advantage of Rust's safe pointer types to avoid copying ASTs and their nodes, among some other memory efficiencies noted in the rust-analyzer
project from where biome_rowan
was forked.
Biome also feels fast because it obviates the need to re-parse unchanged parts of the code, by effectively caching most of its work using Red-Green Syntax trees. Red-Green Syntax trees were originally designed to underpin the Roslyn C# compiler, then used in Swift's libsyntax
implementation and more recently in rust-analyzer
(and now by extension, Biome). They work by representing the parsed syntax as two separate trees. The green tree works as an internal representation of concrete syntax elements like characters or expressions, but without any positional information. Each node in this tree is immutable, allowing for the sharing of nodes with the same shape. The red tree, on the other hand, is what gets exposed to the consumer of the AST (that would be our linting rules). The red tree uses the green tree to lazily reassemble complete trees and adds back in the context needed to traverse the complete AST, namely references to parent nodes and absolute offsets from the start of the source code file. This approach is much more efficient than building every node of every tree each time, because the red tree can be refreshed between code changes without rebuilding and storing a whole tree each time.
2-for-1
Mercifully, Biome moves us on from the mostly pointless distinction of having separate tools and configs for formatting and linting. They are now just two CLI commands within the same tool. For most use cases, even that distinction can be helpfully erased with macro-commands like biome check
(and biome ci
for CI environments) that perform both formatting and linting in one pass.
Ultra-Opinionated
Inheriting the low-configurability philosophy that Prettier introduced, Biome goes even further. It has just 9 distinct formatting options for Javascript at the time of writing. It's freeing to have fewer options and think about other things in your codebase that matter more. You don't even need to declare a biome.json
until you disagree with Biome's default config (I only have once so far). Biome is one of the open-source tools that goes in first for my new TS/JS projects.
👉 Setup guide at Biome