Welcome to the wasm-pack
docs!
This 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 with Node.js. wasm-pack
helps you build rust-generated
WebAssembly packages that you could publish to the npm registry, or otherwise use
alongside any javascript packages in workflows that you already use, such as webpack.
This project is a part of the rust-wasm group. You can find more info by visiting that repo!
Quickstart
- Install
rust
usingrustup
. - Install this tool.
- Run
wasm-pack new hello-wasm
. cd hello-wasm
- Run
wasm-pack build --target web
. - This tool generates files in a
pkg
dir - Import it:
import init, { greet } from "./pkg/hello_wasm.js"
, initialize it:await init()
, and then use it:greet()
- To publish to npm, run
wasm-pack publish
. You may need to login to the registry you want to publish to. You can login usingwasm-pack login
.
Prerequisites
First you'll want to install the wasm-pack
CLI, and wasm-pack -V
should print the version that you just installed.
Next, since wasm-pack
is a build tool, you'll want to make sure you have
Rust installed. Make sure rustc -V
prints out at least 1.30.0.
Finally, if you're using wasm-pack
to publish to NPM, you'll want
to install and configure npm
. In the future, we intend to rewrite the
npm registry client bits so that the need for a Node runtime is eliminated. If
you're excited about that work- you should reach out to the maintainers and get
involved!
Using a non-rustup setup? Learn how to configure it for wasm-pack here.
npm
Currently, wasm-pack
requires that you have npm installed to pack and publish your
package. Longterm, this will be replaced by a Rust only version.
If you would rather use another package manager that interfaces with the npm registry
you may, however, the pack
, publish
, and login
commands wrap the npm CLI interface
and as a result require that npm be installed.
You can install npm by following these instructions.
npm Account
Part of the wasm-pack
workflow is to publish your package to the npm Registry.
Regardless of which package manager CLI tool you prefer, if you wish to publish your package to the npm registry you'll need an npm account.
You can find information about signing up for npm here.
nodejs
Currently, wasm-pack
generated npm modules require us to you have fetch polyfill in your node project.
If there is a module from wasm-pack build --target nodejs
you may encounter some errors regarding global Headers
, Request
, Response
and fetch
Web APIs.
Common errors:
ReqwestError(reqwest::Error { kind: Builder, source: "JsValue(ReferenceError: Headers is not defined
ReqwestError(reqwest::Error { kind: Builder, source: "JsValue(ReferenceError: Request is not defined
var ret = getObject(arg0) instanceof Response;
ReferenceError: Response is not defined
Workaround
Import or declare fetch and objects: Headers, Request, Response
// CommonJS
const fetch = require('node-fetch');
// ES Module
import fetch from 'node-fetch';
// @ts-ignore
global.fetch = fetch;
// @ts-ignore
global.Headers = fetch.Headers;
// @ts-ignore
global.Request = fetch.Request;
// @ts-ignore
global.Response = fetch.Response;
Non-Rustup setups
wasm-pack
compiles your code using the wasm32-unknown-unknown
target. wasm-pack
will automatically add this target for Rustup setups if you don't already have it installed by doing rustup target add wasm32-unknown-unknown
. However, if you're not using Rustup, then we won't be able to do this automatically, and you'll have to do this yourself.
Manually add wasm32-unknown-unknown
Disclaimer: This is not guaranteed to work for every setup. These instructions below are specific for setups that match the exact rustc release, which means that the downloaded wasm32 target can be incompatible.
To manually add the wasm32-unknown-unknown
target you will need to download it from the rust-lang website and put the contents in the correct folder.
All the targets for all the different rustc
versions are not presented in a human way on a website (yet) for you to just select the one you want and download it, one reason for this is that Rustup handles all of this for you and the packaging of targets was mainly built for tools. However, the following steps will walk through how to do this.
First, check what version of rustc
you're using by running rustc --version
. This should display something like: rustc 1.33.0 (2aa4c46cf 2019-02-28)
. Then you need to download the correct wasm32 target for your rustc version. The rustc version is part of the url, which means for rustc 1.33.0
the url will look like this: https://static.rust-lang.org/dist/rust-std-1.33.0-wasm32-unknown-unknown.tar.gz
.
Here's some examples of urls for different rustc versions:
- Nightly https://static.rust-lang.org/dist/rust-std-nightly-wasm32-unknown-unknown.tar.gz
- Specific date nightly (2019-03-10) https://static.rust-lang.org/dist/2019-03-10/rust-std-nightly-wasm32-unknown-unknown.tar.gz
- Beta https://static.rust-lang.org/dist/rust-std-beta-wasm32-unknown-unknown.tar.gz
You should be able to download this either by doing wget https://static.rust-lang.org/dist/rust-std-1.33.0-wasm32-unknown-unknown.tar.gz
or by just visiting the url in a web browser.
After you have downloaded this tarball at a location of your choice, you should unpack it. This should result in a folder named rust-std-1.33.0-wasm32-unknown-unknown
that contains some folders and files, but the interesting one is a folder called rust-std-wasm32-unknown-unknown
which contains a lib
and that should contain a rustlib
folder and in that, a folder called wasm32-unknown-unknown
. This is the folder we want to move.
Here's how the structure should look like for rustc 1.33.0:
rust-std-1.33.0-wasm32-unknown-unknown
├── components
├── install.sh
├── rust-installer-version
└── rust-std-wasm32-unknown-unknown
├── lib
│ └── rustlib
│ └── wasm32-unknown-unknown
To know where we should move this wasm32-unknown-unknown
folder we need to run rustc --print sysroot
which should print a path that looks something like this (this will vary on different operating systems): /home/user/rust/rust-1.33.0-2019-02-28-2aa4c46cf
. That folder should contain a lib
folder that contains a rustlib
folder. We should move the wasm32-unknown-unknown
to this folder.
On unix-like operating systems we can do that with the following command:
mv rust-std-1.33.0-wasm32-unknown-unknown/rust-std-wasm32-unknown-unknown/lib/rustlib/wasm32-unknown-unknown /home/user/rust/rust-1.33.0-2019-02-28-2aa4c46cf/lib/rustlib/
and that should be it!
Commands
wasm-pack
has several commands to help you during the process of building
a Rust-generated WebAssembly project.
new
: This command generates a new project for you using a template. Learn morebuild
: This command builds apkg
directory for you with compiled wasm and generated JS. Learn morepack
andpublish
: These commands will create a tarball, and optionally publish it to a registry, such as npm. Learn more
Deprecated Commands
init
: This command has been deprecated in favor ofbuild
.
Log levels
By default wasm-pack
displays a lot of useful information.
You can cause it to display even more information by using --verbose
, or you can silence all stdout by using --quiet
.
You can also use --log-level
to have fine-grained control over wasm-pack's log output:
--log-level info
is the default, it causes all messages to be logged.--log-level warn
causes warnings and errors to be displayed, but not info.--log-level error
causes only errors to be displayed.
These flags are global flags, so they can be used with every command, and they must come before the command:
wasm-pack --log-level error build
wasm-pack --quiet build
wasm-pack --verbose build
wasm-pack new
The wasm-pack new
command creates a new RustWasm project for you,
using cargo-generate
under the hood.
It takes 3 parameters, name, template, and mode:
wasm-pack new <name> --template <template> --mode <normal|noinstall|force>
The default template is rustwasm/wasm-pack-template
.
Name
The wasm-pack new
command must be given a name argument, e.g.:
wasm-pack new myproject
Template
The wasm-pack new
command can be given an optional template argument, e.g.:
wasm-pack new myproject --template https://github.com/rustwasm/wasm-pack-template
The template can be an address to a git repo that contains a cargo-generate
template.
Mode
The wasm-pack new
command can be given an optional mode argument, e.g.:
wasm-pack new myproject --mode noinstall
The mode passed can be either "normal", "noinstall", or "force". "normal" is passed by default.
noinstall
means that wasm-pack should not attempt to install any underlying tools.
If a necessary tool cannot be found, the command will error.
force
means that wasm-pack should not check the local Rust version. If a local Rust
is an unacceptable Rust version, the command will error.
wasm-pack build
The wasm-pack build
command creates the files necessary for JavaScript
interoperability and for publishing a package to npm. This involves compiling
your code to wasm and generating a pkg folder. This pkg folder will contain the
wasm binary, a JS wrapper file, your README
, and a package.json
file.
The pkg
directory is automatically .gitignore
d by default, since it contains
build artifacts which are not intended to be checked into version
control.0
Path
The wasm-pack build
command can be given an optional path argument, e.g.:
wasm-pack build examples/js-hello-world
This path should point to a directory that contains a Cargo.toml
file. If no
path is given, the build
command will run in the current directory.
Output Directory
By default, wasm-pack
will generate a directory for its build output called pkg
.
If you'd like to customize this you can use the --out-dir
flag.
wasm-pack build --out-dir out
The above command will put your build artifacts in a directory called out
, instead
of the default pkg
.
Generated file names
Flag --out-name
sets the prefix for output file names. If not provided, package name is used instead.
Usage examples, assuming our crate is named dom
:
wasm-pack build
# will produce files
# dom.d.ts dom.js dom_bg.d.ts dom_bg.wasm package.json README.md
wasm-pack build --out-name index
# will produce files
# index.d.ts index.js index_bg.d.ts index_bg.wasm package.json README.md
Profile
The build
command accepts an optional profile argument: one of --dev
,
--profiling
, or --release
. If none is supplied, then --release
is used.
This controls whether debug assertions are enabled, debug info is generated, and which (if any) optimizations are enabled.
Profile | Debug Assertions | Debug Info | Optimizations | Notes |
---|---|---|---|---|
--dev | Yes | Yes | No | Useful for development and debugging. |
--profiling | No | Yes | Yes | Useful when profiling and investigating performance issues. |
--release | No | No | Yes | Useful for shipping to production. |
The --dev
profile will build the output package using cargo's default
non-release profile. Building this way is
faster but applies few optimizations to the output, and enables debug assertions
and other runtime correctness checks. The --profiling
and --release
profiles
use cargo's release profile, but the former enables debug info as well, which
helps when investigating performance issues in a profiler.
The exact meaning of the profile flags may evolve as the platform matures.
Target
The build
command accepts a --target
argument. This will customize the JS
that is emitted and how the WebAssembly files are instantiated and loaded. For
more documentation on the various strategies here, see the documentation on
using the compiled output.
wasm-pack build --target nodejs
Option | Usage | Description |
---|---|---|
not specified or bundler | Bundler | Outputs JS that is suitable for interoperation with a Bundler like Webpack. You'll import the JS and the module key is specified in package.json . sideEffects: false is by default. |
nodejs | Node.js | Outputs JS that uses CommonJS modules, for use with a require statement. main key in package.json . |
web | Native in browser | Outputs JS that can be natively imported as an ES module in a browser, but the WebAssembly must be manually instantiated and loaded. |
no-modules | Native in browser | Same as web , except the JS is included on a page and modifies global state, and doesn't support as many wasm-bindgen features as web |
deno | Deno | Outputs JS that can be natively imported as an ES module in deno. |
Scope
The build
command also accepts an optional --scope
argument. This will scope
your package name, which is useful if your package name might conflict with
something in the public registry. For example:
wasm-pack build examples/js-hello-world --scope test
This command would create a package.json
file for a package called
@test/js-hello-world
. For more information about scoping, you can refer to
the npm documentation here.
Mode
The build
command accepts an optional --mode
argument.
wasm-pack build examples/js-hello-world --mode no-install
Option | Description |
---|---|
no-install | wasm-pack build implicitly and create wasm binding without installing wasm-bindgen . |
normal | do all the stuffs of no-install with installed wasm-bindgen . |
Extra options
The build
command can pass extra options straight to cargo build
even if
they are not supported in wasm-pack. To use them simply add the extra arguments
at the very end of your command, just as you would for cargo build
. For
example, to build the previous example using cargo's offline feature:
wasm-pack build examples/js-hello-world --mode no-install -- --offline
0 If you need to include additional assets in the pkg directory and your NPM package, we intend to have a solution for your use case soon. ↩
wasm-pack test
The wasm-pack test
command wraps the wasm-bindgen-test-runner
CLI allowing you to run wasm tests in different browsers without needing to install the different
webdrivers yourself.
wasm-pack test --help
Path
The wasm-pack test
command can be given an optional path argument.
This path should point to a directory that contains a Cargo.toml
file. If no
path is given, the test
command will run in the current directory.
# Run tests for the current directory's crate
wasm-pack test
# Run tests for a specified crate
wasm-pack test crates/crate-in-my-workspace
Profile
The test
command accepts an optional profile argument: --release
.
If none is supplied, then a debug test build will be used.
Test environment
Choose where to run your tests by passing in any combination of testing environment flags.
--headless
is useful for running browser tests in a headless browser as part of a CI process.
wasm-pack test --node --firefox --chrome --safari --headless
Extra options
The test
command can pass extra options straight to cargo test
even if they are not
supported in wasm-pack.
To use them simply add the extra arguments at the very end of your command, just
as you would for cargo test
.
cargo test -h
for a list of all options that you can pass through.
Running only some tests
When debugging a specific issue, you may find yourself wanting to run a subset of tests, instead of your entire suite of tests.
Here are a few examples of how to run a subset of your tests:
# Example directory structure
$ tree crates/foo
├── Cargo.toml
├── README.md
├── src
│ ├── diff
│ │ ├── diff_test_case.rs
│ │ └── mod.rs
│ ├── lib.rs
└── tests
├── diff_patch.rs
└── node.rs
# Run all tests in tests/diff_patch.rs in Firefox
wasm-pack test crates/foo --firefox --headless --test diff_patch
# Run all tests in tests/diff_patch.rs that contain the word "replace"
wasm-pack test crates/foo --firefox --headless --test diff_patch replace
# Run all tests inside of a `tests` module inside of src/lib/diff.rs
wasm-pack test crates/foo --firefox --headless --lib diff::tests
# Same as the above, but only if they contain the word replace
wasm-pack test crates/foo --firefox --headless --lib diff::tests::replace
Note that you can also filter tests by location in which they're supposed to run. For example:
# Run all tests which are intended to execute in Node.js
wasm-pack test --node
# Run all tests which are intended to be executed in a browser
wasm-pack test --firefox --headless
pack and publish
The publish
and pack
commands interact with the pkg directory that's
created when you run wasm-pack build
. The pack
command creates a tarball
from the pkg directory and the publish
command creates a tarball from the
pkg directory and publishes it to the NPM registry.
Underneath, these commands use npm pack
and npm publish
. You can read
more about these in the NPM documentation:
Both these commands take the path to the pkg directory as the first argument. You can either set the argument directly to the pkg directory or to the parent of the pkg directory:
$ wasm-pack pack myproject/pkg
| 🎒 packed up your package!
$ wasm-pack pack myproject
| 🎒 packed up your package!
If you try to call pack
or publish
on another directory, you get an error:
$ wasm-pack pack myproject/src/
Unable to find the pkg directory at path 'myproject/src/', or in a child directory of 'myproject/src/'
If you don't set a path, they use the current directory as the path.
Publishing tagged releases
You can also publish tagged releases with the optional --tag
argument, e.g.
wasm-pack publish --tag next
By default, the latest
tag is used to identify the current version of a package,
and npm install <pkg> (without any @<version> or @<tag> specifier) installs the latest tag.
You can read more about distribution tags on NPM.
wasm-pack init (DEPRECATED)
This command has been deprecated in favor of build
, which does the same thing, but is
a much more representative name for the command. Read the docs for build
.
Tutorials
We have two tutorials that help you get started with wasm-pack
:
- If you want to create and publish a package: npm browser packages
- If you'd like to develop a Wasm library alongside a JavaScript application using Webpack: Hybrid applications with Webpack
Hybrid Applications with Webpack
The goal of this tutorial is to introduce you to the rust-webpack-template
and the wasm-pack
workflow by building the example app in the template.
This tutorial is aimed at folks who are both beginners to WebAssembly and Rust- you don't need much Rust knowledge to complete this tutorial.
Be sure to have read and followed the Prerequisites.
Getting Started
You can create a new Rust-WebAssembly webpack project by using the rustwasm webpack-template.
Run:
npm init rust-webpack my-app
The last argument will be your project name. After you run the command, you will have a directory with a new project, ready to go. We'll talk about what's been included in this template further in this guide.
Run The Code
The Rust Webpack template is designed for creating monorepo-style Web applications with Rust-generated WebAssembly and Webpack without publishing your wasm to NPM. This portion of the tutorial will explain how to build a Webpack JavaScript project that will run your WebAssembly code in the browser.
Scaffold a JavaScript Project
To generate a new Rust Webpack project, we've used the rust-webpack
npm template.
npm init rust-webpack your-package-name
A new project folder will be created with the name you supply.
If we look in the project, we'll see the following:
.gitignore
: ignoresnode_modules
LICENSE-APACHE
andLICENSE-MIT
: most Rust projects are licensed this way, so these are included for youREADME.md
: the file you are reading now!index.html
: a bare bones html document that includes the webpack bundlejs/index.js
: example JS file with a comment showing how to import and use a wasm pkgpackage.json
andpackage-lock.json
:- pulls in devDependencies for using webpack:
- defines a
start
script to runwebpack-dev-server
webpack.config.js
: configuration file for bundling your JS with webpackcrate/src/lib.rs
: your Rust crate code!
Your Rust Crate
The scaffolded project includes an example Rust WebAssembly webpack crate.
Inside the crate/src/lib.rs
file we see a run
function that's callable from our JS file:
# #![allow(unused_variables)] #fn main() { // Called by our JS entry point to run the example. #[wasm_bindgen] pub fn run() -> Result<(), JsValue> { set_panic_hook(); // ... let p: web_sys::Node = document.create_element("p")?.into(); p.set_text_content(Some("Hello from Rust, WebAssembly, and Webpack!")); // ... Ok(()) } #}
Now, open up the js/index.js
file. We see our Rust-generated wasm run
function being
called inside our JS file.
import("../crate/pkg").then(module => {
module.run();
});
Run The Project
To generate our Rust-compiled to wasm code, in the root directory we run:
npm run build
This will create our bundled JavaScript module in a new directory dist
.
We should be ready to run our project now! In the root directory, we'll run:
npm start
Then in a web browser navigate to http://localhost:8080
and you should be greeted
with text in the body of the page that says "Hello from Rust, WebAssembly, and Webpack!"
If you did congrats! You've successfully used the rust-webpack template!
npm Browser Package Tutorial
The goal of this tutorial is to introduce you to the wasm-pack
workflow by building a small npm
package designed to be used in a browser application.
This tutorial is aimed at folks who are both beginners to WebAssembly and Rust- you don't need much Rust knowledge to complete this tutorial.
Be sure to have done the following before starting:
- Install
wasm-pack
- Read and install the Prerequisites.
⚠️ We strongly recommend that you install Node.js using a version manager. You can learn more here.
Getting Started
You can create a new Rust-WebAssembly project by using the rustwasm wasm-pack-template.
To so do, you'll need the cargo-generate
tool. To install cargo-generate
:
cargo install cargo-generate
Then run:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
You will be prompted to give your project a name. Once you do, you will have a directory with a new project, ready to go. We'll talk about what's been included in this template further in this guide.
Manual Setup
⚠️ If you'd rather not use a template, or are having trouble with the template, you can do a manual setup by following these instructions.
Manual Setup
⚠️ This is not the recommended way to start a wasm-pack
project! If you ended up
here by mistake, go check out our recommended project start.
Step 1: Create a New Rust Library Project
You can create a new Rust project named my-lib
using this command.
cargo new --lib my-lib
The --lib
flag specifies that the project is a library, which is important
because we will be calling this code from JavaScript.
Step 2: Edit your Cargo.toml
File
Add the wasm-bindgen
dependency
You will need to add wasm-bindgen
to your Cargo.toml
in the dependencies
section. wasm-bindgen
is a tool that facilitates interoperability between
wasm modules and JavaScript.
⚠️ If you are coming from JavaScript, you might note that when we add the dependency
there is no ^
or ~
symbol- it looks like we're locking to the 0.2
version.
However, that's not the case! In Rust, the ^
is implied.
Add crate-type
Next, add a [lib]
section, with a new field named crate-type
set to
"cdylib"
. This specifies that the library is a C compatible dynamic library,
which helps cargo
pass the correct flags to the Rust compiler when targeting
wasm32
.
After making these changes, your Cargo.toml
file should look something like
this:
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Ashley Williams <ashley666ashley@gmail.com>"]
description = "babby's first wasm package"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ashleygwilliams/hello-wasm"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen="0.2"
Step 3: Write some Rust!
Now that your crate is correctly configured in your Cargo.toml
file, the only step
left to setup your project is to have some Rust code in your src/lib.rs
file.
Browser Example
The template we have gives you a quick "Hello, World" project to use for compiling into a WebAssembly library that you can use in the browser:
# #![allow(unused_variables)] #fn main() { extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { fn alert(s: &str); } #[wasm_bindgen] pub fn greet() { alert("Hello, World!"); } #}
And that's it! We'll talk about what this code does in the template deep dive, which you are all
setup for now. Happy wasm-pack
ing!
Template Deep Dive
⚠️ This section is a deep dive into the contents of the files of a "Hello, World" project, specifically written for people who are not that familiar with Rust. If you'd rather just checkout the workflow, feel free to skip this section!
⚠️ If you haven't used a template to set up your project, the contents of your files may look slightly different than what is described here.
What the Template Gave Us
Let's start by taking a look at what the template generated for us.
Cargo.toml
- the Cargo manifestsrc/lib.rs
- main library modulesrc/utils.rs
- a utility modulewee_alloc
- a tiny memory allocatortests/web.rs
- running headless browser tests
Cargo.toml
Cargo.toml
is the manifest file for Rust's package manager, cargo
. This file contains
metadata such as name, version, and dependencies for packages, which are call "crates" in Rust.
There's a bunch of metadata that the template gives us, but there are three key parts to discuss:
1. crate-type
[lib]
crate-type = ["cdylib", "rlib"]
A Rust-wasm
crate is a bit different from a normal crate, and as a result, we need to note
this in our Cargo.toml
.
This [lib]
annotation is typically not needed in Cargo projects, and if you're
familiar with other Rust crates you'll remember that the most common crate types
are rlib
(the default) or bin
for binaries (which don't need a crate-type
annotation).
Here though crate-type = ["cdylib"]
typically signifies that you'd like the
compiler to create a dynamic system library, but for WebAssembly target it
simply means "create a *.wasm
file without a start
function". On other
platforms this output type will create *.so
file on Linux, *.dylib
on
macOS, and *.dll
Windows.
We also specify crate-type = ["rlib"]
to ensure that our library can be unit
tested with wasm-pack test
(which we'll see later). Without this we wouldn't
be able to test our library because the cdylib
crate type is incompatible with
wasm-pack
's style of unit tests.
You can read more about linking and crate types, here.
2. wasm-bindgen
dependency
wasm-bindgen
is our most important dependency. This package allows us to use the
#[wasm-bindgen]
attribute to tag code that represents the interface we want between
our JavaScript and Rust-generated wasm
. We can import JS and export Rust by using this
attribute.
wasm-bindgen = "0.2"
We'll see more about how to use this library when we discuss what has been generated in lib.rs
.
⚠️ If you are coming from JavaScript, you might note that when we add the dependency
there is no ^
or ~
symbol- it looks like we're locking to the 0.2
version.
However, that's not the case! In Rust, the ^
is implied. You can read more about this in the
cargo documentation on specifying dependencies.
3. [features]
and wee_alloc
, console_error_panic_hook
dependencies
As part of our effort to design a template that helps people discover useful crates
for their particular use case, this template includes two dependencies that can be
very useful for folks developing Rust-wasm
crates: console_error_panic_hook
and
wee_alloc
.
Because these dependencies are useful primarily in a specific portion of the Rust-wasm
crate development workflow, we've also set up a bit of glue code that allows us to include
them both as dependencies, but also allows them to be optionally included.
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.1", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.2", optional = true }
In our code, we'll mark certain parts of code as running only if certain [features]
are enabled, specifically, console_error_panic_hook
and wee_alloc
. By default,
only console_error_panic_hook
is enabled. To disable or enable either feature, by
default, we can edit the default
vector under [features]
.
To learn more about these features, we discuss them in-depth in the src/lib.rs
and
src/utils.rs
sections.
Briefly, they include:
- console_error_panic_hook for logging panic messages to the developer console.
- wee_alloc, an allocator optimized for small code size.
src/lib.rs
lib.rs
is the template's main source file. The name lib.rs
commonly implies that this Rust project will be compiled as a library.
It contains three key parts:
We'll start with the most important part of lib.rs
-- the two #[wasm_bindgen]
functions (which you can find at the bottom of the file). In many cases, this is the only part of lib.rs
you will need to modify.
1. Using wasm_bindgen
To expose functionality from the wasm-bindgen
crate more conveniently we can use the use
keyword.
use
allows us to conveniently refer to parts of a crate or module. You can learn more about how Rust
lets you write modular code in this chapter of the book.
# #![allow(unused_variables)] #fn main() { use wasm_bindgen::prelude::*; #}
Many crates contain a prelude, a list of things that are convenient to import
all at once. This allows common features of the module to be conveniently
accessed without a lengthy prefix. For example, in this file we can use
#[wasm_bindgen]
only because it is brought into scope by the prelude.
The asterisk at the end of this use
indicates that everything inside the module wasm_bindgen::prelude
(i.e. the module prelude
inside the crate wasm_bindgen
) can be referred to without prefixing it with wasm_bindgen::prelude
.
For example, #[wasm_bindgen]
could also be written as #[wasm_bindgen::prelude::wasm_bindgen]
, although this is not recommended.
1. #[wasm_bindgen]
functions
The #[wasm_bindgen]
attribute indicates that the function below it will be accessible both in JavaScript and Rust.
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen] extern { fn alert(s: &str); } #}
The extern
block imports the external JavaScript function alert
into Rust. This declaration is required to call alert
from Rust. By declaring it in this way, wasm-bindgen
will create JavaScript stubs for alert
which allow us to pass strings back and forth between Rust and JavaScript.
We can see that the alert
function requires a single parameter s
of type &str
, a string. In Rust, any string literal such as "Hello, test-wasm!"
is of type &str
. So, alert
could be called by writing alert("Hello, test-wasm!");
.
We knew to declare alert
in this way because it is how we would call alert
in JavaScript -- by passing it a string argument.
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen] pub fn greet() { alert("Hello, test-wasm!"); } #}
If we were to write the greet
function without the #[wasm_bindgen]
attribute, then greet
would not be easily accessible within JavaScript. Furthermore, we wouldn't be able to natively convert certain types such as &str
between JavaScript and Rust. So, both the #[wasm_bindgen]
attribute and the prior import of alert
allow greet
to be called from JavaScript.
This is all you need to know to interface with JavaScript, at least to start! You can learn a bunch more by reading the
wasm-bindgen
documentation!
If you are curious about the rest, read on.
2. Crate Organization
# #![allow(unused_variables)] #fn main() { mod utils; #}
This statement declares a new module named utils
that is defined by the contents of utils.rs
. Equivalently, we could place the contents of utils.rs
inside the utils
declaration, replacing the line with:
# #![allow(unused_variables)] #fn main() { mod utils { // contents of utils.rs } #}
Either way, the contents of utils.rs
define a single public function set_panic_hook
. Because we are placing it inside the utils
module, we will be able to call the function directly by writing utils::set_panic_hook()
. We will discuss how and why to use this function in src/utils.rs
.
# #![allow(unused_variables)] #fn main() { // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator. if #[cfg(feature = "wee_alloc")] { #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #}
At compile time this will test if the wee_alloc
feature is enabled for this
compilation. If it's enabled we'll configure a global allocator (according to
wee_alloc
's docs), otherwise it'll compile to nothing.
As we saw earlier, the default
vector in [features]
only contains "console_error_panic_hook"
and not "wee_alloc"
. So, in this case, this
block will be replaced by no code at all, and hence the default memory allocator will be used instead of wee_alloc
.
src/utils.rs
The purpose of utils.rs
is to define the utils
module, which contains a single function set_panic_hook
. This function becomes part of the utils
module in lib.rs
, as described in the preceding section.
If the console_error_panic_hook
feature is not enabled, then set_panic_hook
is defined to be an inlined empty function. So, there is no run-time performance or code-size penalty incurred by its use.
We will discuss:
1. Defining set_panic_hook
# #![allow(unused_variables)] #fn main() { pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then // we will get better error messages if our code ever panics. // // For more details see // https://github.com/rustwasm/console_error_panic_hook#readme #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } #}
Here, we define a function that's preceded by a cfg
attribute. This attribue,
#[cfg(feature = "console_error_panic_hook")]
, tells Rust to check if the
console_error_panic_hook
feature is set at compile time. If it is, it will call
this function. If it isn't- it won't!
2. What is console_error_panic_hook
?
The crate console_error_panic_hook
allows debugging Rust panic
messages in a web browser, making it much easier to debug WebAssembly code.
Let's compare what happens when Rust code panics before and after enabling the feature:
Before: "RuntimeError: Unreachable executed"
After: "panicked at 'index out of bounds: the len is 3 but the index is 4', libcore/slice/mod.rs:2046:10"
To do this, a panic hook is configured that logs panics to the
developer console via the JavaScript console.error
function.
Note though that console_error_panic_hook
is not entirely automatic, so you'll
need to make sure that utils::set_panic_hook()
is called before any of our
code runs (and it's safe to run set_panic_hook
many times).
For more details, see the console_error_panic_hook
repository.
wee_alloc
What is wee_alloc
?
WebAssembly code is frequently transmitted over the wire to users, so compiled code size is often important to ensure an application loads quickly and is responsive.
wee_alloc
is a tiny allocator designed for WebAssembly that has a (pre-compression) code-size footprint of only a single kilobyte.
An analysis suggests that over half of the bare minimum WebAssembly memory footprint is required by Rust's default memory allocator. Yet, WebAssembly code often does not require a sophisticated allocator, since it often just requests a couple of large initial allocations.
wee_alloc
trades off size for speed. It has a tiny code-size
footprint, but it is not competitive in terms of performance with the
default global allocator, for example.
For even more details, see the wee_alloc
repository, or
general documentation about
shrinking code size of WebAssembly binaries.
Enabling wee_alloc
In lib.rs
, we have the configuration for wee_alloc
inside a cfg_if!
macro:
# #![allow(unused_variables)] #fn main() { cfg_if! { if #[cfg(feature = "wee_alloc")] { #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; } } #}
This code block is intended to initialize wee_alloc
as the global memory
allocator, but only if the wee_alloc
feature is enabled at compile time. The
feature can be enabled by passing extra options while building:
$ wasm-pack build --features wee_alloc
or alternatively you could turn it on by default in Cargo.toml
:
[features]
default = ["console_error_panic_hook", "wee_alloc"]
tests/web.rs
web.rs
is an integration test defined with Cargo that is
intended to be run in a headless web browser via the wasm-pack test
command.
It contains three key parts:
1. #[wasm_bindgen_test]
functions
The #[wasm_bindgen_test]
is like the normal Rust #[test]
attribute, except it defines a test accessible to WebAssembly and
headless web browser testing.
Note: Eventually
#[test]
will work with WebAssembly as well! Currently though custom test frameworks are not stable.
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen_test] fn pass() { assert_eq!(1 + 1, 2); } #}
Here the pass
function is a unit test which asserts that arithmetic works in
WebAssembly like we'd expect everywhere else. If the test panics (such as the
assert_eq!
being false) then the test will fail, otherwise the test will
succeed.
The reference documentation for #[wasm_bindgen_test]
should have
more information about defining these tests.
2. Crate Configuration
Other than the test in this module, we'll also see:
# #![allow(unused_variables)] #fn main() { use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #}
Like we saw earlier in src/lib.rs
the *
import pulls in everything from
wasm_bindgen_test
, notably the wasm_bindgen_test_configure
macro and the
wasm_bindgen_test
attribute.
The wasm_bindgen_test_configure
macro (denoted by ending in !
) is used to
indicate that the test is intended to execute in a web browser as opposed to
Node.js, which is the default.
3. #![cfg]
directives
The last part we'll notice about this crate is this statement at the top:
# #![allow(unused_variables)] #![cfg(target_arch = "wasm32")] #fn main() { #}
This statement means that the test is only intended for the wasm32
architecture, or the wasm32-unknown-unknown
target. This enables cargo test
to work in your project if the library is also being developed for other
platforms by ensuring that these tests only execute in a web browser.
Building your project
We've written our code so now we need to build it.
We are writing a crate that should be used in the browser, so we run this in our terminal:
$ wasm-pack build
If you were writing a package that should be used in Node.js (with CommonJS
modules, e.g. require
), you would run this in your terminal:
$ wasm-pack build --target nodejs
This command when run does a few things:
- It'll compile your code to wasm if you haven't already
- It'll generate a
pkg
folder with the wasm file, a JS wrapper file around the wasm, your README, and apackage.json
file.
Testing your project
Now after writing and building code, let's actually execute it! You can execute tests with:
$ wasm-pack test --firefox
[INFO]: Checking for the Wasm target...
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target/wasm32-unknown-unknown/debug/deps/web-9e7d380f8600b08e.wasm
Interactive browsers tests are now available at http://127.0.0.1:8000
Note that interactive mode is enabled because `NO_HEADLESS`
is specified in the environment of this process. Once you're
done with testing you'll need to kill this server with
Ctrl-C.
The console won't finish just yet, but as indicated you can visit http://127.0.0.1:8000 in your web browser to see the test output:
running 1 test
test web::pass ... ok
test result: ok. 1 passed; 0 failed; 0 ignored
and we've now executed our first tests in a web browser!
If you'd like to execute tests in a headless web browser (you don't need to manually visit a page) you can do:
$ wasm-pack test --headless --firefox
and similarly if you're developing a project for Node.js you can also execute
wasm-pack test --nodejs
to run tests in Node.
Be sure to see the testing reference documentation for other supported features as well!
Package Code for npm
We've made our code so now we need to package it all up. In your project directory run the following command:
$ wasm-pack build --scope MYSCOPE
where MYSCOPE
is your npm username. Normally you could just type wasm-pack build
but since
other people are doing this tutorial as well we don't want conflicts with the wasm-add
package
name! This command when run does a few things:
- It'll compile your code to wasm if you haven't already
- It'll generate a pkg folder with the wasm file, a JS wrapper file around the wasm, your README,
and a
package.json
file.
This is everything you need to upload your code to npm! Let's do just that!
First off you'll need to login to npm with the account you made earlier if you didn't already have one:
$ wasm-pack login
Next you'll need to go into the pkg
directory and actually upload the package:
$ cd pkg
$ npm publish --access=public
Now normally if things are not scoped you can just do npm publish
but if you give it a scope
you'll need to tell npm that this is actually public so it can publish it. We need to do that here
since we gave our packages a scope to avoid conflicting with each other! Next up is actually running
the code and verifying we got it from npm and how we can use that code.
Run The Code From npm
This portion of the tutorial will help you create a Webpack JavaScript project that will run your WebAssembly code in the browser.
Scaffold a JavaScript Project
To scaffold a project that we can use our new package in, we'll use an npm template called
create-wasm-app
. To use this run this command in a directory different than your Rust
project:
npm init wasm-app my-new-wasm-app
Instead of my-new-wasm-app
you can choose a different project name.
The tool will create a directory with that name.
If we look in that directory, we'll see the following:
.gitignore
: ignoresnode_modules
LICENSE-APACHE
andLICENSE-MIT
: most Rust projects are licensed this way, so these are included for youREADME.md
: the file you are reading now!index.html
: a bare bones html document that includes the webpack bundleindex.js
: example js file with a comment showing how to import and use a wasm pkgpackage.json
andpackage-lock.json
:- pulls in devDependencies for using webpack:
- defines a
start
script to runwebpack-dev-server
webpack.config.js
: configuration file for bundling your js with webpack
Add Your npm Package
The scaffolded project includes an example WebAssembly package, hello-wasm-pack
, in your
package.json
. Go into the package.json
file, add your package, and remove the
hello-wasm-pack
dependency from the "dependencies"
section.
Now, open up the index.js
file. Replace the hello-wasm-pack
in the first line with the
name of your package:
import * as wasm from "<your package name>";
wasm.greet();
Run The Project
Before we run our project, we need to make sure we install our dependencies:
npm install
We should be ready to run our project now! To run our project we'll run:
npm start
Then in a web browser navigate to http://localhost:8080
and you should be greeted with an
alert box that says "Hello World!".
If you did congrats you've successfully uploaded your first bit of wasm code to npm and used it properly!
Cargo.toml
Configuration
wasm-pack
can be configured via the package.metadata.wasm-pack
key in
Cargo.toml
. Every option has a default, and is not required.
There are three profiles: dev
, profiling
, and release
. These correspond to
the --dev
, --profiling
, and --release
flags passed to wasm-pack build
.
The available configuration options and their default values are shown below:
[package.metadata.wasm-pack.profile.dev]
# Should `wasm-opt` be used to further optimize the wasm binary generated after
# the Rust compiler has finished? Using `wasm-opt` can often further decrease
# binary size or do clever tricks that haven't made their way into LLVM yet.
#
# Configuration is set to `false` by default for the dev profile, but it can
# be set to an array of strings which are explicit arguments to pass to
# `wasm-opt`. For example `['-Os']` would optimize for size while `['-O4']`
# would execute very expensive optimizations passes
wasm-opt = ['-O']
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
# Should we enable wasm-bindgen's debug assertions in its generated JS glue?
debug-js-glue = true
# Should wasm-bindgen demangle the symbols in the "name" custom section?
demangle-name-section = true
# Should we emit the DWARF debug info custom sections?
dwarf-debug-info = false
# Should we omit the default import path?
omit-default-module-path = false
[package.metadata.wasm-pack.profile.profiling]
wasm-opt = ['-O']
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = false
omit-default-module-path = false
# `wasm-opt` is on by default in for the release profile, but it can be
# disabled by setting it to `false`
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[package.metadata.wasm-pack.profile.release.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = false
omit-default-module-path = false
Contributing
Prerequisites
The technical prerequisites for contributing to this project are the same as for using it. You can find them documented here.
You'll also want to check out the contributing guidelines.
🏃♀️ Up and Running
- fork and clone the
rustwasm/wasm-pack
repository - install [node/npm]
cd wasm-pack
cargo run
. To test command line arguments you can runcargo run -- <args>
.
Documentation
Documentation lives in the /docs
directory. Each command has its own page.
Additionally there are extra pages explaining the prerequisites, setup, and how to
contribute (which you are reading now!).
Tests
Tests live in the /tests
directory. To run the tests you can run:
cargo test
You can also manually test the CLI tool by running:
cargo run -- <args>
...for example:
cargo run -- init /tests/fixtures/js-hello-world --scope=ag_dubs