```Bun v1.3.9``` 布恩 v1.3.9
Bun v1.3.9

原始链接: https://bun.com/blog/bun-v1.3.9

## Bun 发布总结 本次 Bun 版本带来了全面的性能改进和错误修复。**包脚本执行**通过 `--parallel` 和 `--sequential` 标志增强了并发或有序运行,同时支持 `--filter` 和 `--workspaces`。测试中的模拟现在使用 `Symbol.dispose` 自动恢复模拟。 **性能提升** 包括正则表达式的 SIMD 加速(某些情况下快高达 3.9 倍)和 Markdown 渲染(快 3-15%)。对 `String.prototype.startsWith`、`Set/Map#size` 和 `String.prototype.trim` 的优化进一步提高了速度。CPU 分析器现在支持通过 `--cpu-prof-interval` 配置采样间隔。 **关键修复** 解决了 ARM64 处理器兼容性问题、`NO_PROXY` 处理(现在适用于显式代理)以及 HTTP/2 服务器连接升级问题。Node.js 兼容性通过修复 Windows 上的 `fs` 操作和 `Function.prototype.toString()` 得到了改进。 多个 TypeScript 类型定义已被更正。 最后,WebSocket 客户端崩溃和 HTTP 请求挂起问题已得到解决,同时修复了 HTTP 服务器分块编码解析器中的安全漏洞。

## Bun v1.3.9 及并行/顺序执行 最近的 Hacker News 讨论集中在 JavaScript 运行时 Bun v1.3.9 中的新并行和顺序执行功能上。用户质疑这些功能的需求,认为简单的 bash 脚本可以实现类似的结果。 主要亮点包括跨平台兼容性(允许 Windows 用户构建/测试)、能够从 `package.json` 运行多个命令而无需重复调用 Bun,以及查询/过滤特定命令的能力(例如运行所有测试)。 相关讨论还涉及最佳术语: “并行和顺序” 与 “并行和串行”。虽然两者都易于理解,但评论员更倾向于在任务运行器中使用“顺序”,因为任务会一个接一个地完成,并指出“串行”带有电气工程中的并发含义。最终,内置功能比依赖外部脚本提供了更简洁、更具可扩展性的解决方案。
相关文章

原文

To install Bun

curl

curl -fsSL https://bun.sh/install | bash

powershell

powershell -c "irm bun.sh/install.ps1|iex"

docker

docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun

Run multiple package.json scripts concurrently or sequentially with Foreman-style prefixed output. Includes full --filter and --workspaces integration for running scripts in parallel or sequential across workspace packages.

# Run "build" and "test" concurrently from the current package.json
bun run --parallel build test

# Run "build" and "test" sequentially with prefixed output
bun run --sequential build test

# Glob-matched script names
bun run --parallel "build:*"

# Run "build" in all workspace packages concurrently
bun run --parallel --filter '*' build

# Run "build" in all workspace packages sequentially
bun run --sequential --workspaces build

# Multiple scripts across all packages
bun run --parallel --filter '*' build lint test

# Continue running even if one package fails
bun run --parallel --no-exit-on-error --filter '*' test

# Skip packages missing the script
bun run --parallel --workspaces --if-present build

Each line of output is prefixed with a colored, padded label so you can tell which script produced it:

build | compiling...
test  | running suite...
lint  | checking files...

When combined with --filter or --workspaces, labels include the package name:

pkg-a:build | compiling...
pkg-b:build | compiling...

--parallel starts all scripts immediately with interleaved, prefixed output. --sequential runs scripts one at a time in order. By default, a failure in any script kills all remaining scripts — use --no-exit-on-error to let them all finish.

Pre/post scripts (prebuild/postbuild) are automatically grouped with their main script and run in the correct dependency order within each group.

How is this different from --filter?

bun --filter="pkg" <script> respects dependency order. It doesn't start a script until all it's dependendents are also run. This can be an issue when using long-lived watch-like scripts. --parallel and --sequential do not respect dependency order so they won't wait.

The net.Server → Http2SecureServer connection upgrade pattern now works correctly. This pattern is used by libraries like http2-wrapper, crawlee, and custom HTTP/2 proxy servers that accept raw TCP connections on a net.Server and forward them to an Http2SecureServer via h2Server.emit('connection', rawSocket).

import { createServer } from "node:net";
import { createSecureServer } from "node:http2";
import { readFileSync } from "node:fs";

