diff --git a/Cargo.lock b/Cargo.lock
index 9ee1f740c66..72de516da99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,12 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+[[package]]
+name = "async-once-cell"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a"
+
[[package]]
name = "async-trait"
version = "0.1.89"
@@ -187,6 +193,12 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "base16"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -2909,6 +2921,17 @@ dependencies = [
"digest",
]
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "shlex"
version = "1.3.0"
@@ -2981,6 +3004,17 @@ dependencies = [
"lock_api",
]
+[[package]]
+name = "split-wasm"
+version = "0.1.0"
+dependencies = [
+ "async-once-cell",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "yew",
+]
+
[[package]]
name = "ssr-e2e"
version = "0.1.0"
@@ -3880,6 +3914,28 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "wasm_split_helpers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84"
+dependencies = [
+ "async-once-cell",
+ "wasm_split_macros",
+]
+
+[[package]]
+name = "wasm_split_macros"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0"
+dependencies = [
+ "base16",
+ "quote",
+ "sha2",
+ "syn 2.0.117",
+]
+
[[package]]
name = "web-sys"
version = "0.3.91"
@@ -4234,6 +4290,7 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
name = "yew"
version = "0.23.0"
dependencies = [
+ "async-once-cell",
"base64ct",
"bincode 2.0.0-rc.3",
"console_error_panic_hook",
@@ -4254,6 +4311,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
+ "wasm_split_helpers",
"web-sys",
"yew-macro",
]
diff --git a/examples/split-wasm/.cargo/config.toml b/examples/split-wasm/.cargo/config.toml
new file mode 100644
index 00000000000..b65cc8fb321
--- /dev/null
+++ b/examples/split-wasm/.cargo/config.toml
@@ -0,0 +1,2 @@
+[target.'cfg(target_arch = "wasm32")']
+rustflags=["-Clink-args=--emit-relocs"]
diff --git a/examples/split-wasm/Cargo.toml b/examples/split-wasm/Cargo.toml
new file mode 100644
index 00000000000..0e7ac495cc9
--- /dev/null
+++ b/examples/split-wasm/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "split-wasm"
+version = "0.1.0"
+authors = []
+edition = "2021"
+license = "MIT OR Apache-2.0"
+
+[dependencies]
+async-once-cell = "0.5.3"
+yew = { path = "../../packages/yew", features = ["csr"] }
+wasm-bindgen = "*"
+wasm-bindgen-futures = "*"
+
+[dependencies.web-sys]
+version = "0.3"
+features = ["HtmlInputElement"]
diff --git a/examples/split-wasm/build.sh b/examples/split-wasm/build.sh
new file mode 100755
index 00000000000..4f7b6b3d399
--- /dev/null
+++ b/examples/split-wasm/build.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+set -e
+shopt -s extglob
+
+CARGO="cargo"
+WASM_BINDGEN=~/.cache/trunk/"$(cargo tree --package wasm-bindgen --depth=0 --format="{p}" -e normal | sed -e 's/ v/-/g')/wasm-bindgen"
+echo "$WASM_BINDGEN"
+WASM_OPT="$(ls -dv1 ~/.cache/trunk/wasm-opt-version_* | tail -n 1)"/bin/wasm-opt # will select most recently installed :)
+
+PROFILE="release"
+THIS_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
+TARGET_DIR=$(cd -- "$THIS_DIR"/../../target/ &> /dev/null && pwd)
+OPT=1
+
+$CARGO build --target wasm32-unknown-unknown \
+ $(case $PROFILE in "debug") ;; "release") echo "--release" ;; *) echo '--profile "${PROFILE}"' ;; esac)
+
+mkdir -p dist/
+GLOBIGNORE=".:.."
+rm -rf dist/*
+mkdir dist/.stage
+(
+ wasm_split_cli --verbose "$TARGET_DIR/wasm32-unknown-unknown/${PROFILE}/split-wasm.wasm" "$THIS_DIR"/dist/.stage/ \
+ > "$THIS_DIR"/dist/.stage/split.log
+)
+echo "running wasm-bindgen"
+$WASM_BINDGEN dist/.stage/main.wasm --out-dir dist/.stage --no-demangle --target web --keep-lld-exports --no-typescript
+if [ "$OPT" == 1 ] ; then
+ echo "running wasm-opt"
+ for wasm in dist/.stage/!(main).wasm ; do
+ $WASM_OPT -Os "$wasm" -o dist/"$(basename -- "$wasm")"
+ done
+else
+ for wasm in dist/.stage/!(main).wasm ; do
+ mv "$wasm" dist/"$(basename -- "$wasm")"
+ done
+fi
+echo "moving to dist dir"
+mv dist/.stage/*.!(wasm) dist
+#rmdir dist/.stage
+cp index.html dist/
+
diff --git a/examples/split-wasm/index.html b/examples/split-wasm/index.html
new file mode 100644
index 00000000000..6d96bbcd761
--- /dev/null
+++ b/examples/split-wasm/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ Yew • Split WASM
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/split-wasm/src/main.rs b/examples/split-wasm/src/main.rs
new file mode 100644
index 00000000000..4e6c354bc56
--- /dev/null
+++ b/examples/split-wasm/src/main.rs
@@ -0,0 +1,20 @@
+use std::cell::Cell;
+
+use wasm_bindgen::prelude::wasm_bindgen;
+
+// You can use a global variable from javascript, or a static
+// and even thread local variable without any changes.
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(thread_local_v2, js_name = "globalFoo")]
+ static GLOBAL_FOO: u32;
+}
+thread_local! {
+ static COUNTER: Cell = const { Cell::new(0) };
+}
+
+mod yew;
+
+pub fn main() {
+ yew::main();
+}
diff --git a/examples/split-wasm/src/yew.rs b/examples/split-wasm/src/yew.rs
new file mode 100644
index 00000000000..6f428c74f2c
--- /dev/null
+++ b/examples/split-wasm/src/yew.rs
@@ -0,0 +1,70 @@
+use std::future::{pending, Future};
+
+use web_sys::HtmlInputElement;
+use yew::lazy::declare_lazy_component;
+use yew::prelude::*;
+use yew::suspense::Suspension;
+use yew::Renderer;
+
+use super::{COUNTER, GLOBAL_FOO};
+
+// ---------------------------------------------------------------------------
+// A counter component — the one we'll load lazily.
+// Uses use_state, which triggers re-renders via the scope it was created with.
+// ---------------------------------------------------------------------------
+
+#[derive(Clone, Debug, PartialEq, Properties)]
+pub struct CounterProps {
+ pub label: AttrValue,
+}
+
+#[component]
+fn Counter(props: &CounterProps) -> Html {
+ let count = use_state(|| 0_i32);
+ let onclick = {
+ let count = count.clone();
+ Callback::from(move |_| count.set(*count + 1))
+ };
+ let global_foo = GLOBAL_FOO.with(|f| *f);
+ let render_counter = COUNTER.with(|cnt| {
+ let c = cnt.get();
+ cnt.set(c + 1);
+ c
+ });
+
+ html! {
+
+
{"This component is loaded from a separate bundle, render count: "}{render_counter}
+
{"Here is a number loaded from (shared) memory: "}{global_foo}
+
{ &props.label }
+
{ *count }
+
+
+ }
+}
+
+declare_lazy_component!(Counter as LazyAddition in lazy_addition);
+
+#[component]
+fn Pending() -> HtmlResult {
+ Err(Suspension::from_future(pending()).into())
+}
+
+#[component]
+fn App() -> Html {
+ let toggle = use_state(|| false);
+ let show = *toggle;
+ html! {
+ <>
+ ().checked())} />
+
+ {"not yet loaded"} }}>
+ if show { } else { }
+
+ >
+ }
+}
+
+pub fn main() {
+ let _ = Renderer::::new().render();
+}
diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml
index 36c4f538147..6ec4197879a 100644
--- a/packages/yew/Cargo.toml
+++ b/packages/yew/Cargo.toml
@@ -34,6 +34,8 @@ serde = { workspace = true, features = ["derive"] }
tracing = "0.1.44"
tokise = "0.2.0"
rustversion.workspace = true
+wasm_split_helpers = "0.2.0"
+async-once-cell = "0.5.3"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures.workspace = true
diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs
index c9bb5ad1de5..47d7f34bb06 100644
--- a/packages/yew/src/html/component/mod.rs
+++ b/packages/yew/src/html/component/mod.rs
@@ -53,6 +53,12 @@ impl Context {
&self.props
}
+ /// The component's props as an Rc
+ #[inline]
+ pub(crate) fn rc_props(&self) -> &Rc {
+ &self.props
+ }
+
#[cfg(feature = "hydration")]
pub(crate) fn creation_mode(&self) -> RenderMode {
self.creation_mode
diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs
index 88ec635377b..3ceceaa2d43 100644
--- a/packages/yew/src/html/component/scope.rs
+++ b/packages/yew/src/html/component/scope.rs
@@ -390,7 +390,7 @@ mod feat_csr_ssr {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
- use crate::html::component::lifecycle::UpdateRunner;
+ use crate::html::component::lifecycle::{RenderRunner, UpdateRunner};
use crate::scheduler::{self, Shared};
#[derive(Debug)]
@@ -473,6 +473,16 @@ mod feat_csr_ssr {
scheduler::start();
}
+ #[inline]
+ pub(crate) fn schedule_render(&self) {
+ scheduler::push_component_render(
+ self.id,
+ Box::new(RenderRunner {
+ state: self.state.clone(),
+ }),
+ );
+ }
+
#[inline]
pub(super) fn arch_send_message(&self, msg: T)
where
diff --git a/packages/yew/src/lazy.rs b/packages/yew/src/lazy.rs
new file mode 100644
index 00000000000..05b62488f84
--- /dev/null
+++ b/packages/yew/src/lazy.rs
@@ -0,0 +1,236 @@
+//! Implements lazy fetching of components
+
+// A simple wrapper is easy to implement. This module exists to support message passing and more
+// involved logic
+
+use std::cell::RefCell;
+use std::future::Future;
+use std::rc::Rc;
+
+use crate::html::Scope;
+use crate::scheduler::Shared;
+use crate::suspense::Suspension;
+use crate::virtual_dom::VComp;
+use crate::{BaseComponent, Context};
+
+type ScopeRef = Shared