All Articles
CategoryFull-Stack
Reading Time
13 min read
Published
2026-10-02
Word Count
3,208words

Grab a coffee — this one is a deep dive!

Bun 1.4: From Zig to Rust — What Actually Changed?

Summary

Bun 1.4's headline change is the Rust migration: Node.js compatibility numbers, Bun.WebView, Bun.cron() and real sources — when should you pick Bun, and when Node?

  • Bun 1.4 is the first release to move the core codebase from Zig to Rust; it now passes +1,517 new tests from the Node.js v26.3.0 test suite.
  • Bun.Image, Bun.WebView, Bun.markdown, Bun.cron() and Bun.Terminal built 15 npm dependencies directly into the runtime.
  • Idle CPU dropped 5×; memory usage dropped by up to 35% in the overall summary, and 13%-48% for HTTP servers — all Bun's own measurement.
  • Bun itself says it is 'not 100% compatible with Node.js yet'; security-critical modules like node:tls have a lower pass rate (85%).
Bun 1.4: From Zig to Rust — What Actually Changed?

Bun 1.4 is the first release to move the core codebase from Zig to Rust — one of the biggest JavaScript runtime rewrites in recent years. The team sums it up directly in the announcement: "And it rewrites Bun from Zig to Rust." In this piece we look, source by source, at the scope of the Bun 1.4 Rust migration, the Node.js compatibility numbers, new APIs like Bun.WebView and Bun.cron(), the measured performance gains, and which job you should pick Bun for.

💡 Pro Tip: Before you go to production, pin a fixed version with bun --version; the 1.4.x line is still getting frequent patches, so never run the canary build in prod.

Table of Contents

Bun's own phrasing: "Zig to Rust" — scope and limits

Bun's official announcement (bun.com/blog/bun-v1.4) sums up the migration in a single sentence; the technical background of the port is covered in a separate post, bun.com/blog/bun-in-rust. Claude Code v2.1.181 (June 17, 2026) and later already use this Rust port. According to the same post, the migration covers 535,496 lines of Zig code excluding comments — that exact figure is Bun's own measurement.

One caveat worth noting: a generalization like "the entire runtime is now Rust" goes beyond the source's own statement.

Bun's own wording is "migration from Zig to Rust" — a specific scope. At FFI boundaries and in some low-level layers, unsafe Rust blocks and C++ bindings are still needed; by Bun's own measurement, about 4% of the Rust code sits inside an unsafe block (~27,000 lines out of ~780,000), and 78% of those blocks are single-line — a pointer from C++, or one call into a C library. The default binary is unambiguous, though: Bun v1.4.0 is the first Bun release written in Rust. Read this as complete but still-maturing, and track it via npm's dist-tags.latest.

The process-side numbers come straight from Bun's own writeup: the migration took 11 days, the bug-fixing-compiler-errors phase was split across 64 Claude instances, and the pre-merge cost, at API pricing, came to roughly $165,000. The same post says the rewrite introduced 19 known regressions, and that every one of them has been fixed.

Release timeline

The "when, in which release" question for the rewrite can be verified one-to-one via the GitHub Releases API and the npm registry:

Version
Date
Note
v1.3.14
May 13, 2026
Last major Zig-based release before the rewrite
v1.4.0
August 20, 2026 (GitHub: 2026-08-20T14:07:21Z)
First official Rust-based release
v1.4.2
September 5, 2026 (GitHub: 2026-09-05T05:55:48Z)
Current stable (as of September 23, 2026)

The 99-day gap between v1.3.14 and v1.4.0 — computed directly from the two releases' GitHub Releases dates — is indirect evidence that the rewrite's scope was not a small job. To confirm which version is currently considered "stable" in your own environment, you can query the npm registry directly:

bash
1# npm dist-tags — separate the latest and canary tags
2curl -s https://registry.npmjs.org/-/package/bun/dist-tags

Node.js compatibility, in numbers

