跪拜 Guibai
← Back to the summary

Bun 1.4.4: 2,025 Files Formatted in 2.6 Seconds, With bun check, bun install, and Bun.SQL in the Same Patch

Conclusion first: Bun v1.4.4 packs bun fmt, bun check, bun install, and Bun.SQL all into one patch release. I benchmarked the first three on my own machine; the official benchmarks are listed below.

bun fmt: where the 286x speedup comes from

Bun's official comparison ran on Prettier's own repository (2,616 files):

Tool Time Peak memory
bun fmt 59.7 ms 48 MB
Prettier 3.9.10 17.1 s 459 MB

On the four projects vitest, date-fns, monkeytype, and h3, it was 234 to 425 times faster than Prettier 3.9.9. Compared with oxfmt 0.74.0, it was 31 times faster on fumadocs (2,452 files), 6.2 times faster on vite, and 3.0 times faster on RSSHub.

My benchmark (Windows, Bun 1.4.4): date-fns main branch, 2,025 TS/JS files, first pass 2,643 ms, second pass 916 ms, 20 threads. time shows user CPU was only 0.077 seconds, so the time goes to threads and I/O.

Migration only needs three commands

bun fmt --doctor checks compatibility first. I set up a dummy project with a .prettierrc, and the terminal said:

Using .prettierrc (Prettier config)
You can remove: @trivago/prettier-plugin-sort-imports, prettier,
prettier-plugin-tailwindcss

For CI, use bun fmt --check (exit code 1 when files need formatting). For hooks, use --staged:

#!/bin/sh
bun fmt --staged

Controlled experiment: three lines of badly formatted code were fixed in 21.43 ms, and the output matched Prettier conventions.

Compatibility baseline: 9,648 of the 9,731 Prettier test cases pass (99.1%); 330 open-source repos match oxfmt output 94.6% of the time. Four differences are deliberate. For example, with CSS font-family: Font\ Awesome, Prettier 3.9.9 outputs Font\Awesome (treating \A as a hex escape), while bun fmt keeps the original text.

bun check: it saves more than time

Project v1.4.4 v1.4.3
MUI docs 1.31 s / 1.3 GB 10.28 s / 11.2 GB
Supabase studio 1.27 s 2.84 s
VS Code 2.82 s 4.09 s

For monorepos, use bun check -b: 4,000 independent projects went from 13.65 s to 0.87 s (15.7x), with peak memory dropping from 1,987 MB to 872 MB. On my small project, two files were checked in 31.41 ms, catching TS2345 and suggesting I install @types/bun.

bun install: two changes that save the day

Exponential backoff retries (up to 5 attempts, 50/100/200/400/800 ms with +/-20% jitter) mean an installation can still complete even when a registry keeps returning 503 for one second; Yarn Berry lockfiles can be migrated directly (59.59 ms). This version fixed 133 install issues.

Other things worth remembering:

Bun.SQL: 48% less CPU per query

PostgreSQL client user-space instruction counts: point SELECT -48%, 100-row SELECT -30%, INSERT -21%, batch insert 1,000 rows x 10 columns -38%. query.cancel() can kill a runaway query; a 1,000 MiB COPY stream ran at 255 MiB/s with peak RSS of 59 MiB.

I got bun:sqlite user-defined functions working on an in-memory database:

db.function("add2", (a, b) => a + b);
db.query("SELECT add2(12, 4) AS sum").get(); // { sum: 16 }

db.aggregate("add_all", {
  start: 0,
  step: (total, value) => total + value,
});
db.query("SELECT add_all(dollars) AS total FROM expenses").get();

Should you upgrade

bun fmt can be switched tonight (--doctor as a safety net plus full config compatibility); bun check is worth trying (compare its output with tsc first); Bun.SQL's new capabilities are worth waiting on (migrate only when you have real slow queries).

