Deno Fresh WASM: Code Modules in Rust
Tags: webdev, deno, rust, webassembly
Published: 2023-02-01 09:06:25

---
title: "Deno Fresh WASM: Code Modules in Rust"
published: "true"
description: "Deno Fresh WASM: how you can code your own WASM modules in Rust 🦀 and easily integrate them into your Deno Fresh 🍋 project."
tags: "webdev, deno, rust, wasm"
canonical_url: "https://rodneylab.com/deno-fresh-wasm/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w61wzhefj4srk0tsysrg.png"
---

## 🤔 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&rsquo;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&nbsp;lines of code. This means you can follow along even with minimal Rust knowledge.

![Deno Fresh WASM: Screen capture shows a browser window with the word FRESH as title and a blurred image below.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/feuwoqvb1ta2kus5inll.png)

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.

![Deno Fresh WASM: Screen capture shows a browser window with the word FRESH as title and a clear image of a Dinosaur with a mouth full of lemons below.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/885d7mddw53hogah4csp.png)

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&rsquo;s spin up a fresh Deno project:

```shell
deno run -A -r https://fresh.deno.dev my-fresh-app && cd $_
deno task start
```

If this is your first time using Deno see the post on <a href="https://rodneylab.com/getting-started-deno-fresh/">Getting Started with Deno Fresh to get Deno up and running on your system</a>.

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 <a href="http://localhost:8000">`http://localhost:8000`</a> 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:

```json
{
  "tasks": {
    "start": "deno run -A --watch=static/,routes/ dev.ts",
    "wasmbuild": "deno run -A https://deno.land/x/wasmbuild@0.10.2/main.ts"
  },
  "importMap": "./import_map.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}
```

We can use this new script to add a skeleton Rust WASM library to our project:

```shell
deno task wasmbuild new
```

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`:

```toml
[package]
name = "rs_lib"
version = "0.0.1"
authors = ["Rodney Johnson <ask@rodneylab.com>"]
edition = "2021"
license = "BSD-3-Clause"
repository = "https://github.com/rodneylab/deno"
description = "Basic image processing"
 
[lib]
crate_type = ["cdylib"]
 
[profile.release]
codegen-units = 1
incremental = true
lto = true
opt-level = "z"
 
[dependencies]
photon-rs = "=0.3.1"
wasm-bindgen = "=0.2.83"
```

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 <a href="https://doc.rust-lang.org/cargo/reference/profiles.html">tweak these parameters</a>.

### Deno Fresh WASM: Rust Source

Now you can edit `rs_lib/src/lib.rs` to look like this:

```rust
use photon_rs::{
  native::open_image_from_bytes,
  transform::{resize, SamplingFilter},
};
use wasm_bindgen::prelude::*;
 
#[wasm_bindgen]
extern "C" {
  // Use `js_namespace` here to bind `console.log(..)` instead of just
  // `log(..)`
  #[wasm_bindgen(js_namespace = console)]
  fn log(s: &str);
}
 
macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
 
const PLACEHOLDER_WIDTH: u32 = 10;
 
#[wasm_bindgen]
pub fn base64_placeholder(data: &mut [u8]) -> String {
  let image = match open_image_from_bytes(data) {
    Ok(value) => value,
    Err(_) => {
      console_log!("Unable to open image");
      return String::from("");
    }
  };
  let aspect_ratio = image.get_width() / image.get_height();
 
  resize(
    &image,
    PLACEHOLDER_WIDTH,
    PLACEHOLDER_WIDTH / aspect_ratio,
    SamplingFilter::Lanczos3,
  )
  .get_base64()
}
```

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 &ldquo;hello world&rdquo; `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 <a href="https://rodneylab.com/getting-started-rust-cloudflare-workers/#gear-rust-setup">Rust set up on your system</a>. 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.

```shell
cargo install wasm-pack
deno task wasmbuild
```

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` &mdash; 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&rsquo;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:

```tsx
import { asset, Head } from "$fresh/runtime.ts";
import { Handlers, PageProps } from "$fresh/server.ts";
import Image from "@/islands/Image.tsx";
import { base64_placeholder, instantiate } from "@/lib/rs_lib.generated.js";
import { Fragment } from "preact";
 
