mcpField
B
bun-dev-server

bun-dev-server

github.com/daleseo開発442 回インストール40 週間前に更新
リポジトリ

bun-dev-server について

bun-dev-serverは、Bun.serveを使った開発サーバーの設定を案内するスキルです。React、API、フルスタックアプリ、静的ファイルに対応し、HMRとReact Fast Refreshの設定も扱います。

主な機能

  1. 01サーバー種別の選択
  2. 02React HMRの設定
  3. 03Hono APIサーバーの設定
  4. 04環境とスクリプトの設定
  5. 05HTTPSとAPIプロキシの設定
  6. 06一般的な問題のトラブルシューティング

活用シーン

  1. 01React開発サーバーの作成
  2. 02自動リロード対応APIサーバーの構築
  3. 03単一サーバーでのフロントエンドとAPIの統合
  4. 04既存バックエンドへのAPIリクエスト転送

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/daleseo/bun-skills --skill bun-dev-server

インストール後、Claude Code で/bun-dev-serverを入力して読み込まれたか確認します。

同梱ファイルreferences/hmr-examples.md

詳細

Bun 開発サーバーのセットアップ

Bun.serve と Hot Module Replacement(HMR)、React Fast Refresh を使用した高パフォーマンスな開発サーバーのセットアップを支援します。

ワークフロー

1. サーバーの種類を決める

必要な開発サーバーの種類をユーザーに尋ねます。

  • React/フロントエンドアプリ:React Fast Refresh を使う SPA
  • API サーバー:自動リロード機能付きの REST/GraphQL API
  • フルスタックアプリ:フロントエンドと API の組み合わせ
  • 静的サーバー:ライブリロード機能付きのファイルサーバー
2. 前提条件を確認する
# Verify Bun installation
bun --version

# Check if project has package.json
ls -la package.json

package.json がない場合は、最初に bun init を実行するよう提案します。

3. 依存関係をインストールする

React アプリの場合:

bun add react react-dom
bun add -d @types/react @types/react-dom

Hono を使った API の場合(推奨):

bun add hono

フルスタックの場合:

bun add react react-dom hono
bun add -d @types/react @types/react-dom
4. サーバー設定を作成する
React 開発サーバー

プロジェクトのルートに server.ts を作成します。

import type { ServerWebSocket } from "bun";

const clients = new Set<ServerWebSocket<unknown>>();

const server = Bun.serve({
  port: 3000,

  async fetch(request, server) {
    const url = new URL(request.url);

    // WebSocket for HMR
    if (url.pathname === "/_hmr") {
      const upgraded = server.upgrade(request);
      if (upgraded) return undefined;
      return new Response("WebSocket upgrade failed", { status: 500 });
    }

    // Serve index.html for SPA routing
    if (url.pathname === "/" || !url.pathname.includes(".")) {
      return new Response(
        Bun.file("public/index.html"),
        { headers: { "Content-Type": "text/html" } }
      );
    }

    // Serve static files
    const filePath = `public${url.pathname}`;
    const file = Bun.file(filePath);

    if (await file.exists()) {
      return new Response(file);
    }

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

  websocket: {
    open(ws) {
      clients.add(ws);
      console.log("HMR client connected");
    },

    close(ws) {
      clients.delete(ws);
      console.log("HMR client disconnected");
    },

    message(ws, message) {
      // Handle client messages if needed
    },
  },
});

console.log(`🚀 Dev server running at http://localhost:${server.port}`);

// Watch for file changes
const watcher = Bun.file.watch(import.meta.dir + "/src", {
  recursive: true,
});

for await (const event of watcher) {
  if (event.kind === "change" && event.path.endsWith(".tsx")) {
    console.log(`📝 File changed: ${event.path}`);

    // Notify all connected clients to reload
    for (const client of clients) {
      client.send(JSON.stringify({ type: "reload" }));
    }
  }
}

public/index.html を作成します。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bun + React App</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/index.tsx"></script>

  
  <script>
    const ws = new WebSocket('ws://localhost:3000/_hmr');

    ws.addEventListener('message', (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'reload') {
        console.log('🔄 Reloading...');
        window.location.reload();
      }
    });

    ws.addEventListener('close', () => {
      console.log('❌ HMR connection lost. Reconnecting...');
      setTimeout(() => window.location.reload(), 1000);
    });
  </script>
