Enyert Vinas

Posted on 1st February 2022

WASM PART III: The final step, Integration!

news-paper Clojure | Software Development |

This article presents the final step of our series of posts: WASM PART I and WASM PART II about using WASM, ClojureScript and Rust.

In WASM PART II we presented the presentation layer of our project like a simple game named TiedUp. In this post, we want to explain to you the process to integrate the presented frontend (this can used for any ClojureScript project) with the WASM + Rust logic.

Let’s start explaining the general process to engage all our pieces!

The general process

First of all, we want to remember that we have a ClojureScript project. With this fact in mind, we can make the following assumption: “ClojureScript can use npm packages as dependencies”.

On the other hand, we are able to create npm packages using Rust, WASM and a tool named wasm-pack.

Now, using the last two sentences we can say that ClojureScript can use packages created using Rust + WASM!

Amazing, right? But please, don’t drink too much KoolAid and follow us presenting an explanation of every part of the general process.

Building a npm package using Rust + WASM

To continue with our idea, we have to introduce Cargo.

Cargo is the main Rust package manager, with this tool we can download our dependencies and manage our development life-cycle.

We will be using Cargo to accomplish the following steps:

  1. Install wasm-pack
  2. Generate our base package project skeleton.
  3. Resolve the relationship between the wasm-pack crate and our Rust project.

Cargo is installed automatically when you install Rust. For instruction about Rust installation, please refer to this site.

Installing wasm-pack

wasm-pack is a compilation tool that use Rust code to interop with JavaScript.

To install wasm-pack, we will use the following command:

cargo install wasm-pack

Generate our base package project skeleton

Assuming that you have already installed Cargo and wasm-pack, it’s time to create our base package project skeleton.

To accomplish this objective, we will use the following command:

cargo new --lib tiedup-json-loader

This command creates a skeleton with the following structure:

├── Cargo.toml
└── src
    └── lib.rs

We have a root folder named tiedup-json-loader and two important files: Cargo.toml and src/lib.rs.

Inside Cargo.toml file we will include information related to the development life-cycle and dependencies, so Cargo will use this file as a guide about which crates(dependencies) to download and how to compile the project.

The other file is src/lib.rs. This file contains our source code, so we can change the functionality of our Rust crate modifying it.

For our purposes, the Cargo.toml file looks like this:

name = "tiedup-json-loader"
version = "0.1.0"
edition = "2018"

name = "tiedup-json-loader"
version = "0.1.0"
edition = "2018"

serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }

For more information about the structure of the Cargo.toml file, please check this link.

Build our dependency using wasm-pack

Now, it’s time to build our package to be used from JavaScript or ClojureScript projects. To accomplish this objective, use the following command in the root folder of the project:

wasm-pack build --target bundler

Assuming that you have NodeJS and npm installed, this command will build a npm package for you.

After this execution, a pkg folder will be generated. Please change to this folder using the command:

cd pkg

And now we have to link the package with npm platorm with the following command:

npm link

This will upload your package to npm, so your code is ready to be used, congratulations! Let see how to use it in ClojureScript.

Using npm modules as dependencies in ClojureScript

For our specific project we’ll explain how to import our modules using shadow-cljs, but this can be done with other tools like figwheel.

The first thing to do here is install shadow-cljs from npm with the following command:

npm install shadow-cljs

You can add the -g modifier to do a global installation but I recommend you to do this local.

The second step is the creation of a shadow-cljs.edn as a configuration file with the following content:

{:source-paths ["src"]
 :dev-http {8080 "target/"}
 :builds {:client {:output-dir "target/"
                   :asset-path "./"
                   :target :browser
                   :modules {:client {:init-fn app.main/main!}}
                   :devtools {:after-load app.main/reload!}}}}

Now we can use npm install tiedup-json-loader or yarn add tiedup-json-loader to our tiedup project as dependency, then import it in code as follows:

(ns app.main
  (:require ["tiedup-json-loader" :as tiedup]))

Wrapping up!

In this series of articles, we wanted to present you a high level vision about how to integrate ClojureScript code with WASM.

We used Rust in conjunction with wasm-pack to compile and generate packages linked with npm modules, and we showed you how to use these modules in ClojureScript using shadow-cljs.

We hope this information helps you to build wonderful applications with all these new tools.

Happy Hacking! 🙂