At Motius, we are given a chance to explore new and emerging technologies. In our recent Motius Discovery camp I decided to try combining Rust web assembly with Vue. The idea is to have the business logic handled by Rust, giving the safety the amazing Rust compiler provides, with the established DOM manipulation of Vue. By using Vue here, we have access to the full ecosystem of styling libraries, something which is at this point still lacking from the pure Rust frameworks out there. I happen to be using Tailwind here, but there are plenty of options.
Building our playground
To get started, we first need a bundler. A dev environment with hot reloading would also be nice. My first thought was to simply use vue-cli to set up a Vue project, and add wasm compilation. Since vue-cli uses webpack, wasm-pack-plugin should allow this. Installing this and adding some basic config to vue.config.js does show some initial promise, and I’m able to run Rust code from within Vue. However, the way webpack chunks the Rust code limits the way it can be imported. Imports for Rust have to be done asynchronously, which means we can’t do them in our top level imports in a given .vue file, but rather have to do them within an async function. I don’t think this is going to give us the flexibility we need.
Looking around for other options led me to parcel. This promises simple Vue and Rust Wasm compatibility. So I give it a try, and immediately it looks like it’s what we need right out of the box. I can import the Rust code without any special treatment. Now that we have a toolchain, let’s start building an app.
Build a Better Beer
In my experience, most tutorial apps with new technologies don’t have enough substance to see how the technology works in the real world — what works in a todo list, won’t necessarily work when scaling it up to a full calendar app. So, I wanted to build something with a bit more substance.
Since I brew beer, I thought I would make a beer recipe app. But this is a web development article, and not a homebrewing guide, so I’ll skip over most of the details of the beer brewing process. If you’re interested though (and who isn’t interested in beer?), the fantastic how to brew will tell you everything you need to know.
Our first components will need to tell us how much sugar we can expect in the wort given the grains we intend to use. We need a form in Vue to select the malts and their amounts, as well as the amount of water we’ll use. This data needs to be passed to some structs in Rust, which define the rules for calculating the sugar content. The results should then be shown in Vue. There’s going to be a big code dump here; buckle up.
Managing the State
What we have now works, but I don’t think it’s going to scale very well. Everything is just in App.vue or lib.rs. As we keep adding more components, these will grow huge, and we can’t have that. Facebook has established a pretty solid standard for state management in the frontend development world with flux, which is also the principle behind the very popular redux and vuex. I want something along these lines, although I only have two weekends in which to make it, and one of them has been spent on initial setup, so it’s probably going to fall short. I guess I’m also going to need a name for this new stack. Since it’s a combination of flux and rust, and also a bit rough around the edges, I think I’ll go with “fluster”.
The goal is to have a global state, which can only be manipulated in controlled ways. They must be immutable, rather than changing directly, the structs will provide mutation functions which will return a new object with the changes applied. The state will be available to any component which cares to look at it. The main connection here is actually quite easy to set up. All we need to achieve all of this is our own Vue mixin:
I think it’s important to recognize here that this is not a full implementation of flux in Rust. It’s merely inspired by it. The most important difference is that instead of the powerful actions and dispatchers, I simply have mutation functions. I have a vague idea of how I would push it further to align better, and if there is any interest from others, maybe I’ll develop this further. But for now, this is all I have.
As a small timesaver, I also made a standardized pattern of adding a get_json function to each struct, allowing me to reduce the amount of individual getters I need to write. Now, in order to access any property from the fluster store, you can simply have a single computed value for the full object:
Which can then be accessed by anything else in the Vue component. Any functions on the struct can be called just as easily. As an example of a full component:
As you can see here — individual components now have access to the globally stored Equipment state. Similar changes have been made to the earlier components dealing with the Recipe state. This works pretty well, although it doesn’t mesh very well with the 2-way data binding central to Vue. I now wonder if maybe React would have been a better fit, but here we are.
Where Do We Go From Here?
Ultimately this shows some promise. It’s certainly not a production ready system, but I was able to more cleanly integrate the two systems over a couple of weekends than I’d expected. The Rust Wasm community has done a great job of building up the toolset. The development environment wasn’t perfect. While I did have hot reloading, changes to the Rust code would take a long time to compile. It also seemed to keep writing new files into my /tmp folder with each compilation. After a few hours of active development my hard drive would be full, and I would need to restart to clean it up. Additionally, I have so far been unable to configure jest to work with parcel, which makes testing a bit of a problem. But to be honest, I didn’t try very hard.
The full code can be found on GitHub.