Architecture Overview
This page is the mental model for how an Electrobun app is built, what’s inside it, and what happens when it runs. Nothing here is required reading to ship an app — but when you want to know why the framework is shaped the way it is, this is the place.
The short version: Hutch does everything at build time and ships nothing; your main process owns the app at runtime; webviews render UI in isolation and talk to the main process over bridges; and distribution is a small self-extractor plus verified transactional updates served from static storage.
Build-time architecture
Hutch is the only build orchestrator. When you run a build, it:
- Loads tasks and any optional exact Electrobun or external package-manager
override from
hutch.config.ts. - Loads the app, build, and distribution description from
electrobun.config.ts. - Verifies the selected platform core archive in
~/.hutchand refreshes the generated project.hutch/devkitSDK projection. - Bundles TypeScript views using dependencies already installed by Hutch’s built-in resolver or the project’s explicitly selected external manager.
- Bundles a Cottontail or Bun main process, or compiles a Zig, Rust, Go, or Odin main process.
- Assembles the platform application bundle.
- For canary and stable builds, creates the self-extracting wrapper, compressed update archive, metadata, optional patch, and platform installer.
- Applies configured code signing and notarization.
Hutch and its compiler toolchains are not shipped inside the finished app — what your users download contains only what the app needs to run.
Hutch’s built-in npm-compatible resolver is the JavaScript default:
hutch install reads package.json, writes hutch.lock, and hutch pm exec
runs an already-installed binary from the nearest project’s
node_modules/.bin. The top-level hutch.config.ts packageManager selection
may instead name npm, Bun, pnpm, Yarn, or a custom executable; in that explicit
mode, hutch install and hutch pm ... delegate to the selected tool. Cargo,
Go modules, and other language managers keep their normal ownership.
Runtime architecture
Every Electrobun application contains:
- a small platform launcher
- the selected main-process runtime or native executable
- Electrobun Core and the platform native wrapper
- bundled application code and view assets
- optional CEF and WGPU libraries
- updater metadata and patch tooling
At runtime there’s a clear division of authority. The main process owns application state and privileged native objects; it opens windows, views, menus, trays, and GPU surfaces through Electrobun Core. Each webview is a separate native browsing context that communicates with the main process only through Electrobun’s event and RPC bridges — page JavaScript never holds privileged APIs directly. The renderer controls its exact operating-system process model.
Main-thread model
Native GUI frameworks require their event loop on the process main thread,
and Electrobun respects that instead of fighting it. Cottontail and Bun run
your application JavaScript on a worker while Electrobun Core owns the
main-thread event loop. Native SDKs expose the same arrangement directly:
application code initializes the core, starts its work on another thread
where needed, and enters runMainThread.
Application resources
The exact platform layout differs, but the resource model is consistent:
Resources/|-- app/| |-- bun/ # Bundled JavaScript main process, when selected| `-- views/ # Bundled and copied webview assets`-- version.json # Installed version and update metadataUse the views:// URL scheme to load files under Resources/app/views —
it resolves to the right place on every platform, so you never construct
filesystem paths to your own assets.
On macOS, executables and dynamic libraries live under Contents/MacOS,
assets under Contents/Resources, and optional frameworks under
Contents/Frameworks. Windows and Linux bundles use equivalent bin and
Resources directories.
Webview engines
System views are the default, which is a big part of why Electrobun apps stay small — the browser engine is already on the user’s machine:
- macOS: WKWebView
- Windows: WebView2
- Linux: WebKitGTK 4.1
When a pinned Chromium engine matters more than download size, bundle CEF. macOS and Windows applications can select the renderer per view. A Linux process uses its CEF wrapper when CEF is bundled and GTK WebKit otherwise.
Two custom elements extend what a webview can hold. <electrobun-webview>
creates a separately composited native browser view — a real independent
browsing context, not an iframe. <electrobun-wgpu> places a native GPU
surface in the same layout. Electrobun synchronizes their bounds, visibility,
masking, and input behavior with the host document, so they behave like
ordinary elements on the page.
Distribution wrapper
A canary or stable release wraps the full runnable app in a small native self-extractor whose payload is a Zstandard-compressed archive. On first launch, the wrapper verifies and extracts the application to its managed location, then starts the inner app — from the user’s perspective, the app just opens.
Hutch emits a DMG on macOS, a Setup ZIP on Windows, and a self-extracting setup archive on Linux. See Bundling and Distribution for the artifact layout.
Updates
The installed app compares its local hash with hosted update metadata, follows available hash-named patches or downloads the named compressed archive. A native post-exit helper then validates the bundle identity and replaces the app with rollback protection before relaunching it. The same transaction is used on Windows, macOS, and Linux; Windows uses Task Scheduler only to run the helper after locked processes exit.
There’s no update server to run — static object storage is sufficient. The application decides when to check, download, install, and relaunch through the Updater API.
Development output
Dev builds skip the distribution artifacts and route main-process, webview,
and native diagnostics to the invoking terminal. System webviews and CEF both
forward browser console output in development mode, so console.log in a
view shows up next to your main-process logs.