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?
}
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.