trunk-rs / trunk

Build, bundle & ship your Rust WASM application to the web.
https://trunkrs.dev/
Apache License 2.0
3.53k stars 255 forks source link

How do I attach a function to window? #230

Closed WestXu closed 3 years ago

WestXu commented 3 years ago

Newbie here.

Previously I use wasm-pack doing this:

<script type="module">
    console.log("Initializing wasm...")
    import init, { my_set_panic_hook, my_function, MyStruct } from './pkg/my_project.js';
    await init();
    my_set_panic_hook();
    console.log("Initialized.");

    window.my_function= my_function; 👈
    window.MyStruct = MyStruct; 👈
</script>
<script>
    window.my_function(); 👈
    window.MyStruct.new(); 👈
</script>

What's the equivalent pub fn main() using trunk?

And if this is not the right way to call wasm function from js, what is?

lukechu10 commented 3 years ago

Currently there isn't a way to access the generated wasm manually. Trunk automatically injects that first script tag into the generated html file.

Instead, you can set window.my_function and window.MyStruct from inside WASM by using web_sys and js_sys::Reflect

WestXu commented 3 years ago

Currently there isn't a way to access the generated wasm manually. Trunk automatically injects that first script tag into the generated html file.

Instead, you can set window.my_function and window.MyStruct from inside WASM by using web_sys and js_sys::Reflect

Is there any snippets your can share on this?

I tried this:

use js_sys::Reflect;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn my_function() -> Result<(), JsValue> {
    Ok(())
}

fn main() {
    let window = web_sys::window().expect("no global `window` exists");

    Reflect::set(&window, &"my_function".into(), &my_function.into())
        .expect("Failed reflecting my_function.");
}

But I got:

error[E0277]: the trait bound `wasm_bindgen::JsValue: From<fn() -> Result<(), wasm_bindgen::JsValue> {my_function}>` is not satisfied
  --> src/main.rs:17:63
   |
17 |     Reflect::set(&window, &"my_function".into(), &my_function.into())
   |                                                               ^^^^ the trait `From<fn() -> Result<(), wasm_bindgen::JsValue> {my_function}>` is not implemented for `wasm_bindgen::JsValue`
   |
   = help: the following implementations were found:
             <wasm_bindgen::JsValue as From<&'a String>>
             <wasm_bindgen::JsValue as From<&'a T>>
             <wasm_bindgen::JsValue as From<&'a str>>
             <wasm_bindgen::JsValue as From<Array>>
           and 70 others
   = note: required because of the requirements on the impl of `Into<wasm_bindgen::JsValue>` for `fn() -> Result<(), wasm_bindgen::JsValue> {my_function}`

error: aborting due to previous error; 1 warning emitted
lukechu10 commented 3 years ago

Ah yes, you must create a Closure instead and cast it into a JsValue. There are docs on how to do that here: https://docs.rs/wasm-bindgen/0.2.75/wasm_bindgen/closure/struct.Closure.html

thedodd commented 3 years ago

@WestXu looks like you are probably in need of something like this: https://github.com/thedodd/trunk/pull/184 Which allows customization of the app's init script.

thedodd commented 3 years ago

Otherwise, you could use the snippets system (https://trunkrs.dev/assets/#js-snippets) and pass along a Rust/WASM function for the JS function to use.

Not 100% sure what your requirements are. Perhaps describing what your ultimate goal is will help folks to find a viable solution.

WestXu commented 3 years ago

Ah yes, you must create a Closure instead and cast it into a JsValue. There are docs on how to do that here: https://docs.rs/wasm-bindgen/0.2.75/wasm_bindgen/closure/struct.Closure.html

Wow I literally don't understand a single word in this pile of whatever:

Closure::wrap(Box::new(|| {
    web_sys::console::log_1(&"inverval elapsed!".into());
}) as Box<dyn FnMut()>).as_ref().unchecked_ref(),;

I eventually go with this:

#[wasm_bindgen]
pub struct Wrapper {}

#[wasm_bindgen]
impl Wrapper {
    #[wasm_bindgen]
    pub fn my_function(&self) -> Result<(), JsValue> {
        Ok(())
    }
}

fn main() {
    Reflect::set(
        &web_sys::window().unwrap(),
        &JsValue::from("wrapper"),
        &JsValue::from(Wrapper {}),
    )
    .unwrap();
}

And use it like this:

<script>
    window.wrapper.my_function();
</script>

Yea, as long as it works.

WestXu commented 3 years ago

@WestXu looks like you are probably in need of something like this: #184 Which allows customization of the app's init script.

This is definitely helpful.

thedodd commented 3 years ago

@WestXu yea, until browsers start exposing a more direct WASM ABI with something similar to WASI, the browser ABI will have this rough FFI feel to it. Lots of frameworks offer a slightly nicer pattern on top of this, but ultimately you are passing a boxed fn as a closure.

WestXu commented 3 years ago

@WestXu yea, until browsers start exposing a more direct WASM ABI with something similar to WASI, the browser ABI will have this rough FFI feel to it. Lots of frameworks offer a slightly nicer pattern on top of this, but ultimately you are passing a boxed fn as a closure.

I won't blame it though. This project is amazing already. I'm happy with what it is doing now.

Feel free to close it.

thedodd commented 3 years ago

Sounds good! Thanks for the discussion and sharing your findings.