const h2Server = createSecureServer({
  key: readFileSync("key.pem"),
  cert: readFileSync("cert.pem"),
});

h2Server.on("stream", (stream, headers) => {
  stream.respond({ ":status": 200 });
  stream.end("Hello over HTTP/2!");
});

const netServer = createServer((rawSocket) => {
  // Forward the raw TCP connection to the HTTP/2 server
  h2Server.emit("connection", rawSocket);
});

netServer.listen(8443);

mock() and spyOn() now implement Symbol.dispose, enabling the using keyword to automatically restore mocks when they go out of scope. This eliminates the need to manually call mockRestore() or rely on afterEach cleanup.

import { spyOn, expect, test } from "bun:test";

test("auto-restores spy", () => {
  const obj = { method: () => "original" };

  {
    using spy = spyOn(obj, "method").mockReturnValue("mocked");
    expect(obj.method()).toBe("mocked");
  }

  // automatically restored when `spy` leaves scope
  expect(obj.method()).toBe("original");
});

[Symbol.dispose] is aliased to mockRestore, so it works with both spyOn() and mock():

import { mock } from "bun:test";

const fn = mock(() => "original");
fn();
expect(fn).toHaveBeenCalledTimes(1);

fn[Symbol.dispose](); // same as fn.mockRestore()
expect(fn).toHaveBeenCalledTimes(0);

Previously, setting NO_PROXY only worked when the proxy was auto-detected from http_proxy/HTTP_PROXY environment variables. If you explicitly passed a proxy option to fetch() or new WebSocket(), the NO_PROXY environment variable was ignored.

Now, NO_PROXY is always checked — even when a proxy is explicitly provided via the proxy option.

// NO_PROXY=localhost
// Previously, this would still use the proxy. Now it correctly bypasses it.
await fetch("http://localhost:3000/api", {
  proxy: "http://my-proxy:8080",
});

// Same fix applies to WebSocket
const ws = new WebSocket("ws://localhost:3000/ws", {
  proxy: "http://my-proxy:8080",
});

Bun now supports the --cpu-prof-interval flag to configure the CPU profiler's sampling interval in microseconds, matching Node.js's flag of the same name. The default interval is 1000μs (1ms).

# Sample every 500μs for higher resolution profiling
bun --cpu-prof --cpu-prof-interval 500 index.js

If used without --cpu-prof or --cpu-prof-md, Bun will emit a warning.

Using --bytecode with --format=esm is now supported. Previously, this was unsupported due to missing functionality in JavaScriptCore and now it's fully supported.

When --bytecode is used without an explicit --format, it continues to default to CommonJS. In a future version of Bun, we may change that default to ESM to make the behavior more consistent.

Thanks to @alistair!

Fixed crashes on older ARM64 processors (Cortex-A53, Raspberry Pi 4, AWS a1 instances) caused by mimalloc emitting LSE atomic instructions that require ARMv8.1 or later. Bun now correctly targets ARMv8.0 on Linux aarch64, using outline atomics for runtime dispatch.

