Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wasm32-wasi target for SSR #295

Closed
andreanidouglas opened this issue Jan 10, 2023 · 12 comments
Closed

Support wasm32-wasi target for SSR #295

andreanidouglas opened this issue Jan 10, 2023 · 12 comments
Labels
enhancement New feature or request

Comments

@andreanidouglas
Copy link

andreanidouglas commented Jan 10, 2023

for server rendering, if you want to run the wasm in a runner without v8 functionality, you would probably will need a wasm32-wasi target.

unfortunately, due to some dependencies, mostly wasm-bindgen, leptos code won't run, because of binds to JS specific imports.

$ cargo build --target=wasm32-wasi --release
$ wasmtime /target/wasm32-wasi/release/lepts-ssr-worker.wasm

Error: Failed to instantiate component 'lepts-ssr-worker'

Caused by:
    unknown import: `__wbindgen_placeholder__::__wbindgen_object_drop_ref` has not been defined
Error: exit status: 1
@ThePrimeagen
Copy link

ah yes, this is what was killing me :)

very much interestingness!

@gbj
Copy link
Collaborator

gbj commented Jan 10, 2023

This is somewhat weird; when you're compiling for any other target I'm aware of, wasm-bindgen will compile but panic if you try to run it.

I guess I haven't tried this before! It should be relatively easy to make the dependencies on wasm-bindgen and probably web-sys optional.

Edit: Ahhh I see of course, CloudFlare Workers are running V8 which is why I haven't run into this before.

@ThePrimeagen
Copy link

yaya!

I was trying to get leptos to run with spin and it would fail with the linked error :)

@gbj
Copy link
Collaborator

gbj commented Jan 11, 2023

Huh this is actually a much bigger issue than I would have thought. It is not just that wasm-bindgen can't be a dependency; as a result, nothing else that depends on wasm-bindgen can be a dependency. This means that you can't even import types from web-sys (like all the event types and HTML element types), even if you don't actually use them at runtime. This completely messes with the ability to do things like create event handlers:

let (name, set_name) = create_signal(cx, "The name is ThePrimeagen".to_string());

// this can't compile for WASI because we're not allowed to have `web-sys`
// and the type of `ev` is inferred to be `web_sys::InputEvent`
let on_change = move |ev| {
  set_name(event_target_value(&ev));
};

view! { cx,
  <input on:change=on_change type="text" value=name/>
}

For normal targets, this compiles fine—it would panic if you tried to run the click handler on the server, but you obviously never do. With WASI/Spin, you compile but get

unknown import: `__wbindgen_placeholder__::__wbindgen_describe` has not been defined

I could hypothetically create like a wasi feature, but your code would get super ugly because you'd need to feature-gate out all the event listeners

let (name, set_name) = create_signal(cx, "The name is ThePrimeagen".to_string());

// gross
#[cfg(not(feature = "wasi"))]
let on_change = move |ev| {
  set_name(event_target_value(&ev));
};

// then I'd have to exclude on:change from the SSR code--fine
view! { cx,
  <input on:change=on_change type="text" value=name/>
}

There are basically four ways out of this:

  1. Leptos mocks and reexports most of web-sys, in a way that uses web-sys on the client and stubs on the server, so that it can support WASI by not using wasm-bindgen. Yikes.
  2. Leptos adds a wasi feature. Opting into it means you lose web-sys types for this and need the opt out as in the example above. Kind of gross.
  3. Use a Wasm-based serverless platform like Cloudflare Workers that's built on V8, so does support wasm-bindgen.
  4. Use a platform that lets you deploy native binaries so you can build on top of an Actix or Axum server to serve your app.

Personally I think 3 or 4 is best. I have most of a PR for 2 but I think it's a pretty painful solution.

@gbj gbj added the enhancement New feature or request label Jan 11, 2023
@akesson
Copy link
Contributor

akesson commented Jan 11, 2023

Related: rustwasm/wasm-bindgen#2471 but doesn't seem like it will be solved anytime soon.

Maybe a

  1. Fork web-sys and webidl and hack the webidl generation code to also generate wasm-bindgen dependency-free types.

@andreanidouglas
Copy link
Author

Is it the responsibility to have this implementation on the view framework or the runner itself?

let say, yew and sycamore have the same problem? Would the bindgen free implementation duplicated throughout this code bases? Would React or Svelte generate all the JS specific code, to run on a JS engine without this native support?
imo: the runner should support if it wants to serve such applications

@gbj
Copy link
Collaborator

gbj commented Jan 11, 2023

let say, yew and sycamore have the same problem? Would the bindgen free implementation duplicated throughout this code bases?

Yew and Sycamore (and every other Rust/Wasm web framework I've seen) have an even worse version of the same problem: they use #[cfg(target_arch = "wasm32")] to check "am I in the browser?" in various places which means, as far as I can tell, you can't really run them on the server side as Wasm even in a V8 Wasm environment like Cloudflare Workers.

Would React or Svelte generate all the JS specific code, to run on a JS engine without this native support?

I'm what the React/Svelte part of your question means.

imo: the runner should support if it wants to serve such applications

Yeah it seems like a pretty fundamental problem if they want to support rendering any Rust frontend frameworks in a WASI environment.

@andreanidouglas
Copy link
Author

@gbj React/Svelte were just examples of JS frameworks, to illustrate the point

what if nodejs didn't have the necessary infrastructure to support reactivity. Who would be responsible for the implementation? Maybe a bad example ;)

@gbj
Copy link
Collaborator

gbj commented Jan 11, 2023

Ah I see what you ean. Yeah, it's a little more like "Everyone knows you can't actually access Web APIs in NodeJS. But what if NodeJS did not allow your source JS files to contain any references to Web APIs (like DOM elements or events), even if you don't try to run them?" Yeah, then server-side rendering any meaningful app with Node would be impossible.

@gbj
Copy link
Collaborator

gbj commented Jan 11, 2023

Great news! The answer here is simpler than I thought: I know nothing about wasmtime but apparently you can run wasmtime --trap-unknown-imports and this error goes away.

I don't know if you can pass this as an argument via Spin but it should resolve the issue for you.

@nilslice
Copy link

@gbj - I'd be surprised if this actually works though when running the module. If code attempts to call one of the wbg imports, the runtime will trap? that should stop execution. Were you able to run the module with this flag and see it behave the way you'd expect it to?

@gbj
Copy link
Collaborator

gbj commented Jan 12, 2023

@nilslice This is exactly my point: every Rust web framework that tries to handle frontend and backend code, is set up such that it can compile code that references wasm-bindgen functions without ever running them. The issue was that code that was compiled with references to wasm-bindgen types or functions, even if they were never used at runtime, could not be run in wasmtime.

In the Leptos case for example,

#[component]
fn SimpleExample(cx: Scope) -> impl IntoView {
  let (name, set_name) = create_signal(cx, "The name is ThePrimeagen".to_string());

  let on_change = move |ev| { // ev is inferred to be `web_sys::MouseEvent`
    set_name(event_target_value(&ev));
  };

  view! { cx,
    <input on:change=on_change type="text" value=name/>
  }
}

The function on_change depends on a web-sys event type, which in turns draws on wasm-bindgen. It will never, ever be called on the server. The rest of the code translates into HTML rendering when you run it on the server.

The framework is set up to be able to compile wasm-bindgen code like this to a native binary, which is fine as long as you never try to run it. The issue was that wasmtime wouldn't even load code that had been compiled with wasm-bindgen as a dependency. --trap-unknown-imports was, I think, what I was missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants