rustwasm / wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://rustwasm.github.io/docs/wasm-bindgen/
Apache License 2.0
7.76k stars 1.07k forks source link

Support async functions for wasm_bindgen(start) #1904

Closed Pauan closed 4 years ago

Pauan commented 4 years ago

We currently support async functions in a variety of places, including in unit tests and in pub functions exposed to JS.

When writing applications, it's common to have a single "root" async function which kickstarts everything. Right now you have to do something like this:

async fn main_async() -> Result<(), JsValue> {
    // ...
}

#[wasm_bindgen(start)]
pub fn main_js() {
    spawn_local(async {
        main_async().await.unwrap_throw();
    });
}

But it would be great if we could instead do this:

#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> {
    // ...
}

The semantics is that it would generate a new shim function which calls spawn_local and logs the Err.

It should support a return value of either () or Result<(), JsValue>.

daxpedda commented 2 years ago

I was surprised to find that:

#[wasm_bindgen(start)]
async fn main() { }

isn't supported.

Pauan commented 2 years ago

@daxpedda It is supported, you have to do this:

#[wasm_bindgen(start)]
pub async fn main_js() { }

This is also supported:

#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> { Ok(()) }
daxpedda commented 2 years ago

I mean specifically fn main, if you name it something else Rust will complain that you don't have a main function if you are making a binary.

Currently if you put #[wasm_bindgen(start)] on async fn main() it will complain that the main function can't be async. This is why other solutions out there like tokio or async_std remove the async and put their block_on inside, which wasm-bindgen could do too. Not sure of the limitations though.

See #3076.

Pauan commented 2 years ago

wasm-bindgen does not support creating binaries, you must always create it as a lib. Binaries have a ton of extra code bloat, increasing the file size of your Wasm for no additional benefit.

A wasm-bindgen app that uses #[wasm_bindgen(start)] is effectively a binary, even if it's being compiled as a lib.

daxpedda commented 2 years ago

wasm-bindgen does not support creating binaries, you must always create it as a lib.

I am a bit confused, what does that mean exactly? wasm-bindgen generates .wasm and .js files, none of these are binaries as far as I know. What I meant was letting Rust have a binary target, which still produces a .wasm file that can be used by wasm-bindgen. wasm-bindgen has support to detect the main function.

A wasm-bindgen app that uses #[wasm_bindgen(start)] is effectively a binary, even if it's being compiled as a lib.

I am aware of the size difference. There are tons of use-cases where they don't really matter. Personally I'm writing libraries and applications that are supposed to be as cross-platform as possible. In these cases these apps are so big that the bloat generated by having a binary target is something I don't really care about. I wish of course that the bloat could be removed, but sadly Cargo has limitations around this that prevent this from being easy.

Cross-platform examples is another case where it requires extra code and effort by the user to write those, because they do require a main function.

Basically what I'm saying is, I know this is a complete convenience thing, it's has disadvantages, but considering it should be quiet simple for wasm-bindgen to support this case, it's a benefit users would appreciate I believe.

Pauan commented 2 years ago

What I meant was letting Rust have a binary target, which still produces a .wasm file that can be used by wasm-bindgen.

Yes, that's exactly what I mean. You should not be compiling your wasm-bindgen code as a bin, you should always be compiling it like this:

[lib]
crate-type = ["cdylib"]

If you use wasm-pack or the Rollup plugin (recommended) then it will always compile it as a lib, as intended.

Personally I'm writing libraries and applications that are supposed to be as cross-platform as possible.

In that case you should use cfg to select the right implementation based on the platform:

// lib.rs
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub async fn main_js() {
    ...
}
// main.rs
fn main() {
    ...
}

You already need to use cfg when creating cross-platform libraries or applications, because Wasm behaves very differently from native platforms, so you need Wasm-specific APIs.

With the above code, it will use main() for the non-Wasm code, and main_js() for the Wasm code. Both the main.rs and lib.rs can co-exist peacefully in the same crate.

And the main.rs file can import the things which are exported from lib.rs, so you won't have any code duplication. All of your code lives in lib.rs, and main.rs is just a tiny shim that calls the lib.rs code. This is idiomatic for Rust.

