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開発の新しいスタンダードになりつつあります。

参考リソース

← 一覧に戻る
PR
PR
PR
PR