Bun.Markdown now uses SIMD-accelerated scanning to find characters that need HTML escaping (&, <, >, "), resulting in 3-15% faster Markdown-to-HTML rendering throughput. Larger documents with fewer special characters see the biggest gains.

Thanks to @billywhizz for the contribution!

Cached frequently-used HTML tag strings (div, p, h1-h6, etc.) in the React renderer for Bun.markdown.react(), avoiding repeated string allocations on every element creation.

Input sizeBeforeAfterImprovement
Small (121 chars)3.20 µs2.30 µs28% faster
Medium (1,039 chars)15.09 µs14.02 µs7% faster
Large (20,780 chars)288.48 µs267.14 µs7.4% faster

String object count reduced by 40% and heap size reduced by 6% for a typical render.

AbortSignal.abort() now skips creating and dispatching an Event object when there are no registered listeners, avoiding unnecessary object allocation and dispatch overhead. This results in a ~6% improvement in micro-benchmarks (~16ms saved per 1M calls).

CaseBeforeAfterImprovement
no listener271 ms255 ms~6%
with listener368 ms370 ms(same)

Thanks to @sosukesuzuki for the contribution!

RegExp SIMD Acceleration

Regular expressions got a major performance boost with a new SIMD-accelerated prefix search, inspired by V8's approach. When a regex has alternatives with known leading characters (e.g., /aaaa|bbbb/), JSC now uses SIMD instructions to scan 16 bytes at a time, rapidly rejecting non-matching positions before falling back to scalar matching. This is implemented for both ARM64 (using TBL2) and x86_64 (using PTEST), so all platforms benefit.

The x86_64 codegen also gained new constant materialization primitives (move128ToVector, move64ToDouble, move32ToFloat) using broadcast and shuffle instructions, which are necessary for the SIMD regex paths and future SIMD optimizations.

  • 579b96614b75 — SIMD fast prefix search for RegExp (ARM64)
  • b7ed3dae4a6a — SIMD fast prefix search for RegExp (x86_64)
  • aa596dded063 — x86_64 constant materialization for SIMD masks

RegExp JIT: Fixed-Count Parentheses

Non-capturing parenthesized subpatterns with fixed-count quantifiers like (?:abc){3} previously fell back to the slower Yarr interpreter. They are now JIT-compiled using a counter-based loop, yielding a ~3.9x speedup on affected patterns. A follow-up patch also added JIT support for fixed-count subpatterns with capture groups (e.g., /(a+){2}b/), correctly saving and restoring capture state across iterations.

  • ac63cc259d74 — JIT support for non-capturing fixed-count parentheses (~3.9x faster)
  • c8b66aa0832b — JIT support for fixed-count subpatterns with captures

String#startsWith Optimized in DFG/FTL

String.prototype.startsWith is now an intrinsic in the DFG and FTL JIT tiers, with constant folding support when both the string and search term are known at compile time.

BenchmarkSpeedup
string-prototype-startswith1.42x faster
string-prototype-startswith-constant-folding5.76x faster
string-prototype-startswith-with-index1.22x faster

Set#size and Map#size Optimized in DFG/FTL and Inline Caches

The .size getter on Set and Map is now handled as an intrinsic in the DFG/FTL tiers and inline caches, eliminating the overhead of a generic getter call.

BenchmarkSpeedup
set-size2.24x faster
map-size2.74x faster

String#trim Optimized

String.prototype.trim, trimStart, and trimEnd now use direct pointer access via span8()/span16() instead of indirect str[i] character access, avoiding repeated bounds checking.

BenchmarkSpeedup
string-trim1.17x faster
string-trim-end1.42x faster
string-trim-start1.10x faster

Object.defineProperty Handled in DFG/FTL

Object.defineProperty is now recognized as an intrinsic in the DFG and FTL JIT tiers. While this patch alone doesn't change benchmark numbers, it lays the groundwork for future optimizations that can specialize based on descriptor shape.

String.prototype.replace Returns Ropes

When using "string".replace("search", "replacement") with string arguments, JSC now constructs a rope (lazy concatenation) instead of eagerly copying the entire result. This avoids unnecessary allocations for the common case where the result is only used briefly. This aligns with V8's behavior.

Node.js compatibility improvements

  • Fixed: existsSync('.'), statSync('.'), and other node:fs operations incorrectly failing on Windows due to '.' being normalized to an empty string instead of the current directory.
  • Fixed: Function.prototype.toString() whitespace now matches V8/Node.js
  • Fixed 3 rare crashes in node:http2

Bun APIs

  • Fixed: Bun.stringWidth incorrectly reporting Thai SARA AA (U+0E32), SARA AM (U+0E33), and their Lao equivalents (U+0EB2, U+0EB3) as zero-width characters instead of width 1. These are spacing vowels, not combining marks, so common Thai words like คำ now correctly return a width of 2 instead of 1.

Web APIs

  • Fixed: a crash that could occur in the WebSocket client when using binaryType = "blob" and receiving "data" events when no event listener attached.
  • Fixed: Sequential HTTP requests with proxy-style absolute URLs (e.g. GET http://example.com/path HTTP/1.1) hanging on the 2nd+ request when using keep-alive connections. This affected HTTP proxy servers built with Bun, which could only handle one request per connection.
  • Fixed: A security issue in the HTTP server chunked encoding parser that could lead to request smuggling.

TypeScript types

  • Fixed: Bun.Build.CompileTarget TypeScript type was missing SIMD variants like bun-linux-x64-modern, causing type errors when cross-compiling with specific architecture targets.
  • Fixed: Missing bun-linux-x64-baseline and bun-linux-x64-modern compile target types in TypeScript definitions, which caused type errors when using Bun.build() with these valid targets.
  • Fixed: Socket.reload() TypeScript types now correctly expect { socket: handler } to match runtime behavior, which requires the handler to be wrapped in a socket property.

Thanks to 10 contributors!

联系我们 contact @ memedata.com