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 compling then storing compiled version and useing hash to check for changes
- Figure out how to hook in iced tasks and subscriptions for async operations
- Rust and rustup installed
- mise installed
- Clone the repository:
git clone https://github.com/n1ght-hunter/igloo.git
cd igloo- Install required Rust target:
just setup
# or manually:
rustup target add wasm32-wasip2
mise install- Build plugins and run the example:
# Build all plugins (Rust, TypeScript, Python) and run
just build-plugins
just run
# Or build individual plugins:
just build-rust # Build Rust plugin
just build-js # Build TypeScript/JavaScript plugin
just build-py # Build Python pluginIgloo 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 required traits:
use igloo_guest::*;
#[derive(Debug, Clone)]
pub enum MyMessage {
ButtonPressed,
// ... other messages
}
pub struct MyPlugin {
counter: u32,
}
impl MyPlugin {
pub fn new() -> Self {
Self { counter: 0 }
}
pub fn update(&mut self, message: MyMessage) {
match message {
MyMessage::ButtonPressed => {
self.counter += 1;
}
}
}
pub fn view(&self) -> Element {
column![
text!("Count: {}", self.counter),
button("Click me").on_press(MyMessage::ButtonPressed)
].into()
}
}
impl igloo_guest::Application<MyPlugin, MyMessage> for MyPlugin {
fn new() -> Self
where
Self: Sized,
{
MyPlugin::new()
}
fn view(&self) -> Element<MyMessage> {
self.view()
}
fn update(&mut self, message: MyMessage) {
self.update(message);
}
}
// Export the plugin
igloo_guest::export_guest!(MyPlugin, MyMessage);- 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 test_host::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:
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:
just run - Generate bindings:
just 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.
