mcpField
E
electrobun

electrobun

github.com/gyorkluuDevelopment54 installs
Repository

About electrobun

electrobun is a desktop app framework built with TypeScript, Bun, and the system's native webview. It provides typed, bidirectional RPC between the main process and webview, alongside window, menu, and updater features.

Key features

  1. 01TypeScript development and bundling with Bun
  2. 02Use of the system's native webview
  3. 03Typed, bidirectional RPC between main and webview
  4. 04Windows, menus, system tray, and file dialogs
  5. 05App updater and lifecycle events

Use cases

  1. 01Starting a desktop app from React, Svelte, or other templates
  2. 02Building a desktop app with multiple windows
  3. 03Calling between the UI and main process
  4. 04Implementing menus, tray controls, or file selection

Install

Copy the command and run it in a terminal.

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

After installing, type/electrobunin Claude Code to confirm it loaded.

Details

From the project README

Electrobun

Electrobun is a TypeScript-first desktop app framework using Bun (runtime + bundler) and the system's native webview. Build apps that are ~14MB, with updates ~14KB, and startup <50ms.

Quick Start

Initialize New Project
bunx electrobun init          # Interactive scaffold
bun run dev                   # Development mode
bun run build                 # Production bundle

Choose from templates:

  • hello-world: Minimal starter
  • react-tailwind-vite: React + Tailwind + Vite
  • svelte: Svelte framework
  • photo-booth: Camera access example
  • multitab-browser: Tab-based browser example
Project Structure
my-app/
├── electrobun.config.ts      # Build configuration
├── src/
│   ├── bun/
│   │   └── main.ts           # Main process (Bun)
│   └── views/
│       └── mainview/
│           ├── index.html
│           └── index.ts      # Webview frontend
└── package.json

Core Concepts

Main Process vs Webview
  • Main Process (src/bun/main.ts): Runs in Bun, has full system access, manages windows, handles RPC
  • Webview Process (src/views/*/index.ts): Runs in OS webview, sandboxed, handles UI, calls RPC
BrowserWindow

Create and manage application windows.

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"],
});

Key Options:

  • title: Window title
  • url: Load URL (use views:// protocol for bundled views)
  • width, height: Window dimensions
  • x, y: Window position (optional)
  • frame: Show standard window frame (true/false)
  • styleMask: Array of window controls (macOS)
  • titleBarStyle: "default" | "hidden" | "hiddenInset"
  • trafficLightPosition: Custom position for macOS traffic lights

Methods:

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: Main ↔ Webview Communication

Electrobun's killer feature — typed, fast, bidirectional RPC.

Main Process (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 };
    }
  }
});

Key Points:

  • Fully type-safe (with TypeScript)
  • Bidirectional (main can call webview, webview can call main)
  • Async by default
  • Serialize data automatically (JSON)
Application Menu
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" },
    ]
  }
]);

Built-in roles: undo, redo, cut, copy, paste, selectAll, minimize, close, quit

Context Menu
import { ContextMenu } from "electrobun/bun";

win.on("context-menu", (event) => {
  ContextMenu.show([
    { label: "Copy", action: () => { /* copy logic */ } },
    { type: "separator" },
    { label: "Paste", action: () => { /* paste logic */ } },
  ]);
});
System Tray
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");
Auto Updater
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);
});
Paths & Assets
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

Example: Persistent State

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));
Window Events
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"));
App Lifecycle Events
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();
  }
});

Build Configuration

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",
  },
});

Common Patterns

Multiple Windows
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;
}
Opening External Links
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);
  }
});
Draggable Title Bar
<!-- In webview HTML -->
<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>
File Dialogs
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"] },
  ]
});

Platform Notes

macOS
  • Uses WKWebView
  • Requires code signing for distribution
  • Notarization required for Gatekeeper
  • Install Xcode Command Line Tools for development
Windows
  • Uses WebView2 (Edge)
  • Requires Visual Studio Build Tools for development
  • Code signing recommended for SmartScreen
Linux
  • Uses WebKit2GTK
  • Install development packages:
    sudo apt install libgtk-3-dev libwebkit2gtk-4.1-dev
    

Next Steps

  • Advanced window management: See electrobun-window-management skill for multi-window apps and BrowserView
  • RPC patterns: See electrobun-rpc-patterns skill for type safety and performance
  • Native UI: See electrobun-native-ui skill for menus, trays, and dialogs
  • Distribution: See electrobun-distribution skill for packaging and updates
  • Debugging: See electrobun-debugging skill for troubleshooting

Resources

Comments

Markdown supported