mirror of
https://github.com/bspeice/speice.io
synced 2024-11-14 22:18:10 -05:00
184 lines
49 KiB
HTML
184 lines
49 KiB
HTML
|
<!doctype html><html lang=en dir=ltr class="blog-wrapper blog-post-page plugin-blog plugin-id-default" data-has-hydrated=false><meta charset=UTF-8><meta name=generator content="Docusaurus v3.6.0"><title data-rh=true>Isomorphic desktop apps with Rust | The Old Speice Guy</title><meta data-rh=true name=viewport content="width=device-width,initial-scale=1.0"><meta data-rh=true name=twitter:card content=summary_large_image><meta data-rh=true property=og:url content=https://speice.io/2018/09/isomorphic-apps><meta data-rh=true property=og:locale content=en><meta data-rh=true name=docusaurus_locale content=en><meta data-rh=true name=docusaurus_tag content=default><meta data-rh=true name=docsearch:language content=en><meta data-rh=true name=docsearch:docusaurus_tag content=default><meta data-rh=true property=og:title content="Isomorphic desktop apps with Rust | The Old Speice Guy"><meta data-rh=true name=description content="I both despise Javascript and am stunned by its success doing some really cool things. It's"><meta data-rh=true property=og:description content="I both despise Javascript and am stunned by its success doing some really cool things. It's"><meta data-rh=true property=og:type content=article><meta data-rh=true property=article:published_time content=2018-09-15T12:00:00.000Z><link data-rh=true rel=icon href=/img/favicon.ico><link data-rh=true rel=canonical href=https://speice.io/2018/09/isomorphic-apps><link data-rh=true rel=alternate href=https://speice.io/2018/09/isomorphic-apps hreflang=en><link data-rh=true rel=alternate href=https://speice.io/2018/09/isomorphic-apps hreflang=x-default><script data-rh=true type=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><link rel=alternate type=application/rss+xml href=/rss.xml title="The Old Speice Guy RSS Feed"><link rel=alternate type=application/atom+xml href=/atom.xml title="The Old Speice Guy Atom Feed"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css integrity=sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM crossorigin><link rel=stylesheet href=/assets/css/styles.ae6ff4a3.css><script src=/assets/js/runtime~main.751b419d.js defer></script><script src=/assets/js/main.62ce6156.js defer></script><body class=navigation-with-keyboard><script>!function(){var t,e=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();t=null!==e?e:"light",document.documentElement.setAttribute("data-theme",t)}(),function(){try{for(var[t,e]of new URLSearchParams(window.location.search).entries())if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id=__docusaurus><div role=region aria-label="Skip to main content"><a class=skipToContent_fXgn href=#__docusaurus_skipToContent_fallback>Skip to main content</a></div><nav aria-label=Main class="navbar navbar--fixed-top"><div class=navbar__inner><div class=navbar__items><button aria-label="Toggle navigation bar" aria-expanded=false class="navbar__toggle clean-btn" type=button><svg width=30 height=30 viewBox="0 0 30 30" aria-hidden=true><path stroke=currentColor stroke-linecap=round stroke-miterlimit=10 stroke-width=2 d="M4 7h22M4 15h22M4 23h22"/></svg></button><a class=navbar__brand href=/><div class=navbar__logo><img src=/img/logo.svg alt="Sierpinski Gasket" cl
|
||
|
<a href=https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript target=_blank rel="noopener noreferrer">this duality</a> that's
|
||
|
led me to a couple of (very) late nights over the past weeks trying to reconcile myself as I
|
||
|
bootstrap a simple desktop application.</p>
|
||
|
<p>See, as much as
|
||
|
<a href=https://webassembly.org/docs/faq/#is-webassembly-trying-to-replace-javascript target=_blank rel="noopener noreferrer">Webassembly isn't trying to replace Javascript</a>,
|
||
|
<strong>I want Javascript gone</strong>. There are plenty of people who don't share my views, and they are
|
||
|
probably nicer and more fun at parties. But I cringe every time "Webpack" is mentioned, and I think
|
||
|
it's hilarious that the
|
||
|
<a href=https://ecma-international.org/publications/standards/Ecma-402.htm target=_blank rel="noopener noreferrer">language specification</a>
|
||
|
dramatically outpaces anyone's
|
||
|
<a href=https://kangax.github.io/compat-table/es2016plus/ target=_blank rel="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 <a href=https://babeljs.io/ target=_blank rel="noopener noreferrer">Babel</a> is a nice tongue-in-cheek reference.</p>
|
||
|
<p>Yet for as much hate as <a href=https://electronjs.org/ target=_blank rel="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' <a href=https://wxwidgets.org/ target=_blank rel="noopener noreferrer">wxWidgets</a>.</p>
|
||
|
<p>Now there are other native solutions (<a href=https://github.com/LeoTindall/libui-rs/ target=_blank rel="noopener noreferrer">libui-rs</a>, <a href=https://github.com/PistonDevelopers/conrod target=_blank rel="noopener noreferrer">conrod</a>, <a href=https://github.com/kenz-gelsoft/wxRust target=_blank rel="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, <a href=https://mnt.io/2018/08/28/from-rust-to-beyond-the-asm-js-galaxy/ target=_blank rel="noopener noreferrer">a separate blog post</a> had
|
||
|
to be modified because <a href=https://github.com/WebAssembly/binaryen/pull/1642 target=_blank rel="noopener noreferrer">upstream changes</a> broke a
|
||
|
<a href=https://github.com/rustwasm/wasm-bindgen/pull/787 target=_blank rel="noopener noreferrer">Rust tool</a> the post tried to use. The post
|
||
|
ultimately
|
||
|
<a href=https://mnt.io/2018/08/28/from-rust-to-beyond-the-asm-js-galaxy/#comment-477 target=_blank rel="noopener noreferrer">got updated</a>, <strong>but
|
||
|
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 <a href=http://asmjs.org/ target=_blank rel="noopener noreferrer">asm.js</a> and <a href=https://kripken.github.io/emscripten-site/ target=_blank rel="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
|
||
|
<a href=https://en.wikipedia.org/wiki/Here_be_dragons target=_blank rel="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
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust target=_blank rel="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><img decoding=async loading=lazy alt="Electron app using WASM" src=/assets/images/electron-percy-wasm-9ccb2be15a9bed6da44486afc266bad5.png width=800 height=319 class=img_ev3q></p>
|
||
|
<p>...which you can also try out if you want:</p>
|
||
|
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token function" style="color:hsl(221, 87%, 60%)">git</span><span class="token plain"> clone https://github.com/speice-io/isomorphic-rust.git</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(35, 99%, 36%)">cd</span><span class="token plain"> isomorphic_rust/percy</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token function" style="color:hsl(221, 87%, 60%)">yarn</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">install</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">yarn</span><span class="token plain"> start</span><br></span></code></pre><div class=buttonGroup__atx><button type=button aria-label="Copy code to clipboard" title=Copy class=clean-btn><span class=copyButtonIcons_eSgA aria-hidden=true><svg viewBox="0 0 24 24" class=copyButtonIcon_y97N><path fill=currentColor d="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><svg viewBox="0 0 24 24" class=copyButtonSuccessIcon_LjdS><path fill=currentColor d=M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z /></svg></span></button></div></div></div>
|
||
|
<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 <a href=https://github.com/rustwasm/wasm-bindgen target=_blank rel="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 <a href=https://webpack.js.org/ target=_blank rel="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
|
||
|
<a href=https://www.reddit.com/r/rust/comments/98lpun/unable_to_load_wasm_for_electron_application/ target=_blank rel="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><a href=https://webassembly.org/docs/web/#additional-web-embedding-api target=_blank rel="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><img decoding=async loading=lazy alt="TypeError: Incorrect response MIME type. Expected &#39;application/wasm&#39;." src=/assets/images/incorrect-MIME-type-a977835e8dcbfdb20fdda3c67ee4f76c.png width=795 height=301 class=img_ev3q></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 <a href=https://electronjs.org/docs/api/protocol target=_blank rel="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
|
||
|
<a href=https://github.com/webpack/webpack/issues/7918 target=_blank rel="noopener noreferrer">open a PR with <code>webpack</code></a> and use regex to remove
|
||
|
calls to <code>instantiateStreaming</code> in the
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/blob/master/percy/build.sh#L21-L25 target=_blank rel="noopener noreferrer">build script</a>:</p>
|
||
|
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token function" style="color:hsl(221, 87%, 60%)">cargo</span><span class="token plain"> +nightly build </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--target</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">wasm32-unknown-unknown </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">\</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> wasm-bindgen </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$WASM_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/debug/</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$WASM_NAME</span><span class="token string" style="color:hsl(119, 34%, 47%)">.wasm"</span><span class="token plain"> --out-dir </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token plain"> --no-typescript </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">\</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># Have to use --mode=development so we can patch out the call to instantiateStreaming</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/node_modules/webpack-cli/bin/cli.js"</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--mode</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">development </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/app_loader.js"</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-o</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/bundle.js"</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">\</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-i</span><span class="token plain"> </span><span class="token string" style="color:hsl(119,
|
||
|
<p>Once that lands, the
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/blob/master/percy_patched_webpack/build.sh#L24-L27 target=_blank rel="noopener noreferrer">build process</a>
|
||
|
becomes much simpler:</p>
|
||
|
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain" style=display:inline-block></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token function" style="color:hsl(221, 87%, 60%)">cargo</span><span class="token plain"> +nightly build </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--target</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">wasm32-unknown-unknown </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">\</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> wasm-bindgen </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$WASM_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/debug/</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$WASM_NAME</span><span class="token string" style="color:hsl(119, 34%, 47%)">.wasm"</span><span class="token plain"> --out-dir </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token plain"> --no-typescript </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&&</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">\</span><span class="token plain"></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/node_modules/webpack-cli/bin/cli.js"</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--mode</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">production </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/app_loader.js"</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-o</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"</span><span class="token string variable" style="color:hsl(221, 87%, 60%)">$APP_DIR</span><span class="token string" style="color:hsl(119, 34%, 47%)">/bundle.js"</span><br></span></code></pre><div class=buttonGroup__atx><button type=button aria-label="Copy code to clipboard" title=Copy class=clean-btn><span class=copyButtonIcons_eSgA aria-hidden=true><svg viewBox="0 0 24 24" class=copyButtonIcon_y97N><path fill=currentColor d="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><svg viewBox="0 0 24 24" class=copyButtonSuccessIcon_LjdS><path fill=currentColor d=M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z /></svg></span></button></div></div></div>
|
||
|
<p>But we're not done yet! After we compile Rust into WASM and link WASM to Javascript (via
|
||
|
<code>wasm-bindgen</code> and <code>webpack</code>), we still have to make an Electron app. For this purpose I used a
|
||
|
starter app from <a href=https://electronforge.io/ target=_blank rel="noopener noreferrer">Electron Forge</a>, and then a
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/blob/master/percy/package.json#L8 target=_blank rel="noopener noreferrer"><code>prestart</code> script</a>
|
||
|
to actually handle starting the application.</p>
|
||
|
<p>The
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/blob/master/percy/package.json#L8 target=_blank rel="noopener noreferrer">final toolchain</a>
|
||
|
looks something like this:</p>
|
||
|
<ul>
|
||
|
<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>
|
||
|
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">it looks like the Rust project used to create this wasm file was linked against</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">a different version of wasm-bindgen than this binary:</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain" style=display:inline-block></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">rust wasm file: 0.2.21</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> this binary: 0.2.17</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain" style=display:inline-block></span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">Currently the bindgen format is unstable enough that these two version must</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">exactly match, so it's required that these two version are kept in sync by</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">either updating the wasm-bindgen dependency or this binary.</span><br></span></code></pre><div class=buttonGroup__atx><button type=button aria-label="Copy code to clipboard" title=Copy class=clean-btn><span class=copyButtonIcons_eSgA aria-hidden=true><svg viewBox="0 0 24 24" class=copyButtonIcon_y97N><path fill=currentColor d="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><svg viewBox="0 0 24 24" class=copyButtonSuccessIcon_LjdS><path fill=currentColor d=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": <a href=https://chinedufn.github.io/percy/ target=_blank rel="noopener noreferrer">percy</a> and <a href=https://github.com/DenisKolodin/yew target=_blank rel="noopener noreferrer">yew</a>. Between those,
|
||
|
I managed to get <a href=https://github.com/speice-io/isomorphic-rust/tree/master/percy target=_blank rel="noopener noreferrer">two</a>
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/tree/master/percy_patched_webpack target=_blank rel="noopener noreferrer">examples</a> running
|
||
|
using <code>percy</code>, but was unable to get an
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/tree/master/yew target=_blank rel="noopener noreferrer">example</a> running with <code>yew</code> because
|
||
|
of issues with "missing modules" during the <code>webpack</code> step:</p>
|
||
|
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">ERROR </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token plain"> ./dist/electron_yew_wasm_bg.wasm</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">Module not found: Error: Can</span><span class="token string" style="color:hsl(119, 34%, 47%)">'t resolve '</span><span class="token function" style="color:hsl(221, 87%, 60%)">env</span><span class="token string" style="color:hsl(119, 34%, 47%)">' in '</span><span class="token plain">/home/bspeice/Development/isomorphic_rust/yew/dist'</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/electron_yew_wasm_bg.wasm</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/electron_yew_wasm.js</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/app.js</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/app_loader.js</span><br></span></code></pre><div class=buttonGroup__atx><button type=button aria-label="Copy code to clipboard" title=Copy class=clean-btn><span class=copyButtonIcons_eSgA aria-hidden=true><svg viewBox="0 0 24 24" class=copyButtonIcon_y97N><path fill=currentColor d="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><svg viewBox="0 0 24 24" class=copyButtonSuccessIcon_LjdS><path fill=currentColor d=M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z /></svg></span></button></div></div></div>
|
||
|
<p>If you want to work with the browser APIs directly, your choices are <a href=https://crates.io/crates/percy-webapis target=_blank rel="noopener noreferrer">percy-webapis</a> or <a href=https://crates.io/crates/stdweb target=_blank rel="noopener noreferrer">stdweb</a> (or
|
||
|
eventually <a href=https://crates.io/crates/web-sys target=_blank rel="noopener noreferrer">web-sys</a>). See above for my <code>percy</code> examples, but when I tried
|
||
|
<a href=https://github.com/speice-io/isomorphic-rust/tree/master/stdweb target=_blank rel="noopener noreferrer">an example with <code>stdweb</code></a>, I was
|
||
|
unable to get it running:</p>
|
||
|
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class=codeBlockContent_biex><pre tabindex=0 class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class=codeBlockLines_e6Vv><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">ERROR </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token plain"> ./dist/stdweb_electron_bg.wasm</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain">Module not found: Error: Can</span><span class="token string" style="color:hsl(119, 34%, 47%)">'t resolve '</span><span class="token function" style="color:hsl(221, 87%, 60%)">env</span><span class="token string" style="color:hsl(119, 34%, 47%)">' in '</span><span class="token plain">/home/bspeice/Development/isomorphic_rust/stdweb/dist'</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/stdweb_electron_bg.wasm</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/stdweb_electron.js</span><br></span><span class=token-line style="color:hsl(230, 8%, 24%)"><span class="token plain"> @ ./dist/app_loader.js</span><br></span></code></pre><div class=buttonGroup__atx><button type=button aria-label="Copy code to clipboard" title=Copy class=clean-btn><span class=copyButtonIcons_eSgA aria-hidden=true><svg viewBox="0 0 24 24" class=copyButtonIcon_y97N><path fill=currentColor d="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><svg viewBox="0 0 24 24" class=copyButtonSuccessIcon_LjdS><path fill=currentColor d=M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z /></svg></span></button></div></div></div>
|
||
|
<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 <a href=https://github.com/speice-io/isomorphic-rust/tree/master/minimal target=_blank rel="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: <a href=https://crates.io/crates/js-sys target=_blank rel="noopener noreferrer">js-sys</a> and <a href=https://crates.io/crates/web-sys target=_blank rel="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 <a href=https://www.typescriptlang.org/ target=_blank rel="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
|
||
|
(<a href=https://github.com/webpack/webpack/pull/7983 target=_blank rel="noopener noreferrer">#7983</a>)</li>
|
||
|
<li>Browser API libraries (<code>web-sys</code> and <code>stdweb</code>) need to make sure they can support running in
|
||
|
Electron (see module error above)</li>
|
||
|
<li>Projects need to stabilize. There's talk of <code>stdweb</code> being turned into a Rust API
|
||
|
<a href=https://github.com/rustwasm/team/issues/226#issuecomment-418475778 target=_blank rel="noopener noreferrer">on top of web-sys</a>, and percy
|
||
|
<a href=https://github.com/chinedufn/percy/issues/24 target=_blank rel="noopener noreferrer">moving to web-sys</a>, both of which are big changes</li>
|
||
|
<li><code>wasm-bindgen</code> is great, but still in the "move fast and break things" phase</li>
|
||
|
<li>A good "boilerplate" app would dramatically simplify the start-up costs;
|
||
|
<a href=https://github.com/chentsulin/electron-react-boilerplate target=_blank rel="noopener noreferrer">electron-react-boilerplate</a> comes to
|
||
|
mind as a good project to imitate</li>
|
||
|
<li>More blog posts/contributors! I think Electron + Rust could be cool, but I have no idea what I'm
|
||
|
doing</li>
|
||
|
</ul></div></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Blog post page navigation"><a class="pagination-nav__link pagination-nav__link--prev" href=/2018/09/primitives-in-rust-are-weird><div class=pagination-nav__sublabel>Older post</div><div class=pagination-nav__label>Primitives in Rust are weird (and cool)</div></a><a class="pagination-nav__link pagination-nav__link--next" href=/2018/10/case-study-optimization><div class=pagination-nav__sublabel>Newer post</div><div class=pagination-nav__label>A case study in heaptrack</div></a></nav></main></div></div></div><footer class=footer><div class="container container-fluid"><div class="footer__bottom text--center"><div class=footer__copyright>Copyright © 2024 Bradlee Speice</div></div></div></footer></div>
|