electrobun について
electrobun は、TypeScript、Bun、システム標準の Webview を使うデスクトップアプリフレームワークです。メインプロセスと Webview 間の型安全な双方向 RPC に加え、ウィンドウ、メニュー、アップデーター機能を備えます。
主な機能
- 01Bun による TypeScript 開発とバンドル
- 02システム標準 Webview の利用
- 03メインプロセスと Webview 間の型安全な双方向 RPC
- 04ウィンドウ、メニュー、システムトレイ、ファイルダイアログ
- 05アプリのアップデーターとライフサイクルイベント
活用シーン
- 01React、Svelte などのテンプレートからデスクトップアプリを開始
- 02複数ウィンドウを持つデスクトップアプリの構築
- 03UI とメインプロセス間の呼び出し
- 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スキルを参照してください
リソース
コメント
Markdown 対応