🤔 Why use WASM Modules with Deno? #
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.
🧱 What are we Building? #
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).
🍋 Spinning up a Fresh Deno Project #
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 http://localhost:8000
in your browser to see the skeleton project.
⚙️ Setting up the Project for Rust WASM #
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 wasmbuild
script:
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
code (rs_lib/src/lib.rs
). We will update these next.
🦀 Rust Code #
To make the photon-rs
crate available in the project, we add it to
Cargo.toml
:
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 13
– 17
). If either performance or size is an issue, you might want to tweak these parameters .
Deno Fresh WASM: Rust Source #
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
seen are format!
, and println!
.
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
works.
Moving on, 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.
🔨 Compiling the Module #
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 lib/rs_lib.generated.js
— the JavaScript code we will use import in the next section. If you scan through that file,
you will see the base64_to_image
function we created in Rust. Although
this is a JavaScript file, it has type information in JSDoc annotations meaning we have types for use
in TypeScript in the next section.
Using the Module #
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
into a data
which is a UInt8Array
.
That is all there is to it!
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.
🗳 Poll #
🙌🏽 Deno Fresh WASM: Wrapping Up #
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 wasm-bindgen
docs . 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.
🏁 Deno Fresh WASM: Summary #
Can you use the wasm-pack Rust tooling to create WASM modules for Deno? #
- 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.
Can any Rust code compile to WASM? #
- You might find some code does not compile into WASM, especially where the code accesses the file system or needs libraries installed on the end-user’s system. An example of the latter is crates which use openssl. The openssl crate is a wrapper on C code. As a workaround in that case try alternatives which use pure Rust implementations. Having said all that, you should be able to use any JavaScript APIs in your Rust code, so the FileReader API for example is one you should check out if you need to work with files. Be sure to test your code early and often to avoid disappointment!
Is there a size limit for WASM modules? #
- 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.
Can you publish your WASM module on deno.land/x? #
- 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.
🙏🏽 Deno Fresh WASM: Feedback #
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!
https://t.co/RTkwLfgl4u pic.twitter.com/kj35IcMts6
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, @rodney@toot.community 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.