Hello team !
After many experiments and several prototypes, an initial PR was opened to lay the groundwork: meteor/meteor#14311. The current work is proposed in meteor/meteor#14751 ![]()
The goal is not simply to run the existing Node bundle with Bun. This work adds a native ESM server path and a production host that uses Bun’s network and file primitives directly, while preserving compatibility with existing Meteor packages ![]()
TLDR
This PR adds an opt-in ESM server bundle, Bun development support, a native Bun DDP WebSocket transport, and an optional Bun.serve() production host. On the T450 benchmark machine, Bun started about twice as fast, used roughly half the peak RSS, and delivered substantially higher DDP and static-file throughput on the measured workloads. The existing Node legacy path remains unchanged.
Disclaimer
As stated in the PR, this was an AI-assisted project. Codex Astra and Luna helped me brainstorm the idea, break down the work, and create the implementation plan. Gemini 3.8 then implemented and iterated on the core code. In parallel, Luna created the plan for a separate application used to test regressions and performance, and Claude Fable implemented that Kitchen Sink test application together with its regression and benchmark harness ![]()
I deliberately used different models for the core codebase and for the test application, so that the implementation was checked against an independent application rather than only against its own assumptions. I made the technical decisions with Codex, including whether and where to use Bun-specific APIs ![]()
The Bun port is deliberately a port
I considered using this work as an opportunity to refactor Meteor’s foundations, but I kept this PR focused on the Bun port. For transparency, my internal telemetry estimates roughly 273 million Gemini tokens, 42 million Claude tokens excluding repeated cached context (about 2.5 billion when that replay is counted), and 22 million Codex tokens, over six calendar days from September 8 to September 13. These figures describe model-context processing; they are not a benchmark of engineering effort or a general measure of cost
I also ran a separate, unvalidated experiment asking an AI system to recreate Meteor with Bun from scratch. It produced a modern prototype in about an afternoon, with features such as offline support, Capacitor, PWA, MCP, and multiple databases. That is a separate direction to explore, not a claim that the prototype is production-ready or equivalent to Meteor. The difficult part is no longer generating code; it is validating the result with human review and real applications ![]()
What is added
meteor build --format=esmproduces a standard ESM server bundle that can run on Node or Bun.meteor run --runtime=bunenables development under Bun, including reloads and IPC between the runner and the server process.- DDP uses a WebSocket transport instead of SockJS under Bun. Under Node, SockJS remains the default transport, while uWebSockets.js remains available where its native Node addon is supported and can be selected explicitly.
bun-host.mjsprovides an optional production host based onBun.serve().
What the native Bun host does
Bun.serve() listens directly on the public port and routes requests according to their type:
- Static files are served with
Bun.file()from the Meteor manifests, with ETags and immutable caching. Assets therefore do not need to pass through the Express stack. - DDP WebSocket connections are accepted directly by Bun.
server.upgrade(req)is Bun’s API for turning an HTTP request into a WebSocket connection. The connection is then handed to Meteor’sStreamServer, the internal component that handles the DDP protocol: handshakes, method calls, publications, and messages sent to the client. In this mode, the DDP path does not go through SockJS. - Dynamic routes, SSR, and Express/Connect middleware remain compatible with the Meteor ecosystem. They are forwarded to the WebApp server through a local Unix socket.
- This forwarding uses Bun’s native
fetch(Bun.fetch) with the{ unix: socketPath }option, preserving request bodies and streaming responses.
This separation makes it possible to use Bun’s native primitives where they provide a real benefit, without rewriting the existing Meteor middleware stack ![]()
Initial numbers
An in-depth benchmark was run on a ThinkPad T450 (i7-5600U, 4 cores, 15 GB RAM), my second machine, with very few other programs running. The same application, MongoDB instance, and workloads were used for Node legacy, Node ESM, Bun ESM, and bun-host
For startup time and memory, lower is better. For throughput, higher is better ![]()
| Metric | Node legacy | Node ESM | Bun ESM | Bun bun-host |
|---|---|---|---|---|
| Cold startup (ms, lower is better) | 2,085 | 2,096 | 1,075 | 1,084 |
| Peak RSS (MB, lower is better) | 494 | 581 | 246 | 222 |
| DDP method throughput at concurrency 100 (req/s, higher is better) | 14,896 | 11,761 | 23,100 | 21,526 |
| 2FA throughput (ops/s, higher is better) | 39,518 | 46,141 | 73,067 | 107,153 |
| Static HTTP throughput at concurrency 1 (req/s, higher is better) | ~595 | ~601 | 760 | 4,381 |
These figures are indicative: they apply to this machine, these versions, and these workloads. They are not a general performance guarantee. The complete benchmark and its methodology are included in the PR so that others can run it themselves; an in-depth pass takes several hours, approximately six hours on this T450.
Compatibility and validation
Kitchen Sink is an independent Meteor test application designed to verify the observable behavior of a real application. It contains 238 probes and eight profiles covering Node legacy, Node ESM, Bun ESM, development under Bun, and the native Bun host.
The executable profiles pass with newFailures = 0, meaning that no probe which passed on the Node reference becomes red on the profile under test. The two known Blaze failures (#512 and #514) were already present before this port: they reproduce on the Node reference and are unrelated to Bun.
Legacy Node/CommonJS behavior remains unchanged. The ESM/Bun path is opt-in, allowing gradual adoption and direct comparisons with the current behavior.
Current limitations
- The ESM bundle and
bun-hostare new and still need to be exercised against more Meteor applications and Atmosphere packages. - Packages using Node-specific native addons may require a separate audit.
- Performance figures should be reproduced on other machines and workloads before drawing general conclusions.
The PR is here: meteor/meteor#14751.
I would be interested in feedback on:
- which applications and packages should be tested first under Bun;
- which Node or Meteor APIs still cause problems;
- which workloads should be added to the benchmark;