> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Wasm

Rslib natively supports WebAssembly (WASM) modules, allowing you to directly import and use `.wasm` files in your project using the ESM import mechanism defined by the [WebAssembly ESM Integration](https://github.com/WebAssembly/esm-integration) proposal.

:::note

When importing `.wasm` modules using WebAssembly ESM Integration, only ESM output is supported, so [format](/config/lib/format.md) must be set to `'esm'` (the default).

:::

## Use Wasm modules

Rslib supports the following ways to use `.wasm` modules.

### Static imports and re-exports

You can use standard ESM syntax to import a `.wasm` module and access its instantiated exports, or re-export them directly:

```ts
import { add } from './add.wasm';
import * as wasm from './add.wasm';
import './add.wasm';

export { add } from './add.wasm';
export * from './add.wasm';
export * as wasm from './add.wasm';
```

### Dynamic import

You can use `import()` to dynamically import a `.wasm` module and access its instantiated exports:

```ts
const wasm = await import('./add.wasm');
```

### Source phase import

You can use [Source Phase Imports](https://github.com/tc39/proposal-source-phase-imports) to obtain a compiled `WebAssembly.Module` and instantiate it manually with a custom import object:

```ts
import source addModule from './add.wasm';

const { instance } = await WebAssembly.instantiate(addModule, {
  env: { now: Date.now },
});
```

You can also use `import.source()` to obtain a compiled `WebAssembly.Module` dynamically:

```ts
const addModule = await import.source('./add.wasm');
```

:::note

TypeScript cannot currently parse `import source` or `import.source()`. If you need to generate declaration files, use these forms in JavaScript files.

:::

## Output modes

You can use [lib.wasm.mode](/config/lib/wasm.md#wasmmode) to select the output mode for `.wasm` modules:

- [bundle](/config/lib/bundle.md) is `true`: Only `compile` mode is available.
- [bundle](/config/lib/bundle.md) is `false`: `preserve` mode is used by default when the `.wasm` modules are resolved and loaded by a downstream build tool that supports WebAssembly ESM Integration (such as Rsbuild or Rspack) or a target runtime with native support (such as [Node.js](https://nodejs.org/api/esm.html#wasm-modules) versions matching `>=24.5.0`). If the consumer does not support this feature, or you do not want to rely on it to handle the `.wasm` modules, use `compile` mode.

The following source files demonstrate how to configure the `compile` and `preserve` modes and their build outputs.


**src/index.ts**

```ts
export { useAdd } from './utils.js';
```


**src/utils.ts**

```ts
import { add } from './add.wasm';

export const useAdd = (a: number, b: number) => add(a, b);
```


**src/add.wasm**

```wasm
(type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add)
  (export "add" (func 0)))
```


### `compile` mode

Rslib parses each `.wasm` module, generates the JavaScript glue code required to load and instantiate it, and emits its binary as a static asset to the directory specified by [output.distPath.wasm](/config/rsbuild/output.md#outputdistpath) (`dist/static/wasm` by default), with a content hash in the filename.

Depending on the [bundle](/config/lib/bundle.md) configuration, Rslib emits the following files in the `dist` directory:


**bundle**


**index.js**

```js
function __webpack_require__(moduleId) {
  // Read from the cache and execute the registered module...
}

__webpack_require__.add = (modules) => {
  // Register modules...
};
__webpack_require__.v = async (
  exports,
  wasmModuleId,
  wasmModuleHash,
  importsObj,
) => {
  // Load static/wasm/[contenthash].module.wasm based on output.target...
  const bytes = await loadWasmBytes(wasmModuleHash);
  const { instance } = await WebAssembly.instantiate(bytes, importsObj);
  return Object.assign(exports, instance.exports);
};

__webpack_require__.add({
  './src/add.wasm'(module, exports, __webpack_require__) {
    module.exports = __webpack_require__.v(exports, module.id, '[contenthash]');
  },
});

const add = await __webpack_require__('./src/add.wasm');
const useAdd = (a, b) => (0, add.add)(a, b);
export { useAdd };
```


**static/wasm/[contenthash].module.wasm**

```wasm
(type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add)
  (export "add" (func 0)))
```



**bundleless**


**index.js**

```js
export { useAdd } from './utils.js';
```


**utils.js**

```js
import { __webpack_require__ } from './rslib-runtime.js';

__webpack_require__.add({
  './src/add.wasm'(module, exports, __webpack_require__) {
    module.exports = __webpack_require__.v(exports, module.id, '[contenthash]');
  },
});

const add = await __webpack_require__('./src/add.wasm');
const useAdd = (a, b) => (0, add.add)(a, b);
export { useAdd };
```


**rslib-runtime.js**

```js
function __webpack_require__(moduleId) {
  // Read from the cache and execute the registered module...
}

__webpack_require__.add = (modules) => {
  // Register modules...
};
__webpack_require__.v = async (
  exports,
  wasmModuleId,
  wasmModuleHash,
  importsObj,
) => {
  // Load static/wasm/[contenthash].module.wasm based on output.target...
  const bytes = await loadWasmBytes(wasmModuleHash);
  const { instance } = await WebAssembly.instantiate(bytes, importsObj);
  return Object.assign(exports, instance.exports);
};

export { __webpack_require__ };
```


**static/wasm/[contenthash].module.wasm**

```wasm
(type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add)
  (export "add" (func 0)))
```



The generated loading code depends on [output.target](/config/rsbuild/output.md#outputtarget):

- `web`: Loads `.wasm` files with `fetch`.
- `node`: Loads `.wasm` files with asynchronous Node.js file system APIs.

### `preserve` mode

Rslib keeps imports of `.wasm` modules in the JavaScript output and emits them unchanged in the `dist` directory, preserving their source-relative paths and original filenames:


**index.js**

```js
export { useAdd } from './utils.js';
```


**utils.js**

```js
import { add } from './add.wasm';

const useAdd = (a, b) => add(a, b);
export { useAdd };
```


**add.wasm**

```wasm
(type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add)
  (export "add" (func 0)))
```


`preserve` mode retains the original `.wasm` filenames, so [output.filenameHash](/config/rsbuild/output.md#outputfilenamehash) does not affect them.

:::note Path and filename constraints for JavaScript output

Rslib updates imports of `.wasm` files in the JavaScript output to point to the emitted files, but does not update import module names recorded inside `.wasm` binaries.

If any of these module names resolve to JavaScript output, avoid changing its relative paths or filenames with these options:

- [output.distPath.js](/config/rsbuild/output.md#outputdistpath)
- [output.filename.js](/config/rsbuild/output.md#outputfilename)
- [lib.autoExtension](/config/lib/auto-extension.md)

:::

## Inline Wasm modules


[Added in v1.0.2](https://github.com/web-infra-dev/rslib/releases/tag/v1.0.2)

Add the `?inline` query to an import of a `.wasm` module to embed its binary into the JavaScript output. Rslib does not emit a `.wasm` file for these imports:

```js
import { add } from './add.wasm?inline';
```

[Source phase imports](#source-phase-import) do not currently support the `?inline` query.

## Disable Wasm handling


[Added in v1.0.1](https://github.com/web-infra-dev/rslib/releases/tag/v1.0.1)

When [lib.wasm](/config/lib/wasm.md) is set to `false`, Rslib preserves the original import specifiers for `.wasm` modules imported using ESM syntax, without resolving the modules or emitting the corresponding `.wasm` files. You are responsible for managing these files and ensuring that the imports resolve correctly from the emitted JavaScript files.

:::note

- When using `?inline`, [lib.wasm.mode](/config/lib/wasm.md#wasmmode) does not affect inlining. When [lib.wasm](/config/lib/wasm.md) is set to `false`, Rslib does not process the module and preserves the import with `?inline` in the output.
- `.wasm` files referenced with `new URL('./add.wasm', import.meta.url)` go through the static asset pipeline and are not affected by [lib.wasm](/config/lib/wasm.md).

:::

## Use with wasm-bindgen

[`wasm-bindgen`](https://wasm-bindgen.github.io/wasm-bindgen/) is a tool for building WebAssembly libraries with Rust. The `--target` option generates output for different runtime environments.

Rslib currently supports the following targets:

- `bundler` (recommended): The JavaScript glue imports the `.wasm` module as an ES module while providing the imports required to instantiate it. Both `compile` and `preserve` modes are supported. When using `preserve` mode, follow the [path and filename constraints described above](#preserve-mode).
- `module`: The JavaScript glue uses a [Source Phase import](#source-phase-import) to obtain the compiled `WebAssembly.Module`, then constructs the import object and instantiates the module. Both `compile` and `preserve` modes are supported.
- `web` / `experimental-nodejs-module`: The JavaScript glue locates and loads the `.wasm` file through `new URL('./pkg.wasm', import.meta.url)`. Rslib treats the file as a static asset, so [lib.wasm.mode](/config/lib/wasm.md#wasmmode) does not apply.

## Type declaration

TypeScript does not provide built-in module declarations for `.wasm` files. If you use TypeScript, add a declaration file next to the `.wasm` file using the `.d.wasm.ts` extension, and enable [`allowArbitraryExtensions`](https://www.typescriptlang.org/tsconfig/allowArbitraryExtensions.html) in `tsconfig.json`:

```ts title="src/add.d.wasm.ts"
export function add(a: number, b: number): number;
```

### Inline imports

When using TypeScript, add the [preset types](/guide/basic/typescript.md#preset-types) provided by `@rslib/core` to `compilerOptions.types` in `tsconfig.json`. If this option already lists other type packages, append it to the existing array:

```json title="tsconfig.json"
{
  "compilerOptions": {
    "types": ["@rslib/core/types"]
  }
}
```

Then create a declaration file next to the `.wasm` file and declare the named exports for the exact `?inline` import path:

```ts title="src/wasm.d.ts"
export {};

declare module './add.wasm?inline' {
  export const add: (a: number, b: number) => number;
}
```

The `export {}` at the top marks the declaration file as a module, allowing TypeScript to recognize the relative module declaration below. You can then use a named import:

```ts title="src/index.ts"
import { add } from './add.wasm?inline';
```

Re-exporting the `.wasm` module directly preserves `?inline` in the generated declaration file. To avoid this, keep the `.wasm` import in an internal module and export wrapped functions or values instead.
