Bun 1.2リリース - Node.js互換性向上とパフォーマンス最適化の全貌

2025.12.02

Bunは、Zig言語で書かれた超高速なJavaScriptランタイム兼パッケージマネージャーです。Bun 1.2では、Node.js互換性が大幅に向上し、エンタープライズでの採用がさらに加速しています。

Bun 1.2の主な新機能

概要

flowchart TB
    subgraph Bun12["Bun 1.2"]
        subgraph Compat["Node.js互換性"]
            C1["node:cluster 完全サポート"]
            C2["node:dgram (UDP) サポート"]
            C3["node:v8 部分サポート"]
            C4["90%+ のnpmパッケージが動作"]
        end

        subgraph Features["新機能"]
            F1["組み込みS3クライアント"]
            F2["Bun.color() カラー処理API"]
            F3["改善されたWatch Mode"]
            F4["HTML/CSS バンドラー"]
        end

        subgraph Perf["パフォーマンス"]
            P1["HTTPサーバー: 30%高速化"]
            P2["メモリ使用量: 20%削減"]
            P3["パッケージインストール: 2x高速化"]
        end
    end

Node.js互換性の向上

node:cluster のサポート

// cluster-server.ts - マルチプロセスサーバー
import cluster from 'node:cluster';
import { cpus } from 'node:os';
import { serve } from 'bun';

const numCPUs = cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  // CPUコア数分のワーカーをフォーク
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    // 自動再起動
    cluster.fork();
  });

  // ワーカーからのメッセージ受信
  cluster.on('message', (worker, message) => {
    console.log(`Message from worker ${worker.process.pid}:`, message);
  });
} else {
  // ワーカープロセス
  const server = serve({
    port: 3000,
    fetch(req) {
      return new Response(`Hello from worker ${process.pid}`);
    },
  });

  console.log(`Worker ${process.pid} started on port ${server.port}`);

  // プライマリへメッセージ送信
  process.send?.({ status: 'ready', pid: process.pid });
}

node:dgram (UDP) のサポート

// udp-server.ts
import dgram from 'node:dgram';

const server = dgram.createSocket('udp4');

server.on('error', (err) => {
  console.error(`Server error:\n${err.stack}`);
  server.close();
});

server.on('message', (msg, rinfo) => {
  console.log(`Server got: ${msg} from ${rinfo.address}:${rinfo.port}`);

  // 応答を返す
  const response = Buffer.from(`Received: ${msg}`);
  server.send(response, rinfo.port, rinfo.address);
});

server.on('listening', () => {
  const address = server.address();
  console.log(`UDP server listening on ${address.address}:${address.port}`);
});

server.bind(41234);

// udp-client.ts
const client = dgram.createSocket('udp4');
const message = Buffer.from('Hello UDP Server');

client.send(message, 41234, 'localhost', (err) => {
  if (err) {
    console.error('Send error:', err);
    client.close();
    return;
  }
  console.log('Message sent');
});

client.on('message', (msg) => {
  console.log(`Received: ${msg}`);
  client.close();
});

組み込みS3クライアント

S3操作の簡素化

// Bun.s3 - ネイティブS3クライアント

// 環境変数から自動で認証情報を取得
// AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION

// ファイルのアップロード
const file = Bun.file('./large-file.zip');
await Bun.s3.write('my-bucket/uploads/large-file.zip', file);

// ストリーミングアップロード
const response = await fetch('https://example.com/large-video.mp4');
await Bun.s3.write('my-bucket/videos/video.mp4', response);

// テキストデータの書き込み
await Bun.s3.write(
  'my-bucket/data/config.json',
  JSON.stringify({ setting: 'value' }),
  { contentType: 'application/json' }
);

// ファイルの読み取り
const data = await Bun.s3.file('my-bucket/data/config.json').text();
const config = JSON.parse(data);

// ArrayBufferとして読み取り
const buffer = await Bun.s3.file('my-bucket/images/photo.jpg').arrayBuffer();

// ストリーミング読み取り
const stream = Bun.s3.file('my-bucket/large-data.csv').stream();

// ファイル情報の取得
const s3File = Bun.s3.file('my-bucket/document.pdf');
console.log({
  exists: await s3File.exists(),
  size: await s3File.size,
  lastModified: await s3File.lastModified,
});