Two things I have not figured out: the official benchmarks are all on Linux/macOS, so my 2,643 ms on Windows cannot be compared directly; and bun fmt is a port of oxc_formatter, which itself is a port of Biome's formatter. Two layers of porting rely on a 99.1% pass rate; if the 0.9% lands in your project, it becomes your problem.

Bun v1.4.4 migration checklist + benchmark script bundle

Five migration steps for bun fmt (can be done tonight)

  1. Check compatibility: run bun fmt --doctor at the project root. It tells you which config it reads (.prettierrc / .oxfmtrc.json / .editorconfig) and which packages can be removed (prettier, lint-staged, various sort-imports plugins).
  2. Hook into CI: add bun fmt --check first (lists files needing formatting, exit code 1); don't change the default command until CI is green.
  3. Replace hooks: bun fmt --staged formats the staging area and re-stages it, replacing lint-staged:
    # .husky/pre-commit
    #!/bin/sh
    bun fmt --staged
    
  4. Only changed files: bun fmt --changed (or --changed=origin/dev) formats files that differ from the default branch.
  5. Remove packages: after a week with no rollback, remove prettier and related plugins from devDependencies.

bunfig.toml template ([fmt] section)

[fmt]
printWidth = 100
semi = false
sortImports = true        # built-in import sorting; sort-imports plugin can be removed
sortTailwindcss = true    # built-in Tailwind class sorting
sortPackageJson = true    # built-in package.json key sorting

[[fmt.overrides]]
files = ["*.test.ts"]
options = { printWidth = 120 }

bun install backoff retry (CI resilience against flaky registries)

# Default: up to 5 retries, intervals 50/100/200/400/800 ms with +/-20% jitter
bun install

# Custom initial delay (ms): 250 -> 500 -> 1000 -> 2000 -> 4000
BUN_CONFIG_HTTP_RETRY_BACKOFF=250 bun install

# Docker layering: download dependencies without creating node_modules
COPY package.json bun.lock ./
RUN bun pm fetch
COPY . .
RUN bun install --offline --frozen-lockfile

Three tiers of upgrade advice

Tier Target Reason
Switch tonight bun fmt --doctor safety net + fully compatible config + --check for CI
Worth trying bun check 1.4-7.8x on large projects, big memory drop; compare output with tsc first
Wait and see Bun.SQL COPY stream / query.cancel() the features are real, but migrate only with a real slow-query scenario

Test snippets

// Particle 1: sqlite-demo.ts

// Testing new Bun v1.4.4 features: bun:sqlite user-defined functions + update hooks
import { Database } from "bun:sqlite";

const db = new Database(":memory:");
db.run("CREATE TABLE expenses (dollars INTEGER)");

// Custom scalar function
db.function("add2", (a: number, b: number) => a + b);
console.log("add2(12, 4) =", db.query("SELECT add2(12, 4) AS sum").get());

// Custom aggregate function
db.aggregate("add_all", {
  start: 0,
  step: (total: number, value: number) => total + value,
});
db.run("INSERT INTO expenses VALUES (10), (20), (5)");
console.log("add_all =", db.query("SELECT add_all(dollars) AS total FROM expenses").get());

// Update hook
db.onInsert = (_db, table, rowid) => console.log(`inserted ${table} ${rowid}`);
db.run("INSERT INTO expenses (dollars) VALUES (100)");


// Particle 2: glob-demo.ts
const cases: [string, string][] = [
  ["**/!(*.test).ts", "src/cart.ts"],
  ["**/!(*.test).ts", "src/cart.test.ts"],
  ["src/@(app|lib)/**/*.ts", "src/app/a/b.ts"],
  ["shard-{1..3}.json", "shard-2.json"],
  ["shard-{1..9..2}.json", "shard-5.json"],
];
for (const [pattern, path] of cases) {
  console.log(
    `${pattern.padEnd(26)} vs ${path.padEnd(20)} => ${new Bun.Glob(pattern).match(path)}`,
  );
}

Original tech blog · Open source project sharing · AI full-stack creation community idao.fun