Search code examples
rustwebassemblywasm-bindgen

How can I call a JavaScript function that is a module with wasm-bindgen?


I'm trying to use the Web3 JavaScript library from Rust and I'm stuck. The standard usage of the library starts with:

// In Node.js use: const Web3 = require('web3');

let web3 = new Web3(Web3.givenProvider || "ws://localhost:8545");

The module you're supposed to import is a constructor, that also has some other properties. My Rust code that's supposed to bind this API looks like this:

#[wasm_bindgen(module = "web3")]
extern "C" {
    type Web3;

    #[wasm_bindgen(constructor)]
    fn new(_: &Provider) -> Web3;

    type Provider;

    static givenProvider: Provider;
}

Which ends up outputting import { Web3, givenProvider } from 'web3'; and trying to run new Web3(...) which fails. It should be doing something like import * as Web3 from 'web3';, running new Web3(...) and referencing Web3.givenProvider.

How can I get wasm-bindgen to output code like that?


Solution

  • EDIT: The original answer is wrong. You can import things defined like that using wasm-bindgen, and they are legal ES6. Or at least the same concept is available in ES6 modules. They call them default exports/imports. It's a bit awkward, but the way to import them is to use js_name = "default". Like so:

    #[wasm_bindgen(module = "web3")]
    extern "C" {
        #[wasm_bindgen(js_name = "default")]
        type Web3;
    
        #[wasm_bindgen(constructor, js_class = "default")]
        fn new(_: &Provider) -> Web3;
    
        #[wasm_bindgen(static_method_of = Web3, getter, js_class = "default")]
        fn givenProvider() -> Provider;
    
        type Provider;
    }
    

    You need the js_class parameter on methods, it doesn't remember that Web3's js_name is default.


    Old, wrong answer:

    The reason you can't get wasm-bindgen to generate code like that is because it's not legal ES6. ECMAScript modules use named exports for everything. Web3 is actually a CommonJS module, and those do support having a single anonymous export.

    The reason it almost works is because I was using webpack, and webpack lets you import CommonJS modules using ES6 syntax, even though the semantics are slightly different.

    The solution is to make a little shim, exporting the CommonJS module from an ES6 module:

    export let Web3 = require('web3');
    

    Then this binding will work:

    #[wasm_bindgen(module = "/src/web3-wrapper.js")]
    extern "C" {
        type Web3;
    
        #[wasm_bindgen(constructor)]
        fn new(_: &Provider) -> Web3;
    
        #[wasm_bindgen(static_method_of = Web3, getter)]
        fn givenProvider() -> Provider;
    
        type Provider;
    }