// ファイルの削除
await Bun.s3.delete('my-bucket/temp/old-file.txt');

// 署名付きURL生成
const signedUrl = await Bun.s3.presign('my-bucket/private/file.pdf', {
  expiresIn: 3600, // 1時間
  method: 'GET',
});

// リスト取得
for await (const object of Bun.s3.list('my-bucket/uploads/')) {
  console.log(object.key, object.size, object.lastModified);
}

S3を使ったファイルサーバー

// s3-file-server.ts
import { serve } from 'bun';

serve({
  port: 3000,

  async fetch(req) {
    const url = new URL(req.url);
    const path = url.pathname.slice(1); // 先頭の/を除去

    if (req.method === 'GET') {
      // S3からファイルを直接ストリーミング
      const s3File = Bun.s3.file(`my-bucket/${path}`);

      if (!(await s3File.exists())) {
        return new Response('Not Found', { status: 404 });
      }

      return new Response(s3File.stream(), {
        headers: {
          'Content-Type': s3File.type || 'application/octet-stream',
          'Content-Length': String(await s3File.size),
        },
      });
    }

    if (req.method === 'PUT') {
      // リクエストボディをS3にストリーミング
      await Bun.s3.write(`my-bucket/${path}`, req);
      return new Response('Uploaded', { status: 201 });
    }

    if (req.method === 'DELETE') {
      await Bun.s3.delete(`my-bucket/${path}`);
      return new Response('Deleted', { status: 200 });
    }

    return new Response('Method Not Allowed', { status: 405 });
  },
});

Bun.color() API

カラー処理の新API

// Bun.color() - 高速なカラー処理

// CSS色文字列の解析
const red = Bun.color('red');
console.log(red); // { r: 255, g: 0, b: 0, a: 1 }

// HEXからの変換
const hex = Bun.color('#3498db');
console.log(hex); // { r: 52, g: 152, b: 219, a: 1 }

// RGBからの変換
const rgb = Bun.color('rgb(100, 150, 200)');
console.log(rgb); // { r: 100, g: 150, b: 200, a: 1 }

// HSLからの変換
const hsl = Bun.color('hsl(210, 60%, 50%)');
console.log(hsl); // { r: ..., g: ..., b: ..., a: 1 }

// 配列形式での出力
const [r, g, b, a] = Bun.color('#ff6b6b', 'array');
console.log(r, g, b, a); // 255, 107, 107, 1

// HEX文字列への変換
const hexString = Bun.color({ r: 255, g: 100, b: 50, a: 1 }, 'hex');
console.log(hexString); // '#ff6432'

// CSS形式への変換
const cssRgb = Bun.color({ r: 255, g: 100, b: 50, a: 0.5 }, 'css');
console.log(cssRgb); // 'rgba(255, 100, 50, 0.5)'

// カラー操作
function lighten(color: string, amount: number): string {
  const { r, g, b, a } = Bun.color(color)!;
  return Bun.color({
    r: Math.min(255, r + amount),
    g: Math.min(255, g + amount),
    b: Math.min(255, b + amount),
    a,
  }, 'hex');
}

function darken(color: string, amount: number): string {
  const { r, g, b, a } = Bun.color(color)!;
  return Bun.color({
    r: Math.max(0, r - amount),
    g: Math.max(0, g - amount),
    b: Math.max(0, b - amount),
    a,
  }, 'hex');
}

console.log(lighten('#3498db', 30)); // 明るく
console.log(darken('#3498db', 30));  // 暗く

改善されたWatch Mode

ファイル監視の強化

// bun --watch による開発サーバー

// package.json
{
  "scripts": {
    "dev": "bun --watch src/index.ts",
    "dev:hot": "bun --hot src/server.ts"
  }
}

// --watch: プロセス再起動
// --hot: ホットリロード(状態保持)
// hot-reload-server.ts
import { serve } from 'bun';

// グローバル状態(ホットリロード時に保持される)
declare global {
  var requestCount: number;
}
globalThis.requestCount ??= 0;

serve({
  port: 3000,

  fetch(req) {
    globalThis.requestCount++;

    return new Response(`
      <html>
        <body>
          <h1>Hot Reload Demo</h1>
          <p>Request count: ${globalThis.requestCount}</p>
          <p>Last updated: ${new Date().toISOString()}</p>
        </body>
      </html>
    `, {
      headers: { 'Content-Type': 'text/html' },
    });
  },
});