Pauan commented 2 years ago

Minor correction. In order to have both a lib.rs and a main.rs at the same time, you have to specify this in your Cargo.toml:

[lib]
crate-type = ["lib", "cdylib"]

Now it will work correctly. Here is a zip file containing a working example which contains both Wasm and non-Wasm code:

cross-platform-example.zip

You can use cargo build or cargo run for the non-Wasm binary, and yarn build for the Wasm binary. This is the ideal idiomatic setup.

And as a bonus, because it's using Rollup you get a ton of useful features for free:

If you don't use the Rollup plugin, then you'll have to do the above steps yourself, manually. It's a lot more work.

daxpedda commented 2 years ago

I know of these Rust-side solutions, what I'm trying to get at here is that there is some room for improvement that don't require additional workarounds.

Basically I'm trying to find a solution for Rust users that require zero additional work or reduce it to a minimum, it would be great if that has no overhead, but here we are. I do believe that adding the ability to handle main is an additional convenience Rust users can benefit from, downsides should be documented and solutions should be offered.

In a perfect world we solve the fact that compiling binaries has overhead on the Rust side, that's something I didn't look into.

Thank you for your input anyway.

Pauan commented 2 years ago

Basically I'm trying to find a solution for Rust users that require zero additional work or reduce it to a minimum, it would be great if that has no overhead, but here we are.

The zip file I gave you is the minimal amount of work. Compiling to Wasm requires a lot of things, simply compiling as a bin is not enough, additional setup is always required.

That's why the Rollup plugin automatically handles 99% of everything, so that it is as easy to use as possible.

It is as easy as doing yarn install and then yarn build, with everything taken care of automatically, similar to cargo build.

If you choose to not do that, then you'll have to manually build with the right flags, manually call wasm-bindgen with the right flags, download wasm-opt and call it with the right flags, handle inline_js in some way... it's a mess.

That's why we created things like the Rollup plugin, to make the process as easy as possible. It minimizes the amount of work that the user has to do, and it minimizes the amount of extra knowledge that the user needs.

The wasm-bindgen CLI was never intended to be used directly by users, it was always intended as a low-level tool which is used by higher-level tools. Think of wasm-bindgen as being similar to rustc, and the Rollup plugin as being similar to cargo.

daxpedda commented 2 years ago

I'm gonna try to be as clear as possible. Take your zip file. Try to add an example. You have to add this workaround:

#[wasm_bindgen(start)]
pub async fn real_main() {
    // your code goes here
}

fn main() { }

If #[wasm_bindgen(start)] would support async fn main() this workaround would not be needed. Clearly this workaround works and it's not a big deal. But additional support is still an improvement.

Pauan commented 2 years ago

If you are creating a cross-platform application, then you will have to use async fn for both mains, which means you will need to choose an async library, so your code will be something like this:

// lib.rs
#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> {
    call_init_function().await;
    Ok(())
}
// main.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    call_init_function().await;
    Ok(())
}

And the code will be different if you're using async-std, etc.

This is not a "workaround", this is just how Rust works. You have two different mains, with different attributes, different return types, different async runtimes. So it makes perfect sense that you will define it as two different functions.

If you don't define it as two functions, but as just one async fn main, then you wouldn't be able to choose the runtime or return type. So it wouldn't work.

When creating cross-platform libraries or applications, it is completely normal to have two implementations: one for Wasm and a different one for everything else. For example:

#[cfg(feature = "wasm")]
pub fn foo() -> u32 {
    // Wasm implementation goes here
}

#[cfg(not(feature = "wasm"))]
pub fn foo() -> u32 {
    // non-Wasm implementation goes here
}

This is not because of any limitation in wasm-bindgen, it's just how cross-platform libraries work in Rust in general: you provide a different implementation for each target platform.

daxpedda commented 2 years ago

Uhm, let's say you don't want to make a cross-platform example, let's say you want a pure wasm32-unknown-unknown library with an example.

#[wasm_bindgen(start)]
pub async fn real_main() {
    // your code goes here
}

fn main() { }

is longer then this:

#[wasm_bindgen(start)]
async fn main() {
    // your code goes here
}