Bun 1.4 is run against Node.js v26.3.0's own test suite and now passes +1,517 new tests — the biggest jump in a single release since Bun 1.0. Over the release cycle (1.3 → 1.4), more than 2,900 issues were closed in total. Don't confuse that with the 128 bugs the Rust rewrite itself fixed: the 2,900+ figure is the release announcement's overall issue-closure count, while 128 is the number, given in the "bun-in-rust" post, of bugs that were reproducible one-to-one on v1.3.14.

Pass rates by module:

Node.js module
Passing / Total tests
node:quic
235 / 237
node:http (rewritten)
403 / 415
node:fs
349 / 358
node:tls
191 / 224
node:sqlite
18 / 18 (100%)

Some modules pass 97-100% of Node's own test set. But Bun itself is clear: "Bun is not 100% compatible with Node.js yet." That limit is the basis for the "which job needs Bun, which needs Node" advice at the end of this piece — there's still risk, especially for codebases that depend on native addons or need rarely-used API surfaces.

The node:tls row stands out: at 191/224, roughly 85%, it's the module with the lowest pass rate. In a security-critical layer like TLS/certificate validation, that gap matters — if you run mTLS or a custom certificate chain, test this module specifically before moving to Bun. node:quic and node:fs sit above 97%, so the risk is lower for everyday web-service scenarios (filesystem operations, HTTP/3).

Bun.WebView: does it replace Playwright?

Introduced in v1.3.12 and expanded in v1.4.0, Bun.WebView is described in the official announcement as "headless browser automation built into Bun, without Puppeteer or Playwright." It works with real user input (clicks, scroll); on macOS it uses the system WebKit, and on all three platforms it can drive an installed Chrome/Chromium/Edge over CDP (Chrome DevTools Protocol). There's also a .cdp() escape hatch for advanced scenarios.

So does this replace Playwright in your test infrastructure? The source is clear: the same post notes, separately, that Playwright also runs on Bun. So Bun.WebView isn't a "Playwright is retired" mandate — it's a built-in, dependency-free alternative. Drop the puppeteer/playwright dependency entirely for simple smoke tests or scraping; for comprehensive E2E matrices needing multiple browser engines (Firefox/WebKit), Playwright is still more mature. Check the official Bun docs for the full API surface (method signatures, option objects).

Bun.Image / Bun.markdown / Bun.Terminal

Three built-in APIs added back-to-back during the 1.4 cycle directly shrink your dependency list:

API
Introduced in
What it does
Note
Bun.Image
v1.3.14
sharp-like image processing, no native addon required
1.38× faster than sharp on 1080p PNG→400×400 JPEG, 1.19× on JPEG→WebP
Bun.markdown
v1.3.8
GFM (GitHub Flavored Markdown) rendering
HTML output is not auto-sanitized
Bun.Terminal
v1.3.5
Native PTY (pseudo-terminal), no node-pty
Works on macOS, Linux, Windows

The sanitization note on Bun.markdown matters: if you render Bun.markdown output directly to the page from user-supplied Markdown, you need a separate sanitization layer (for example something like DOMPurify) against XSS — that's a security layer the tool itself doesn't provide.

What these three APIs share is that they eliminate an entire class of CI problem: packages with native addons like sharp are notorious for build failures across OS/architecture combinations (especially Alpine-based Docker images) — glibc/musl mismatches, missing prebuilds, and other classic breakage. Because Bun.Image provides this functionality without a native addon build, that failure class disappears at the source. The same logic applies to node-pty (Bun.Terminal) and node-cron (Bun.cron, below): all three traditionally required a platform-specific build step.

Bun.cron(): a scheduled-job layer

Bun.cron() registers scheduled jobs at the operating-system level — meaning it integrates with crontab on Linux, launchd on macOS, and Task Scheduler on Windows in the background. The signature design is a deliberate choice: it works the same way as Cloudflare Workers' scheduled(controller) signature for Cron Triggers, so a team coming from Workers has almost no learning curve. According to the source these are two distinct modes: when you pass a file, as in the example below, the job is registered with the operating system; when you pass a function instead, the job runs on the event loop with no system cron involved. The source also states that jobs never overlap — that holds for both modes: a second run of the same job isn't triggered before the previous one finishes.

