Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ compilation and the glue code.
- Works with `wasm_bindgen` exports and with plain functions.
- Finds the nearest `Cargo.toml` by walking up from the `.rs` file, so you do not configure the crate path by hand.
- Builds for `web` and `node` targets, and across the bundlers listed above.
- Types `.rs` imports in TypeScript: an ambient floor keeps imports valid, and generated `.d.rs.ts` sidecars (plus an editor Language Service plugin) give the exact `#[wasm_bindgen]` signatures.

You write computation-heavy code in Rust, pull in crates from the Rust ecosystem, and call the result from JavaScript
without managing a separate compile-and-link pipeline.
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface LoaderOptions {
| `web.wasmPathModifier` | `string[]` | `["/"]` | Modify WASM request path |
| `node.bundle` | `boolean` | `false` | Bundle WASM in JS file |
| `logLevel` | `string` | `"info"` | Logging verbosity |
| `types` | `boolean` | `false` | Write `.d.rs.ts` type sidecars on build |

## Target environments

Expand Down Expand Up @@ -130,6 +131,9 @@ plugin(loader.bun({
|------------|-------------------------------------------------------|------------------|--------------------------------|
| `target` | `"web" \| "node"` | webpack `target` | Override the build target |
| `logLevel` | `"verbose" \| "info" \| "warn" \| "error" \| "quiet"` | `"info"` | Control logging verbosity |
| `types` | `boolean` | `false` | Also write the `<name>.d.rs.ts` type sidecar during the build |

`types` is available on every surface (the loader, the Vite/Rollup/esbuild/Bun plugins, and the Next.js helper). It is one of three ways to generate the type sidecars, alongside the `gen-types` CLI and the editor Language Service plugin. See [TypeScript types](../examples/typed-imports.md) for the full setup.

### Bun options

Expand Down
4 changes: 4 additions & 0 deletions docs/docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ Each example here is a complete project: the setup steps, the config files, and
## Desktop applications

- [Electron](./electron.md) - main and renderer processes with Webpack (`electron-main` uses the `node` strategy, `electron-renderer` uses `web`), with the wasm bytes inlined into both.

## TypeScript

- [TypeScript types](./typed-imports.md) - type `.rs` imports with the ambient floor, the `gen-types` CLI, the build-time `types` option, and the editor Language Service plugin.
159 changes: 159 additions & 0 deletions docs/docs/examples/typed-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
sidebar_position: 10
---

# TypeScript types

A `.rs` import can be a fully typed module in TypeScript, ESLint, and your editor. This page shows how to make `import lib from "./math.rs"` resolve, and how to get the exact signatures of your `#[wasm_bindgen]` exports.

## How it works

Types come in two layers:

- **The floor** - the package ships an ambient `declare module "*.rs"`. Once you reference it, every `.rs` import is valid and loosely typed (a record of callables), so the import never errors even before anything is generated.
- **Precise sidecars** - for each `.rs`, a `<name>.d.rs.ts` file carries the exact signatures, generated from wasm-bindgen's own types. TypeScript resolves it through `allowArbitraryExtensions` and it overrides the floor for that file.

wasm-bindgen already knows the types; the loader normally discards them. The pieces below keep them and reshape them to match what a `.rs` import actually exposes at runtime.

## Make `.rs` imports valid

Reference the shipped floor from your `tsconfig.json`:

```json title="tsconfig.json"
{
"files": ["node_modules/rust-wasmpack-loader/types/rs.d.ts"],
"include": ["src"]
}
```

That alone clears the `Cannot find module './math.rs'` error. The import is typed as `Record<string, (...args: any[]) => any>` until precise types are generated.

## Get precise types

### 1. Turn on arbitrary-extension resolution

```json title="tsconfig.json"
{
"compilerOptions": {
"moduleResolution": "bundler",
"allowArbitraryExtensions": true
}
}
```

`allowArbitraryExtensions` (TypeScript 5.0+) is what lets `import "./math.rs"` resolve to a `math.d.rs.ts` sidecar. `moduleResolution` must be `bundler`, `node16`, or `nodenext`.

### 2. Generate the sidecars

The sidecars are generated from the Rust. Pick whichever path fits your workflow; they all produce the same `<name>.d.rs.ts` and can be combined.

#### In the editor - the Language Service plugin

Add the plugin to `tsconfig.json` and your editor types `.rs` imports live, with nothing to run:

```json title="tsconfig.json"
{
"compilerOptions": {
"plugins": [{ "name": "rust-wasmpack-loader/tsserver" }]
}
}
```

The plugin loads in any tsserver-based editor (VS Code, JetBrains, Neovim) with no editor extension. In VS Code you must use the workspace TypeScript once: run **"TypeScript: Select TypeScript Version"** and choose **"Use Workspace Version"** (a `.vscode/settings.json` with `"typescript.tsdk": "node_modules/typescript/lib"` prompts for it).

Because the types come from a wasm-pack build, a freshly opened `.rs` shows the loose floor for the second or two the first build takes, then the precise types appear. Editing a `.rs` refreshes on save.

#### On build - the `types` option

Set `types: true` on the loader (or any plugin) and a normal build also writes the sidecar next to each `.rs`, reusing the build it already runs:

```javascript title="webpack.config.js"
{
test: /\.rs$/,
use: {
loader: "rust-wasmpack-loader",
options: { types: true },
},
}
```

The same `types: true` option works on the Vite, Rollup, esbuild, and Bun plugins, and on the `withRustWasm` Next.js helper. It is off by default, so it never writes files unless you ask.

#### In CI or on demand - the CLI

The package ships a `gen-types` command that writes the sidecars for matched `.rs` files. Wire it into a typecheck or CI step:

```json title="package.json"
{
"scripts": {
"typecheck": "rust-wasmpack-loader gen-types && tsc --noEmit"
}
}
```

```bash
# generate for specific files, or pass globs; --watch regenerates on change
npx rust-wasmpack-loader gen-types src/math.rs --watch
```

### 3. Ignore the generated sidecars

The sidecars are build output. Add them to `.gitignore`:

```gitignore title=".gitignore"
*.d.rs.ts
```

`tsc`, ESLint, and the editor read whatever sidecar is present; the floor covers anything not generated yet, so a fresh clone or a CI run that has not generated never errors.

## Example

The [`example/typed-imports`](https://github.com/yeskiy/rustwasm-loader/tree/main/example/typed-imports) project wires all of this together.

```rust title="math.rs"
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: i32) -> i32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}

#[wasm_bindgen]
pub fn cap(s: &str) -> String {
s[0..1].to_uppercase() + &s[1..]
}
```

```typescript title="src/index.ts"
import lib from "../math.rs";

// `lib.fibonacci` is typed `(n: number) => number`, `lib.cap` is
// `(s: string) => string`. A typo like `lib.fib()` is a compile error.
export const fib10 = lib.fibonacci(10);
export const capped = lib.cap("hello");
```

```json title="tsconfig.json"
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"allowArbitraryExtensions": true,
"strict": true,
"types": [],
"plugins": [{ "name": "rust-wasmpack-loader/tsserver" }]
},
"files": ["node_modules/rust-wasmpack-loader/types/rs.d.ts"],
"include": ["src/index.ts"]
}
```

## Notes

- **Functions are typed; classes are not yet.** A `#[wasm_bindgen]` function gets its exact signature. An exported `struct`/class stays on the loose floor for now, since the loader does not yet expose wasm-bindgen classes on the default export.
- **The plugin is editor-only.** `tsc` on the command line never loads a Language Service plugin, so it reads the on-disk sidecar instead. Generate it with `types: true` or the CLI for `tsc` and CI; the plugin keeps the editor live.
- **`typescript` is a dependency** of the loader (the generator and the plugin use the compiler API), so it is installed for you.
2 changes: 1 addition & 1 deletion docs/docs/getting-started/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ graph LR

- Import `.rs` files like any other module, with no separate build step to wire up
- Hot reload during development
- TypeScript integration through generated type definitions
- Typed `.rs` imports: an ambient floor for valid imports, generated `.d.rs.ts` sidecars for the exact `#[wasm_bindgen]` signatures, and an editor Language Service plugin for live types
- Webpack 5+ with explicit targets (web, node)
- Rspack through the same Webpack-compatible loader
- Bun runtime support
Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const sidebars = {
"docs/examples/vite",
"docs/examples/next",
"docs/examples/electron",
"docs/examples/typed-imports",
],
},
{
Expand Down
Loading