As for cross-platform:

#[wasm_bindgen(start)]
pub async fn main_js() {
    call_init_function().await;
}

#[tokio::main]
async fn main() {
    call_init_function().await;
}

is longer then this (at least less duplicate code):

#[cfg_attr(target_arch = "wasm", wasm_bindgen(start))]
#[cfg_attr(not(target_arch = "wasm"), tokio::main)]
async fn main_js() {
    call_init_function().await;
}

When creating cross-platform libraries or applications, it is completely normal to have two implementations: one for Wasm and a different one for everything else.

This is not true at all in my experience, certainly this applies to a lot of libraries and applications. But there are tons of libraries that provide abstractions that make it seamless for users not to have two implementations. Really the example you have posted is great, getrandom is used in random, users of random have a completely seamless cross-platform experience. In fact a lot of libraries in my experience put a lot of effort to be as seamless as possible.


I'm not sure why there is such a big misunderstanding here. Your arguments aren't wrong, I agree with almost all of them. I am arguing that there is an improvement that can be made here. It's small, but it does exist.

If I understand your argument correctly, you are trying to tell me that there are ways to solve this without the improvement I'm suggesting here. I completely agree. I think you are also arguing that basically adding cross-platform code is inevitable. I mostly agree.

Again, only because there are other ways, doesn't mean we shouldn't try to improve. Only because cross-platform code is inevitable, doesn't mean we shouldn't try to minimize it.

Pauan commented 2 years ago

If you compile it in the correct way (using wasm-pack or the Rollup plugin), then you don't need the fn main() { } at all, so your code looks like this:

#[wasm_bindgen(start)]
async fn main_js() {
    // your code goes here
}

The only reason why you added in the fn main() {} is because you are trying really hard to compile it as a bin (which is not idiomatic for wasm-bindgen), but if you simply compile it as a lib (using cargo build --lib) then the problem disappears completely.

is longer then this (at least less duplicate code):

#[cfg_attr(target_arch = "wasm", wasm_bindgen(start))]
#[cfg_attr(not(target_arch = "wasm"), tokio::main)]
async fn main() {
    call_init_function().await;
}

That only works if you don't have a return type, wasm-bindgen must support main with a return type (like Result).

So how will your suggestion work with a different return type?

In fact a lot of libraries in my experience put a lot of effort to be as seamless as possible.

Yes, that's because they provide 2+ implementations. So if you want a nice seamless experience for your library / application, then you must do the same.


And in any case, I think your code is far less readable than just having two separate functions. Those attributes are a mess to understand.

Having separate functions also makes it very easy to support different runtimes:

// lib.rs
#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> {
    call_init_function().await;
    Ok(())
}
// main.rs
#[cfg(feature = "tokio")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    call_init_function().await;
    Ok(())
}

#[cfg(feature = "async-std")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    async_std::task::block_on(async {
        call_init_function().await;
        Ok(())
    });
}

And in particular, it is idiomatic for async-std to use block_on (which doesn't work at all with your proposal).

Your code is very brittle, unidiomatic, doesn't support Result, and is also harder to read. It is not worth it at all just to save a small handful of characters.

In addition, your code does not work with wasm-pack, the Rollup plugin, the Webpack plugin, or other Wasm tools. But if you structure things in the correct idiomatic way, then everything works perfectly.

Pauan commented 2 years ago

I am arguing that there is an improvement that can be made here. It's small, but it does exist.

Your suggestion is much more than just adding in support for async fn main. All of the Wasm tooling would need to be changed to support it. And because bin code has a lot of extra filesize bloat, we are avoiding that until rustc fixes that problem.

Rust already has a reputation of producing large filesizes, we don't want to make that reputation worse by supporting bin crates until that rustc bug is fixed.

You are trying to do things in a very weird, and unsupported way, and so of course you're going to run into problems. If you simply compile things in the correct way (the way that wasm-bindgen and the Wasm tools have been designed to work), then there is no issue.

Like I said before, users are not supposed to be using the wasm-bindgen CLI, it is low-level and for internal use. You will have a much easier and happier time if you use the correct tools (wasm-pack, or the Rollup plugin).

ranile commented 2 years ago

I don't think compiling binaries as a lib is the "correct" way. wasm-pack, webpack, etc all are built around libraries.

If you were to build a pure WASM binary, say with Trunk, it's not unreasonable to have a binary crate (side note: all Yew examples and trunk examples are structured this way). There are certainly valid cases where async fn main is wanted in a WASM package that is compiled as a binary crate.

It's better to support this use case than not. Adding a #[cfg(...)] on fn main is better than having a fn main, fn main_js


Like I said before, users are not supposed to be using the wasm-bindgen CLI, it is low-level and for internal use. You will have a much easier and happier time if you use the correct tools (wasm-pack, or the Rollup plugin).

That's true in a world in a world where Trunk doesn't exist.

There's no right or wrong way to do things. Rust's philosophy is to be unopinionated and we should stick to that. If one wants to drop down to wasm-bindgen CLI and use it, all the power to them

daxpedda commented 2 years ago

The only reason why you added in the fn main() {} is because you are trying really hard to compile it as a bin (which is not idiomatic for wasm-bindgen), but if you simply compile it as a lib (using cargo build --lib) then the problem disappears completely.

I have specifically tried to avoid arguing about this, which is why I mentioned Cargo examples. Cargo examples can't be simply made libs, it's unsupported, it will create compile errors if you don't specify a main function.

So how will your suggestion work with a different return type?

An error.

Yes, that's because they provide 2+ implementations. So if you want a nice seamless experience for your library / application, then you must do the same.

Winit is a good example of a huge library that is surprisingly seamless. Wgpu is not completely seamless, but the cross-platform code required to tack on is quiet minimal in comparison.

Again, this is about best-effort, not about perfect.

And in any case, I think your code is far less readable than just having two separate functions. Those attributes are a mess to understand.

Sure. I can agree with that actually.

Having separate functions also makes it very easy to support different runtimes:

I don't understand this argument. Sure, that's great, but you don't always need different runtimes.

And in particular, it is idiomatic for async-std to use block_on (which doesn't work at all with your proposal).

I don't get at all what you are trying to say here. async-std also has a macro for main: https://docs.rs/async-std/latest/async_std/attr.main.html, if that is what you mean?

Your code is very brittle, unidiomatic, doesn't support Result, and is also harder to read. It is not worth it at all just to save a small handful of characters.

I don't understand how it's brittle. It doesn't have to support Result. It's worth it for me.

In addition, your code does not work with wasm-pack, the Rollup plugin, the Webpack plugin, or other Wasm tools. But if you structure things in the correct idiomatic way, then everything works perfectly.

I don't use wasm-pack or Rollup plugins or any of the other stuff you mentioned. I use wasm-server-runner. For production code I have written my own small tools who just pass it through wasm-bindgen, wasm-opt and SWC for minimization.

I understand if the way you use wasm-bindgen doesn't really align with how I use it, but wasm-bindgen is an incredibly important library for the Rust ecosystem, it should support a lot of use-cases.

Pauan commented 2 years ago

@hamza1311 I don't think compiling binaries as a lib is the "correct" way.

I'm sorry, but you're wrong. Even for creating applications, you are supposed to compile wasm-bindgen with --lib. That is how it has always been, and it will continue to be that way until rustc fixes the issue with bloated bin code.

wasm-pack (and the Rollup plugin / Webpack plugin) are the officially supported ways to compile Rust to Wasm. Trunk is not officially supported, it is a third-party system which does its own (unidiomatic) things, it is not compatible with the wider Rust Wasm ecosystem.

There are certainly valid cases where async fn main is wanted in a WASM package that is compiled as a binary crate.

Every use case can use async fn main_js instead, as intended.

It's better to support this use case than not.

We want to push users toward doing the correct thing. If users start compiling bin crates, they will complain about large file sizes (yes they have complained in the past).

That's true in a world in a world where Trunk doesn't exist.

Trunk has a lot of issues, it is not production ready. And it also produces very inefficient unoptimized code.

I think it's unfortunate that some people are using Trunk, because then they blame Trunk problems on Rust Wasm, even though it's a problem with Trunk only.

I have seen many people saying that "Rust Wasm produces huge files, Rust Wasm is super slow", even though that's not true, it's a problem specifically with Trunk and Yew.

Rust's philosophy is to be unopinionated and we should stick to that.

It's the complete opposite, Rust is an extremely opinionated language. There is only one package manager, one build system (cargo).

Pauan commented 2 years ago

@daxpedda I have specifically tried to avoid arguing about this, which is why I mentioned Cargo examples.

It is not possible to run Wasm code directly with Cargo, so things like cargo test, cargo bench, cargo run etc. don't work, and have never worked. Rust Wasm is a different system.

In the long run it would be great to unify them, but that requires a lot of changes in rustc and cargo, it will not happen anytime soon.

An error.

So you think that wasm-bindgen should support pub async fn main_js both with and without Result, and should support async fn main without Result, but it will error with Result?

That doesn't sound very good to me, that sounds incredibly unintuitive and inconsistent.

Winit is a good example of a huge library that is surprisingly seamless. Wgpu is not completely seamless, but the cross-platform code required to tack on is quiet minimal in comparison.

You don't seem to understand my point. You keep mentioning other libraries like as if it disproves my point, but in actuality you are reinforcing my point.

My point is that all of those libraries and applications provide 2+ implementations. That's why they are seamless. You are creating a library / application, therefore your code will need to provide 2+ implementations. That's normal. I'm not talking about you using other libraries, I'm talking about your code.

Having a second (tiny) main function is quite minimal, it is not a big burden at all.

I don't understand this argument. Sure, that's great, but you don't always need different runtimes.

If you are creating a library or application to be used by other people, then having that option is good to have, it benefits your users.

So why would you choose an implementation which makes that impossible? And also bloats up your filesize for no reason? There is so little benefit to your suggestion, but so many problems and limitations.

I don't use wasm-pack or Rollup plugins or any of the other stuff you mentioned. I use wasm-server-runner. For production code I have written my own small tools who just pass it through wasm-bindgen, wasm-opt and SWC for minimization.

Even if you don't, your users will, because those are the officially supported and idiomatic ways to use Rust Wasm.

And by using the official tools, you make life much easier on yourself, you don't need to re-implement their features, and the tools are automatically kept up-to-date, so you don't need to worry about breaking changes.

A lot of work has gone into making the official tools good, third-party tools tend to be much more limited in features, and produce worse code.

I understand if the way you use wasm-bindgen doesn't really align with how I use it, but wasm-bindgen is an incredibly important library for the Rust ecosystem, it should support a lot of use-cases.

It's not about "how I use it", I'm a former Rust Wasm Core team member, I've contributed heavily to wasm-bindgen. I understand very well how wasm-bindgen works, and how it is intended to be used.

wasm-bindgen was never intended to be used by users, wasm-pack (and the plugins) were always the official tool that users are supposed to use. If you deviate from that, you will have issues, and those issues are not the fault of wasm-bindgen.

daxpedda commented 2 years ago

I'm gonna stop arguing about the other points, I think we are not going to reach any form of conclusion on any of them.

I think the big point of contention here is that in your opinion wasm-bindgen should be used in a very specific way and all other ways are "wrong". I don't think there is anything to argue about here, wasm-bindgen from your perspective is just not for me then.

I will say that I wish I could use wasm-pack and all the other tools you have mentioned, but for various reasons they just don't work out for me. Over time I basically gave up on them and reduced it to the minimum of wasm-bindgen and other "non-official" tools that are as unopinionated as possible. I feel very strongly about the fact that a library as low-level as wasm-bindgen should be as unopinionated as possible, I do believe that this is a strong point in Rust to allow for maximum re-usability, to me this is also the strong point about open source.

This doesn't mean I believe opinionated libraries or applications don't make sense, I'm not making blanket statements here.

ranile commented 2 years ago

That's true in a world in a world where Trunk doesn't exist.

Trunk has a lot of issues, it is not production ready. And it also produces very inefficient unoptimized code.

I think it's unfortunate that some people are using Trunk, because then they blame Trunk problems on Rust Wasm, even though it's a problem with Trunk only.

I have seen many people saying that "Rust Wasm produces huge files, Rust Wasm is super slow", even though that's not true, it's a problem specifically with Trunk and Yew.

What issues? I've used both trunk and wasm-pack and have not experienced any issues. I just tested the binary size and the trunk binary was smaller in my test. You can test that by:

  1. Git clone https://github.com/hamza1311/yew/
  2. cd examples/router
  3. trunk build --release
  4. Checkout branch trunk-vs-wasm-pack
  5. wasm-pack build

dist is generated by trunk, pkg by wasm-pack:

>> ls -l dist pkg/

dist:
.rw-r--r--  281 hamza 10 Sep 02:03 index-2e4e699b9f23b3d5.css
.rw-r--r--  683 hamza 10 Sep 02:03 index.html
.rw-r--r--  34k hamza 10 Sep 02:03 router-2afdc71d0293d9ef.js
.rw-r--r-- 387k hamza 10 Sep 02:03 router-2afdc71d0293d9ef_bg.wasm

pkg/:
.rw-r--r--  248 hamza 10 Sep 02:02 package.json
.rw-r--r-- 1.9k hamza 10 Sep 02:02 README.md
.rw-r--r--   79 hamza 10 Sep 02:02 router.d.ts
.rw-r--r--   99 hamza 10 Sep 02:02 router.js
.rw-r--r--  30k hamza 10 Sep 02:02 router_bg.js
.rw-r--r-- 392k hamza 10 Sep 02:02 router_bg.wasm
.rw-r--r--  986 hamza 10 Sep 02:02 router_bg.wasm.d.ts

The binary size in debug mode, where rustc produces huge binaries, doesn't matter and should not be used as a point of comparison.

We want to push users toward doing the correct thing. If users start compiling bin crates, they will complain about large file sizes (yes they have complained in the past).

Is the existence of frameworks/libraries like Yew, Seed, Dioxus incorrect? These frameworks/libraries create binaries which are the entire web application. That is completely different from creating libraries - using wasm-pack and such - that are used by web applications build with JS.


I do not believe we are going to reach a conclusion on this and calling each other "wrong" or saying that one and only one way of doing things is the correct way is productive so I'm going to end this discussion here.

Pauan commented 2 years ago

@daxpedda I will say that I wish I could use wasm-pack and all the other tools you have mentioned, but for various reasons they just don't work out for me.

That's a very serious issue! If you're having problems with the official tools, then we want to know about it so we can fix it! What issues have you had?

I feel very strongly about the fact that a library as low-level as wasm-bindgen should be as unopinionated as possible, I do believe that this is a strong point in Rust to allow for maximum re-usability, to me this is also the strong point about open source.

As I said before, the issue isn't really with wasm-bindgen. We would like to support bin crates, but it requires changes/fixes in rustc. It's an issue with rustc itself.

Similarly, we would like for everything to work seamlessly with cargo (such as cargo test, cargo bench, etc.) but that also requires changes in rustc/cargo.

daxpedda commented 2 years ago

That's a very serious issue! If you're having problems with the official tools, then we want to know about it so we can fix it! What issues have you had?

Personally back then I had issues with getting wasm-opt to run and running tests. I don't remember the exact details, but I'm surprised your are surprised. wasm-pack is not a small project, any sufficiently big project will have bugs or missing features that don't cover the use-cases that everybody might have. wasm-pack has a lot of open issues that make it unusable for some people. Also wasm-pack was basically unmaintained for a very long time, it might have improved a lot in the meantime, I wouldn't know.

As I said before, the issue isn't really with wasm-bindgen. We would like to support bin crates, but it requires changes/fixes in rustc. It's an issue with rustc itself.

Similarly, we would like for everything to work seamlessly with cargo (such as cargo test, cargo bench, etc.) but that also requires changes in rustc/cargo.

I agree of course, but I'm getting the impression that you have a very non-compromising stance. In the meantime people like me just don't use the "official" way of doing things to get by. This is what I'm seeking support for. But I think you made your stance clear that supporting anything "non-official" is in fact a bad thing.

Pauan commented 2 years ago

@hamza1311 What issues? I've used both trunk and wasm-pack and have not experienced any issues.

JS tools like Rollup and Webpack have had an absurd amount of effort put into them. Trunk is very new, very immature. I hope that one day it will become great, but right now it's just missing too many features that you need in production.

I just tested the binary size and the trunk binary was smaller in my test.

I checked out that git repo and ran Trunk on the router example. I immediately got this error:

2022-09-09T21:36:15.100030Z  INFO 📦 starting build
2022-09-09T21:36:15.100341Z  INFO spawning asset pipelines
2022-09-09T21:36:15.244765Z  INFO building router
2022-09-09T21:36:15.244910Z  INFO compiling sass/scss path="index.scss"
2022-09-09T21:36:15.245183Z ERROR ❌ error
error from HTML pipeline

Caused by:
    0: error from asset pipeline
    1: error spawning sass call
    2: No such file or directory (os error 2)
Error: error from HTML pipeline

Caused by:
    0: error from asset pipeline
    1: error spawning sass call
    2: No such file or directory (os error 2)
>> router warning: unused config key `unstable.share-generics` in `/tmp/yew/examples/.cargo/config.toml`

So instead I tested it out on my own example:

https://github.com/Pauan/rust-dominator/tree/master/examples/counter

With Trunk I got this result: 92,394 bytes for the .wasm + .js file.

With the Rollup plugin I got this result: 62,480 bytes for the .wasm + .js file (that's a 48% reduction in file size).

That is completely different from creating libraries - using wasm-pack and such - that are used by web applications build with JS.

You are very mistaken. Tools like wasm-pack and the plugins are not only for creating libraries, they are for applications too.

In fact the Rollup plugin has explicit support for being used as the entry point in Rollup, which is specifically for applications. The Rollup and Webpack plugins are primarily used for applications, not libraries.

These frameworks/libraries create binaries which are the entire web application.

And that is also what wasm-pack and the plugins do, they can also create entire web applications, they are not only for libraries.

I personally use the Rollup plugin for all of my web applications. I've even used it to create Chrome / Firefox extensions (which is something that Trunk cannot do).

Is the existence of frameworks/libraries like Yew, Seed, Dioxus incorrect?

No, I never said that. I said that Trunk has a lot of issues and produces extremely unoptimized code. If you compile Yew / Seed / Dioxus in the official way (e.g. using the Rollup plugin) then you will get a much better result.

Pauan commented 2 years ago

@daxpedda Personally back then I had issues with getting wasm-opt to run and running tests. I don't remember the exact details, but I'm surprised your are surprised.

Yes, wasm-pack has in the past had issues with wasm-opt. That has been completely fixed by the Rollup plugin.

Unit testing is very complicated, it's not very well supported by wasm-bindgen right now, unfortunately.

I actually suggest writing your unit tests with something like Karma or Jest or Playwright or something like that.

I think it should be possible to create a system which would convert #[wasm_bindgen_test] into a JS test runner like Karma.

wasm-pack has a lot of open issues that make it unusable for some people. Also wasm-pack was basically unmaintained for a very long time, it might have improved a lot in the meantime, I wouldn't know.

Yes, there was some bad personal drama that happened with wasm-pack. I believe most of the issues have been fixed, and the Rollup plugin is well maintained and does not have any known issues.

But I think you made your stance clear that supporting anything "non-official" is in fact a bad thing.

Imagine that you encountered a bug with cargo. Instead of reporting that bug, you decided to create your own custom build tool which calls rustc manually.

Then you encounter some issues, because you're using rustc in very strange ways that the Rust developers never intended. So you go onto the Rust bug tracker and file a bug report asking for them to support your specific way of using rustc.

Of course the Rust developers will be very confused, and they're going to ask why you're not using cargo, because cargo is what users are supposed to use. Everything is designed around cargo.

And instead of changing rustc to support your weird way of doing things, the Rust developers would much rather just fix the cargo bug which was preventing you from using cargo in the first place.

The situation is very similar with wasm-bindgen.

Have you tried using the zip that I gave you earlier? I have put in quite a lot of effort into making sure that the Rollup plugin is fast and bug-free.

daxpedda commented 2 years ago

For some perspective, I think the use-cases that you have are just completely different then what, for example, I have. Like more then half of the things you listed in response to hamza1311 is stuff that I don't require and don't want to pay the cost for.

I'm most definitely not going to use Rollup unless somebody puts in the effort to convince me of this, just taking a short glance at it makes it very unattractive to me. The same really applies to most of the tools you talked about and the zip file you linked.

This is why I find these tools unattractive:

As I mentioned before, wasm-server-runner does everything I need during development and for production I have my own toolchain that does exactly what I need and not even a tiny bit more, I know what it does and I can change and adjust it anytime.


I think your argument about Cargo was poor and I honestly consider it being done in bad faith.

I use Cargo because it works for me, if it wouldn't I would fork it and use that. I don't use wasm-pack because it doesn't work for me, so I found alternatives. I'm working with Web Workers at the moment, I had to fork wasm-bindgen because I required some adjustments to get things working (I provided PRs for those).

As you can see here I'm quite active GitHub, I write up issues and provide PRs almost on a daily basis. But this is really irrelevant. I'm privileged to have this kind of time and motivation, others don't, they just fork stuff or look for alternatives when they need it. There is nothing wrong with that in my opinion.

I consider it also alright that you don't share this opinion, clearly you have a vision for wasm-bindgen and related projects that I just don't share and that's okay. I'm just providing feedback here, hope it helps.

Pauan commented 2 years ago

@daxpedda I can definitely sympathize with the desire to do everything in Rust. Before wasm-bindgen existed, I contributed to cargo-web, which was an all-Rust toolchain for compiling Rust to Wasm. It was a very nice experience.

Originally our plan was to do a lot more in Rust. But the unavoidable fact is that you are compiling to Wasm + JS. Although I personally quite dislike the JS ecosystem (and JS tooling in general), they are currently the best tools available for the compilation and optimization of Wasm/JS.

An incredible amount of effort has gone into the creation of JS tooling, the Rust Wasm tools can never compare to it. So if you want to do anything more complicated than just "compile Rust to Wasm and run it", then you're going to need JS tools. JS tooling is basically mandatory in any sort of production setting. You can only avoid JS tools in very simple use cases.

And so after examining the available options, the design that we settled on was to use wasm-bindgen to handle the Rust side of things, and to use JS tools to handle the JS side of things. That means each tool can do what it does best, and so users get the best end result.

You talk about "supporting every use case", but that's exactly what we are doing. There are tons and tons and tons of use cases that just cannot be supported by simple tools like Trunk (or custom build systems). By leveraging the existing JS tools we can support those use cases basically "for free".

Unlike your custom build system, which is only for you, we have to support a lot of people, and a lot of different use cases.

I try to use things that at best cover my use-case and nothing else to reduce bloat.

That's not a bad position to take, but I think bloat in build tools is far less of a problem compared to bloat in the actual compiled code.

Cargo and the Rust stdlib have a ton of features that I've never used, but I wouldn't call them "bloat", because none of that bloat makes it into the final compiled code. And in many cases that extra "bloat" in the build tools means smaller or faster compiled code, which is ultimately a good thing for the users.

I think your argument about Cargo was poor and I honestly consider it being done in bad faith.

I don't really understand that, the situations are almost identical, it's a very good analogy.

rustc is a low-level tool that just compiles individual Rust code, it doesn't do anything fancy.

cargo is a high-level tool that handles package management, building, optimization, etc. basically it's the glue code that makes everything work.

wasm-bindgen is a low-level tool that just compiles Rust Wasm, it doesn't do anything fancy.

The Rollup plugin is a high-level tool that handles package management, building, optimization, etc. basically it's the glue code that makes everything work.

There is nothing wrong with that in my opinion.

When you just need to Get Things Done, that's fine, but reporting issues helps everybody, not just you. If the issue can be fixed at the source, then it can benefit millions of people. Economies of scale are important.

That's how humanity progresses forward, not by each person reinventing the wheel, but by people working together to make something better. That's why we don't have a dozen different forks of cargo. Having one official way to do things is really helpful (whether it be cargo, rustdoc, rustfmt, etc.). That's the entire philosophy of Rust.