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
- 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:
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"