mcpField
E
electrobun

electrobun

github.com/gyorkluu開発54 回インストール
リポジトリ

electrobun について

electrobun は、TypeScript、Bun、システム標準の Webview を使うデスクトップアプリフレームワークです。メインプロセスと Webview 間の型安全な双方向 RPC に加え、ウィンドウ、メニュー、アップデーター機能を備えます。

主な機能

  1. 01Bun による TypeScript 開発とバンドル
  2. 02システム標準 Webview の利用
  3. 03メインプロセスと Webview 間の型安全な双方向 RPC
  4. 04ウィンドウ、メニュー、システムトレイ、ファイルダイアログ
  5. 05アプリのアップデーターとライフサイクルイベント

活用シーン

  1. 01React、Svelte などのテンプレートからデスクトップアプリを開始
  2. 02複数ウィンドウを持つデスクトップアプリの構築
  3. 03UI とメインプロセス間の呼び出し
  4. 04メニュー、トレイ操作、ファイル選択の実装

インストール

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

$npx skills add https://github.com/gyorkluu/electrobun-skills --skill electrobun

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

詳細

Electrobun

Electrobun は Bun(ランタイム+バンドラー)とシステム標準のネイティブ WebView を使用する、TypeScript を中心としたデスクトップアプリフレームワークです。約 14MB のアプリを構築でき、アップデートは約 14KB、起動時間は 50ms 未満です。

クイックスタート

新しいプロジェクトを初期化する
bunx electrobun init          # Interactive scaffold
bun run dev                   # Development mode
bun run build                 # Production bundle

次のテンプレートから選択できます。

  • hello-world:最小限のスターター
  • react-tailwind-vite:React + Tailwind + Vite
  • svelte:Svelte フレームワーク
  • photo-booth:カメラアクセスの例
  • multitab-browser:タブベースのブラウザーの例
プロジェクト構成
my-app/
├── electrobun.config.ts      # Build configuration
├── src/
│   ├── bun/
│   │   └── main.ts           # Main process (Bun)
│   └── views/
│       └── mainview/
│           ├── index.html
│           └── index.ts      # Webview frontend
└── package.json

コアコンセプト

メインプロセスと WebView
  • メインプロセス(src/bun/main.ts):Bun で実行され、システムへの完全なアクセス権を持ち、ウィンドウを管理し、RPC を処理します
  • WebView プロセス(src/views/*/index.ts):OS の WebView で実行され、サンドボックス化されており、UI を処理して RPC を呼び出します
BrowserWindow

アプリケーションのウィンドウを作成および管理します。

import { BrowserWindow } from "electrobun/bun";

const win = new BrowserWindow({
  title: "My App",
  url: "views://mainview/index.html",
  width: 1200,
  height: 800,
  frame: true,  // Standard window frame
  styleMask: ["titled", "closable", "miniaturizable", "resizable"],
});

主なオプション:

  • title:ウィンドウのタイトル
  • url:読み込む URL(バンドルされたビューには views:// プロトコルを使用)
  • width、height:ウィンドウのサイズ
  • x、y:ウィンドウの位置(任意)
  • frame:標準のウィンドウフレームを表示するかどうか(true/false)
  • styleMask:ウィンドウコントロールの配列(macOS)
  • titleBarStyle:「default」 | 「hidden」 | 「hiddenInset」
  • trafficLightPosition:macOS の信号機ボタンのカスタム位置

メソッド:

win.loadURL("views://otherview/index.html")
win.setTitle("New Title")
win.resize({ width: 1024, height: 768 })
win.move({ x: 100, y: 100 })
win.show() / win.hide()
win.close()
win.focus()
win.minimize() / win.maximize() / win.fullscreen()
RPC:メインプロセスと WebView 間の通信

Electrobun の最大の特長は、型付けされた高速な双方向 RPC です。

メインプロセス(bun/main.ts):

import { BrowserWindow } from "electrobun/bun";

const win = new BrowserWindow({ /* ... */ });

// Define what main exposes TO the webview
win.defineRpc({
  handlers: {
    async getUser(id: string) {
      // Full system access here
      const user = await db.getUser(id);
      return { name: user.name, id: user.id };
    },
    async saveFile(path: string, content: string) {
      await Bun.write(path, content);
      return { success: true };
    }
  }
});

// Call webview methods FROM main
const result = await win.rpc.updateUI({ data: "new data" });

WebView(views/mainview/index.ts):

import { Electroview } from "electrobun/browser";

const electroview = new Electroview();

// Call main process functions
const user = await electroview.rpc.getUser("123");
const result = await electroview.rpc.saveFile("/path/to/file.txt", "content");

// Define handlers main can call
electroview.defineRpc({
  handlers: {
    async updateUI(data: any) {
      // Update DOM here
      document.getElementById("content").textContent = data.data;
      return { updated: true };
    }
  }
});

主なポイント:

  • 完全な型安全性(TypeScript 使用時)
  • 双方向(メインプロセスから WebView、WebView からメインプロセスを呼び出せます)
  • デフォルトで非同期
  • データを自動でシリアライズ(JSON)
アプリケーションメニュー
import { ApplicationMenu } from "electrobun/bun";

ApplicationMenu.setMenu([
  {
    label: "File",
    submenu: [
      { 
        label: "New Window", 
        accelerator: "CmdOrCtrl+N",
        action: () => createWindow()
      },
      { type: "separator" },
      { 
        label: "Quit", 
        accelerator: "CmdOrCtrl+Q",
        action: () => process.exit(0)
      },
    ]
  },
  {
    label: "Edit",
    submenu: [
      { role: "undo" },
      { role: "redo" },
      { type: "separator" },
      { role: "cut" },
      { role: "copy" },
      { role: "paste" },
    ]
  }
]);

組み込みのロール: undo、redo、cut、copy、paste、selectAll、minimize、close、quit

コンテキストメニュー
import { ContextMenu } from "electrobun/bun";

win.on("context-menu", (event) => {
  ContextMenu.show([
    { label: "Copy", action: () => { /* copy logic */ } },
    { type: "separator" },
    { label: "Paste", action: () => { /* paste logic */ } },
  ]);
});
システムトレイ
import { Tray } from "electrobun/bun";

const tray = new Tray({
  icon: "assets://tray-icon.png",
  tooltip: "My App",
  menu: [
    { label: "Show", action: () => win.show() },
    { label: "Hide", action: () => win.hide() },
    { type: "separator" },
    { label: "Quit", action: () => process.exit(0) },
  ]
});

// Update icon dynamically
tray.setIcon("assets://tray-icon-active.png");
自動アップデーター
import { Updater } from "electrobun/bun";

const updater = new Updater({
  url: "https://updates.myapp.com/latest.json",
  autoCheck: true,
  interval: 60 * 60 * 1000, // Check every hour
});

updater.on("update-available", async (info) => {
  console.log("Update available:", info.version);
  // Show dialog, then:
  updater.downloadAndInstall();
});

updater.on("update-downloaded", () => {
  console.log("Update ready, restart to apply");
});

updater.on("error", (err) => {
  console.error("Updater error:", err);
});
パスとアセット
import { paths } from "electrobun/bun";

// OS directories
paths.appData      // App data directory
paths.userData     // User-specific data
paths.resources    // Bundled resources
paths.home         // User home directory
paths.temp         // Temporary directory

// Reference bundled assets:
// views://viewname/file.html  → views folder
// assets://file.png           → assets folder

例:永続化される状態

import { join } from "path";

const stateFile = join(paths.userData, "state.json");

// Read
const state = JSON.parse(await Bun.file(stateFile).text() || "{}");

// Write
await Bun.write(stateFile, JSON.stringify(state));
ウィンドウイベント
win.on("close", () => {
  console.log("Window closing");
});

