DioxusLabs / dioxus

Fullstack app framework for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
20.32k stars 779 forks source link

Is there a way to show a spinner or loading... while it's waiting for the wasm to finish loading? #852

Closed knarkzel closed 1 year ago

knarkzel commented 1 year ago

Specific Demand

Show spinner while waiting for wasm to load.

Implement Suggestion

I think it's possible with Trunk, but I'm not sure. Any pointers would be helpful.

ealmloff commented 1 year ago

There are two approaches you can take here: 1) Use SSR + Hydration to hydrate a loading spinner (see this example for hydration)

[package]
name = "dioxus-hmr"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dioxus = "0.3"
dioxus-web = { version = "0.3", features = ["hydrate"] }

# server side rendering
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus-ssr = "0.3"
// run with:
// cargo run # to create index.html
// dioxus serve # to run in browser

use std::io::Write;

use dioxus::prelude::*;

fn main() {
    #[cfg(target_arch = "wasm32")]
    dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
    #[cfg(not(target_arch = "wasm32"))]
    {
        let template_pre = r#"<!DOCTYPE html>
<html class="dark:bg-slate-900 dark:text-slate-400">
<head>
  <title>My App</title>
  <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/output.css" />
  </head>
<body>
  <div id="main">"#;
        let template_post = r#"</div>
         <script type="module">
    import init from "/{base_path}/assets/dioxus/{app_name}.js";
    init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then(wasm => {
      if (wasm.__wbindgen_start == undefined) {
        wasm.main();
      }
    });
  </script>
</body>
</html>"#;
        let mut file = std::fs::File::create("index.html").unwrap();
        let mut vdom = VirtualDom::new(app);
        let _ = vdom.rebuild();
        let renderered = dioxus_ssr::pre_render(&vdom);
        file.write_fmt(format_args!("{template_pre}{renderered}{template_post}"))
            .unwrap();
    }
}

fn app(cx: Scope) -> Element {
    let loading = use_state(cx, || true);
    if **loading {
        // this will force a re-render on the client
        loading.set(false);
        render! {
            "loading..."
        }
    } else {
        render! {
            Counter {}
        }
    }
}

fn Counter(cx: Scope) -> Element {
    let count = use_state(cx, || 0);
    render! {
        div {
            button {
                onclick: move |_| {
                    count.set(**count + 1);
                },
                "Increment",
            }
            "Count: {count.get()}"
        }
    }
}

You wouldn't actually need a server here, you could just use ssr to generate the html with the spinner that then is swapped out immediately for the real page 2) Use use_eval with getElementById to remove a specific element after the wasm has loaded

Option 1 can also be used to create a shim of your page and have loading spinners in specific areas that need WASM to operate which is much more difficult with option 2.