28 janvier 2025
Bun: A Revolutionary All-in-One JavaScript Tool?
6 minutes de lecture
Bun is a JavaScript runtime (and much more) whose primary focus is its performance. Released in 2021, it has continued to gain popularity and become a serious competitor to the main runtimes already on the market: Node and Deno. The recent version 1.2 adds a set of features and performances, bringing Bun closer to a 1:1 parity with what Node offers.
Bun: More Than Just a Runtime
Beyond being a runtime, Bun offers a set of tools that replace some big names already in place in the ecosystem.
A Package Manager
You're probably familiar with npm, yarn, and PNPM— the most widely used package managers in the JavaScript ecosystem. Each has its benefits and drawbacks: speed, support for monorepos, disk space optimization. Bun offers a package manager between 10 and 25x faster than its competitors. Like its rivals, it supports monorepos, optimizes disk space, and package patching is possible.
Migrating to Bun
Migrating to Bun can be somewhat complex depending on the package manager you use. A command is available for migration, but it requires an up-to-date package-lock.json
, so the migration from Yarn or PNPM can be more complicated.
# NPM
bun pm migrate
# Yarn
npx synp --source-file yarn.lock
npm i --lockfile-version 3 --frozen-lockfile
bun pm migrate
# PNPM
npm i --package-lock-only
bun pm migrate
A Bundler
With its integrated bundler, Bun is a serious competitor to Webpack, Rspack, and Vite. Bun's bundler's API is almost identical to that of ESBuild, and its performance is also comparable, with a slight edge for Bun. Therefore, migration from ESBuild to Bun is straightforward. The bundler also integrates the creation of executables, including the Bun binary in the output file.
A Test Runner
Bun integrates its own test runner, thus competing with Jest, Vitest, or the test runner built into Node. In terms of performance, compared to Jest, the numbers are somewhat staggering: Bun can perform a suite of 266 SSR tests with React faster than Jest would display its version number. In terms of figures, Bun is 100x faster than Jest and 10x faster than Vitest. Bun has the advantage of not requiring any configuration: it's ready to use.
The Performance Aspect
A significant part of Bun's communication focuses on performance, far surpassing that of its competitors, whether for the runtime or the package manager. While this is true for the latter, benchmarks indicate some variation when it comes to the runtime, and depending on use cases, Bun may or may not have an advantage.
- Comparison of Bun with Node: bun-vs-node-benchmarks.pages.dev
- Generating QR Code with Node vs Bun: Is Bun really much faster than Node.js ?
At present, the package manager part is increasingly being adopted across the most popular frameworks in the JS ecosystem (NextJS, React-Native). As for the runtime, it's still too early to say whether Bun is, at present, a go-to for large-scale applications. To put it frankly, this really depends on the use case, and it's important not to rush when launching a project.
Some benchmarks:
Additional Features
At the end of the day, Bun’s API aims to be compatible with that of Node. The 1.2 version improves this goal, making Bun 90% compatible with Node's APIs, which covers the majority of the most used APIs. As it stands, Bun can run most Node applications without modifications. Bun also offers additional APIs, but this implies incompatibility with Node. Once these APIs are implemented, switching back to Node would imply a significant migration project. Indeed, this requires writing imports specific to Bun, thus making the use of Node impossible, since these imports are only recognized by Bun.
- S3 support: Bun provides an API that allows communication with all S3-type storage providers (AWS S3, Cloudflare R2, MinIO, etc.). It's also possible to use the
s3://
protocol during afetch
request or when instantiating a file withBun.file
. - PostgreSQL driver: Bun integrates compatibility with PostgreSQL databases without using the Postgres library. In terms of performance, Bun is 50% faster than the competition. MySQL support will arrive in a future version.
- C compiler: Bun integrates a C file compiler, allowing C code to run from JavaScript. This can be useful when implementing low-level features involving the N-API add-on. There's no longer any need to go through tools like node-gyp, whose installation is often complicated due to its dependency on Python.
- TypeScript transpiler: a type transpiler is integrated into Bun, allowing runtime transpilation from TypeScript to JavaScript.
- FileSystemRouter: a similar API to what Next.JS offers to maneger routing based on file system arborecence.
HTTP Server
Bun's API for creating an HTTP server is quite interesting to use. Where a traditional Node server requires manually managing route matching, Bun offers an API that includes static routes. Thus, a specific URL can be associated with a response, such as JSON or simply an HTML file.
import { serve } from 'bun'
import home from './home.html'
serve({
static: {
'/': home,
'json': Response.json({ message: 'Hello World' })
}
})
You can also handle matching manually:
import { serve } from 'bun'
import home from './home.html'
import notFound from './404.html'
serve({
fetch(request, response) {
if (request.url === '/') {
return home
}
if (request.url === "/json") {
return Response.json({ message: 'Hello World' })
}
return notFound
}
})
In both cases, Bun also takes care of HTML bundling by including the JS and CSS files in the HTML, then transforming the source attributes of the tags.
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./app.tsx"></script>
</body>
</html>
The file served by Bun will be:
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<link rel="stylesheet" href="./index-[hash].css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./index-[hash].js"></script>
</body>
</html>
For comparison, here's what an implementation might look like in Node (without library):
import http from 'http'
import fs from 'fs'
const server = http.createServer((req, res) => {
if (req.url === '/') {
fs.readFile('./home.html', (err, data) => {
if (err) {
res.writeHead(404)
res.end()
} else {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(data)
}
})
} else if (req.url === '/json') {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ message: 'Hello World' }))
} else {
fs.readFile('./404.html', (err, data) => {
if (err) {
res.writeHead(404)
res.end()
} else {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end(data)
}
})
}
})
server.listen(3000)
Should You Adopt Bun?
Bun not only offers a very complete API but also powerful tools to replace those well-established in the JavaScript ecosystem. However, it's important not to rush: using Bun requires understanding its implications and weighing the pros and cons. Bun may not solve every problem, and in my opinion, it's still too early to use 100% of the content Bun offers.
Currently, its function as a package manager is very pleasing, saving significant time when installing dependencies, whether locally or in a CI environment.
The use of the runtime could be interesting for small scripts that aren't too complex, but I wouldn't currently risk using it on larger-scale applications. However, it's still interesting following the development of Bun, which is already a major player in the JavaScript ecosystem.