Incular logoIncular
API ReferencePlatform & Testing

Hot reload

Patch application code into a running desktop app without losing state.

[dependencies]
incular = "0.1"

[features]
hot-reload = ["incular/hot-reload"]

Hot reload swaps edited function bodies into the running process and rebuilds every window in place. Signals, scroll offsets, focus, text input and open windows all keep their values, because only code is replaced.

Builds are driven by the Dioxus CLI (dx), which compiles each edit into a small patch library. Run it from your application crate and name the desktop platform you are on (--windows, --macos or --linux):

dx serve --hot-patch --windows --features hot-reload

The feature does nothing in release builds or when the app is started any other way, such as cargo run.

To try it from a checkout of the repository, serve the bundled example and edit examples/hot_reload/main.rs while it runs:

dx serve --hot-patch --windows -p incular-hot-reload-example

What gets reloaded

CodeAfter a patch
Window root builders (Application::new, open_window_with, restorable window factories)New code, immediately
Everything a root builder constructs: child widgets, nested builders, event callbacksNew code, immediately
RouteRegistry page buildersNew code on the next navigation; pages already on the stack keep their built content
Closures created once outside a build, such as an Effect, a spawned task, or a listener registered in mainOld code until they are created again
main itself, statics, and code in dependenciesNot reloaded; restart

Limits

  • A patch replaces code, never data. Changing a struct's fields, a closure's captures, or a signal's value type needs a restart.
  • Only the crate being served is patched; edits to its dependencies are not.
  • Serve a binary crate that nothing else in the workspace depends on. With dx 0.7.5, --example targets of a library package are not reliably patched: sources outside the package directory are not watched, and a package with dependents in the same workspace fails the patch build.
  • Keep root builders in a stable position. Adding or removing closures above a root builder in the same function can change which closure a patch maps to; moving the UI into a named fn(&mut BuildContext) -> Widget avoids this.
use incular::prelude::*;

fn app(_cx: &mut BuildContext) -> Widget {
    Text::new("Edit me while the app runs").into()
}

fn main() {
    let app = Application::new(app).expect("valid application");
    incular::run(app).expect("native application");
}

Without the Dioxus CLI

The patch transport is a thin layer over two runtime operations. A custom driver can deliver patches itself and call Application::apply_hot_patch(patch), or call Application::reassemble() to rebuild every window after swapping code some other way.

Related: DevTools.

On this page