A Dutch lanscape with a windmill and housesA Dutch landscape

The last couple decades have seen a trend, a trend away from native desktop apps, towards web and mobile apps. Developers know that building for the web can net them the eyeballs of every computer user, and they have to build mobile native because phone companies have systemically repressed web apps in favor of native ones. Remember Steve Jobs' proclaimed that "You can create amazing Web 2.0 apps that look exactly like native apps on the iPhone" at WWDC 2007? That certainly didn't come to pass, maybe because it would have prevented them from taking a large cut of In App Purchases. Difficult to say...

Then in 2013, Github built Atom Shell, a framework that allowed them to transform a web app into a cross platform desktop app. While developed for Atom, their hackable open source text editor, they realized the potential of it as a framework for any app. And so, in 2014, Atom Shell was renamed to Electron, and open sourced under the MIT license. A couple years later, in 2016, Electron hit version 1, and apps built with it became submittable to the Mac and Windows App store later that year. And the web apps on the desktop took off.

Atom Text Editor
I always preferred Sublime Text, it was always so much snappier

Big names like Discord, Slack, Microsoft Teams, Spotify, Zoom, Visual Studio Code, and tons of others all bought in and built desktop apps with it. Since desktop apps were now essentially regular web apps, it allowed web developers to design diverse cross platform interfaces with what they already knew. But it wasn't without its downsides. Electron packages the full Chromium browser, a Node Server, and the V8 Javascript engine inside itself. This means that Electron apps eat far more than their fair share of memory, cpu, battery, and disk space. Running more than one or two Electron apps could bog down your computer, and it encouraged apps to require internet access to work. The rise of Electron (and React Native) solidified HTML/CSS and Javascript as the most coveted skills for developers across the web, desktop, and mobile spheres.

Electron's flaws did not go unnoticed by all, and now we have some new contenders for GUI frameworks that can run on the web and desktop.

New Options

Tauri

Around July 2019, Tauri was created, promising to provide an "optimized, secure, and frontend-independent application for multi-platform deployment". Following in the footsteps of electron, it continues to allow the use of HTML, CSS, and JS frameworks for frontend design, but replaced the Chromium browser and Node server with their own Rust backend. By doing so, they've improved security, massively reduced memory and cpu usage, and remain cross platform. Today one can use any number of JS web frameworks like Svelte, Vite, Next.js, or vanilla JS or even some Rust frameworks like egui or Yew to render the frontend, giving unparalleled choice for developers.

Since no server is bundled in, it is required to build either a Single Page App(an app that downloads itself as files and runs on your computer) or a statically generated app(one that is rendered to the server and served as static files). Unfortunately, using Server Side Rendering would require the addition of a server to the bundle.

Egui

One of the leading Rust GUI frameworks, Egui, is a simple, fast, and highly portable immediate mode GUI library for Rust apps. What's an immediate mode GUI you ask? Let's take a look at the egui docs.

In a retained GUI you create a button, add it to some UI and install some on-click handler (callback). The button is retained in the UI, and to change the text on it you need to store some sort of reference to it. By contrast, in immediate mode you show the button and interact with it immediately, and you do so every frame (e.g. 60 times per second). This means there is no need for any on-click handler, nor to store any reference to it. Egui has seen plenty of integration with Rust game engines, allowing for the creation of in-game menus and interfaces. Because it is built with Rust, it can be compiled into both a native app, and directly to Webassembly, where it can be run in the browser. -egui Docs

Immediate mode GUIs are simpler to write, simpler to render, and eliminate entire classes of bugs related to state management and event handling of retained mode GUIs like Qt. But they do take a small performance hit since the GUI needs to be rerendered after each frame(or mouse movement/interaction).

Egui also leverages Rust's ability to compile to both a native app and to Wwebassembly, allowing one to run your app in any web browser!

As a sidenote, one of the common refrains I hear is that Webassembly has no native way to interact with the DOM, and thus suffers a large performance penalty when it has to call out to Javascript to do so. While that is debatable as a general rule, it is certainly true for most Webassembly based web frameworks right now. Benchmarks put Webassembly based frameworks behind vanilla JS, and most fast JS frameworks. It's still faster than React though!

The neat thing about Egui is that absolutely none of that applies. Egui uses WebGL directly, so there is no DOM at all. Everything is drawn to a Canvas and displayed by the GPU. While this certainly has some downsides in accessibility and observability, it completely obviates that pesky layer crossing cost. The only remaining major downside for WASM apps are the fairly large bundle size.

Why these two?

I'm specifically calling out these two GUI frameworks, because they both allow you to achieve what Electron can. They both can deploy a web app and a desktop app from the same codebase. So that when you can build the next Discord, or Zoom, or Spotify, you can avoid those nasty performance hurdles and leverage the power of Rust! Let's try them out!

Project Setup

Tauri

