Search code examples
rustwasm-bindgen

Is it possible to #[wasm_bindgen] public structs and functions defined in another crate?


I have a crate lib that contains tons of structs and impls. Then I have another one called web that will make the portability of the core lib to the web. There is also api in case I want the app to be server-side.

  • myproject-api
  • myproject-lib
  • myproject-web

What I don't want is to add all the wasm dependencies to lib, only in the web project and expose parts of the main library to the web. Is it possible to #[wasm_bindgen] the structs defined in the myproject-lib in myproject-web?


Solution

  • Not directly. The #[wasm_bindgen] attribute relies on being able to parse the struct and impls in order to generate the bindings. You would have to create wrapper types and functions for the attribute to bind to.

    Say your myproject-lib looks like:

    pub struct MyStruct {
        pub foo: i32,
    }
    
    impl MyStruct {
        pub fn bar(&self) {
            // do something
        }
    }
    

    The bindings would be implemented in myproject-web like:

    use myproject_lib::*;
    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen(js_name = MyStruct)]
    pub struct MyStructWrapper(MyStruct);
    
    #[wasm_bindgen(js_class = MyStruct)]
    impl MyStructWrapper {
        #[wasm_bindgen(getter)]
        pub fn foo(&self) -> i32 {
            self.0.foo
        }
    
        #[wasm_bindgen(setter)]
        pub fn set_foo(&mut self, value: i32) {
            self.0.foo = value;
        }
    
        pub fn bar(&self) {
            self.0.bar();
        }
    }
    

    As you can see, everything is done very explicitly.

    • encapsulate the original struct and export it with the original name using js_name and js_class
    • list out and implement getters and setters for public fields
    • add simple forwarding functions to the original struct.

    I believe the more widely-used method is to add the bindings to the original library but only enabled by a feature. This avoids much duplication, is less of a headache to implement, and ensures the bindings are always in-sync.

    Add a "wasm" feature that adds wasm-bindgen as an opt-in a dependency in your Cargo.toml:

    [features]
    wasm = ["wasm-bindgen"]
    
    [dependencies]
    wasm-bindgen = { version = "X.X.X", optional = true }
    

    Then you can use cfg and cfg_attr to only enable the wasm_bindgen attributes when the feature is enabled:

    #[cfg(feature = "wasm")]
    use wasm_bindgen::prelude::*;
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    pub struct MyStruct {
        pub foo: i32,
    }
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    impl MyStruct {
        pub fn bar(&self) {
            // do something
        }
    }