curl -fsSL https://bun.sh/install | bash
npm install -g bun
powershell -c "irm bun.sh/install.ps1|iex"
scoop install bun
brew tap oven-sh/bun
brew install bun
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
bun upgrade
Bun's REPL has been completely rewritten in Zig, replacing the previous third-party npm package. The new REPL starts instantly without downloading any packages, and includes a full-featured terminal UI.
In the next version of Bun
Bun gets a native REPL pic.twitter.com/RLtaUymgWu
Features:
Copy to clipboard - .copy command copies the expression to clipboardTop-level await - you can use it.ESM import & require - all the ways to load modules just work.Syntax highlighting — JavaScript code is colorized as you type.Line editing with Emacs keybindings — Ctrl+A/E to jump to start/end of line, Ctrl+K/U to kill to end/start, Ctrl+W to delete word backward, Ctrl+L to clear screen, and arrow key navigation.Persistent history — Command history is saved to ~/.bun_repl_history and navigable with Up/Down arrows or Ctrl+P/N.Tab completion — Complete object properties and REPL commands.Multi-line input — Automatic continuation detection for incomplete expressions.REPL commands — .help, .exit, .clear, .load, .save, .editor.Special variables — _ holds the last expression result, _error holds the last error.Proper REPL semantics — const and let declarations are hoisted to var for persistence across lines, top-level await works out of the box, import statements are converted to dynamic imports, and object literals like { a: 1 } are detected without needing parentheses:> const x = 42
> x + 1
43
> await fetch("https://example.com").then(r => r.status)
200
> import { readFile } from "fs/promises"
> { name: "bun", version: Bun.version }
{ name: "bun", version: "1.3.1" }
You can now use bun build --compile --target=browser to produce self-contained HTML files with all JavaScript, CSS, and assets inlined directly into the output. This supports TypeScript, JSX, React, CSS, ESM, CJS, and everything else Bun's bundler already supports.
This is useful for distributing .html files that work via file:http:// URLs without needing a web server or worrying about CORS restrictions.
API:
await Bun.build({
entrypoints: ["./index.html"],
target: "browser",
compile: true,
});
All entrypoints must be .html files. Cannot be used with --splitting.
TC39 Standard ES DecoratorsBun's transpiler now fully supports TC39 stage-3 standard ES decorators — the non-legacy variant used when experimentalDecorators is not enabled in your tsconfig.json.
This has been one of the most requested features since 2023. Previously, Bun only supported legacy/experimental TypeScript decorators, which meant code using the modern decorator spec — including the accessor keyword, decorator metadata via Symbol.metadata, and the ClassMethodDecoratorContext/ClassFieldDecoratorContext APIs — would either fail to parse or produce incorrect results.
Now, all of the following work correctly:
function logged(originalMethod: any, context: ClassMethodDecoratorContext) {
const name = String(context.name);
return function (this: any, ...args: any[]) {
console.log(`Entering ${name}`);
const result = originalMethod.call(this, ...args);
console.log(`Exiting ${name}`);
return result;
};
}
class Example {
@logged
greet(name: string) {
console.log(`Hello, ${name}!`);
}
}
new Example().greet("world");
// Entering greet
// Hello, world!
// Exiting greet
Auto-accessors with the accessor keyword are now supported, including on private fields:
import { Signal } from "signal-polyfill";
function signal(target: any) {
const { get } = target;
return {
get() {
return get.call(this).get();
},
set(value: any) {
get.call(this).set(value);
},
init(value: any) {
return new Signal.State(value);
},
};
}
class Counter {
@signal accessor #value = 0;
get value() {
return this.#value;
}
increment() {
this.#value++;
}
}
const c = new Counter();
c.increment();
console.log(c.value); // 1
Field decorators with addInitializer, decorator metadata, and correct evaluation ordering all work as specified:
function wrapThis, T>(value: T, ctx: ClassFieldDecoratorContextThis, T>) {
ctx.addInitializer(function () {
console.log("Initialized", this);
});
return (initialValue: T) => initialValue;
}
class A {
@wrap
public a: number = 1;
}
const a = new A(); // "Initialized" A {}
Legacy decorators (experimentalDecorators: true in tsconfig.json) continue to work as before.
Faster event loop on macOS & LinuxEnsuring everyone really understands. https://t.co/REGFQ2se1G pic.twitter.com/jvoXgSVyYe
— Ben Dicken (@BenjDicken) February 10, 2026 Windows ARM64 SupportBun now natively supports Windows on ARM64 (Snapdragon, etc.). You can install and run Bun on ARM64 Windows devices, and cross-compile standalone executables targeting bun-windows-arm64.
await Bun.build({
entrypoints: ["./path/to/my/app.ts"],
compile: {
target: "bun-windows-arm64",
outfile: "./myapp", // .exe added automatically
},
});
Or from the CLI:
bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp
When you import { Button } from 'antd', the bundler normally has to parse every file that antd/index.js re-exports — potentially thousands of modules. Bun's bundler now detects pure barrel files (re-export index files) and only parses the submodules you actually use.
In the next version of Bun
Bun's bundler & frontend dev server gets automatic barrel-file optimization.
This makes libraries like lucida-react build up to 2x faster pic.twitter.com/LxS0Y4VjcI
This works in two modes:
Automatic mode: Packages with "sideEffects": false in their package.json get barrel optimization automatically — no configuration needed.Explicit mode: Use the new optimizeImports option in Bun.build() for packages that don't have "sideEffects": false.await Bun.build({
entrypoints: ["./app.ts"],
optimizeImports: ["antd", "@mui/material", "lodash-es"],
});
A file qualifies as a barrel if every named export is a re-export (export { X } from './x'). If a barrel file has any local exports, or if any importer uses import *, all submodules are loaded as usual.
export * re-exports are always loaded to avoid circular resolution issues — only named re-exports that aren't used by any importer are deferred. Multi-level barrel chains (A re-exports from B re-exports from C) are handled automatically via BFS un-deferral.
Fewer closures in bundled outputTo make ESM & CJS work as people expect, all bundlers must generate additional wrapping code around modules. This wrapper code has some overhead. And in v1.3.10, Bun's bundled output for ESM & CJS projects now has significantly less overhead.
Measured on a ~23 MB single-bundle app with 600+ React imports:
MetricBeforeAfterDeltaTotal objects745,985664,001−81,984 (−11%)Heap size115 MB111 MB−4 MBGetterSetter34,62513,428−21,197 (−61%)Function221,302197,024−24,278 (−11%)JSLexicalEnvironment70,10144,633−25,468 (−36%)These improvements apply automatically to all Bun.build() and bun build output — no code changes required.
--retry flag for bun testYou can now set a default retry count for all tests using the --retry flag. This is useful for handling flaky tests in CI environments without adding { retry: N } to every individual test.
# Retry all failing tests up to 3 times bun test --retry 3
Per-test { retry: N } options still take precedence over the global flag:
import { test, expect } from "bun:test";
// Uses the global --retry count
test("flaky network call", async () => {
const res = await fetch("https://example.com/api");
expect(res.ok).toBe(true);
});
// Overrides the global --retry count
test("very flaky test", { retry: 5 }, () => {
// ...
});
You can also configure this in bunfig.toml:
[test] retry = 3
When using the JUnit XML reporter, each retry attempt is now emitted as a separate entry. Failed attempts include a element, followed by the final passing . This gives CI systems and flaky test detection tools per-attempt timing and result data using standard JUnit XML.
Thanks to @alii for the contribution!
ArrayBuffer output for Bun.generateHeapSnapshot("v8")Bun.generateHeapSnapshot("v8") now accepts an optional second argument "arraybuffer" to return the heap snapshot as an ArrayBuffer instead of a string. This avoids the overhead of creating a JavaScript string for large snapshots and prevents potential crashes when heap snapshots approach the max uint32 string length.
The ArrayBuffer contains UTF-8 encoded JSON that can be written directly to a file or decoded with TextDecoder:
const snapshot = Bun.generateHeapSnapshot("v8", "arraybuffer");
// Write directly to a file — no string conversion needed
await Bun.write("heap.heapsnapshot", snapshot);
// Or decode and parse if needed
const parsed = JSON.parse(new TextDecoder().decode(snapshot));
Previously, all HTTP connections using custom TLS configurations — such as client certificates (mTLS) or custom CA certificates — had keepalive disabled, forcing a new TCP+TLS handshake on every request.
Custom TLS connections now properly participate in keepalive pooling. Identical TLS configurations are deduplicated via a global registry with reference counting, and the SSL context cache uses bounded LRU eviction (max 60 entries, 30-minute TTL).
This is automatically enabled when using fetch() or bun install.
Updated Root CertificatesBun's bundled root certificates have been updated from NSS 3.117 to NSS 3.119 (Firefox 147.0.3). This removes 4 distrusted CommScope root certificates per Mozilla's NSS 3.118 changes:
CommScope Public Trust ECC Root-01CommScope Public Trust ECC Root-02CommScope Public Trust RSA Root-01CommScope Public Trust RSA Root-02This update resolves TLS connection failures that some users experienced after Cloudflare rotated example.com's certificate to a chain terminating at the removed AAA Certificate Services (Comodo) root CA.
Upgraded JavaScriptCore EngineBun's underlying JavaScript engine (JavaScriptCore) has been upgraded, bringing several performance improvements and bug fixes.
Deep Rope String Slicing — 168x fasterRepeated string concatenation using += previously created deeply nested rope strings that caused O(n²) behavior when slicing. The engine now limits rope traversal depth and falls back to flattening the string, dramatically improving performance.
let s = "";
for (let i = 0; i 100_000; i++) {
s += "A";
}
// Slicing this string is now up to 168x faster
s.slice(0, 100);
String.prototype.endsWith is now optimized in the DFG/FTL JIT tiers with a dedicated intrinsic. Constant-foldable cases are up to 10.5x faster, and the general case is 1.45x faster.
const str = "hello world";
str.endsWith("world"); // up to 10.5x faster when constant-folded
RegExp flag property getters (.global, .ignoreCase, .multiline, .dotAll, .sticky, .unicode, .unicodeSets, .hasIndices) now have inline cache and DFG/FTL support, making them ~1.6x faster.
Intl.formatToParts — up to 1.15x fasterIntl formatToParts methods now use pre-built structures for returned part objects, reducing allocation overhead.
Other Engine ImprovementsBigInt values now store digits inline, eliminating a separate allocation and pointer indirectionString iterator creation is now optimized in DFG/FTL, enabling allocation sinkingInteger modulo operations in DFG/FTL now avoid expensive fmod double operations when inputs are integer-likeThe JIT worklist thread count has been increased from 3 to 4Register allocator improvements for better spill slot coalescingBug FixesFixed: RegExp.prototype.test() returning incorrect results due to stale captures in FixedCount groups (@pchasco)Fixed: Infinite loop in RegExp JIT when using non-greedy backreferences to zero-width captures (@pchasco)Fixed: Incorrect RegExp backtracking from nested alternative end branches (@pchasco)Fixed: WebAssembly ref.cast/ref.test producing wrong results due to inverted condition in B3 optimization (@nickaein)structuredClone is up to 25x faster for arraysstructuredClone and postMessage now have a fast path when the root value is a dense array of primitives or strings. Instead of going through the full serialization/deserialization machinery, Bun keeps data in native structures and uses memcpy where possible.
This optimization applies automatically when cloning arrays of numbers, strings, booleans, null, or undefined — the most common case for postMessage payloads and deep copies.
const numbers = Array.from({ length: 1000 }, (_, i) => i);
structuredClone(numbers); // 25.3x faster
const strings = Array.from({ length: 100 }, (_, i) => `item-${i}`);
structuredClone(strings); // 2.2x faster
const mixed = [1, "hello", true, null, undefined, 3.14];
structuredClone(mixed); // 2.3x faster
Non-eligible inputs (objects, nested arrays) are unchanged with no regression.
Thanks to @sosukesuzuki for the contribution!
structuredClone is faster for arrays of objectsstructuredClone and postMessage now use a fast path when cloning dense arrays of simple objects, completely bypassing byte-buffer serialization. This is the most common real-world pattern — arrays of flat objects with primitive or string values.
// This is now 1.7x faster than before
const data = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
const cloned = structuredClone(data);
When the array contains objects that share the same shape (same property names in the same order), a structure cache skips repeated property transitions during deserialization — making same-shape object arrays especially fast.
This builds on the existing fast paths for dense arrays of primitives and strings (up to 25x faster for integer arrays), extending the optimization to the object case.
BenchmarkNode.js v24.12Bun v1.3.8Bun v1.3.1[10 objects]2.83 µs2.72 µs1.56 µs (1.7x faster)[100 objects]24.51 µs25.98 µs14.11 µs (1.8x faster)The fast path falls back to normal serialization for objects with getters/setters, nested objects/arrays, non-enumerable properties, or elements like Date, RegExp, Map, Set, and ArrayBuffer.
Thanks to @sosukesuzuki for the contribution!
Faster structuredClone for numeric arraysEliminated a redundant zero-fill in the structuredClone fast path for Int32 and Double arrays. Previously, an internal buffer was zero-initialized and then immediately overwritten with the actual data. Now the buffer is constructed directly from the source data in a single copy.
Thanks to @sosukesuzuki for the contribution!
Buffer.slice() / Buffer.subarray() is ~1.8x fasterBuffer.slice() and Buffer.subarray() have been moved from a JS builtin to a native C++ implementation, eliminating closure allocations and JS→C++ constructor overhead on every call. An int32 fast path skips toNumber() coercion when arguments are already integers — the common case for calls like buf.slice(0, 10).
BenchmarkBeforeAfterSpeedupBuffer(64).slice()27.19 ns14.56 ns1.87×Buffer(1024).slice()27.84 ns14.62 ns1.90×Buffer(1M).slice()29.20 ns14.89 ns1.96×Buffer(64).slice(10)30.26 ns16.01 ns1.89×Buffer(1024).slice(10, 100)30.92 ns18.32 ns1.69×Buffer(1024).slice(-100, -10)28.82 ns17.37 ns1.66×Buffer(1024).subarray(10, 100)28.67 ns16.32 ns1.76×Thanks to @sosukesuzuki for the contribution!
path.parse() is 2.2–7x fasterpath.parse() now uses a pre-built object structure for its return value, avoiding repeated property transitions on every call. This brings ~2.2–2.8x speedups for typical paths and up to ~7x for edge cases like empty strings.
import { posix } from "path";
// 2.2x faster (119ns vs 267ns)
posix.parse("/home/user/dir/file.txt");
// => { root: "/", dir: "/home/user/dir", base: "file.txt", ext: ".txt", name: "file" }
// 7x faster (21ns vs 152ns)
posix.parse("");
// => { root: "", dir: "", base: "", ext: "", name: "" }
Thanks to @sosukesuzuki for the contribution!
Fixed: Bun.spawn() stdio pipes breaking Python asyncio-based MCP serversBun's subprocess stdio pipes used shutdown() calls on their underlying socketpairs to make them unidirectional. On SOCK_STREAM sockets, shutdown(SHUT_WR) sends a FIN to the peer — which caused programs that poll their stdio file descriptors for readability (like Python's asyncio.connect_write_pipe()) to interpret it as "connection closed" and tear down their transport prematurely.
This broke all Python MCP servers using the model_context_protocol SDK whenever they took more than a few seconds to initialize. The shutdown() calls have been removed entirely — the socketpairs are already used unidirectionally by convention, and the calls provided no functional benefit.
// Python MCP servers spawned via Bun.spawn() now work correctly
const proc = Bun.spawn({
cmd: ["python3", "mcp_server.py"],
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
// Previously, the Python server's asyncio write transport would
// be torn down after a few seconds of initialization delay.
// Now it stays open as expected.
const response = await new Response(proc.stdout).text();
, \r, \t, \v, \0nnn (octal), and \xHH (hex)Fixed: Bun.$ shell template literals leaking internal __bunstr_N references in output when an interpolated value contained a space and a subsequent value contained multi-byte UTF-8 characters (e.g., Í, €)Fixed: Scanner-detected crash in the seq shell builtin when called with only flags and no numeric arguments (e.g. await Bun.$\seq -w``)Fixed: crash in the shell interpreter when setupIOBeforeRun fails (e.g., stdout handle unavailable on Windows), which caused a segfault during GC sweepTypeScript typesFixed: TypeScript types for Bun.build() now correctly allow splitting to be used together with compile (@alii)WindowsFixed: Crash that could occur when spawning processes or writing to pipes in long-lived applicationsFixed: Crash on Windows when a standalone executable with compile.autoloadDotenv = false spawned a Worker in a directory containing a .env file. The dotenv loader was mutating environment state owned by another thread, causing a ThreadLock assertion panic. (Thanks to @Hona!)Fixed: "switch on corrupt value" panic on Windows impacting Claude Code & Opencode usersFixed: Hypothetical crash on Windows when GetFinalPathNameByHandleW returned paths exceeding buffer capacityThanks to 11 contributors!@alanstott@alii@cirospaciari@dylan-conway@hk-shao@hona@jarred-sumner@martinamps@prekucki@robobun