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:
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:
Several recently-published research articles have demonstrated a new class of timing attacks (Meltdown and Spectre) that work on modern CPUs. Our internal experiments confirm that it is possible to use similar techniques from Web content to read private information between different origins.
![]()
You need to configure the server response headers to enable SharedArrayBuffer, for example, in Vite:
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.
Since we finished the `wasm32-wasi-preview1-threads` target in https://github.com/napi-rs/napi-rs/pull/1669. We need to design a release workflow for wasm package. ## Goals - Automatically fallback to a WASM implementation on platforms where pre-compiling native addons is not supported. - In WebContainer, it can be downloaded and installed automatically, without the need for additional configuration. ## Issues There are three potential implementations. 1. Setting up postinstall scripts for every NAPI-RS package. Detect if the platform is supported, and download the wasm package if not supported. 2. Treat the wasm package as a regular platform-specified package, and set the `os` to a special value like `webcontainer`, so that the package managers can download it on WebContainer automatically. 3. Always distributing wasm package and their dependencies with the package. Unfortunately, these implementations have their own issues. The `postinstall` will not run in the `WebContainer` environment, so setup `postinstall` solution is not perfect for `WebContainer`, beside that, I hate postinstall. Platform-specified package solution need the `WebContainer` host to change some behavior about `process.platform`, it may break some other third-party packages and raise more issues. Always distributing wasm package will increase the download size significantly. There is `308.7kb` bundled runtime JavaScript code besides the wasm file itself.
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:
supportedArchitectures:
cpu:
- current
- wasm32For 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:
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=wasm32Build 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
└── VERSIONYou 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"