javascriptrustwebassemblywasm-bindgen

How to import JavaScript functions with wasm-bindgen with --target no-modules?


I am trying to work out how to call a JavaScript function using Rust and wasm-bindgen. Due to lack of browser support, I cannot use wasm-bindgen with ES6 modules together with a Web Worker.

As far as I can tell, declaring that a JavaScript function exists for me to call on the Rust side is straightforward

#[wasm_bindgen]
extern {
    fn logProgress(percent: f64);
}

I have no idea where to define the JavaScript implementation however. If I try to just call a Rust function from JavaScript that calls the undefined logProgress then I get a runtime error: Error: logProgress is not defined

I can see from the wasm-bindgen docs that if I was using wasm-bindgen with ES6 modules then I could change the rust code to

#[wasm_bindgen(module = "/progress.js")]
extern {
    fn logProgress(percent: f64);
}

and declare the JavaScript function in progress.js

export function logProgress(percent) {
    console.log(percent)
    // actual implementation would not just log
}

Since I am instead importing my Rust APIs via the wasm_bindgen global, I presume I should be able to define the implementation somewhere around the same part in my Web Worker, but I have searched through a lot of docs and can't find anything on how to do this.

importScripts('foo_wasm.js')
wasm_bindgen('foo_wasm_bg.wasm').then(fooWasmModule => {
    memory = fooWasmModule.memory
    const { Foo, Bar, Baz, foobar } = wasm_bindgen;
    // JS has 'imported' the Rust structs and functions
    // How to 'export' the JS functions to Rust?
}

Solution

  • Guided by @eggyal comment. wasm-bindgen provides a WorkerGlobalScope type inside web-sys crate, however, you cannot use it as it will not have the functions you have defined in your own worker.

    So instead what you want to do is to rely on ducktyping. You define your own WorkerGlobalScope type and then treat imports as methods of the object.

    #[wasm_bindgen]
    extern "C" {
      type WorkerGlobalScope;
    
      #[wasm_bindgen(method)]
      fn logProgress(this: &WorkerGlobalScope, percent: f64);
    }
    

    You then make use of it like so:

    #[wasm_bindgen]
    pub fn call_from_worker() {
      let global = js_sys::global().dyn_into::<WorkerGlobalScope>();
      if let Ok(worker) = global {
        worker.logProgress(99.9);
      }
    }
    

    The downside is that the cast will fail if you try to use it outside a worker.