Docs
Concepts
WebAssembly

WebAssembly

There is a amazing WebAssembly course developed by @Dominic Elm (opens in a new tab): Learn WebAssembly

NAPI-RS supports building WebAssembly target and running it in the browser and Node.js. For now we only support the wasm32-wasip1-threads (opens in a new tab) target.

💡

In theory, wasm32-unknown-unknown and wasm32-wasip1 targets can also be supported, but these two targets are only suitable for people who have deep understanding of WebAssembly. For example, you need to handle cases where std::threads is used in your code and dependencies yourself, which makes compilation very complex. At the current stage, NAPI-RS's WebAssembly support is targeted at users who use WebAssembly as a fallback in Node.js, as well as users who develop playgrounds and repro in browsers/StackBlitz. These users are not very sensitive to bundle size, so we choose to only support the wasm32-wasip1-threads target by default to reduce noise when this feature is in the early stage.

The example app below is a simple image transformer, it's using @napi-rs/image (opens in a new tab) directly:

original imageOriginal Size: 0 B
Quality:
transformed webp imageSize: 0 B

You can use the package like this:

import { Transformer } from '@napi-rs/image'
 
export async function transform() {
  const imageBytes = await fetch('https://images-assets.nasa.gov/image/carina_nebula/carina_nebula~orig.png')
    .then(res => res.arrayBuffer())
  const transformer = new Transformer(imageBytes)
  const webp = await transformer.toWebp()
}

You can build it with the Vite or Webpack without any additional configuration.

Server configuration

To enable WebAssembly to use features like threads and Atomics, and to allow Rust/C/C++ source code to be compiled directly to WebAssembly without modifications, NAPI-RS uses the SharedArrayBuffer feature in the browser.

SharedArrayBuffer (opens in a new tab) is disabled by default due to historical security issues related to SharedArrayBuffer:

Mitigations: Landing new class of timing attacks @Luke Wagner

blog.mozilla.org

preview

You need to configure the server response headers to enable SharedArrayBuffer, for example, in Vite:

vite.config.ts
import { defineConfig } from 'vite'
 
export default defineConfig({
  plugins: [
    {
      name: 'configure-response-headers',
      enforce: 'pre',
      configureServer: (server) => {
        server.middlewares.use((_req, res, next) => {
          res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
          res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
          next()
        })
      },
    },
  ],
})

Install the WebAssembly package

To reduce the installation size of NAPI-RS packages, WebAssembly packages are not installed by default. We achieve this by adding the cpu: ["wasm32"] field in the package.json of WebAssembly packages, which makes package managers automatically skip the installation of WebAssembly packages.

WebAssembly package release strategy @Brooooooklyn

napi-rs/napi-rs

preview

For different package managers, there are different ways to install WebAssembly packages.

yarn

For yarn v4, you can set the supportedArchitectures (opens in a new tab) in the .yarnrc.yml file to install wasm32 packages:

.yarnrc.yml
supportedArchitectures:
  cpu:
    - current
    - wasm32

For yarn v1, you can use --ignore-engines to install wasm32 packages, unfortunately, there is no other effective way to install wasm32 package with yarn v1 since it's not maintained.

yarn install --ignore-engines

pnpm

pnpm supports the supportedArchitectures (opens in a new tab) too. You can set it in the pnpm-workspace.yaml file:

pnpm-workspace.yaml
supportedArchitectures:
  cpu:
    - current
    - wasm32

npm

npm supports the --cpu (opens in a new tab) flag since v10.2.0, you can use it to install wasm32 arch packages manually:

npm install --cpu=wasm32

Build the C/C++ dependencies

If there are C/C++ codes in your dependencies tree, you need to config the wasi-sdk (opens in a new tab) before building the WebAssembly package.

The @napi-rs/cli respect the WASI_SDK_PATH environment variable while building the wasm32-wasip1-threads target, this is what WASI_SDK_PATH folder looks like:

❯ lsd --tree --depth 1 $WASI_SDK_PATH
wasi-sdk
├── bin
├── lib
├── share
└── VERSION

You can download the wasi-sdk from the GitHub releases and set the WASI_SDK_PATH environment variable to the wasi-sdk folder:

# on macOS aarch64
 
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz
tar -xvf wasi-sdk-25.0-arm64-macos.tar.gz
export WASI_SDK_PATH="$(pwd)/wasi-sdk-25.0-arm64-macos"