Coincidentally I'm seriously researching this topic so I cannot claim to have all the answers. Please correct me if I misunderstood part(s) of the ecosystem.
In short, the answer depends on the bigger picture of the project.
Personally I found it hard to actually understand the point and benefits of the RustxWebassembly ecosystem. It helps to keep in mind that your WASM code becomes a dynamic library. A self-contained "portable brain" that you plug into a platform.
So you have a plugin, but to make it dance you need platform interaction. Typically this interaction is glue code that knows about the platform capabilities and your WA interface.
I found my confusion stems from the fact that web-sys exists, which promises that you can call WebGL and other methods directly from within WASM. The crate is called web -sys, which is a non accepted, but common convention for wrapping system native APIs into safe rust. The crates stdweb, js-sys and web-sys import javascript functions from the browser platform, which is not exactly native but it depends on how you look at it.
Utilizing the WASM sys crates comes down to writing glue code in rust or in javascript.
Let's say you decide to write all code in Rust. This hasn't answered the actual question; how to structure the distinction between platform(/glue) code and app code. This post goes a bit into how to structure your code (with diagrams) and shortly describes the experience of each approach.
So, following the advice of the mentioned post, you could follow the first approach on your list; Use the WASM entry point to construct a canvas, WebGL context and register itself with window.requestAnimationFrame, without leaving Rust. This is possible through the bindings from web-sys.
Thankfully others have walked this path before; gate, raze rgx, which left lessons for us to learn.
Both gate and raze have a very elaborate and manual glue layer written in javascript. I think primarily because of the requirement on specific javascript packages (eg audio).
But all the previous links are about engines and implementations of single-player games. The networking code should be considered in our evaluation! This is the point where I'm uncertain about a good approach.
The browser already has an event loop, so it's logical that our platform glue code handles threading and I/O; The browser will call into WASM when it's time to draw a frame or when socket data was received.
On the other side, there is this "ideal"(?) case where you "just" give a single thread to the, completely self-contained, WASM code and let it sort out everything itself. I/O or blanks are, according to this model, interrupts which preemptive/non-preemptive wake/schedule a rust future to perform the requested task. This is a literal brain analogy where sensory neurons are the interrupts.
I have no idea how feasible this idea actually is; I haven't found anyone else with a public source who did it like this. The WASM spec is also evolving still, I'm not familiar enough with the platform or spec direction to say this approach is supposedly supported.
I'm waiting for a blog post mentioning WASM+Async I/O that will come out next week which will shed some light onto the possibility of executors and rust futures in WASM (I hope).
Note that there is already a crate that abstracts "JS Promise" <-> "Rust Future". My initial impression of this crate was that it promotes platform event loops instead of WASM event loops, but the WASM+Async interview will definitely contain information about the direction of the WASM-team and possibilities.