Bun v1.3.13 Here is a factual summary of the article: Bun version 1.3.13 introduces two new flags for `bun test`: `--isolate`, which runs each test file in a fresh global environment within the same process, and `--parallel`, which distributes test files across multiple worker processes to speed up large test suites. The update also adds a `--shard` flag for splitting tests across CI jobs and a `--changed` flag that only runs test files affected by recent git changes. Additionally, `bun install` now streams tarballs directly to disk for improved performance. To install Bun 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 To upgrade Bun bun upgrade bun test --isolate and bun test --parallel bun test --isolate and bun test --parallel In the next version of Bun — Bun @bunjavascript bun test gets experimental support for per-file test isolation, and we made it fast. pic.twitter.com/va8GKDh3fy April 16, 2026 Two new flags for bun test that dramatically speed up large test suites: --isolate runs each test file in a fresh global environment within the same process. Between files, Bun drains microtasks, closes all sockets, cancels timers, kills subprocesses, and creates a clean global object. A VM-level transpilation cache means shared dependencies are only parsed once — subsequent files reuse the cached source, skipping redundant transpilation entirely. --parallel =N distributes test files across up to N worker processes defaults to CPU count . Files are partitioned for cache locality, and idle workers steal work from the busiest remaining queue. Workers automatically run with --isolate between files. Output remains identical to serial execution — per-test console.log / console.error output is buffered and flushed atomically, so files never interleave. Run tests with isolation fresh global per file bun test --isolate ./tests Run tests in parallel across all CPU cores bun test --parallel ./tests Run tests in parallel with 8 workers bun test --parallel=8 ./tests Both flags work with existing options including --bail , --randomize , --dots , JUnit reporting, LCOV coverage, and snapshots. All transpiler/resolver flags --define , --loader , --tsconfig-override , --conditions , etc. are forwarded to workers. JEST WORKER ID and BUN TEST WORKER ID in bun test --parallel are also set as environment variables. bun test --shard=M/N for splitting tests across CI jobs bun test --shard=M/N for splitting tests across CI jobsSplit test files across multiple CI runners with the new --shard flag, matching the syntax used by Jest, Vitest, and Playwright. In a GitHub Actions matrix with 3 jobs: bun test --shard=1/3 bun test --shard=2/3 bun test --shard=3/3 Test files are sorted by path for determinism and distributed round-robin across shards, keeping each shard balanced to within one file of each other. The shard index is 1-based 1 <= index <= count . Composes naturally with other flags: — sharding is applied after the changed-files filter --changed — shuffle happens after shard selection, within the shard --randomize If a shard ends up with zero files e.g. 2 test files with --shard=5/5 , it exits 0 gracefully rather than erroring with "No tests found ". Invalid inputs like 0/3 , 4/3 , or 1/0 produce a clear error message and exit non-zero. --shard=2/3: running 3/10 test files f01.test.ts: pass t f04.test.ts: pass t f07.test.ts: pass t bun test --changed bun test --changed bun test now supports a --changed flag that only runs test files affected by your git changes. This works by building the full import graph of your test files and filtering down to only those that transitively depend on a file that git reports as changed. Run tests affected by uncommitted changes unstaged + staged + untracked bun test --changed Run tests affected by changes since a specific commit, branch, or tag bun test --changed=HEAD~1 bun test --changed=main Combine with --watch to re-filter on every restart bun test --changed --watch When combined with --watch , editing any local source file — even one not currently imported by the selected tests — triggers a re-run. Each restart re-queries git, so the filtered set always tracks the working tree. The graph analysis scans imports without entering node modules and without linking or emitting code, so the overhead is minimal. If no changed files are found, --watch keeps the process alive while bun test --changed without --watch exits cleanly. bun install streams tarballs to disk bun install streams tarballs to diskIn the next version of Bun — Jarred Sumner @jarredsumner bun install streams tarballs to disk In a large repo, this reduced memory by 17x pic.twitter.com/3WYMLJ5RoJ April 18, 2026 bun install now extracts package tarballs while they are still downloading , instead of buffering the entire .tgz and decompressed .tar in memory before extraction. Only the in-flight HTTP chunks plus libarchive's fixed per-archive buffers are needed — the full archive is never materialized in memory. Integrity hashing runs incrementally over the compressed bytes and is verified before the extracted tree is promoted into the cache. Error handling and retries remain unchanged. Streaming extraction is enabled by default. If you encounter issues, it can be disabled by setting BUN FEATURE FLAG DISABLE STREAMING INSTALL=1 . Faster faster-bun-install-with-isolated-linker bun install with isolated linker bun install with isolated linkerIn a peer-heavy monorepo, bun install --linker=isolated ; | Before | After | |---|---| | 20.5s | 2.4s | The final .bun/ store layout is byte-identical to previous versions. Previously hanging installs now complete in seconds. Thanks to @robobun for the contribution Source maps use up to 8x less memory source-maps-use-up-to-8x-less-memory Bun's internal source map representation has been rearchitected. Instead of decoding VLQ mappings into a full in-memory list on first access, Bun now writes a compact bit-packed binary format directly during transpilation and reads it in place — no whole-file decode step, no VLQ round-trip. The new format exploits the structure of transpiler output most mappings share the same source index, and generated/original columns frequently match to store mappings at ~2.4 bytes per mapping , down from 20 bytes previously. Memory usage TypeScript compiler, tsc.js , 563k mappings : | Representation | Resident after first .stack | |---|---| Mapping.List Bun v1.3.12 | ~11.3 MB 20 B/mapping | | LEB128 stream | 2.92 MB 5.4 B/mapping | Bit-packed windows | 1.29 MB 2.4 B/mapping | Decoding now costs close to 0. Lookups cost about 6% more. Encoding gets faster. | Benchmark | This release | v1.3.12 | Δ | |---|---|---|---| error-capturestack.mjs mitata, multi-window | 1.37–1.41 µs | 1.27–1.32 µs | +6–8% | plain while 1 new Error .stack loop | 657 ns | ~810 ns | −19% | | 5-frame multi-window synthetic 1500-line file | 818 ns | 769 ns | +6% | first .stack on a 150k-line module | ~0.1 ms | ~5 ms | −98% | | RSS load → first stack 150k-line module | +0.06 MB | +2.3 MB | The main tradeoff is that after compression the new format is ~20% larger than the VLQ-encoded compressed equivalent — but compressing sourcemaps is unnecessary for server-side JavaScript. bun build --compile binaries also benefit: the blob is embedded directly and loaded as a zero-copy view at runtime, shrinking compiled binaries by ~1.8 MB for large source maps. Bun's runtime uses 5% less memory bun-s-runtime-uses-5-less-memory In the next version of Bun — Bun @bunjavascript Bun's JavaScript runtime uses 5% less memory pic.twitter.com/kXp6zFC6Yi April 14, 2026 Bun's memory allocators have been upgraded: - mimalloc moves from v2 to v3 along with several bugfixes in our internal fork - We implemented libpas scavenger support for both Windows & Linux, reclaiming memory faster Together these reduce baseline memory usage and fix a class of hangs and crashes in long-running processes across macOS, Linux, and Windows. Upgraded JavaScriptCore engine upgraded-javascriptcore-engine Bun's underlying JavaScript engine WebKit's JavaScriptCore has been upgraded, merging 1,316 upstream commits. This brings a wide range of performance improvements and bug fixes. Performance improvements from upstream performance-improvements-from-upstream Inline cache for — setting array length is now IC-cached array.length = N Inline cache for undefined , true , false , null as property keys String length folding in DFG/FTL — the compiler can now constant-fold .length on known strings— toUpperCase intrinsic toUpperCase is now JIT-intrinsified String indexOf single-character fast path in DFG/FTL Redundant mov removed from await/yield bytecode Cached default date formatters — Date.toLocaleString and friends are faster on repeat calls Wider bulk copy in GC-safe memcpy/memmove — faster garbage collector memory operations SIMD-accelerated — faster case-insensitive string comparisons equalIgnoringASCIICase SIMD fast path for identifier parsing Thanks to @sosukesuzuki for doing the upgrade Faster faster-addeventlistener-dispatchevent-and-dom-events addEventListener , dispatchEvent , and DOM events addEventListener , dispatchEvent , and DOM eventsCherry-picked ~270 audited upstream WebKit commits into Bun's forked WebCore bindings layer, bringing performance wins to Bun's event system and promise internals. File streaming improvements file-streaming-improvements When using new Response Bun.file path or routes: { "/route": new Response Bun.file path } in Bun.serve , file responses on SSL and Windows now stream incrementally instead of buffering the entire file into memory. Previously, this was only supported when using HTTP and only for static routes. This significantly reduces memory usage for large file responses in those environments. Previously, this was only supported for static routes. Range Request Support in range-request-support-in-bun-serve Bun.serve Bun.serve Bun.serve now supports Range requests for file-backed responses, both in static routes: entries and in fetch & dynamic handler responses. Incoming Range: bytes=... headers on whole-file 200 responses are automatically handled, returning 206 Partial Content with the appropriate Content-Range header, or 416 Range Not Satisfiable when the range is invalid. js const server = Bun.serve { port: 3000, routes: { "/video.mp4": new Response Bun.file "./video.mp4" , }, fetch req { return new Response Bun.file "./large-file.bin" ; }, } ; // Clients can now request byte ranges: const res = await fetch "http://localhost:3000/video.mp4", { headers: { Range: "bytes=0-1023" }, } ; console.log res.status ; // 206 console.log res.headers.get "Content-Range" ; // "bytes 0-1023/..." Suffix ranges bytes=-500 , open-ended ranges bytes=1024- , and all standard forms from RFC 9110 are supported. Multi-range requests fall through to a full-body response. Up to 5.5x faster gzip compression with zlib-ng up-to-5-5x-faster-gzip-compression-with-zlib-ng Bun's zlib dependency has been upgraded from the Cloudflare zlib fork last updated Oct 2023 to zlib-ng https://github.com/zlib-ng/zlib-ng 2.3.3 — the same library used by Node.js 24+ and Chromium. zlib-ng is actively maintained and provides runtime-dispatched SIMD acceleration across AVX-512, AVX2, SSE2, NEON, SVE, and RISC-V vector extensions for CRC32, Adler32, longest-match, and chunk-copy operations. This is a drop-in improvement — no API changes, no code changes required. | Operation | Before | After | Speedup | |---|---|---|---| gzipSync html-128K L1 | 275 µs | 107 µs | 2.59× | gzipSync html-1M L1 | 2.23 ms | 892 µs | 2.50× | gzipSync json-128K L6 | 897 µs | 483 µs | 1.86× | deflate 123K L6 async | 373 µs | 68 µs | 5.48× | gunzipSync html-1M | 561 µs | 522 µs | 1.07× | gunzipSync binary-128K | 31.6 µs | 26.7 µs | 1.18× | createGzip stream L1 1M | 3.76 ms | 2.68 ms | 1.40× | createGunzip stream 1M | 1.24 ms | 1.18 ms | 1.05× | fetch 11KB gzip decode | 42.9 µs | 41.6 µs | parity | Compression is significantly faster across the board, with decompression seeing modest gains. The only trade-off is ~2µs higher per-stream initialization cost from larger internal state structs, which is amortized away on payloads ≥4KB. Faster array iteration in Bun's internals faster-array-iteration-in-bun-s-internals Array iteration in Bun's internal C++/Zig code is now up to 1.43× faster for common cases. When iterating over a JavaScript array that uses simple Int32 or Contiguous storage the common case , Bun now reads elements directly from JSC's butterfly memory instead of calling getIndex per element. This optimization is applied inside JSArrayIterator.next , so every internal call site benefits automatically — including expect .toContain , expect .toBeOneOf , new Blob ... , and more. | Benchmark | Before | After | Speedup | |---|---|---|---| expect arr .toContain last 1000 ints | 11,493 ns | 8,031 ns | 1.43× | expect x .toBeOneOf arr 1000 ints | 13,736 ns | 10,643 ns | 1.29× | new Blob 100 strings + 100 buffers | 9,703 ns | 8,301 ns | 1.17× | new Blob 1000 strings | 56,817 ns | 49,630 ns | 1.14× | The fast path safely revalidates the butterfly pointer before each read, falling back to the generic path if the array is mutated during iteration e.g. by a getter or toString side effect . Thanks to @sosukesuzuki for the contribution SHA3 support in WebCrypto and sha3-support-in-webcrypto-and-node-crypto node:crypto node:crypto Bun now supports SHA3-224, SHA3-256, SHA3-384, and SHA3-512 hash algorithms across both the Web Crypto API and node:crypto . This works with crypto.createHash , crypto.createHmac , crypto.getHashes , crypto.subtle.digest , and crypto.subtle.sign / verify with HMAC. python import crypto from "crypto"; // node:crypto const hash = crypto.createHash "sha3-256" ; hash.update "Hello, world " ; console.log hash.digest "hex" ; // = "f345a219da005ebe9c1a1eaad97bbf38a10c8473e41d0af7fb617caa0c6aa722" const hmac = crypto.createHmac "sha3-256", "secret-key" ; hmac.update "Hello, world " ; console.log hmac.digest "hex" ; // Web Crypto API const digest = await crypto.subtle.digest "SHA3-256", new TextEncoder .encode "Hello, world " , ; console.log Buffer.from digest .toString "hex" ; This also includes an update to BoringSSL, which brings ML-KEM and ML-DSA NIST FIPS 203/204 post-quantum algorithms into the underlying library for future support. X25519 x25519-derivebits-support-in-subtlecrypto deriveBits support in SubtleCrypto deriveBits support in SubtleCrypto SubtleCrypto.deriveBits now works with the X25519 algorithm, completing support for X25519-based key agreement in Bun's Web Crypto API. Previously, calling crypto.subtle.deriveBits with X25519 keys threw a NotSupportedError . This is now fully implemented per the spec, including proper rejection of small-order public keys per RFC 7748 §6.1. js const keyPair = await crypto.subtle.generateKey "X25519", false, "deriveBits", ; const remoteKeyPair = await crypto.subtle.generateKey "X25519", false, "deriveBits", ; const sharedSecret = await crypto.subtle.deriveBits { name: "X25519", public: remoteKeyPair.publicKey }, keyPair.privateKey, 256, ; console.log new Uint8Array sharedSecret ; // 32-byte shared secret Passing null or 0 as the length returns the full 32-byte output: js const bits = await crypto.subtle.deriveBits { name: "X25519", public: remoteKeyPair.publicKey }, keyPair.privateKey, null, // returns full 32-byte output ; Thanks to @panva for the contribution WebSocket client: support websocket-client-support-ws-unix-and-wss-unix ws+unix:// and wss+unix:// ws+unix:// and wss+unix:// The WebSocket client now supports connecting over Unix domain sockets via the ws+unix:// and wss+unix:// URL schemes, matching the convention used by the popular npm ws package. js // Connect to a Unix domain socket const ws = new WebSocket "ws+unix:///tmp/app.sock" ; // With a request path split on first ':', same as the npm ws package const ws = new WebSocket "ws+unix:///tmp/app.sock:/api/stream?x=1" ; // TLS over a Unix socket const ws = new WebSocket "wss+unix:///tmp/app.sock", { tls: { rejectUnauthorized: false }, } ; - The Host header defaults to localhost , matching Node's http.request { socketPath } and the ws package. - Proxies are automatically skipped for Unix socket URLs. wss+unix:// runs a full TLS handshake over the domain socket. Standalone HTML now inlines file-loader assets imported from JS standalone-html-now-inlines-file-loader-assets-imported-from-js When using bun build --compile --target browser on an HTML entry point, assets imported from JavaScript via the file loader e.g. import logo from "./logo.svg" are now correctly inlined as data: URIs in the standalone HTML output. Previously, only assets referenced directly in HTML like