ts
1// Bun.cron() — same scheduled(controller) signature as Cloudflare Workers Cron Triggers
2export default {
3 async scheduled(controller: { cron: string }) {
4 if (controller.cron === "0 3 * * *") {
5 await runNightlyBackup();
6 }
7 },
8};

This removes a separate npm dependency like node-cron — one less package in your dependency graph means one less risk surface in the supply chain.

Registering at the OS level points to an important difference from the traditional node-cron approach: node-cron is a scheduler that runs inside the process itself — if the process crashes or restarts, the job that was supposed to fire at that moment can be lost. Bun.cron(), by contrast, hands registration off to the operating system's own scheduler when you pass a file, rather than keeping it inside the process; in the function form the job stays in-process on the event loop, as noted above.

Idle CPU, RAM and startup-time gains

The most concrete promise of the Rust rewrite is performance. The table below collects the figures from Bun's official announcement — all first-party evidence from Bun itself; there's no guarantee you'll see the same result on your own workload, so plan against your own measurement, not this table.

Metric
Before → After
Source note
Idle CPU ("hello world" micro-benchmark)
Dropped 5×
Bun's own measurement
Claude Code prod CPU (p99)
24% → 10%
Bun's own measurement, a real production example
Claude Code prod CPU (p50)
5.8% → 2.5% (2×)
Bun's own measurement
HTTP server memory usage
Dropped 13%-48% (varies by framework)
Bun's own measurement
Startup time (Windows)
2.5× faster
Bun's own measurement
Startup time (Linux)
2× faster, memory less than half
Bun's own measurement
Binary size
Shrunk by up to 17%
Bun's own measurement

The Claude Code example matters because it's a real production workload — but it's still single-sourced (Bun itself). To confirm these numbers on your own workload, measure the same scenario with both old and new versions on your own environment; this piece doesn't hand you a ready-made benchmark, it shows which command to run: in the same project, run time bun run start first on the old version, then on 1.4, and compare on your own machine.

Don't conflate the two figures: the "5× idle CPU reduction" is a micro-benchmark ("hello world" level), while the 24%→10% and 5.8%→2.5% Claude Code figures are for a real, complex production app. They represent different measurement conditions — your own service will likely land somewhere between the two, so don't plan production around a "5× speedup" expectation; measure in staging with your own traffic.

Which job needs Bun, which needs Node

On the tooling side, 15 npm dependencies were absorbed into Bun over the 1.4 cycle: sharp, puppeteer, marked, node-cron, node-pty, concurrently, npm-run-all, serve-static, json5, fast-xml-parser, tar, string-width, slice-ansi, cli-truncate, wrap-ansi. In practice this means the bun run --parallel command now stands in for npm-run-all/concurrently.

bash
1# instead of concurrently or npm-run-all
2bun run --parallel build:web build:api build:worker

bun:ffi now runs JSC-native instead of through TinyCC, and is 3× faster according to official measurements. Native stream implementations pass 100% of the WPT (Web Platform Tests) suite; for download scenarios, Bun's own measurement shows ~7.5× the throughput versus Node — that last figure is also Bun's first-party measurement, so don't carry it over as absolute truth without confirming it on your own traffic.

The practical upshot is a smaller devDependencies list in package.json and shorter node_modules install time — this translates directly into build time, especially for CI pipelines that install from scratch every build. But capturing the gain requires moving code that calls these packages' APIs over to Bun's built-in equivalent — not an automatic drop-in, a deliberate import change.

Read this table together with the Node.js compatibility limits (the section above):

Scenario
Recommendation
New/mid-size project, you want built-in tooling
Bun
Prod environment where CPU/RAM is critical, your framework supports Bun
Bun (but confirm with your own measurement)
Large/legacy codebase that depends on native addons
Node
Critical infrastructure that needs 100% Node API surface
Node
Multi-browser (Firefox/WebKit) E2E test matrix with Playwright
Node + Playwright, Bun.WebView can complement it

GOLDEN TIP

The most valuable insight in this article

This tip holds the article's most important takeaway.

Easter Egg

You found a hidden gem!

There's a hidden detail in this section. Want to uncover it?

Reader Reward

