9

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?

LeoVen
  • 632
  • 8
  • 18
  • As far as I know: No. But there is an option to not always `#[wasm_bindgen]` the structs, using Rust's [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) alongside [features](https://doc.rust-lang.org/cargo/reference/features.html) to only `#[wasm_bindgen]` when the crate that depends on lib requires it to – MindSwipe Dec 17 '20 at 07:20

1 Answers1

11

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
    }
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 1
    omg dude, I created already 2 or 3 libs with wasm implementations as separated libs till I decided to rethink this way. Thx god I found your answer with featured deps, it has always been the proper way to do so and I have missed it like a dumb Thank u again – Dominux Jun 20 '23 at 19:33