Build, test, pack and publish WASM modules with wasm-pack
Tags: webdev, webassembly, javascript, rust
Published: 2020-01-06 22:51:17

# wasm-pack

JavaScript is the most loved language. The introduction of Node propelled the success of the JavaScript world on the server side. Node made easier for billions of developers to develop and share libraries. WebAssembly modules require JavaScript to seamlessly interoperate inside the JavaScript engine. Together JavaScript and WebAssembly makes the web faster and smaller. 

## Getting Started...

> The `wasm-pack` tool seeks to be a one-stop shop for building and working with rust- generated WebAssembly that you would like to interop with JavaScript, in the browser or Node.js. - wasm-pack website

_Why do you need wasm-pack?_

The `wasm-pack` makes it easy to build and pack the Rust and WebAssembly based projects. Once packed the module is ready to be shared with the world via the npm registry, just like millions (or even billions) of JavaScript libraries out there.

> How to use wasm-pack?

The `wasm-pack` is available as a cargo library. If you are following this book, then you might have already installed the Cargo. To install the wasm-pack run the following command.

```
$ cargo install wasm-pack
```

---

Check out my book on Rust and WebAssembly [here](https://sendilkumarn.com/wasm-book)

---

The above command download, compile, and install the `wasm-pack`. Once installed the `wasm-pack` command is available. To check whether the `wasm-pack` is installed correctly, run

```
$ wasm-pack --version
wasm-pack 0.9.1
```

Let us see how to use `wasm-pack` to build and pack the Rust and WebAssembly projects.

## Write some code ✍️

Let us create a new project using `Cargo`. 

```
$ cargo new --lib wasm_pack_world
  Created library `wasm_pack_world` package
```

The `cargo new --lib` command generates a library with a name `wasm_pack_world`. Open the project in your favourite editor. Open the `cargo.toml` file and add the `wasm-bindgen` dependency in `[dependencies]` segment. 

```
[package]
name = "wasm_pack_world"
version = "0.1.0"
authors = ["Sendil Kumar <sendilkumarn@live.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.69"
```

> Note the `[lib]` segment in the `cargo.toml`. Refer [here](https://doc.rust-lang.org/reference/linkage.html) for more information.

Next open the `src/lib.rs` file and replace the contents with the following:

```
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn get_answer() -> i32 {
    42
}
```

We first import the `wasm_bindgen` library using `use wasm_bindgen::prelude::*`. Next we define a simple function `get_answer` that returns 42 (the universal answer). We added `#[wasm-bindgen]` annotation to the function. 

In the previous example, we have used `cargo` to create the WebAssembly module. While `cargo build --target wasm32-unknown-unknown` converts Rust code into WebAssembly module, but they cannot generate binding file and cannot understand the `#[wasm_bindgen]` annotation.

> The Binding JavaScript file helps to translate the value between JavaScript and WebAssembly.

The `wasm-bindgen-cli` helps to generate the binding JavaScript file. The binding file is kind of translator that translates value between JavaScript and WebAssembly.

The `wasm-pack` comes with a `build` option that does the following two steps:

1. Generate the WebAssembly module 
2. Generate binding JavaScript file 

The `wasm-pack build` generates the WebAssembly module and the binding file.

```
$ wasm-pack build
```

---

## How it works...

This is what happens, when we run `wasm-pack build`.

1. The `wasm-pack` first checks for the installed rust compiler. If installed whether the rust compiler is greater than version 1.30.

2. Then `wasm-pack` checks for the crate configuration. Whether the library indicates that we are generating a dynamic library.

3. Finally, `wasm-pack` validates if there is any wasm-target available for building.  If the `wasm32-unknown-unknown` target is not available. `wasm-pack` will download and add the target.

Once the environment is ready, `wasm-pack` then starts compiling the module and build them.

When the build successfully finished, it creates a `pkg` directory. Inside the `pkg`, it will pipe the output of the `wasm-bindgen`.

```
pkg
├── package.json
├── wasm_pack_world.d.ts
├── wasm_pack_world.js
├── wasm_pack_world_bg.d.ts
├── wasm_pack_world_bg.wasm
└── wasm_pack_world_bg.wasm.d.ts
```
Now, this `pkg` folder can be bundled and shared like any other JavaScript module.

Note that the `wasm-pack` command generates a `package.json` file. The `package.json` is inside the `pkg` folder.

```
{
  "name": "wasm_pack_world",
  "collaborators": [
    "sendilkumarn <sendilkumarn@live.com>"
  ],
  "version": "0.1.0",
  "files": [
    "wasm_pack_world_bg.wasm",
    "wasm_pack_world.js",
    "wasm_pack_world.d.ts"
  ],
  "module": "wasm_pack_world.js",
  "types": "wasm_pack_world.d.ts",
  "sideEffects": false
}
```

> Note: The `wasm-pack`  copies over the `Readme`, `LICENSE` file if we have one. To basically make it easier to have shared documentation between the Rust and WebAssembly version.

The `wasm_pack_world.js` consist of all the necessary `import` and `export` of the `wasm` modules.

```
import * as wasm from "./wasm_pack_world_bg.wasm";
export * from "./wasm_pack_world_bg.js";
```

The `wasm-pack` also generates the necessary type definition `*.d.ts`. 

---

##  Build Using wasm-pack

The `wasm-pack` definitely shortens the build process. It checks whether `wasm-bindgen-cli` is installed. If it is not installed, it installs the required `wasm-bindgen-cli` using `cargo` (under the hoods `wasm-pack` still uses `cargo` and `wasm-bindgen`).

Let us explore further what are the options provided by the wasm-pack and how we can use them.

### Path

The `pkg` directory generated by the `wasm-pack` contains all the necessary build artifacts that you will need to share. The `wasm-pack` build command expected to be run inside a Rust project and it expects `Cargo.toml` file to be present in the directory in which it is executed. You can send in the path information to the wasm-pack and then wasm-pack will run its build inside the path passed in. It is important that the path should have `Cargo.toml`.

```
$ wasm-pack build some/other/path/with/Cargo.toml
```

### --out-dir

Similar to wasm-bindgen, `wasm-pack` supports `--out-dir` to specify the output directory to generate the build artifacts. By default, the build artifacts are generated into the `pkg` folder.  We can customise the output directory with the `--out-dir`.

```
$ wasm-pack build --out-dir path/to/store/the/output/artifacts
```

### --out-name

By default, the files generated are named based on the project name. But we can customise the output file name with the `--out-name` option.

```
$ wasm-pack build --out-name some_other_name
```

The generated output files, will be named `some_other_name`.

```
pkg
├── package.json
├── some_other_name.d.ts
├── some_other_name.js
├── some_other_name_bg.d.ts
└── some_other_name_bg.wasm
```

### build-mode

By default, `wasm-pack` will check for the presence of `wasm-bindgen-CLI` and installs it. But we can override that, if we already have the CLI installed globally.

The wasm-pack has a mode flag and it accepts three arguments as follows:

normal: the default option.

```
$ wasm-pack build --mode normal
```

force:  It forces the build. It even skips the rustc version check.

```
$ wasm-pack build --mode force
```

no-install: It does not install the wasm-bindgen-CLI and uses the globally available wasm-bindgen CLI.

```
$ wasm-pack build --mode no-install
```

### profiling

The `wasm-pack` provides options to make the binary more optimal for our needs. Let us explore them further.

Profiling the generated binaries is the most important step in WebAssembly applications.

> Isn't that a common thing across all the software that we deliver?

We need complete debugging information, during the development. Then we need to optimise the binaries, remove all the debugging information, remove unwanted assertions, compact the source code and make it as small as possible before we deliver it.

The `wasm-pack` provides the following three options for profiling:

* dev
* profiling
* release

#### --dev

The dev profile adds debug assertions, information about the debug, and applies no optimisation on the binaries generated. As the name specifies, it is more suitable for the development purpose.

During the runtime, to assert any value is of the specified format or as expected we will have to assert!(). This ensures that we will not have any weird runtime errors.

Instead of `assert`, we can have `debug_assert!()` to ensure whether a condition is true or false. But they are expensive than compared to the `assert!()` both in terms of time and performance. They are helpful during development. We cannot afford them in the production.

The debug information is an option defined at the kernel level. When enabled, this instructs the compiler to add in some debug information in the resulting binary. Abstractly they are nothing but some additional data that will be included in the binary and used to relate to the binary code which was being executed.

Obviously adding these two data in the binary reduces the performance and bloats up the release binary, but they are extremely helpful during development.

#### --profiling

The `profiling` profile adds only the debug information to the source code. It applies certain optimisation on the binaries but does not include the debug assertions.

#### --release

The `release` profile focus on achieving maximum optimisation, reducing binary size by removing debug information and making it run faster by removing unnecessary assertions. Thus the time take to compile is longer but the resulting binary is small and optimised.

---

### --target

We have seen that the `wasm-bindgen` supports various targets. We can instruct `wasm-pack` to generate the output artifact for those targets via the `--target` flag.

```
--target nodejs - for node.
--target bundler - for running it with bundlers like Webpack and Parcel.
--target web - for running it in modern browsers as an ECMAScript module.
--target no-modules - for running it in browsers just like any other JavaScript.
```

> Note: The WebAssembly still do not support ECMAScript modules, this means even when we pass on --target web flag, the WebAssembly module has to be manually instantiated and loaded.

---

## Pack

> The most important thing for a library developer is pack and publish the artifacts.

The `wasm-pack` helps to build, pack and publish the Rust and WebAssembly based projects into `NPM registry` as an npm package. We have seen how `wasm-pack` makes it simpler to build the Rust into WebAssembly binary along with the binding JavaScript file using `wasm-bindgen`. Let us explore further how `wasm-pack` helps to pack and publish.

The wasm-pack provides a `pack` flag to pack the artifacts that were generated using `wasm-pack build` command. Although it is not necessary to use `wasm-pack` to build the binaries it generates all the boilerplate things that we will need to `pack` the artifacts into a Node module.

In order to pack the built artifacts using `wasm-pack`, we have to run the following command with reference to the pkg (or the directory with which we generated our build artifacts):

```
$ wasm-pack pack pkg
```

We can run the command by passing in the `project_folder/pkg` as its argument. By default, `wasm-pack pack` command search for the `pkg` directory in the current working directory where it is running.

The `wasm-pack pack` command, first identifies whether the folder provided is a `pkg` directory or contains a `pkg` directory as its immediate child. If the check passes, then wasm-pack invokes the `npm pack` command underneath to pack the library into an NPM package.

To bundle the npm package all we need is a valid `package.json` file. That file is generated by the `wasm-pack build` command.

We run the pack command inside the `wasm_pack_world` example and check what happens.

```
$ wasm-pack pack
npm notice
npm notice 📦  wasm_pack_world@0.1.0
npm notice === Tarball Contents ===
npm notice 332B package.json
npm notice 767B wasm_pack_world_bg.wasm
npm notice 120B wasm_pack_world.d.ts
npm notice 186B wasm_pack_world.js
npm notice === Tarball Details ===
npm notice name:          wasm_pack_world
npm notice version:       0.1.0
npm notice filename:      wasm_pack_world-0.1.0.tgz
npm notice package size:  698 B
npm notice unpacked size: 1.4 kB
npm notice shasum:        c8d64ea76edfb27863c93286e92ac7a8150d96c8
npm notice integrity:     sha512-GFoTMM4x41A5w[...]FuIdd4Q5JV5Ig==
npm notice total files:   4
npm notice
wasm_pack_world-0.1.0.tgz
[INFO]: 🎒  packed up your package!
```
As you can see here, the `pack` command creates a tarball package with the contents inside the `pkg` folder with the help of `npm pack` command.

### publish

Once we have packed our application, the next step is to publish it. In order to publish the tarball generated the `wasm-pack` has a `publish` flag.

```
$ wasm-pack publish
Your package hasn't been built, build it? [Y/n]
```

If you answer yes to the question then it asks for you to input the folder in which you want to generate the build artifacts. We can give any folder name or use the default.

```
$ wasm-pack publish
Your package hasn't been built, build it? yes
out_dir[default: pkg]:
```

Then it asks your `target`, i.e., target to which the build should be generated. You can choose between the various options here as discussed in the build recipe.

```
$ wasm-pack publish
Your package hasn't been built, build it? yes
out_dir[default: pkg]: .
target[default: browser]:
> browser
 nodejs
 no-modules
```

Based on the option provided, it generates the artifact in the folder specified. Once the artifacts are produced, they are then ready to be published using npm publish. For npm publish to work correctly we need to be authenticated. You can authenticate to the npm either by using `npm login` or `wasm-pack login`. The `wasm-pack login` command invoke the  `npm login` command and then creates a session.

```
$ wasm-pack login
Username: sendilkumarn
Password: *************
login succeeded.
```

The `wasm-pack publish` command supports two options they are:

* `-a or --access` - To determine the access level of the package to be deployed. The access accepts either public or restricted.
    - `public` - makes the package public
    - `restricted` - makes the package internal.
* `-t or --target` - To support various targets to which the build is produced.

---

## Test with browsers or Node

> Testing should be an integral part of any software development. It is important to have a proper testing environment and measures to ensure the quality of the products that we create.

So far we haven't seen much information with respect to testing. The rustwasm group created `wasm-bindgen-test` crate to assist with testing the WebAssembly applications. The `wasm-pack` provides a wrapper over the `wasm-bindgen-test` library and makes it easy to test the WebAssembly applications that we generate. In order to test the Rust and WebAssembly application using wasm-pack, we can use:

```
$ wasm-pack test
```

### Options & Flags

The WebAssembly application being a part of the Web platform means that it has to support a wide range of browsers or environments.

Similarly, any testing library attached to it should support that wide range of options.

The `wasm-pack test` command supports following options for browser testing:

* --chrome
* --firefox
* --safari

The above flags require the particular driver to be installed and included in the path. The `--chrome` and `--firefox` option will download the driver if not present but the `--safari` option cannot. We can also specify the driver location using the following options respectively

* --chromedriver <path/to/driver>
* --geckodriver <path/to/driver>
* --safaridriver <path/to/driver>

For the `--safari` option, it is mandatory to have the `--safaridriver` and specify the path of the safari driver.

You can specify whether the browsers should run with a UI or not, using the `--headless` option. We can run the test with `--node` flag, that runs the test in the node environment. It is essential to test the artifacts work perfectly in the release mode and the functionality did not fail when we do code optimisation. We can achieve that with `-r` or `--release` option. You can specify whether we want to download a local copy of `wasm-bindgen-CLI` or use the existing one using the `--mode` option. Additionally, since we will be using `cargo test` underneath, the `wasm-pack test` command will also accept the arguments that we generally use along with `cargo test`.

## wasm-pack test

The `wasm-pack` test invokes `cargo build --tests` command. Based on the mode provided it will either download the `wasm-bindgen-cli` dependency and install it or use it from the path.

The `wasm-pack test` command expects either a `browser` or `node` option to be present. That is it has to be either `(chrome | firefox | safari) or node` as one of the option. If it is not present, then it will throw an error.

The `wasm-pack test` command run the necessary test based on the option passed in. For Node, the `wasm-pack test` command invoke the `cargo test` with target `wasm32-unknown-unknown`. For browsers, it first checks the availability of the driver and installs them if it is not. Then it spins up the respective browser and then runs the test using `wasm-bindgen test runner`. Add `wasm_bindgen_test` library as a dependency in the `Cargo.toml`.

```
[package]
name = "wasm_pack_world"
version = "0.1.0"
authors = ["Sendil Kumar Nellaiyapen <sendilkumarn@uber.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.69"

[dev-dependencies]
wasm-bindgen-test = "0.2"
```

Now we can add our own test and verify. Add the following test in the `tests/lib.rs`,

```
#![cfg(target_arch = "wasm32")]

extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;

use wasm_pack_world::get_answer;

#[wasm_bindgen_test]
fn pass_answer() {
 let actual = get_answer();
 assert_eq!(42, actual)
}
```

We first import the library. Then we annotate the method with `#[wasm_bindgen_test]` macro. This enables all the necessary configurations for the tests to execute.

```
$ wasm-pack test --node
  Finished test [unoptimized + debuginfo] target(s) in 0.04s
  Running target/wasm32-unknown-unknown/debug/deps/wasm_pack_world-7723ee9099032a71.wasm
  Running target/wasm32-unknown-unknown/debug/deps/lib-2f76d97dee4a3887.wasm
running 1 test

test lib::pass_answer ... ok

test result: ok. 1 passed; 0 failed; 0 ignored
```
---

If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out [here](https://sendilkumarn.com/wasm-book)

---

# Additional sources to explore 🔎

If you are starting with Rust newly, check out the source code at [here](https://github.com/rustwasm/wasm-pack)  the best place to learn Rust for beginners. The source code is awesomely structured and logged.

To know more about the wasm-pack check Check out the amazing documentation site from the wasm-pack team [here](https://rustwasm.github.io/wasm-pack/book/introduction.html)

If you are curious like me, then the Debug information will be something that you might be interested to know more about. Check out [this](https://en.wikipedia.org/wiki/DWARF)

Check out more about `wasm-bindgen-test` [here](https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/index.html). We will cover them more in detail later.

---

[🐦 Twitter](https://twitter.com/sendilkumarn) // [💻 GitHub](https://github.com/sendilkumarn) // [✍️ Blog](https://sendilkumarn.com/blog) // [🔶 HackerNews](https://news.ycombinator.com/item?id=21998939)

---