</body>
</html>

src/index.tsx を作成します。

import { render } from 'react-dom';
import App from './App';

const root = document.getElementById('root');
render(<App />, root);

src/App.tsx を作成します。

export default function App() {
  return (
    <div>
      <h1>Welcome to Bun + React!</h1>
      <p>Edit src/App.tsx to see HMR in action</p>
    </div>
  );
}
Hono を使った API サーバー

server.ts を作成します。

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';

const app = new Hono();

// Middleware
app.use('*', cors());
app.use('*', logger());

// Routes
app.get('/', (c) => {
  return c.json({ message: 'Welcome to Bun API' });
});

app.get('/api/health', (c) => {
  return c.json({ status: 'ok', timestamp: Date.now() });
});

// Example POST endpoint
app.post('/api/users', async (c) => {
  const body = await c.req.json();
  return c.json({ created: true, data: body }, 201);
});

// Start server
const server = Bun.serve({
  port: process.env.PORT || 3000,
  fetch: app.fetch,
});

console.log(`🚀 API server running at http://localhost:${server.port}`);
フルスタックサーバー

server.ts を作成します。

import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';

const app = new Hono();

// API routes
const api = new Hono();

api.get('/health', (c) => c.json({ status: 'ok' }));
api.get('/users', (c) => c.json({ users: [] }));

app.route('/api', api);

// Serve static files
app.use('/*', serveStatic({ root: './public' }));

// SPA fallback
app.get('*', (c) => c.html(Bun.file('public/index.html')));

const server = Bun.serve({
  port: 3000,
  fetch: app.fetch,
});

console.log(`🚀 Full-stack server at http://localhost:${server.port}`);
5. React Fast Refresh を設定する(上級者向け)

React Fast Refresh を適切に機能させるには、hmr-runtime.ts を作成します。

// React Fast Refresh runtime
let timeout: Timer | null = null;

export function refresh() {
  if (timeout) clearTimeout(timeout);

  timeout = setTimeout(() => {
    // Re-import the App component
    import('./App.tsx?t=' + Date.now()).then((module) => {
      const { render } = require('react-dom');
      const root = document.getElementById('root');
      render(module.default(), root);
    });
  }, 100);
}

// Listen for HMR events
if (import.meta.hot) {
  import.meta.hot.accept(() => {
    refresh();
  });
}
6. 環境設定

.env.development を作成します。

# Server
PORT=3000
NODE_ENV=development

# API
API_URL=http://localhost:3000/api

# Features
ENABLE_HMR=true

.env.production を作成します。

# Server
PORT=8080
NODE_ENV=production

# API
API_URL=https://api.example.com

# Features
ENABLE_HMR=false

server.ts で環境変数を読み込みます。

// Environment is loaded automatically by Bun
const isDev = process.env.NODE_ENV === 'development';
const port = process.env.PORT || 3000;
7. package.json のスクリプトを更新する

開発用スクリプトを追加します。

{
  "scripts": {
    "dev": "bun run --hot server.ts",
    "dev:watch": "bun run --watch server.ts",
    "start": "NODE_ENV=production bun run server.ts",
    "build": "bun build src/index.tsx --outdir=dist --minify",
    "clean": "rm -rf dist"
  }
}

スクリプトの説明:

  • dev:ホットリロードで実行します(ファイルの変更時に再起動します)
  • dev:watch:ウォッチモードで実行します(より高速ですが、クラッシュ時に再読み込みしません)
  • start:本番モード
  • build:本番用にフロントエンドをビルドします
8. TypeScript を設定する

