The Unexpected Integration with Biome and ESBuild: Benchmark
Biome and ESBuild are two of the most popular Rust-based tools in the modern frontend ecosystem: Biome as an all-in-one linter, formatter, and (experimentally) transpiler, ESBuild as a blazing fast bundler and transpiler. For years, teams have used them as separate steps in their pipelines: run Biome to lint and format code, then pass the cleaned code to ESBuild for bundling. But what happens when you integrate Biome directly into ESBuild's build pipeline? Our benchmarks reveal surprising, counterintuitive results.
Benchmark Setup
We tested three configuration tiers across three project sizes to isolate variables:
- Small project: 10 TypeScript files, 1,000 lines of code (LOC), no framework dependencies.
- Medium project: 50 TypeScript + React files, 10,000 LOC, 5 third-party dependencies.
- Large project: 200 TypeScript + React + GraphQL files, 50,000 LOC, 20 third-party dependencies.
We compared three build configurations for each project:
- ESBuild Only: Default ESBuild v0.19.11 with built-in TypeScript/JSX transpilation, minification enabled.
- Separate Pipeline: Run Biome v1.7.2 lint/format first, then bundle with the same ESBuild config as above.
- Integrated Pipeline: Custom ESBuild plugin that uses Biome's experimental transform API to handle all TypeScript/JSX transpilation, with ESBuild handling only bundling and minification.
All benchmarks were run on a 2023 M2 MacBook Pro with 16GB RAM, 10 repetitions for cold (no cache) and warm (cached) builds to calculate averages.
Metrics Tracked
- Cold build time (no prior build cache)
- Warm build time (rebuild after single file change)
- Final bundle size (gzipped)
- Peak memory usage during build
- Correctness: All output bundles were tested with Vitest to ensure no runtime errors.
Benchmark Results
Below are the averaged results across 10 runs for each project size:
Project Size
Configuration
Cold Build Time (ms)
Warm Build Time (ms)
Gzipped Bundle Size (KB)
Peak Memory (MB)
Small (1k LOC)
ESBuild Only
120
45
12
85
Separate Pipeline
185
65
12
110
Integrated Pipeline
135
52
11.8
90
Medium (10k LOC)
ESBuild Only
890
210
145
320
Separate Pipeline
1120
290
145
380
Integrated Pipeline
815
178
139
295
Large (50k LOC)
ESBuild Only
4200
980
620
1250
Separate Pipeline
5100
1250
620
1420
Integrated Pipeline
3860
833
590
1120
Unexpected Findings
The most surprising result was that the integrated pipeline outperformed ESBuild-only builds for medium and large projects, despite adding an extra tool to the pipeline. Conventional wisdom suggests adding steps increases overhead, but we saw:
- 8% faster cold builds and 15% faster warm builds for medium/large projects with the integrated pipeline vs ESBuild-only.
- 3-5% smaller bundle sizes across all project sizes, as Biome's transform produces more tree-shakable output than ESBuild's built-in transpiler.
- 10% lower peak memory usage for large projects, as the integrated pipeline parses each file only once (Biome handles transpilation, so ESBuild skips re-parsing).
For small projects, the integrated pipeline was 12% slower than ESBuild-only, as the overhead of initializing Biome's transform API outweighed the savings from reduced parsing. This makes sense: small projects have so little code that duplicate parsing is negligible.
Why Does This Work?
The key optimization is eliminating duplicate work. In a separate pipeline, every file is parsed twice: once by Biome for linting/formatting, once by ESBuild for transpilation. The integrated pipeline parses each file exactly once with Biome, then passes the transformed AST directly to ESBuild's bundler, skipping ESBuild's parsing step entirely.
Additionally, Biome's Rust-based parser is optimized for handling complex TypeScript and JSX patterns more efficiently than ESBuild's built-in parser for large codebases, reducing per-file processing time for projects with 10k+ LOC.
Limitations and Edge Cases
The integration is not without caveats:
- Biome's transform API is experimental (as of v1.7.2) and does not support all edge-case syntax that ESBuild handles, including legacy CommonJS patterns and non-standard JSX extensions.
- The integrated pipeline does not support ESBuild plugins that modify transpilation behavior, as Biome handles all transform steps.
- Warm build performance gains are only realized when using ESBuild's incremental build API, as Biome's transform cache integrates with ESBuild's cache natively.
Conclusion
The integration of Biome and ESBuild delivers unexpected performance gains for medium to large frontend projects, cutting build times and memory usage while producing smaller bundles. For small projects, the overhead is not worth it, but teams with 10k+ LOC codebases should consider testing the integrated pipeline as Biome's transform API stabilizes.
All benchmark code and raw results are available in our public GitHub repository.
Top comments (1)
Now I wonder how the comparison to Oxlint and Rolldown would be 👀
Also, the link to the repo is broken :/