Skip to content

Wasm splitting in yew#3932

Open
WorldSEnder wants to merge 7 commits intoyewstack:masterfrom
WorldSEnder:split-wasm
Open

Wasm splitting in yew#3932
WorldSEnder wants to merge 7 commits intoyewstack:masterfrom
WorldSEnder:split-wasm

Conversation

@WorldSEnder
Copy link
Member

Description

Add a way to split the wasm bundle in multiple components. This modifies the build process, and uses relocation information emitted by llvm to identify where to "split". There's a bit of glue code in yew to ensure that messages sent to the lazy component are processed and properties are passed along without additional cloning.

The main part of the solution lives in https://github.com/WorldSEnder/wasm-split-prototype as of now. This was implemented in collaboration with the maintainer of leptos.

Checklist

  • I have reviewed my own code
  • I have added examples

@WorldSEnder WorldSEnder requested review from jstarry and ranile October 14, 2025 21:01
github-actions[bot]
github-actions bot previously approved these changes Oct 14, 2025
@WorldSEnder
Copy link
Member Author

WorldSEnder commented Oct 14, 2025

The review pings are mostly because I think both of you will find this interesting, not necessarily as an invite to dig into the code and give meaningful suggestions for improvements (but feel free to if you have the time).

makes it possible to implement a helper macro without the user
pulling in an extra dependency and has tighter version requirements.
github-actions[bot]
github-actions bot previously approved these changes Oct 16, 2025
@github-actions
Copy link

github-actions bot commented Oct 16, 2025

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.433 ns      │ 2.548 ns      │ 2.436 ns      │ 2.439 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.482 ns      │ 2.645 ns      │ 2.484 ns      │ 2.489 ns      │ 100     │ 1000000000

@github-actions
Copy link

github-actions bot commented Oct 16, 2025

Benchmark - SSR

Yew Master

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 291.043 291.476 291.200 0.137
Hello World 10 493.135 503.324 496.541 3.407
Function Router 10 1623.159 1640.431 1628.389 5.161
Concurrent Task 10 1005.632 1006.962 1006.313 0.447
Many Providers 10 1121.940 1150.005 1131.421 8.492

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.426 310.973 310.812 0.184
Hello World 10 480.828 495.118 485.216 4.636
Function Router 10 1603.860 1627.602 1614.185 8.289
Concurrent Task 10 1005.786 1007.013 1006.487 0.420
Many Providers 10 1082.972 1143.054 1108.569 18.418

@github-actions
Copy link

Visit the preview URL for this PR (updated for commit 3cf0e6e):

https://yew-rs-api--pr3932-split-wasm-8cld9gqn.web.app

(expires Wed, 29 Oct 2025 11:11:55 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

let suspension = Suspension::from_future(async move {
// Ignore error in case receiver was dropped
let vtable = C::fetch().await;
let comp = (vtable.imp.create)(&creation_ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates the underlying component with creation_ctx, whose link() returns inner_scope. But inner_scope is never mounted — its state is permanently None.

This makes function component hooks permanently broken after load.

When a function component is created, FunctionComponent::new() captures ctx.link().clone() into a re_render closure:

  let re_render = {
      let link = ctx.link().clone(); // this is inner_scope
      Rc::new(move || link.send_message(()))
  };

Every hook (use_state, use_reducer, etc.) shares this closure. When a state setter fires, it calls inner_scope.send_message(()), which schedules an UpdateRunner. But UpdateRunner::run() checks inner_scope.state, finds None, and silently returns. The component renders once correctly and then becomes completely unresponsive to all state changes. I've confirmed this — a simple use_state counter wrapped in declare_lazy_component! renders 0 and clicking the increment button does nothing.

I created a minimal reproducing repo https://github.com/Madoshakalaka/yew-lazy-hook-bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants