This was a lot of stuff. Stuff for me to install. Stuff for me to run. Stuff for me to (not) understand. So. Much. Stuff!
To be clear, I don’t want to dump on the author of this template. For one thing, it was created in 2019, and Rust frontend has advanced a lot since then1. And the template did what it says. It was a quick start. I just didn’t understand how most of it worked. And it was a bit slow to run on code changes. And making changes to it was frustrating because of all the stuff I didn’t understand.
So when I went to try Dioxus, I wanted to see if I could avoid using any Node technologies, especially a bundler like webpack, and doubly especially avoiding any JS/TS glue code for the app.
Can I avoid that? Well, let’s figure that out.
What Does Webpack Do?
It’s called a “bundler”, which is pretty clear. It takes all your stuff and bundles it into a thing you can run from a local dev server or distribute to production. That stuff includes:
- Your JS/TS2 application code.
- At least one HTML file, which you need to kick off the application in the browser.
- Your CSS, which may need to be generated from SCSS or SASS.
- Anything else.
The output of Webpack is an
index.html file that has been processed to load
your compiled CSS3, your compiled JS4, and maybe other stuff. It will
also place the compiled CSS, images, fonts, etc. in a single directory tree,
usually in a top-level folder named
dist. Then you can start a dev server to
serve this tree or ship it off to production (by making a tarball, a Docker
When working on a frontend application in Rust, you still need to do some of
these tasks. You need to compile your application from Rust to WASM. You need
index.html to load that application. You’ll probably want that
index.html to load a CSS file. You might have fonts or images you need to
distribute. And you can do all of that with webpack.
But you don’t have to!
Just Use Trunk
Trunk is to Rust WASM web apps what Webpack is to JS web apps. It will compile your Rust code to WASM, process SASS or SCSS files, minify things, copy images, etc.
Trunk integrates with the wasm-bindgen tool, which is the CLI tool that turns Rust into WASM. This tool also generates some polyfill code to implement features not yet implemented in all browsers.
wasm-bindgen crate also
provides an API for communicating with JS code from
Rust, which allows you to
integrate with existing JS libraries5. There are a number of libraries that
build on top of this integration, like
wasm-logger, which makes the output
log crate go the browser’s console.
Here’s a very minimal
index.html that will make Trunk compile your Rust app:
Wait, what? There’s nothing in there! If you don’t point it at any code in
your HTML, then Trunk will automatically compile the crate containing your
index.html and turn it into a WASM application that your index.html loads.
If you want some CSS you can add this to the
If you use
rel="sass" then Trunk will compiled that file
into a CSS file. Trunk also hashes the file and puts that hash in the path to
ensure that browsers reload the CSS whenever the CSS source changes.
Other file types all use the same
<link rel="$type" href="/path/to/file.$type"> pattern. Trunk supports icons (for your favicon),
images, and it can copy files and directories wholesale for images, fonts,
etc. At the present, it doesn’t support any sort of fancy processing of those
files natively, and it doesn’t do hashing of them (yet).
However, you can use your own hooks to make it do other stuff by running arbitrary programs. And this is how I’ve been able to avoid using webpack and I’m able to not run node at all for my web app.
Ok, I lied, I do run node.
I’m using the standalone
tailwindcss executable, which effectively bundles
node plus some JS code into a single executable. You can download this from
the tailwindcss GitHub project’s releases
I have an “input CSS file” for tailwind that looks like this:
I also have a
tailwind.config.js file that looks like this:
Then I have this bit of configuration in my
Why can’t I just run
tailwindcss directly. I don’t have a damn clue.
What exactly does
tailwindcss do? To answer that, it’s important to
understand the basic design of Tailwind. Unlike most CSS frameworks, with
Tailwind you don’t build your own SASS/SCSS/CSS file using the framework as a
base. You don’t define new classes based on Tailwind classes.
Instead, Tailwind provides hundreds of small utility classes like
(right margin size 8),
flex (use a flexbox layout for this element), and
text-lg (make this text larger). You use those classes directly in your code
which generates HTML. Here’s an example using JSX:
This example uses three Tailwind classes,
underline. Your first reaction may be shock and horror. Mine was! But when I
read more about the reasoning behind it I realized that this actually works
very nicely with modern frontend web app practices.
Nowadays, you don’t write your HTML in one set of files and then make it
dynamic with separate JS code. Having lots of HTML was the original reason to
use CSS classes. It meant that you could have many different HTML pages with
the same styles easily. Anywhere you embedded a search box you’d slap a
search class on the
In modern apps, your JS (or in my case, Rust) code generates the HTML directly. So your HTML generation can be factored into functions or methods. And that means that you never need to repeat the same sets of Tailwind classes across your application. If you need to reuse some particular piece of layout, you can turn that into a reusable component. Here’s one from my music player:
If I need that set of classes,
"flex flex-row flex-wrap justify-center", in
other components then I can either make a new component for that
those classes, or I can just have a function that returns those classes:
I’m using a real programming language to generate HTML, so I can take advantage of that fact to avoid repeating my CSS classes all over the place6.
Finally, to tie it all together, I load the CSS in my
Remember that I’m running
tailwindcss via Trunk as part of my build process?
Why? If tailwind is just a bunch of already-defined classes what is that
Well, calling it a “bunch” of classes may be understating things. To see how
many, I generated a file containing all of the available-by-default CSS
classes, with various media-size modifiers and pseudo-classes for
so on. It came out to around 7MB. That’s a big CSS file. Too big.
tailwindcss does is figure out which classes you’re using by looking
at your code. Then it generates a CSS file with just the ones that you
need. For my app I currently end up with a file that’s just 19k. That’s much
better than 7MB!
Have I mentioned that Tailwind offers a lot of CSS classes? Take a look at just the padding classes. There are a lot, and the names aren’t all that memorable.
If you typo a name then
tailwindcss simply ignores it and it’s missing from
the generated CSS. As I worked on my app I did this a lot. And it was always
confusing. Was my layout wrong because of my HTML? Was it the CSS classes I
chose? Did the CSS generation process not regen the file, so the class wasn’t
in the generated CSS? Or did I just typo a class name, so something had
“px-13” as a class, which doesn’t exist.
It turns out I made a lot of typos. I kept wasting time trying to debug why my newly modified CSS wasn’t applying any styles, only to realize I’d made a typo.
Wouldn’t it be nice if I could get the Rust compiler to check my CSS class names? Yes, that would be nice.
Wouldn’t it be nice to have something like that for Dioxus? And maybe it could be generic enough for any framework? And maybe it could be written in Rust?
Yes, that would be fantastic! And I’ll talk about that in my next blog post,
covering my new
tool. I’ll also cover the Dioxus
helper crate I wrote that makes it super easy8 to use SVG
icons from HeroIcons in your Dioxus project.
The first commit was in March, 2019, so it’s been about three years. ↩︎
Compiled from SASS or generated by
tailwindcssor, in the case of plain CSS files, just concatenated together and maybe minified. ↩︎
Fortunately, I haven’t had to do that yet, because these sorts of cross-language integrations are often painful in my experience. ↩︎
Of course, “can” is the operative word here. Am I doing this consistently? No, because I’m doing a lot of experimentation so I’m okay with some quick cut and paste for now. But if I was building something that I expected many other people to hack on and maintain, I would (I hope) exercise more discipline. ↩︎
Oh yay, another tool to run and another config file in my project I don’t understand. ↩︎
Barely an inconvenience! ↩︎