6.2 KiB
layout | title | description | category | tags | |||
---|---|---|---|---|---|---|---|
post | More Isomorphic Desktop Apps with Rust | They suck less now. |
|
In honor of Webpack 5.0 being released and dramatically easing integration of WebAssembly, it's time to revisit previous work done to use Electron as a backend for Rust GUI applications.
Structure:
- Why did I want to do this?: Potential to use Electron+WASM as Rust GUI; not truly native, but at least experimenting with.
- Problems: Hard to use ecosystem, crates don't compile, MIME types, async loading
- What changed?: Webpack makes WASM loading asynchronous and easy in v5, Chrome now sets MIME type
- Demonstration and screenshots
- Closing thoughts: is it worth it?
I wasn't expecting to write this, but it's 2020 and we could all use a win. When last I addressed using WASM + Electron to write desktop applications in Rust, there were ultimately too many issues to recommend this combination as feasible. Since then, there's been a lot of progress, and after finding out [the biggest problem] has been addressed, I decided it was time to take another look at where things stand.
Loading local WASM blobs
Previously, the most significant issue was trying to actually load WASM blobs in Electron. This problem was the result of a combination of factors:
- When using streaming WASM blobs
(
WebAssembly.instantiateStreaming()
), the blob must be returned with aContent-Type
header ofapplication/wasm
.- If the full WASM blob is loaded into memory first, the MIME type restriction does not apply.
- When Chrome loads
file://
URLs, theContent-Type
is unset. - Webpack prioritizes streaming WASM whenever available, and if it fails, has no graceful fallback.
- When building Rust WASM binaries using
wasm-bindgen
, webpack is used to generate JS bindings for the WASM blob.
Putting all this together:
- Electron loads the JS created by
wasm-bindgen
/webpack - This JS file calls attempts to load the WASM blob, which just so happens to be located on disk
- Because the MIME type isn't set, loading fails
This was a well-known issue;
emscripten
even included a graceful fallback for this scneario. It was possible to tweak the JS created by
wasm-bindgen
to load WASM, but using sed
to edit generated code will eventually lead to madness.
I'm unable to figure out when exactly it changed, but requesting WASM blobs from file://
URLs in
Chrome now sets the MIME type, and thus the blob is loaded correctly. Additionally, recent changes
to be released in Webpack 5 (specifically the asyncWebAssembly
and importAsync
experiments) enable loading WASM without a
separate launcher script.
The evolution of Rust
Rust as a language has also made a great deal of progress since late 2018. Previously, some
widely-used crates (like stdweb
) required a nightly
Rust compiler to function. Now, nearly
everything compiles on stable
. In addition, now that Rust supports async/await
, it's much easier
to interact with Javascript. It's still necessary to use some crates like wasm_bindgen
to assist
the interaction, but Rust can now make use of the same asynchronous paradigms that have proven to be
incredibly effective in Javascript.
There's also been great progress on some crates to interact directly with the browser; web-sys
and
js-sys
enable easier interoperation with the browser, where previously users didn't have these
options available.
The tooling and documentation has improved as well. wasm-pack
has proven itself as a reliable "one
stop shop" tool for managing WASM projects. While using wasm-bindgen
and webpack
directly are
still necessary for building Electron apps (due to Webpack v5 not yet released), this should change
in the near future as well.
New examples
(need to put some screenshots and link to the new examples here)
Outstanding issues
While I haven't been directly involved in any of the progress made to improve Rust + WASM, it's incredibly encouraging to see just how far everything has come. Seeing where the ecosystem stands now, I think using Electron + Rust to build desktop applications is feasible. Not necessarily a good idea, not that it offers any specific benefit over using Javascript/Typescript, just that it's now feasible.
Looking forward, the things I think could be beneficial to address are:
Put another way, these are the things that would be necessary for me to consider moving away from Typescript:
- Template/starter project examples
- Being able to
yarn create
a project and have it already set up with two-way JS to Rust bindings would go a long way towards reducing the currently painful setup using eitherwasm-bindgen
orwasm-pack
.
- Being able to
- Comparisons to Typescript and Neon
- Is there a development or performance benefit that comes from using Rust instead of Typescript? Took some time to learn Typescript since the last post, and while it's possible that WASM might execute faster, I'm not sure that Rust offers enough of a benefit to justify the significantly more complex setup. It would be useful to port an existing (small) application to Rust so that other developers can see a representative example of each and make a decision for themselves.
- Instead of embedding Rust in Electron by way of WASM, [Neon] can be used to develop extensions that run natively and are "glued" to Electron via Javascript. Further investigation to clarify the pros/cons of each approach would be helpful; are there situations in which WASM offers benefits over Neon? Vice-versa? Both WASM and Neon already require more complex setups than typical JS/TS setups. Using Rust stdlib instead of Node API's for system access is nice, being forced into loose coupling is nice, but maybe the penalty associated with bundling/distribution/etc. only makes sense for projects as large as Rust-analyzer, etc.
- Automatically generate bindings from TS definition files. Alternately, something like the JS "@types" as a group to supervise maintenance of TS to Rust bindings?