Deno Fresh WASM is pretty easy to set up, just by adding a single dependency to your project. This lets you write code in Rust, compile it to WASM and then use that generated module in your Deno project. That is all straightforward, but why would you do it? We’ll look at three reasons here quickly before cracking on and seeing how to set it all up.
For me the main reason was to help learn Rust. Oftentimes you just need to write small chunks of code for your module so, shoehorning a little Rust WASM into a side-project delivers the benefits of practising Rust. That is without a heavy payout in terms of time investment. More practical reasons for using WASM could be to optimise code and also to leverage libraries in the Rust ecosystem. Image processing is a great example for optimisation here. Although there is the amazing sharp image plugin (based on the C libvips image processing library), links to Node mean it will not run in all environments. In fact we look at image processing in this post. We will do some basic image manipulation using the Rust photon-rs library.
As just mentioned we will be using the photon-rs library within Rust. The main goal is to see an example of integrating Rust WASM into Deno, so we will keep the Rust side simple. In fact, the main Rust function will only have 18 lines of code. This means you can follow along even with minimal Rust knowledge.
The Rust WASM module will generate a Base64 low resolution image placeholder from an image file saved within our project. On a slow connection, it might take a couple of seconds for the full-sized image to load. We show that Base64 image placeholder in the interim. This is a nice pattern to use on websites as it allows you to reduce Cumulative Layout Shift as well as make the page look more interactive while still loading. Overall this contributes to a better user experience.
You can follow along starting from scratch or just read and get the code for the completed project in the Rodney Lab GitHub repo (link further down).
To get going let’s spin up a fresh Deno project:
If this is your first time using Deno see the post on Getting Started with Deno Fresh to get Deno up and running on your system.
Answer the prompts to best suit your own needs. The code below just uses vanilla CSS so there is
no need to set up Tailwind support if you are not a fan. Once it is running you can jump to
in your browser to see the skeleton project.
If you are already familiar with Rust WASM, you might have used the
wasm-pack CLI tool to generate WASM code from Rust. Even though that is not too involved, the Deno
wasmbuild module makes things even easier when using Deno. So we go for the
wasmbuild approach here. When we issued the
deno task start command above
this ran the
start script defined in
deno.json in the project root directory. We will update that file to add a new
We can use this new script to add a skeleton Rust WASM library to our project:
This will create an
rs_lib directory in the project with a
Cargo.toml (analogous to node
package.json file) file and also the Rust skeleton
rs_lib/src/lib.rs). We will update these next.
To make the
photon-rs crate available in the project, we add it to
Note that the
wasm-bindgen crate needed to work with WASM in Rust is
already listed here. Also worth a mention are the optimisation sensible defaults in the
[profile.release] block (lines
17). If either performance or size is an issue, you might want to tweak these parameters
Now you can edit
rs_lib/src/lib.rs to look like this:
Our main module function, which we will call from Deno is
base64_placeholder defined from line
21 down. Above that we have use statements for
the crates and methods we plan to use right at the top. Between the two is a handy macro for creating
console.logs. Although originating from the Rust code, these appear in the console when running our
code in Deno. Macros kind of create syntactic sugar in Rust. Other examples you might already have
You can see an example of invoking our macro in line
26. Once you
have everything working, feel free to stick a “hello world”
console_log on the first line of
base64_placeholder to convince yourself this
base64_placeholder takes the image bytes as an input (we
will read the image file into a
UInt8Array in the TypeScript code and
pass those bytes to this function). Our Rust function uses the image bytes to create an image object.
We resize the image so it is 10 pixels wide, preserving the aspect ratio. Finally we convert that output
to a Base64 string and return that. Rust has implicit returns, meaning the last line of a function
is what gets returned. In our case that will be the Base64 image string.
To compile the module, we can use the
wasmbuild script again. Note
you will need to have Rust set up on your system. This uses
wasm-pack under the hood, so as a first step install it,
using cargo (if you do not yet have it installed). Cargo is Rust tooling for managing crates (Rust
packages). The Rust setup process adds it to your system.
The second step creates a
lib folder in your project (it will be a
little slow the first time you run it). The WASM will be in
lib/rs_lib_bg.wasm. This is assembly code which is not human-readable. As well as the WASM you have
you will see the
base64_to_image function we created in Rust. Although
in TypeScript in the next section.
Hopefully this hasn’t been too complicated thus far. Using the module is pretty
straightforward. We import it (like any other module), then call its asynchronous
instantiate function, and can then use it just like any other import. Here we add a handler to
routes/index.tsx to generate the Base64 placeholder, using our new module:
Note in line
16, we read the image file from disk as binary data
data which is a
That is all there is to it!
That block above uses a path alias to reference files within the project. You can set this us by
We also sneakily added in a line using the Deno std library above, so be sure to update line
Here is the rest of the home route code and also the
Image Preact component
which we reference:
routes/index.tsx — click to expand code.
islands/Image.tsx — click to expand code.
In the post we have had a whistle-stop tour of setting up your first Deno WASM project. In particular, we saw:
- how to use wasmbuild to quickly add a Rust WASM module to your Deno project,
- how to add console logs in your WASM Rust code,
- some basic Rust image manipulation.
The complete is in Rodney Lab GitHub repo . As a next step you might consider publishing your WASM module to deno.land/x so other developers can easily use it. This is something I did with the parsedown module . It has code I use for the Newsletter and parses Markdown to HTML as well as generate email HTML and plaintext emails. Let me know if you would like to see a short video on publishing a Deno module.
If you want to learn more on wasm-pack, there is a wasm-pack book
as well as some fairly detailed
. There are a few resources for learning Rust itself in the December newsletter
. Finally, please get in touch if you would like to see more content on Deno and
Fresh. I hope you found the content useful and am keen to hear about possible improvements.
- wasm-pack tooling does a lot to make creating WASM modules in Rust easy. If you are working in Deno though, you can make your life even easier by using the `wasmbuild` module. This adds skeleton template code to your Deno project. On top you can compile the Rust code from a script within your Deno project. Finally, if you want to take it up a level, you can set Deno to watch your Rust source. That way you can re-compile automatically when the code changes. Under the hood wasmbuild uses wasm-pack so you need to set up both.
- Some hosting providers have low limits on maximum size of your generated WASM. Deno deploy has quite generous limits meaning you can run code using even moderately heavy crates. There are some optimisations detailed in the wasm-bindgen docs. Naturally these will only get you so far, but are worth pursuing if you are not far from your target size.
- Yes you can publish WASM modules to deno.land/x quite easily. This will make your module easily accessible to other developers. You currently need a GitHub account to be able to host your module on deno.lad/x. This process is not too complicated and involves adding a Webhook to your project in GitHub. This helps automatically update the deno.land/x version of your module.
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please consider supporting me through Buy me a Coffee.
Love Deno 🍋 Fresh’s modern web features. Just dropped a post showing how you can:— Rodney (@askRodney) January 18, 2023
- generate Base64 low res image placeholder in 🦀Rust,
- compile the Rust to WASM in Deno,
- use your WASM right in your Fresh TypeScript code
Hope you find it useful!
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter, @[email protected] on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also subscribe to the newsletter to keep up-to-date with our latest projects.