For readers who made it to the end of this article, we've put together a short checklist to help you plan your Bun 1.4 migration safely. If you found the right keyword in the Hidden Feature section, you can use the list below directly as a migration checklist.

FAQ

What changed in Bun 1.4?

Bun 1.4 arrived with the Zig codebase moved to Rust; it started passing +1,517 new tests from the Node.js v26.3.0 test suite, and more than 2,900 issues were closed over the release cycle. New built-in APIs were added, including Bun.Image, Bun.WebView, Bun.markdown, Bun.cron() and Bun.Terminal. Idle CPU dropped 5×; memory usage dropped by up to 35% in the announcement's overall summary, and by 13%-48% in apps running HTTP servers — these figures are Bun's own measurement.

Is Bun really written in Rust?

Yes — in the source's own words: "And it rewrites Bun from Zig to Rust." An 11-day migration converted 535,496 lines of Zig code to Rust. About 4% of the resulting Rust codebase is still inside unsafe blocks (~27,000 lines out of ~780,000), and C++ bindings remain at FFI boundaries; so a generalization like "every line is safe Rust" would be a claim that goes beyond the source's own statement.

Is Bun 1.4 compatible with Node.js 26?

Bun 1.4 is validated against Node.js v26.3.0's test suite; some modules like node:sqlite hit 100%, while modules like node:http/node:fs sit above 95%. But Node.js 26 is still in "Current" status as of September 23, 2026; its move to LTS is on a separate timeline. So saying "LTS-compatible" is premature — "compatible with Node 26's Current test suite" is the more accurate phrasing.

Are Bun's cron and markdown APIs production-ready?

These APIs were newly introduced in the 1.4 cycle, so they don't yet have a long production track record. The cautious path: test on a pinned version in staging first, then promote to prod.

Does Bun.WebView fully replace Puppeteer and Playwright?

No. Bun.WebView offers a dependency-free, built-in option for headless browser automation, but the same source notes that Playwright continues to run on Bun as well. It can be used to cut dependencies for simple smoke-test scenarios; for comprehensive multi-browser E2E matrices, Playwright is still more mature.

How should I migrate an existing Node.js project to Bun 1.4?

Work through it in order: first scan your codebase for use of modules with a low pass rate, like node:tls; then evaluate dropping packages now built into Bun — sharp/puppeteer/node-cron/node-pty/concurrently — from your dependency list. Next, run both your existing Node target and the new Bun target in parallel in CI, paying particular attention to code paths that use node:tls and native addons. Wait a week on a pinned stable version in staging, and if nothing breaks, shift production traffic over gradually. None of these steps requires a "replace everything at once" approach — Bun and Node can be tested side by side on the same package.json for a while.

Conclusion

Bun 1.4 pushes Node.js compatibility forward with concrete numbers alongside its "Zig to Rust" migration, and shrinks the npm dependency graph with built-in APIs like Bun.Image/Bun.WebView/Bun.markdown/Bun.cron()/Bun.Terminal. But the work isn't done: by the Bun team's own account, they're continuing to reduce unsafe usage and move the code closer to idiomatic Rust, the CPU/RAM figures are first-party measurements, and some APIs are new and unproven. The practical path: test on a pinned version in staging, keep a parallel Bun+Node matrix in CI, and roll out to production gradually.

If you've already been through a similar transition on the Node.js runtime side, Next.js 16.3 drops runtime='edge': the return to Node shows how runtime decisions can be reversed. For the ORM-side counterpart of a move to a Rust engine, read Migrating Prisma 6 to 7: the Rust engine is gone, what changed? — you'll see the same "fast, but comes with breakage risk" pattern in a different layer. For a parallel story on the compiler side, TypeScript 7 ships: 10x faster, but ESLint breaks is useful. If you're also revisiting your build tooling, Migrating from Webpack to Vite gives a step-by-step migration plan; and if you want to stay current in the Next.js ecosystem, Next.js 16.3 Instant Navigations and Cache Components guide also covers that release's other major changes.

Sources

Tags

#Bun 1.4#Rust#Node.js#JavaScript Runtime#Bun.cron#Bun.WebView#Performance
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share