<!doctype html><htmllang=endir=ltrclass="blog-wrapper blog-post-page plugin-blog plugin-id-default"data-has-hydrated=false><metacharset=UTF-8><metaname=generatorcontent="Docusaurus v3.6.1"><titledata-rh=true>Isomorphic desktop apps with Rust | The Old Speice Guy</title><metadata-rh=truename=viewportcontent="width=device-width,initial-scale=1.0"><metadata-rh=truename=twitter:cardcontent=summary_large_image><metadata-rh=trueproperty=og:urlcontent=https://speice.io/2018/09/isomorphic-apps><metadata-rh=trueproperty=og:localecontent=en><metadata-rh=truename=docusaurus_localecontent=en><metadata-rh=truename=docusaurus_tagcontent=default><metadata-rh=truename=docsearch:languagecontent=en><metadata-rh=truename=docsearch:docusaurus_tagcontent=default><metadata-rh=trueproperty=og:titlecontent="Isomorphic desktop apps with Rust | The Old Speice Guy"><metadata-rh=truename=descriptioncontent="I both despise Javascript and am stunned by its success doing some really cool things. It's"><metadata-rh=trueproperty=og:descriptioncontent="I both despise Javascript and am stunned by its success doing some really cool things. It's"><metadata-rh=trueproperty=og:typecontent=article><metadata-rh=trueproperty=article:published_timecontent=2018-09-15T12:00:00.000Z><linkdata-rh=truerel=iconhref=/img/favicon.ico><linkdata-rh=truerel=canonicalhref=https://speice.io/2018/09/isomorphic-apps><linkdata-rh=truerel=alternatehref=https://speice.io/2018/09/isomorphic-appshreflang=en><linkdata-rh=truerel=alternatehref=https://speice.io/2018/09/isomorphic-appshreflang=x-default><scriptdata-rh=truetype=application/ld+json>{"@context":"https://schema.org","@id":"https://speice.io/2018/09/isomorphic-apps","@type":"BlogPosting","author":{"@type":"Person","name":"Bradlee Speice"},"dateModified":"2024-11-09T21:40:50.000Z","datePublished":"2018-09-15T12:00:00.000Z","description":"I both despise Javascript and am stunned by its success doing some really cool things. It's","headline":"Isomorphic desktop apps with Rust","isPartOf":{"@id":"https://speice.io/","@type":"Blog","name":"Blog"},"keywords":[],"mainEntityOfPage":"https://speice.io/2018/09/isomorphic-apps","name":"Isomorphic desktop apps with Rust","url":"https://speice.io/2018/09/isomorphic-apps"}</script><linkrel=alternatetype=application/rss+xmlhref=/rss.xmltitle="The Old Speice Guy RSS Feed"><linkrel=alternatetype=application/atom+xmlhref=/atom.xmltitle="The Old Speice Guy Atom Feed"><linkrel=stylesheethref=/katex/katex.min.css><linkrel=stylesheethref=/assets/css/styles.16c3428d.css><scriptsrc=/assets/js/runtime~main.29a27dcf.jsdefer></script><scriptsrc=/assets/js/main.d461af80.jsdefer></script><bodyclass=navigation-with-keyboard><script>!function(){vart,e=function(){try{returnnewURLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{returnwindow.localStorage.getItem("theme")}catch(t){}}();t=null!==e?e:"light",document.documentElement.setAttribute("data-theme",t)}(),function(){try{for(var[t,e]ofnewURLSearchParams(window.location.search).entries())if(t.startsWith("docusaurus-data-")){vara=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><divid=__docusaurus><divrole=regionaria-label="Skip to main content"><aclass=skipToContent_fXgnhref=#__docusaurus_skipToContent_fallback>Skip to main content</a></div><navaria-label=Mainclass="navbar navbar--fixed-top"><divclass=navbar__inner><divclass=navbar__items><buttonaria-label="Toggle navigation bar"aria-expanded=falseclass="navbar__toggle clean-btn"type=button><svgwidth=30height=30viewBox="0 0 30 30"aria-hidden=true><pathstroke=currentColorstroke-linecap=roundstroke-miterlimit=10stroke-width=2d="M4 7h22M4 15h22M4 23h22"/></svg></button><aclass=navbar__brandhref=/><divclass=navbar__logo><imgsrc=/img/logo.svgalt="Sierpinski Gasket"class="themedComponent_mlkZ themedComponent--light_NVdE"><imgsrc=/img/logo-dark.svgalt="Sierpinski Gasket"class="themedComponent_mlkZ
<ahref=https://kangax.github.io/compat-table/es2016plus/target=_blankrel="noopener noreferrer">actual implementation</a>. The answer to this
conundrum is of course to recompile code from newer versions of the language to older versions <em>of
the same language</em> before running. At least <ahref=https://babeljs.io/target=_blankrel="noopener noreferrer">Babel</a> is a nice tongue-in-cheek reference.</p>
<p>Yet for as much hate as <ahref=https://electronjs.org/target=_blankrel="noopener noreferrer">Electron</a> receives, it does a stunningly good job at solving a really hard
problem: <em>how the hell do I put a button on the screen and react when the user clicks it</em>? GUI
programming is hard, straight up. But if browsers are already able to run everywhere, why don't we
take advantage of someone else solving the hard problems for us? I don't like that I have to use
Javascript for it, but I really don't feel inclined to whip out good ol' <ahref=https://wxwidgets.org/target=_blankrel="noopener noreferrer">wxWidgets</a>.</p>
<p>Now there are other native solutions (<ahref=https://github.com/LeoTindall/libui-rs/target=_blankrel="noopener noreferrer">libui-rs</a>, <ahref=https://github.com/PistonDevelopers/conrodtarget=_blankrel="noopener noreferrer">conrod</a>, <ahref=https://github.com/kenz-gelsoft/wxRusttarget=_blankrel="noopener noreferrer">oh hey wxWdidgets again!</a>), but
those also have their own issues with distribution, styling, etc. With Electron, I can
<code>yarn create electron-app my-app</code> and just get going, knowing that packaging/upgrades/etc. are built
in.</p>
<p>My question is: given recent innovations with WASM, <em>are we Electron yet</em>?</p>
<p>No, not really.</p>
<p>Instead, <strong>what would it take to get to a point where we can skip Javascript in Electron apps?</strong></p>
<p>Truth is, WASM/Webassembly is a pretty new technology and I'm a total beginner in this area. There
may already be solutions to the issues I discuss, but I'm totally unaware of them, so I'm going to
try and organize what I did manage to discover.</p>
<p>I should also mention that the content and things I'm talking about here are not intended to be
prescriptive, but more "if someone else is interested, what do we already know doesn't work?" <em>I
expect everything in this post to be obsolete within two months.</em> Even over the course of writing
this, <ahref=https://mnt.io/2018/08/28/from-rust-to-beyond-the-asm-js-galaxy/target=_blankrel="noopener noreferrer">a separate blog post</a> had
to be modified because <ahref=https://github.com/WebAssembly/binaryen/pull/1642target=_blankrel="noopener noreferrer">upstream changes</a> broke a
<ahref=https://github.com/rustwasm/wasm-bindgen/pull/787target=_blankrel="noopener noreferrer">Rust tool</a> the post tried to use. The post
all this happened within the span of a week.</strong> Things are moving quickly.</p>
<p>I'll also note that we're going to skip <ahref=http://asmjs.org/target=_blankrel="noopener noreferrer">asm.js</a> and <ahref=https://kripken.github.io/emscripten-site/target=_blankrel="noopener noreferrer">emscripten</a>. Truth be told, I couldn't get
either of these to output anything, and so I'm just going to say
<ahref=https://en.wikipedia.org/wiki/Here_be_dragonstarget=_blankrel="noopener noreferrer">here be dragons.</a> Everything I'm discussing here
uses the <code>wasm32-unknown-unknown</code> target.</p>
<p>The code that I <em>did</em> get running is available
<ahref=https://github.com/speice-io/isomorphic-rusttarget=_blankrel="noopener noreferrer">over here</a>. Feel free to use it as a starting point,
but I'm mostly including the link as a reference for the things that were attempted.</p>
<h1>An Example Running Application</h1>
<p>So, I did <em>technically</em> get a running application:</p>
<p><imgdecoding=asyncloading=lazyalt="Electron app using WASM"src=/assets/images/electron-percy-wasm-9ccb2be15a9bed6da44486afc266bad5.pngwidth=800height=319class=img_ev3q></p>
<p>...but I wouldn't really call it a "high quality" starting point to base future work on. It's mostly
there to prove this is possible in the first place. And that's something to be proud of! There's a
huge amount of engineering that went into showing a window with the text "It's alive!".</p>
<p>There's also a lot of usability issues that prevent me from recommending anyone try Electron and
WASM apps at the moment, and I think that's the more important thing to discuss.</p>
<h1>Issue the First: Complicated Toolchains</h1>
<p>I quickly established that <ahref=https://github.com/rustwasm/wasm-bindgentarget=_blankrel="noopener noreferrer">wasm-bindgen</a> was necessary to "link" my Rust code to Javascript. At
that point you've got an Electron app that starts an HTML page which ultimately fetches your WASM
blob. To keep things simple, the goal was to package everything using <ahref=https://webpack.js.org/target=_blankrel="noopener noreferrer">webpack</a> so that I could just
load a <code>bundle.js</code> file on the page. That decision was to be the last thing that kinda worked in
this process.</p>
<p>The first issue
<ahref=https://www.reddit.com/r/rust/comments/98lpun/unable_to_load_wasm_for_electron_application/target=_blankrel="noopener noreferrer">I ran into</a>
while attempting to bundle everything via <code>webpack</code> is a detail in the WASM spec:</p>
<blockquote>
<p>This function accepts a Response object, or a promise for one, and ... <strong>[if > it] does not match
the <code>application/wasm</code> MIME type</strong>, the returned promise will be rejected with a TypeError;</p>
<p><ahref=https://webassembly.org/docs/web/#additional-web-embedding-apitarget=_blankrel="noopener noreferrer">WebAssembly - Additional Web Embedding API</a></p>
</blockquote>
<p>Specifically, if you try and load a WASM blob without the MIME type set, you'll get an error. On the
web this isn't a huge issue, as the server can set MIME types when delivering the blob. With
Electron, you're resolving things with a <code>file://</code> URL and thus can't control the MIME type:</p>
<p>There are a couple of solutions depending on how far into the deep end you care to venture:</p>
<ul>
<li>Embed a static file server in your Electron application</li>
<li>Use a <ahref=https://electronjs.org/docs/api/protocoltarget=_blankrel="noopener noreferrer">custom protocol</a> and custom protocol handler</li>
<li>Host your WASM blob on a website that you resolve at runtime</li>
</ul>
<p>But all these are pretty bad solutions and defeat the purpose of using WASM in the first place.
Instead, my workaround was to
<ahref=https://github.com/webpack/webpack/issues/7918target=_blankrel="noopener noreferrer">open a PR with <code>webpack</code></a> and use regex to remove
<li><code>yarn start</code> triggers the <code>prestart</code> script</li>
<li><code>prestart</code> checks for missing tools (<code>wasm-bindgen-cli</code>, etc.) and then:<!---->
<ul>
<li>Uses <code>cargo</code> to compile the Rust code into WASM</li>
<li>Uses <code>wasm-bindgen</code> to link the WASM blob into a Javascript file with exported symbols</li>
<li>Uses <code>webpack</code> to bundle the page start script with the Javascript we just generated<!---->
<ul>
<li>Uses <code>babel</code> under the hood to compile the <code>wasm-bindgen</code> code down from ES6 into something
browser-compatible</li>
</ul>
</li>
</ul>
</li>
<li>The <code>start</code> script runs an Electron Forge handler to do some sanity checks</li>
<li>Electron actually starts</li>
</ul>
<p>...which is complicated. I think more work needs to be done to either build a high-quality starter
app that can manage these steps, or another tool that "just handles" the complexity of linking a
compiled WASM file into something the Electron browser can run.</p>
<h1>Issue the Second: WASM tools in Rust</h1>
<p>For as much as I didn't enjoy the Javascript tooling needed to interface with Rust, the Rust-only
bits aren't any better at the moment. I get it, a lot of projects are just starting off, and that
leads to a fragmented ecosystem. Here's what I can recommend as a starting point:</p>
<p>Don't check in your <code>Cargo.lock</code> files to version control. If there's a disagreement between the
version of <code>wasm-bindgen-cli</code> you have installed and the <code>wasm-bindgen</code> you're compiling with in
<code>Cargo.lock</code>, you get a nasty error:</p>
<divclass="codeBlockContainer_Ckt0 theme-code-block"style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><divclass=codeBlockContent_biex><pretabindex=0class="prism-code language-text codeBlock_bY9V thin-scrollbar"style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><codeclass=codeBlockLines_e6Vv><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">it looks like the Rust project used to create this wasm file was linked against</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">a different version of wasm-bindgen than this binary:</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain"style=display:inline-block></span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">rust wasm file: 0.2.21</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain"> this binary: 0.2.17</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain"style=display:inline-block></span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">Currently the bindgen format is unstable enough that these two version must</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">exactly match, so it's required that these two version are kept in sync by</span><br></span><spanclass=token-linestyle="color:hsl(230, 8%, 24%)"><spanclass="token plain">either updating the wasm-bindgen dependency or this binary.</span><br></span></code></pre><divclass=buttonGroup__atx><buttontype=buttonaria-label="Copy code to clipboard"title=Copyclass=clean-btn><spanclass=copyButtonIcons_eSgAaria-hidden=true><svgviewBox="0 0 24 24"class=copyButtonIcon_y97N><pathfill=currentColord="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"/></svg><svgviewBox="0 0 24 24"class=copyButtonSuccessIcon_LjdS><pathfill=currentColord=M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z/></svg></span></button></div></div></div>
<p>Not that I ever managed to run into this myself (<em>coughs nervously</em>).</p>
<p>There are two projects attempting to be "application frameworks": <ahref=https://chinedufn.github.io/percy/target=_blankrel="noopener noreferrer">percy</a> and <ahref=https://github.com/DenisKolodin/yewtarget=_blankrel="noopener noreferrer">yew</a>. Between those,
I managed to get <ahref=https://github.com/speice-io/isomorphic-rust/tree/master/percytarget=_blankrel="noopener noreferrer">two</a>
using <code>percy</code>, but was unable to get an
<ahref=https://github.com/speice-io/isomorphic-rust/tree/master/yewtarget=_blankrel="noopener noreferrer">example</a> running with <code>yew</code> because
of issues with "missing modules" during the <code>webpack</code> step:</p>
<p>If you want to work with the browser APIs directly, your choices are <ahref=https://crates.io/crates/percy-webapistarget=_blankrel="noopener noreferrer">percy-webapis</a> or <ahref=https://crates.io/crates/stdwebtarget=_blankrel="noopener noreferrer">stdweb</a> (or
eventually <ahref=https://crates.io/crates/web-systarget=_blankrel="noopener noreferrer">web-sys</a>). See above for my <code>percy</code> examples, but when I tried
<ahref=https://github.com/speice-io/isomorphic-rust/tree/master/stdwebtarget=_blankrel="noopener noreferrer">an example with <code>stdweb</code></a>, I was
<p>At this point I'm pretty convinced that <code>stdweb</code> is causing issues for <code>yew</code> as well, but can't
prove it.</p>
<p>I did also get a <ahref=https://github.com/speice-io/isomorphic-rust/tree/master/minimaltarget=_blankrel="noopener noreferrer">minimal example</a>
running that doesn't depend on any tools besides <code>wasm-bindgen</code>. However, it requires manually
writing "<code>extern C</code>" blocks for everything you need from the browser. Es no bueno.</p>
<p>Finally, from a tools and platform view, there are two up-and-coming packages that should be
mentioned: <ahref=https://crates.io/crates/js-systarget=_blankrel="noopener noreferrer">js-sys</a> and <ahref=https://crates.io/crates/web-systarget=_blankrel="noopener noreferrer">web-sys</a>. Their purpose is to be fundamental building blocks that exposes
the browser's APIs to Rust. If you're interested in building an app framework from scratch, these
should give you the most flexibility. I didn't touch either in my research, though I expect them to
be essential long-term.</p>
<p>So there's a lot in play from the Rust side of things, and it's just going to take some time to
figure out what works and what doesn't.</p>
<h1>Issue the Third: Known Unknowns</h1>
<p>Alright, so after I managed to get an application started, I stopped there. It was a good deal of
effort to chain together even a proof of concept, and at this point I'd rather learn <ahref=https://www.typescriptlang.org/target=_blankrel="noopener noreferrer">Typescript</a>
than keep trying to maintain an incredibly brittle pipeline. Blasphemy, I know...</p>
<p>The important point I want to make is that there's a lot unknown about how any of this holds up
outside proofs of concept. Things I didn't attempt:</p>
<ul>
<li>Testing</li>
<li>Packaging</li>
<li>Updates</li>
<li>Literally anything related to why I wanted to use Electron in the first place</li>
</ul>
<h1>What it Would Take</h1>
<p>Much as I don't like Javascript, the tools are too shaky for me to recommend mixing Electron and
WASM at the moment. There's a lot of innovation happening, so who knows? Someone might have an
application in production a couple months from now. But at the moment, I'm personally going to stay
away.</p>
<p>Let's finish with a wishlist then - here are the things that I think need to happen before
Electron/WASM/Rust can become a thing:</p>
<ul>
<li>Webpack still needs some updates. The necessary work is in progress, but hasn't landed yet