Code splittingCarve a subtree out of the main wasm bundle and load it on demand. The `lazy!` macro wraps a `ui!` block in a build-time split point; the chunk fetches the first time the boundary mounts, and native targets compile the block inline.
StatusCode splitting is a work in progress. The `lazy!` macro and the `Element::Lazy` runtime are wired end-to-end, but the underlying wasm-split toolchain is still settling — expect rough edges around chunk naming, dead-code elimination on the main bundle, and cold-load timing.The author surface below is the shape that will ship. Internals may move; the macro syntax is the part to learn first.
The `lazy!` macro`lazy!` wraps a block of UI. The block is interpreted exactly like a `ui!` body — its tail expression must implement `IntoElement`, so the same primitives, components, and helpers compose inside.Use it as a child expression inside a parent `ui!` block. The braces around `lazy! { ... }` are the standard `ui!` escape-to-Rust syntax; the macro returns a `LazyBuilder` that coerces into a `Element` through the surrounding `ui!`.use runtime_core::{lazy, ui};
ui! {
text { "always loaded" }
{ lazy! {
text { "loaded on demand from a separate chunk" }
} }
} What it expands toThe macro hoists the block into a `#[wasm_split]`-annotated async function. The build's wasm-split pass pulls that function (and its reachable callees) into a separate `.wasm` chunk; the main bundle keeps only a stub that fetches the chunk on first call.The hash is derived from the block's tokens plus the call-site span, so two identical-shaped `lazy!` blocks at different sites get distinct chunks. Names are stable across rebuilds when the source doesn't change.On non-wasm targets the `#[wasm_split]` attribute is transparent — the async fn compiles in, the loader resolves synchronously, and the subtree mounts inline.// What you write:
lazy! { text { "loaded on demand" } }
// What the macro expands to (roughly):
{
// Alias runtime-core's re-export so the attribute's
// wasm_split::... expansion resolves — no direct
// wasm-split dependency needed in your crate.
use ::runtime_core::__wasm_split as wasm_split;
#[::runtime_core::__wasm_split::wasm_split(__idealyst_lazy_<hash>)]
async fn __idealyst_lazy_body_<hash>(_: ()) -> Element {
use ::runtime_core::IntoElement as _;
{ ui! { text { "loaded on demand" } } }.into_element()
}
::runtime_core::primitives::lazy::lazy_split(|| {
Box::pin(__idealyst_lazy_body_<hash>(()))
})
} Placeholder and lifecycleThe `LazyBuilder` returned from `lazy!` exposes `.placeholder(...)` and `.on_state(...)` for the load window. The placeholder mounts immediately and is replaced when the chunk's `Element` is ready; `on_state` fires synchronously on each lifecycle transition so you can drive a spinner or error UI elsewhere in the tree.On native, the callback fires once with `LazyState::Rendered` and you never observe `Loading` or `Loaded` — the chunk is compiled in.lazy! { text { "heavy subtree" } }
.placeholder(|| ui! { text { "loading…" } })
.on_state(|state| match state {
LazyState::Loading => log::debug!("chunk fetch in flight"),
LazyState::Loaded => log::debug!("chunk fetched"),
LazyState::Rendered => log::debug!("subtree mounted"),
LazyState::Error(e) => log::warn!("lazy failed: {e}"),
}) v1 constraintsThe block cannot reference enclosing variables. The hoisted function is a plain `fn`, not a closure — it can't carry captured state. If you need to pass data in, hoist it to a signal or a route param the chunk reads itself. Capture forwarding via a typed `Args` struct is the v2 plan.The tail expression must coerce to `Element` via `IntoElement`. A `ui! { ... }` block satisfies this; so does a bare `Element::*` constructor, a `#[component]`-built builder, or another `LazyBuilder` (lazy boundaries nest).