tsconfig.json を更新します。

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "types": ["bun-types"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "server.ts"]
}
9. プロジェクト構成を作成する

プロジェクト構成全体を生成します。

project/
├── server.ts              # Development server
├── src/
│   ├── index.tsx         # App entry point
│   ├── App.tsx           # Main component
│   ├── components/       # React components
│   └── styles/           # CSS files
├── public/
│   ├── index.html        # HTML template
│   └── assets/           # Static assets
├── .env.development
├── .env.production
├── package.json
├── tsconfig.json
└── README.md
10. 上級者向け:ローカル開発用 HTTPS

HTTPS を有効にする場合(一部のブラウザー API で必要です):

import { readFileSync } from 'fs';

const server = Bun.serve({
  port: 3000,
  tls: {
    cert: readFileSync('./localhost.pem'),
    key: readFileSync('./localhost-key.pem'),
  },
  fetch: app.fetch,
});

console.log(`🔒 HTTPS server at https://localhost:${server.port}`);

次のコマンドで証明書を生成します。

# Install mkcert first: brew install mkcert
mkcert -install
mkcert localhost 127.0.0.1 ::1
11. プロキシ設定(既存のバックエンド向け)

API リクエストを別のサーバーにプロキシする必要がある場合:

const app = new Hono();

// Proxy /api requests to backend
app.all('/api/*', async (c) => {
  const url = new URL(c.req.url);
  const backendUrl = `http://localhost:8080${url.pathname}${url.search}`;

  const response = await fetch(backendUrl, {
    method: c.req.method,
    headers: c.req.raw.headers,
    body: c.req.method !== 'GET' ? await c.req.raw.text() : undefined,
  });

  return new Response(response.body, {
    status: response.status,
    headers: response.headers,
  });
});

セットアップのテスト

作成後、次の手順でテストするようユーザーを案内します。

# 1. Start dev server
bun run dev

# 2. Open browser
open http://localhost:3000

# 3. Make a change to src/App.tsx
# 4. Verify HMR reloads the page

# 5. Test API endpoints
curl http://localhost:3000/api/health

トラブルシューティング

HMR が機能しない
// Check if WebSocket connection is established
// Open browser console and look for:
// "HMR client connected"

// If not, verify:
// 1. Port is correct
// 2. No firewall blocking WebSocket
// 3. Server is running with --hot flag
ポートがすでに使用されている
# Find process using port 3000
lsof -ti:3000

# Kill the process
kill -9 $(lsof -ti:3000)

# Or use a different port
PORT=3001 bun run dev
CORS の問題

サーバーに CORS ヘッダーを追加します。

app.use('*', cors({
  origin: 'http://localhost:3000',
  credentials: true,
}));

パフォーマンスのヒント

  1. 開発時には --hot を使用する:ほとんどの場合、--watch より高速です
  2. ファイルウォッチャーの対象範囲を最小限にする:src/ ディレクトリのみを監視します
  3. HTTP/2 を使用する:有効にすると、並列読み込みが高速になります
  4. 静的アセットをキャッシュする:Cache-Control ヘッダーを追加します
app.use('/assets/*', async (c, next) => {
  await next();
  c.header('Cache-Control', 'public, max-age=31536000');
});

完了チェックリスト

  • ✅ 開発サーバーを作成済み
  • ✅ HMR を設定し、テスト済み
  • ✅ 環境変数を設定済み
  • ✅ package.json にスクリプトを追加済み
  • ✅ プロジェクト構成を整理済み
  • ✅ TypeScript を設定済み
  • ✅ ブラウザーが正常に接続
  • ✅ ファイルの変更時に再読み込みが発生

次のステップ

ユーザーに次の項目を提案します。

  1. エラー処理を改善するためにエラーバウンダリーを追加する
  2. ESLint と Prettier をセットアップする
  3. tsconfig.json でパスエイリアスを設定する
  4. 開発用と本番用のビルドを追加する
  5. テスト用に bun-test の追加を検討する

コメント