29 January 2021, 05:02
wasm first steps
After a number of weeks getting to grips with the basics of Rust and some of the syntax I'm going to explore how we can use Rust code with JavaScript via Web Assembly.
Getting Started
I'll be using a template to get started, the folks over at rustwasm have set up the boiler plate to get started. Note that cargo generate is also used.
cargo generate --git https://github.com/rustwasm/wasm-pack-template
using wasm-game-of-life
as name of the project.
cd wasm-game-of-life
here are the contents:
wasm-game-of-life/
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
└── src
├── lib.rs
└── utils.rs
an overview
Cargo.toml
Here is where dependencies and meta data are defined for the cargo, which is Rust's package manager and build tool. As we used a template to inialise the project, the file is already pre-configured with the wasm-bindgen
dependency.
src/lib.rs
This file is the root of the Rust crate that is going to be compiled into WebAssembly. It uses wasm-bindgen
to interface with JavaScript. It currently exports the greet
function which uses the window.alert
method to alert a message.
mod utils;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, wasm-game-of-life!");
}
src/utils.rs
The src/utils.rs module provides common utilities to make working with Rust compiled to WebAssembly easier.
Building the project
I'll be using wasm-pack to orchestrate the following build steps:
- Ensure that we have Rust 1.30 or newer and the
wasm32-unknown-unknown
target installed viarustup
, - Compile the Rust sources into a WebAssembly
.wasm
binary via cargo, - Use
wasm-bindgen
to generate the JavaScript API for using our Rust-generated WebAssembly.
The above points can be satisfied, the following command is ran inside the project directory:
wasm-pack build
When the build is completed, artifacts are generated in the pkg
directory, with the following content:
pkg/
├── package.json
├── README.md
├── wasm_game_of_life_bg.wasm
├── wasm_game_of_life.d.ts
└── wasm_game_of_life.js
wasm-game-of-life/pkg/wasm_game_of_life.d.ts
The .d.ts
file contains Typescript type declarations for the JavaScript glue. One advantage of this is the ability to have calls into WebAssembly functions type checked, meaning an IDE can provide autocompletions and suggestions.
export function greet(): void;
wasm-game-of-life/pkg/package.json
The package.json file contains metadata about the generated JavaScript and WebAssembly package. This is used by npm and JavaScript bundlers to determine dependencies across packages, package names, versions, and a bunch of other stuff. It helps Rust (and ultimately wasm) integrate with JavaScript tooling and allows us to publish the package to npm.
{
"name": "wasm-game-of-life",
"collaborators": [
"Your Name <your.email@example.com>"
],
"description": null,
"version": "0.1.0",
"license": null,
"repository": null,
"files": [
"wasm_game_of_life_bg.wasm",
"wasm_game_of_life.d.ts"
],
"main": "wasm_game_of_life.js",
"types": "wasm_game_of_life.d.ts"
}
Adding the package it to a web page
To add the package to a web page we can bootstrap a web based wasm project using the create-wasm-app JavaScript project template.
Run this command within the project directory:
npm init wasm-app www
Here's the output in the wasm-game-of-life/www
directoy:
wasm-game-of-life/www/
├── bootstrap.js
├── index.html
├── index.js
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── README.md
└── webpack.config.js
Let's take a tour of the files.
wasm-game-of-life/www/package.json
This package.json comes pre-configured with webpack
and webpack-dev-serve
r dependencies, as well as a dependency on hello-wasm-pac
k, which is a version of the initial wasm-pack-template package that has been published to npm.
wasm-game-of-life/www/webpack.config.js
This file configures webpack and its local development server. It comes pre-configured, and you shouldn't have to tweak this at all to get webpack and its local development server working.
wasm-game-of-life/www/index.html
This is the root HTML file for the Web page. It doesn't do much other than load bootstrap.js, which is a very thin wrapper around index.js.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<script src="./bootstrap.js"></script>
</body>
</html>
wasm-game-of-life/www/index.js
The index.js is the main entry point for our Web page's JavaScript. It imports the hello-wasm-pack npm package, which contains the default wasm-pack-template's compiled WebAssembly and JavaScript glue, then it calls hello-wasm-pack's greet function.
import * as wasm from "hello-wasm-pack";
wasm.greet();
Install Dependencies
First, ensure that the local development server and its dependencies are installed by running npm install
within the wasm-game-of-life/www
subdirectory:
npm install
This will read package.json
and install the dependencies listed there, including the webpack javascript bundler and development server.
Note that webpack is not required for working with Rust and WebAssembly, it is just the bundler and development server we've chosen for convenience here. Parcel and Rollup should also support importing WebAssembly as ECMAScript modules. You can also use Rust and WebAssembly without a bundler if you prefer!
Using local package in www
Rather than use the hello-wasm-pack package
from npm, we want to use our local wasm-game-of-life
package instead. This will allow us to incrementally develop our Game of Life program.
Open up wasm-game-of-life/www/package.json
and next to "devDependencies", add the "dependencies" field, including a "wasm-game-of-life": "file:../pkg" entry:
{
// ...
"dependencies": { // Add this three lines block!
"wasm-game-of-life": "file:../pkg"
},
"devDependencies": {
//...
}
}
Next, modify wasm-game-of-life/www/index.js
to import wasm-game-of-life
instead of the hello-wasm-pack
package:
import * as wasm from "wasm-game-of-life";
wasm.greet();
Since we declared a new dependency, we need to install it:
npm install (or npm i)
The web page is now ready to be served locally!
Serving Locally
Next, open a new terminal for the development server. Running the server in a new terminal lets us leave it running in the background, and doesn't block us from running other commands in the meantime. In the new terminal, run this command from within the wasm-game-of-life/www
directory:
npm run start
Navigate your Web browser to http://localhost:8080/ and you should be greeted with an alert message. Anytime you make changes and want them reflected on http://localhost:8080/, just re-run the wasm-pack build command within the wasm-game-of-life directory.
That's it, we have a hello world!