WebAssembly -Part II.A | Getting Started with Rust
The Introduction Before the Introduction
Last time, we started off by explaining the core concepts of WebAssembly and how you can try it for yourself with little to no setup. If you need a refresher, please refer to this link.
This article is more of a how-to on getting up and running with a production-grade web project which incorporates Rust (or any other language for that matter) and WebAssembly into your web pages.
Introduction
Nowadays, as front and back end developers we’re used to having tools save us time and avoid human errors by performing mundane tasks such as code compilation, style checking, code organization, optimization, etc.
In particular, when writing web applications we can set up a bundler or task runner to seamlessly transform, optimize, and bundle our files. Naturally, if incorporating WebAssembly in parts of our code, we expect to have our tools also do lots of heavy lifting for us.
So all being said, this is the type of picture we want to get to paint:
And in case you’re wondering: YES you can update your code (Rust, C++, Go, etc.) and have live-reloading or hot-module-replacement automatically display any changes.
Why Rust?
The beauty of WebAssembly is that it’s agnostic. What do I mean by that?
Any language with a compiler compiling down to Wasm can be used for web development.
So in theory, you could use multiple languages to develop your web application.
Therefore, the question “Why Rust?” is a bit misleading. It sounds like you’re stuck with one Wasm-supported language for you web application.
However, at the time of writing, languages like Rust and C/C++ have better support compared to other languages. By support I mean:
- Production-ready compilers and tool-chains that generate Wasm
- Active communities and helper tools
- Less experimental libraries, and more production ready ones
- Etc.
This is mainly due to the fact that currently WebAssembly supports just flat linear memory. That’s all good for languages like C/C++/Rust, but other modern languages need a garbage collector to run.
But it’s not to say other languages aren’t progressing though. Check out Microsoft’s project Blazor for example.
Lastly, the consensus on the web seems to be: Rust is a safer and faster language than C/C++
So in essence, we’re left with this current picture:
Less talk, more code. How to Setup your Project
Ok, I’ll try a bit less rambling. What we’re going to do now is build our typical hello world application.
For a more complete example you can refer to the Wasm Math Helper further down in the article.
What are we building?
It’s as simple as clicking a button and having WebAssembly (i.e. Rust code) update the DOM to display a greeting.
That’s right, we’re not updating the DOM with JavaScript 😎.
Source Code
Isn’t WebAssembly overkill for this?
Absolutely
So when does it make sense to use WebAssembly?
I’d use WebAssembly wherever there’s performance bottlenecks in a Web or NodeJs application. E.g. imagine your JavaScript code relies on some slow hashing function. So why not use a really fast (even existing) C/C++ library right in the browser?
Project Pre-Requisites
Step 1 [Init]
Open a terminal like command prompt or bash and type in:
mkdir hello-wasm && cd hello-wasm && npm init -y
Step 2 [Install Dev Dependencies]
npm i -D webpack webpack-cli webpack-dev-server clean-webpack-plugin html-webpack-plugin
Step 3 [Create Your Source Folder]
Open up your editor of choice in this directory.
Make sure to create your app.js and index.html files in the src directory
Step 3 [Creating the Web Page]
Paste the below in your index.html file.
Step 4 [Creating the Rust Lib]
In your command prompt or bash make sure you’re inside the src/ directory. Type in the following:
$ cargo new hello_world --lib
What are we doing here?
Cargo is a tool that allows Rust packages to declare their various dependencies and ensure that you’ll always get a repeatable build. -Rust-Lang documentation-
So we’re now left the following:
lib.rs is where we’re going to write our actual Rust code.
Cargo.toml among other things, is going to ensure our dependencies are fetched and built.
Step 5 [Declare Rust Dependencies]
Paste the below into your Cargo.toml file:
crate: in Rust world, this is synonymous to library or package.
[lib] to specify we’re creating a library, not an executable.
[dependencies] wasm-bindgen this library will facilitate high-level interactions between wasm modules and JavaScript. Plus, it’ll help us automatically generate a package containing JavaScript-helper code that’s used to call the .wasm modules we’ll be generating.
[dependencies.web-sys] well, the Rust people explain it well:
It provides raw bindings to all the Web’s APIs: everything from DOM manipulation to WebGL to Web Audio to timers to
fetch
and more!
Step 6 [Creating the Rust Greeting Code]
Back in the lib.rs file get rid of the boiler plate code that’s been generated for you, and paste in the following:
Notice the [wasm_bindgen] attribute at the top of the greet() function. Soon enough this attribute is going to help us package up our .wasm and JS helper files when using the wasm-pack tool (which we’ll get to shortly).
Also, notice how the web_sys crate gives us seamless access to the DOM. Yes, the HTML DOM!
You can already start imagining using powerful Rust or C/C++ libraries for things like rendering graphics in an HTML5 canvas for example…
Step 7 [Packaging up Rust with Wasm-Pack]
In part, wasm-pack is a tool that reads and packages up our Rust libraries with helper JS and .Wasm files. These are the files that we’re going to use in our web application.
Let’s see it in action. In your command prompt or bash make sure you’re in the ./src/hello_world/ directory. Type in:
wasm-pack build
You should end up with a pkg/ directory containing the files that we’ll use for our web app:
Notice the following files:
hello_world.js(or .ts) instead of us directly accessing hello_world_bg.wasm file, this JS file contains helper code that does that for us. Easy!
package.json this is where the money is for me. With wasm-pack you could even go a step further and develop libraries with Rust and then publish them to npm/yarn etc. You’re users won’t even know they’re using highly performant wasm.
Step 8 [Automating wasm-pack]
Ok, this article’s meant to show you how to make your wasm dev life easier. So that being said, scratch step 7. Yes, scratch step 7.
Delete the pkg and target directories.
Now, in your terminal make sure you cd to the root of your project and install the following plugin:
npm i -D @wasm-tool/wasm-pack-plugin
This plugin will track our Rust libraries and automatically re-create the pkg directory in step 7.
We’ll use this plugin soon enough.
Step 9 [Configure Webpack]
In the root of your project create the file webpack.config.js and paste into it the following code:
In the above I’ve added standard webpack config code. But what’s more interesting is the WasmPackPlugin.
Thanks to this plugin, every time we build our Web App (i.e. run Wepback) our Rust code will be automatically compiled and packaged up in the respective pkg/ directory we saw in the previous steps.
Step 10 [Configure Package.json]
In your package.json file’s script section, add the following scripts:
"scripts": {"build": "webpack --env.production --colors","start": "webpack-dev-server --env.development --colors","start-in-prod-mode": "webpack-dev-server --env.production --colors --open"}
Back in your terminal, make sure you cd into the root of the project and type in:
npm start
This should automatically rebuild rust pkg directory
Note: running the other two scripts will cause the Rust code to be compiled in production/release mode, thus generating more compact and efficient wasm files.
Step 11 [app.js]
Paste the following in your app.js
Once the web app users click on the greet button, the code will dynamically import the src/hello_world/pkg/hello_world.js’s greet() function. This function will then help us access the hello_world.wasm file that’s going to update the doc greeting (i.e. the <h1> element).
Step 12 [Let’s see it in action]
By this time, you’re project structure should look like this:
Now, in your terminal and from the root of the project, run:
npm start
Click on the greet button
And voilà!!!
Step 13 [Update the Rust code & see it straight away!]
Make sure the webpack-dev-server is still running. Otherwise, run the npm start command again.
Now go back to the lib.rs file in the src/hello_world/src/ directory. Update the greeting to anything you prefer.
Save your file and go back to the web page on your browser. After you press the greet button again you’ll see your new greeting.
Wasm Math Helper
If you want to take more of a deep dive, I recommend checking out another application I built: WASM Math Helper
It’s a one page application and contains math utilities.
The key point is that the same utility is replicated. One is written with good old fashioned JavaScript, and the other is written with Rust. Both implement the exact same algorithms. As such, we can compare JS and Wasm performance.