win.on("resize", ({ width, height }) => {
  console.log("Window resized:", width, height);
});

win.on("move", ({ x, y }) => {
  console.log("Window moved:", x, y);
});

win.on("focus", () => console.log("Window focused"));
win.on("blur", () => console.log("Window blurred"));
アプリのライフサイクルイベント
import { app } from "electrobun/bun";

app.on("ready", () => {
  console.log("App ready, create windows");
});

app.on("before-quit", () => {
  console.log("App about to quit, cleanup");
});

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

ビルド設定

electrobun.config.ts:

import { defineConfig } from "electrobun";

export default defineConfig({
  app: {
    name: "My App",
    version: "1.0.0",
    identifier: "com.example.myapp",
  },
  build: {
    main: "src/bun/main.ts",
    views: {
      mainview: "src/views/mainview/index.ts",
      settings: "src/views/settings/index.ts",
    },
  },
  icons: {
    mac: "assets/icon.icns",
    win: "assets/icon.ico",
    linux: "assets/icon.png",
  },
  updates: {
    provider: "generic",
    url: "https://updates.myapp.com",
  },
});

よく使われるパターン

複数のウィンドウ
const windows = new Map<string, BrowserWindow>();

function createWindow(id: string, url: string) {
  const win = new BrowserWindow({
    url,
    width: 800,
    height: 600,
  });
  
  windows.set(id, win);
  
  win.on("close", () => {
    windows.delete(id);
  });
  
  return win;
}
外部リンクを開く
import { shell } from "electrobun/bun";

// In main process
shell.openExternal("https://example.com");

// In webview, intercept link clicks
document.addEventListener("click", (e) => {
  const link = (e.target as HTMLElement).closest("a");
  if (link && link.href.startsWith("http")) {
    e.preventDefault();
    electroview.rpc.openExternal(link.href);
  }
});
ドラッグ可能なタイトルバー

<div style="-webkit-app-region: drag; height: 40px; background: #333;">
  <h1 style="-webkit-app-region: no-drag;">My App</h1>
  <button style="-webkit-app-region: no-drag;">Click Me</button>
</div>
ファイルダイアログ
import { dialog } from "electrobun/bun";

// Open file
const result = await dialog.showOpenDialog({
  title: "Select File",
  filters: [
    { name: "Images", extensions: ["png", "jpg", "jpeg"] },
    { name: "All Files", extensions: ["*"] }
  ],
  properties: ["openFile", "multiSelections"]
});

if (!result.canceled) {
  console.log("Selected files:", result.filePaths);
}

// Save file
const saveResult = await dialog.showSaveDialog({
  title: "Save File",
  defaultPath: "untitled.txt",
  filters: [
    { name: "Text Files", extensions: ["txt"] },
  ]
});

プラットフォームに関する注意事項

macOS
  • WKWebView を使用します
  • 配布にはコード署名が必要です
  • Gatekeeper のために公証が必要です
  • 開発用に Xcode Command Line Tools をインストールしてください
Windows
  • WebView2(Edge)を使用します
  • 開発用に Visual Studio Build Tools が必要です
  • SmartScreen 対策としてコード署名を推奨します
Linux
  • WebKit2GTK を使用します
  • 開発用パッケージをインストールしてください:
    sudo apt install libgtk-3-dev libwebkit2gtk-4.1-dev
    

次のステップ

  • 高度なウィンドウ管理:複数ウィンドウのアプリと BrowserView については、electrobun-window-management スキルを参照してください
  • RPC パターン:型安全性とパフォーマンスについては、electrobun-rpc-patterns スキルを参照してください
  • ネイティブ UI:メニュー、トレイ、ダイアログについては、electrobun-native-ui スキルを参照してください
  • 配布:パッケージ化とアップデートについては、electrobun-distribution スキルを参照してください
  • デバッグ:トラブルシューティングについては、electrobun-debugging スキルを参照してください

リソース

コメント