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 World | JSON API | ファイル配信 |
|---|---|---|---|
| Bun 1.2 | 250,000 | 180,000 | 150,000 |
| Bun 1.1 | 190,000 | 140,000 | 120,000 |
| Node.js 20 | 75,000 | 55,000 | 45,000 |
| Deno 1.40 | 95,000 | 70,000 | 60,000 |
パッケージインストール時間 (node_modules)
| ツール | クリーン | キャッシュ有 |
|---|---|---|
| bun install | 2.1秒 | 0.3秒 |
| npm install | 15.2秒 | 8.5秒 |
| yarn install | 12.8秒 | 4.2秒 |
| pnpm install | 8.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開発の新しいスタンダードになりつつあります。