console.log('Server started on port 3000');

プログラマティックなファイル監視

// Bun.watch() - ファイル監視API
const watcher = Bun.watch({
  paths: ['./src'],
  recursive: true,
  filter: (path) => path.endsWith('.ts') || path.endsWith('.tsx'),
});

for await (const event of watcher) {
  console.log(`${event.type}: ${event.path}`);

  if (event.type === 'change' || event.type === 'create') {
    // ファイル変更時の処理
    await runTests(event.path);
  }

  if (event.type === 'delete') {
    // ファイル削除時の処理
    console.log(`File deleted: ${event.path}`);
  }
}

async function runTests(changedFile: string) {
  const testFile = changedFile.replace('.ts', '.test.ts');
  if (await Bun.file(testFile).exists()) {
    const proc = Bun.spawn(['bun', 'test', testFile]);
    await proc.exited;
  }
}

HTMLバンドラー

フロントエンドビルドの簡素化

// bun build --html

// 入力: index.html
// <!DOCTYPE html>
// <html>
// <head>
//   <link rel="stylesheet" href="./styles.css">
// </head>
// <body>
//   <div id="app"></div>
//   <script type="module" src="./app.tsx"></script>
// </body>
// </html>

// ビルドコマンド
// bun build ./index.html --outdir=./dist

// 結果:
// - HTML内のCSS/JSを自動検出
// - TypeScript/TSXを自動コンパイル
// - CSSをバンドル・最適化
// - 依存関係を解決
// プログラマティックなHTMLビルド
const result = await Bun.build({
  entrypoints: ['./src/index.html'],
  outdir: './dist',
  minify: true,
  sourcemap: 'external',
  splitting: true,
  target: 'browser',
  define: {
    'process.env.NODE_ENV': '"production"',
  },
  loader: {
    '.png': 'file',
    '.svg': 'file',
    '.woff2': 'file',
  },
});

if (!result.success) {
  console.error('Build failed:');
  for (const log of result.logs) {
    console.error(log);
  }
  process.exit(1);
}

console.log('Build outputs:');
for (const output of result.outputs) {
  console.log(`  ${output.path} (${output.size} bytes)`);
}

パフォーマンス改善

ベンチマーク比較

HTTPサーバーパフォーマンス (req/sec)

ランタイムHello WorldJSON APIファイル配信
Bun 1.2250,000180,000150,000
Bun 1.1190,000140,000120,000
Node.js 2075,00055,00045,000
Deno 1.4095,00070,00060,000

パッケージインストール時間 (node_modules)

ツールクリーンキャッシュ有
bun install2.1秒0.3秒
npm install15.2秒8.5秒
yarn install12.8秒4.2秒
pnpm install8.5秒1.8秒

最適化されたAPI使用例

// 高速なJSONサーバー
import { serve } from 'bun';

const users = new Map<string, User>();

serve({
  port: 3000,

  async fetch(req) {
    const url = new URL(req.url);

    // Bun.router() - 高速なルーティング(将来の機能)
    if (url.pathname.startsWith('/api/users')) {
      const userId = url.pathname.split('/')[3];

      if (req.method === 'GET') {
        if (userId) {
          const user = users.get(userId);
          if (!user) {
            return Response.json({ error: 'Not found' }, { status: 404 });
          }
          return Response.json(user);
        }
        return Response.json([...users.values()]);
      }

      if (req.method === 'POST') {
        const body = await req.json();
        const id = crypto.randomUUID();
        const user = { id, ...body, createdAt: new Date().toISOString() };
        users.set(id, user);
        return Response.json(user, { status: 201 });
      }
    }

    return new Response('Not Found', { status: 404 });
  },
});

Bunネイティブ機能

Bun.sql (SQLiteの高速操作)

// Bun.sql - 組み込みSQLiteドライバー

import { Database } from 'bun:sqlite';

const db = new Database(':memory:');