interface Data {
  placeholder: string;
}
 
export const handler: Handlers<Data | null> = {
  async GET(_request, context) {
    try {
      await instantiate();
 
      const data = await Deno.readFile("./content/dinosaur-lemon.png");
      const placeholder = base64_placeholder(data);
 
      return context.render({ placeholder });
    } catch (error: unknown) {
      console.error(`Error doing stuff with image file: ${error as string}`);
      return context.render(null);
    }
  },
};
```

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!

That block above uses a path alias to reference files within the project. You can set this us by updating `import_map.json`

```tsx
{
  "imports": {
    "@/": "./",
    "$fresh/": "https://deno.land/x/fresh@1.1.2/",
    "$std/": "https://deno.land/std@0.171.0/",
    "preact": "https://esm.sh/preact@10.11.0",
    "preact/": "https://esm.sh/preact@10.11.0/",
    "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
    "@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
    "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1"
  }
}
```

We also sneakily added in a line using the Deno std library above, so be sure to update line `5` too!

Here is the rest of the home route code and also the `Image` Preact component which we reference:

```tsx
export default function Home(context: PageProps<Data | null>) {
  const { data } = context;
  if (!data) {
    return (
      <Fragment>
        <div>Something went wrong!</div>
      </Fragment>
    );
  }
  const { placeholder } = data;

  return (
    <>
      <Head>
        <title>Deno Fresh 🍋 Rust WASM</title>
        <link rel="stylesheet" href={asset("/fonts.css")} />
        <link rel="stylesheet" href={asset("/global.css")} />
        <link rel="icon" href={asset("/favicon.ico")} sizes="any" />
        <link rel="icon" href={asset("/icon.svg")} type="image/svg+xml" />
        <link rel="apple-touch-icon" href={asset("/apple-touch-icon.png")} />
        <link rel="manifest" href={asset("/manifest.webmanifest")} />
        <title>Deno Fresh 🍋 Rust WASM</title>
        <meta
          name="description"
          content="Deno Fresh WASM: how you can code your own WASM modules in Rust 🦀 and easily integrate them into your Deno Fresh 🍋 project."
        />
      </Head>
      <main className="wrapper">
        <h1 className="heading">FRESH!</h1>
        <Image
          src="/content/dinosaur-lemon.png"
          placeholder={placeholder}
          alt="Cartoon style image of a dinosaur wiht a mouth full of lemons"
        />
      </main>
    </>
  );
}
```

```tsx
import { useEffect, useRef } from "preact/hooks";
 
interface ImageProps {
  alt: string;
  placeholder: string;
  src: string;
}
 
export default function Image({ alt, placeholder, src }: ImageProps) {
  const imageElement = useRef<HTMLImageElement>(null);
 
  useEffect(() => {
    if (imageElement.current) {
      imageElement.current.src = src;
    }
  }, [src]);
 
  return (
    <figure className="image-wrapper">
      <img
        ref={imageElement}
        alt={alt}
        loading="eager"
        decoding="async"
        fetchPriority="high"
        height="256"
        width="256"
        src={placeholder}
      />
    </figure>
  );
}
```

## 🙌🏽 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 <a aria-label="Rodney Lab Git Hub repo: open the full project code in Git Hub" href="https://github.com/rodneylab/deno/tree/main/demos/deno-rust-wasm">Rodney Lab GitHub repo</a>.  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 <a href="https://github.com/rodneylab/parsedown">parsedown module</a>.  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 <a href="https://rustwasm.github.io/docs/wasm-pack/">wasm-pack book</a> as well as some fairly <a href="https://rustwasm.github.io/docs/wasm-bindgen/">detailed `wasm-bindgen` docs</a>.  There are a few <a href="https://newsletter.rodneylab.com/issue/2022-12-29">resources for **learning Rust** itself in the December newsletter</a>.  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: 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 <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

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 <a aria-label="@ask Rodney: message Rodney on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Join the Rodney Lab Element Matrix room" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a>. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.