After reaching out on Twitter, the lovely folks at Tauri(shoutout to @parker_codes for answering my questions and @lucasfernog for creating the Tauri example) created the web example, which shows how to create both a web app and a desktop app from the same repo with Svelte. Let's set it up!

bash
git clone "[email protected]:tauri-apps/tauri.git"
.scripts/setup.sh # This step needs to be run in the repo root, and is missing from the README
cd examples/web
yarn # or npm install

And that's it! One can run the desktop app with yarn tauri dev if you installed the NPM CLI and cargo tauri dev if you didn't, and the web app with yarn dev:web. This works through the power of conditional compilation in your vite.config.ts folder

TypeScript code
import { resolve } from 'path'
import { sveltekit } from '@sveltejs/kit/vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import type { UserConfig } from 'vite'

const TARGET = process.env.TARGET

const plugins = [sveltekit()]

if (TARGET === 'web') {
  plugins.push(wasm())
  plugins.push(topLevelAwait())
  plugins.push(
    viteStaticCopy({
      targets: [
        {
          src: 'core/wasm/pkg/wasm_bg.wasm',
          dest: 'wasm'
        }
      ]
    })
  )
}

const config: UserConfig = {
  plugins,
  resolve: {
    alias: {
      $api:
        TARGET === 'tauri'
          ? resolve('./src/api/desktop')
          : resolve('./src/api/web')
    }
  }
}

export default config

As we can see, if the TARGET env variable is WEB, it'll copy the wasm into the build, and resolve the $api alias to either the desktop or web bridge code that'll call the Rust backend. The desktop folder calls the Rust backend directly with Tauri, and the web folder calls the rust code compiled to Webassembly. Exciting! Unlike the egui example, this example does use the DOM and does suffer some performance penalties. However, it follows a more traditional web development approach, and can benefit from the accessibility improvements that browsers feature.

EDIT:My PR to fix this has been merged. I'm leaving it here for reference struck through.

However, there are two bugs in the example repo. First, the vite config requires you to whitelist the WASM file so it will load for your web app. You should get an error if you try to run it without this, but I didn't always gets one, which was somewhat frustrating. This can be done by modifying the above UserConfig section like so:

TypeScript code
const config: UserConfig = {
  server: {
    fs: {
      // Allow serving files the project root
      allow: ['.']
    }
  },
...
}

Once that's patched, one can generate the web version using yarn dev:web.

Web version of the Tauri Example Greet App
Hitting Greet will open an alert box saying, "Hello, Tauri!

For mac at least, the Desktop version has its own bug. If you run cargo tauri dev and it just keeps waiting for the server to start, you might need to edit the devPath in examples/web/core/tauri/tauri.conf.json and replace 127.0.0.1 with localhost. On my mac, this was the only way to get it to the dev command to run successfully.

The desktop version can be run with cargo tauri dev, and produces an output like so:

Desktop version of the Tauri Example Greet App
Look Ma, a Native App!

After that, it should be smooth sailing!

Egui and Eframe

As mentioned earlier, Egui can be compiled to a native Rust app or to a WASM app through the power of Rust! [Eframe(https://github.com/emilk/egui/tree/master/crates/eframe) is the official framework library for writing apps using egui, and it so happens to feature a handy template that makes creating a dual purpose egui app very easy. Let's dive in!

Firstly, one should have Rust installed, which you can check by running:

bash
rustup update #Check that the latest rust is installed

Then, if you're on an Ubuntu linux clone you need to install these deps

bash
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev

or a Fedora based one:

bash
dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel fontconfig-devel

Than you create your own version of the eframe template repo, then clone it:

bash
git clone https://github.com/benwis/eframe_template
cd eframe_template

Then it's as simple as running cargo run --release for a local version.

The Eframe Template Desktop app
Success!

To get the web version building, we need to install trunk, the Rust web server:

bash
cargo install --locked trunk

Then generate the web version with

bash
trunk serve

which will build the site and serve it on http://127.0.0.1/index.html#dev. By appending #dev to the url, we prevent assets/sw.js from trying to serve us the cached version. Once loaded, even the web version should work fully offline! Like the Tauri version, Egui is compiled to WASM for the Web and then downloaded as static files, and directly built and called when running as an app.

The Eframe Web app, rendered from the same codebase!
I know I have a lot of tabs, this isn't even my final form!

Check out the more fully featured online demo here and experience the wonder. It's got floating tabs, foreground and background, and so much more. It's totally different from the traditional web interface conventions we've come to expect!

TThe fully featured egui demo page. Experience the overlapping tabs and functionality
All directly rendered by WebGL and your GPU!

Conclusion

Whether you're looking to redeploy your Electron app into a more performant version, or just want to give your users a better desktop app experience, Rust has at least two viable options to consider. It's nice to be able to provide that without sacrificing your users' processing power. I look forward to seeing how Tauri and egui develop in this space, and to all the exciting things that can be built using them. If you have any questions or have built something interesting, feel free to msg me on Twitter or Mastodon at the links in the footer. Happy All Hallows Day!