Igloo is a modular plugin system for GUI applications, built with Rust, Iced, and the WebAssembly Component Model. It enables dynamic loading of UI components as secure, sandboxed WebAssembly plugins, making it easy to extend and customize desktop applications.
Igloo lets you:
-
Build desktop GUIs with plugins written in Rust or other WASM-compatible languages
-
Use WIT (WebAssembly Interface Types) for type-safe host/guest communication
-
Run plugins in a secure, isolated environment
-
Host Application: An Iced-based GUI application that manages and renders WebAssembly plugins
-
Guest Plugins: WebAssembly components that define UI elements and behavior
-
WIT (WebAssembly Interface Types): For type-safe communication between host and guest
-
Component Model: For secure, sandboxed plugin execution
- Type-Safe Plugin Communication: WIT-based interfaces for host/guest messaging
- Rich UI Components: Buttons, text, containers, layouts, and more
- Message Passing: Bidirectional communication between host and plugins
- Resource Management: Efficient handling of UI elements across WASM boundary
- Plugin Isolation: Secure sandboxed execution of plugin code
- Multi-Language Support: Rust, TypeScript/JavaScript, and Python
- Add iced canvas support
- Add cwasm plugin support and precompilation guide
- Auto cache plugins by compiling then storing compiled version and using hash to check for changes
- Figure out how to hook in iced tasks and subscriptions for async operations
- Rust and rustup installed
- mise installed (provides all other tooling and task running)
- Clone the repository:
git clone https://github.com/n1ght-hunter/igloo.git
cd igloo- Install required Rust target and tools:
mise run setup
# or manually:
rustup target add wasm32-wasip2
mise install- Build plugins and run the example:
# Build all plugins (Rust, TypeScript, Python) and run
mise run build-plugins
mise run run
# Or build individual plugins:
mise run //plugins/rust:build # Build Rust plugin
mise run //plugins/js:build # Build TypeScript/JavaScript plugin
mise run //plugins/python:build # Build Python pluginRun mise tasks ls --all to see every available task.
Igloo supports plugins written in multiple languages:
| Language | SDK | Status |
|---|---|---|
| Rust | igloo_guest |
Handwritten |
| TypeScript/JavaScript | igloo-ts |
Generated with Claude code, manually tested |
| Python | igloo_py |
Generated with Claude code, works but not checked much |
- Create a new Rust library with
crate-type = ["cdylib"] - Add
igloo_guestas a dependency - Implement the
Applicationtrait:
use igloo_guest::{
Element, Task,
widgets::{button, column, text},
};
#[derive(Debug, Clone)]
pub enum MyMessage {
ButtonPressed,
// ... other messages
}
pub struct MyPlugin {
counter: u32,
}
impl igloo_guest::Application for MyPlugin {
type Message = MyMessage;
/// Build the initial state and the task to run on startup.
fn new() -> (Self, Task<MyMessage>) {
(Self { counter: 0 }, Task::none())
}
fn view(&self) -> Element<MyMessage> {
column()
.push(text(format!("Count: {}", self.counter)))
.push(button(text("Click me")).on_press(MyMessage::ButtonPressed))
.into()
}
fn update(&mut self, message: MyMessage) -> Task<MyMessage> {
match message {
MyMessage::ButtonPressed => {
self.counter += 1;
Task::none()
}
}
}
}
// Export the plugin
igloo_guest::export_guest!(MyPlugin);- Compile to WASM:
cargo build --target wasm32-wasip2 --releaseimport { App, Text, Column, Button, MessageManager, ElementLike } from "igloo-ts";
type State = { count: number };
type Msg = "increment" | "decrement";
class CounterApp implements App<State, Msg> {
init(): State {
return { count: 0 };
}
update(state: State, msg: Msg): State {
switch (msg) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
}
}
view(state: State, messages: MessageManager<Msg>): ElementLike {
return Column.new()
.push(Text.new(`Count: ${state.count}`))
.push(Button.new(Text.new("+")).onPress(messages, () => "increment"))
.push(Button.new(Text.new("-")).onPress(messages, () => "decrement"));
}
}
export const { update, view } = createApp(new CounterApp());from igloo_py import App, igloo_app, Text, Column, Button, MessageManager, ElementLike
@igloo_app
class CounterApp(App[str]):
def __init__(self):
self.count = 0
def update(self, msg: str) -> None:
if msg == "increment":
self.count += 1
elif msg == "decrement":
self.count -= 1
def view(self, messages: MessageManager[str]) -> ElementLike:
return Column.new().push(
Text.new(f"Count: {self.count}")
).push(
Button.new(Text.new("+")).on_press(messages, lambda: "increment")
).push(
Button.new(Text.new("-")).on_press(messages, lambda: "decrement")
)use igloo::plugin_manager::PluginManager;
let mut plugin_manager = PluginManager::new()?;
plugin_manager.add_plugin_from_file("my-plugin", "path/to/plugin.wasm")?;
// In your update loop: returns an `iced::Task` the plugin asked to run.
let task = plugin_manager.plugin_update("my-plugin", message)?;
// In your view:
let plugin_view = plugin_manager.plugin_view("my-plugin")?;- Build all components:
cargo build - Build for WASM target:
cargo build --target wasm32-wasip2 - Run example:
mise run run - Generate bindings:
mise run //packages/igloo-ts:genandmise run //packages/igloo-py:gen
Warning: Loading plugins can be slow during development due to Wasmtime compilation.
Consider adding this to your Cargo.toml for faster builds or running in release mode:
# Optimize wasmtime/cranelift in dev builds for faster WASM compilation
[profile.dev.package.wasmtime]
opt-level = 3
[profile.dev.package.cranelift-codegen]
opt-level = 3
[profile.dev.package.regalloc2]
opt-level = 3or precompile your plugins using wasmtime compile like so:
wasmtime compile --target wasm32-wasip2 path/to/plugin.wasm -o path/to/plugin.cwasm- Iced - GUI framework
- Wasmtime - WebAssembly runtime
- wit-bindgen - Interface generation
- WebAssembly Component Model specification
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.