// テーブル作成
db.run(`
  CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

// プリペアドステートメント
const insertUser = db.prepare(`
  INSERT INTO users (name, email) VALUES ($name, $email)
`);

const getUser = db.prepare(`
  SELECT * FROM users WHERE id = $id
`);

const getAllUsers = db.prepare(`
  SELECT * FROM users ORDER BY created_at DESC
`);

// データ挿入
insertUser.run({ $name: 'Alice', $email: 'alice@example.com' });
insertUser.run({ $name: 'Bob', $email: 'bob@example.com' });

// データ取得
const user = getUser.get({ $id: 1 });
console.log(user); // { id: 1, name: 'Alice', ... }

const users = getAllUsers.all();
console.log(users);

// トランザクション
const insertMany = db.transaction((users: Array<{ name: string; email: string }>) => {
  for (const user of users) {
    insertUser.run({ $name: user.name, $email: user.email });
  }
});

insertMany([
  { name: 'Charlie', email: 'charlie@example.com' },
  { name: 'Diana', email: 'diana@example.com' },
]);

Bun.password (パスワードハッシュ)

// Bun.password - 安全なパスワードハッシュ

// パスワードのハッシュ化
const hash = await Bun.password.hash('my-secure-password', {
  algorithm: 'argon2id', // bcrypt, argon2id, argon2i, argon2d
  memoryCost: 65536,     // 64MB
  timeCost: 3,
});

console.log(hash);
// $argon2id$v=19$m=65536,t=3,p=1$...

// パスワードの検証
const isValid = await Bun.password.verify(
  'my-secure-password',
  hash
);
console.log(isValid); // true

// bcryptの使用
const bcryptHash = await Bun.password.hash('password123', {
  algorithm: 'bcrypt',
  cost: 12,
});

const bcryptValid = await Bun.password.verify('password123', bcryptHash);

Bun.sleep とタイマー

// Bun.sleep - 高精度スリープ

// ミリ秒指定
await Bun.sleep(1000); // 1秒待機

// 名前付きタイマー(キャンセル可能)
const timer = Bun.sleep(5000);
setTimeout(() => timer.cancel(), 2000); // 2秒後にキャンセル

try {
  await timer;
} catch (e) {
  console.log('Timer was cancelled');
}

// sleepSync (同期版)
Bun.sleepSync(100); // 100ms同期待機

移行ガイド

Node.jsからの移行

// package.json
{
  "scripts": {
    // Node.js
    "dev:node": "node --watch src/index.js",
    "build:node": "tsc && node dist/index.js",

    // Bun (TypeScript直接実行)
    "dev": "bun --watch src/index.ts",
    "build": "bun build src/index.ts --outdir=dist",
    "start": "bun dist/index.js"
  }
}
// 互換性チェック
// bun pm untrusted - 信頼されていないパッケージを確認
// bun pm verify    - パッケージの整合性を検証

// 一般的な互換性の問題と解決策:

// 1. __dirname / __filename
// Node.js: globalで利用可能
// Bun: ESMでは import.meta を使用
const __dirname = import.meta.dir;
const __filename = import.meta.file;

// 2. require() in ESM
// Bunでは import.meta.require() を使用
const pkg = import.meta.require('./package.json');

// 3. Native addons
// 一部のネイティブモジュールは非互換
// 代替: BunネイティブAPI or WASM

Express → Elysia 移行例

// Express (Node.js)
import express from 'express';
const app = express();

app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  res.json({ id, name: 'User' });
});

app.listen(3000);

// Elysia (Bun最適化フレームワーク)
import { Elysia } from 'elysia';

const app = new Elysia()
  .get('/users/:id', ({ params: { id } }) => ({
    id,
    name: 'User',
  }))
  .listen(3000);

console.log(`Server running at ${app.server?.hostname}:${app.server?.port}`);

まとめ

Bun 1.2は、Node.jsの代替として実用的なレベルに達しています。

Bun 1.2の強み

特徴メリット
超高速起動開発サイクルの高速化
TypeScript直接実行ビルドステップ不要
オールインワンランタイム+バンドラー+PM
Node.js互換既存コードの再利用

採用判断基準

  • 新規プロジェクト: 積極的に採用可
  • 既存Node.jsプロジェクト: 互換性テスト後に移行
  • エンタープライズ: 段階的導入を推奨

Bunは、JavaScript/TypeScript開発の新しいスタンダードになりつつあります。

参考リンク

← 一覧に戻る