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

# Memory

Rspack supports capturing memory snapshots during builds and HMR updates. When you report a memory issue, include these files to help maintainers investigate.

## For users

### Enable profiling


[Added in v2.3.0](https://github.com/web-infra-dev/rspack/releases/tag/v2.3.0)

Use a debug or profiling package at this version or later (standard release packages do not include allocative support). Snapshot collection is off by default. To enable it, set `RSPACK_ALLOCATIVE_DIR` to an output directory when running your project.

#### Direct Rspack users

If your project depends on `@rspack/core` directly, replace it with the debug package at the same version. For example, change `2.3.0` to `npm:@rspack-debug/core@2.3.0`:

```diff title="package.json"
 {
   "devDependencies": {
-    "@rspack/core": "2.3.0"
+    "@rspack/core": "npm:@rspack-debug/core@2.3.0"
   }
 }
```

Keep the dependency in its original section (`dependencies` or `devDependencies`), then run `pnpm install`.

#### Users of frameworks such as Rsbuild

If your project uses Rspack indirectly through a framework such as Rsbuild, add a package manager override for `@rspack/core`. For pnpm, add this configuration to `package.json`:

```json title="package.json"
{
  "pnpm": {
    "overrides": {
      "@rspack/core": "npm:@rspack-debug/core@2.3.0"
    },
    "peerDependencyRules": {
      "allowAny": ["@rspack/*"]
    }
  }
}
```

Use the same version as the framework's resolved `@rspack/core` dependency (`2.3.0` in this example), then run `pnpm install`. See [Debugging](https://rspack.rs/contribute/development/debugging.md) for more about diagnostic package overrides.

For either approach, you can use a profiling build instead of the debug package. Set `RSPACK_ALLOCATIVE_DIR` when running your project's original build or dev command. Leave it unset to skip snapshot collection. If no suitable package is available, see [Build a local binding](#build-a-local-binding) to build from source.

### Capture memory snapshots

#### Capture a build

Run the project's original build command with an absolute output directory:

```sh
RSPACK_ALLOCATIVE_DIR=/absolute/path/profiles/build-1 pnpm exec rspack build
```

Verify that the build, its configuration, and its plugins all load the intended diagnostic package. For Rspack-integrated frameworks, keep the framework's normal build command and use a compatible diagnostic package override.

After the initial build completes, the output directory contains:

```text
build-1/
  0-build.allocative
  0-build.counts.json
  0-build.metadata.json
  0-build.warnings.txt
```

Use a fresh directory for each process. Snapshot numbering is shared across compilers within a process and resets in a new process. Reusing a directory between processes can overwrite files.

#### Capture HMR updates

Set the same output variable when starting the dev server:

```sh
RSPACK_ALLOCATIVE_DIR=/absolute/path/profiles/hmr-1 pnpm exec rspack dev
```

Edit a source file and wait for each rebuild to finish. A single compiler with three rebuilds produces four byte snapshots:

```text
0-build.allocative
1-rebuild.allocative
2-rebuild.allocative
3-rebuild.allocative
```

Each snapshot also has matching counts, metadata, and warnings files. Watch-mode builds using the same package can also collect snapshots via `RSPACK_ALLOCATIVE_DIR`.

Keep the project's build mode and sourcemap settings while reproducing the issue, and record the edits made for each HMR update.

### Attach data to an issue report

After the build or the intended HMR updates have completed, compress the entire output directory and attach the archive to your issue report. Include all four file types for every snapshot:

| File              | Contents                                                    |
| ----------------- | ----------------------------------------------------------- |
| `*.allocative`    | The Rust data-structure memory snapshot.                    |
| `*.counts.json`   | Module and dependency entry counts.                         |
| `*.metadata.json` | Snapshot identity, memory totals, and collection metadata.  |
| `*.warnings.txt`  | Coverage and accounting warnings; include it even if empty. |

For example, archive the build capture directory:

```sh
tar -czf rspack-memory-build-1.tar.gz -C /absolute/path/profiles build-1
```

For an HMR issue, archive the complete sequence from the initial build through the last relevant rebuild, including all matching files for each snapshot. Keep the original filenames so maintainers can match the snapshots and sidecars.

Include this context in the issue report:

- The Rspack and diagnostic package versions, Node.js version, and operating system.
- The command, configuration, build mode, and sourcemap setting used for the capture.
- A reproduction project or reproduction steps; for HMR, include the edits and their order.
- The observed memory issue, when it occurs, and the build logs. If no snapshots were produced, report that along with the command and logs.

The [developer section](#for-rspack-developers) explains how to inspect the data.

## For Rspack developers

### Use the allocative skill

The Rspack repository includes the [`rspack-allocative` skill](https://github.com/web-infra-dev/rspack/blob/main/.agents/skills/rspack-allocative/SKILL.md). The skill prepares build artifacts, runs the workload, and generates memory snapshots and count reports.

#### Default benchmark projects

The skill uses the repository's built-in benchmark projects by default and prepares the fixtures as needed. With no project or test case specified, it captures the Three.js 10x development build:

```text
$rspack-allocative capture memory and module/dependency entry counts
```

You can also name a preset workload, such as `threejs-10x-production-sourcemap` for a production build with sourcemaps.

#### External projects

To capture an external project, specify its directory and build mode:

```text
$rspack-allocative --project-dir /path/to/my-app production build with sourcemap; collect memory and module/dependency entry counts
```

`--project-dir` is an argument in the skill request, interpreted by the agent — it is not a Rspack CLI option. Relative paths resolve from the agent's initial working directory; quote paths that contain spaces. You can also describe the project directory in natural language. The project directory selects the workload; the Rspack checkout selects the local binding and JavaScript packages. If a project has several configurations, specify the configuration file and build mode in the request.

#### Rspack test cases

The skill also supports collecting data directly from Rspack test cases. Specify the case directory, for example:

```text
$rspack-allocative capture memory for test case hotCases/source-map/disable; run all HMR updates with sourcemaps disabled; keep every memory snapshot and module/dependency entry count report
```

For example, using the `hotCases/source-map/disable` case produces these memory snapshot files:

```text
0-build.allocative
1-rebuild.allocative
2-rebuild.allocative
3-rebuild.allocative
```

Each snapshot also has matching `.counts.json`, `.metadata.json`, and `.warnings.txt` files.

You can use a separate output directory for each run to avoid overwriting data. The skill reports the selected project, build method, capture phase, output paths, and coverage limits. Inspect the snapshots in [Speedscope](https://www.speedscope.app/) as described in [View snapshots](#view-snapshots).

### View snapshots

A snapshot traces ownership paths from the compiler and registered global roots. Its weights are bytes, not execution time or allocation events.

Open [Speedscope](https://www.speedscope.app/) and drag in a `.allocative` file. Use search and zoom to find a data structure, and select **Left Heavy** to explore the largest ownership paths.

Wider frames represent more bytes. The paths describe ownership, not allocation call stacks. The folded format does not carry a unit, so treat the weights as bytes even if the viewer displays a different label. **Time Order** is not a timeline for these snapshots.

Read the matching `.metadata.json` and `.warnings.txt` before interpreting the total. Metadata includes the represented byte total, shared-ownership policy, and global string arena usage. Warnings identify inaccessible nested allocations and other coverage gaps.

### Read entry counts

The `.counts.json` sidecar records module and dependency entries in the current compilation's module graph, grouped by type. These are collection cardinalities — not a process-wide object count or allocation count. An empty graph produces explicit zero-valued module and dependency populations.

Do not derive counts by dividing byte weights by a type's size or by counting lines in a `.allocative` file. A collection can reserve more storage than its length, and multiple references can share one allocation.

The skill automatically prepares `summary.json`, `counts.csv`, and `counts.folded` when count data is available. Open a nonempty `counts.folded` file in Speedscope to explore **entry counts** rather than bytes. Zero-valued populations remain in the table and summary but produce no chart frames.

### Interpret the results

Compare matching snapshots from the same phase and with the same workload. For example, if module and dependency counts stay stable during repeated HMR updates while represented bytes grow, inspect which ownership paths retain more data. This observation alone does not establish a memory leak.

Limitation:

- Snapshots do not measure RSS, peak memory, or cumulative allocations.
- JavaScript and V8 heap objects, callback captures, some external state, allocator metadata, and private container overhead are not fully represented. Nonblocking locks and unsupported nested types can also limit coverage.

## Advanced

### How memory collection works

Rspack maintains a fork of [Buck2](https://github.com/facebook/buck2)'s allocative in [`crates/rspack_allocative`](https://github.com/web-infra-dev/rspack/tree/main/crates/rspack_allocative). The fork uses Rust's compile-time reflection and a compiler driver to collect memory data automatically in most cases, reducing the need for manual `Allocative` implementations.

Borrowed references do not imply ownership; raw pointers and unsupported values are treated as opaque. Shared allocations are counted once under the first encountered owner. These policies avoid double-counting shared payloads, but inaccessible state still limits coverage.

**When manual `Allocative` is needed.** Ordinary structs and most enums are instrumented automatically, but some cases require explicit adapters or annotations:

- **Third-party types** require an `Allocative` implementation in `rspack_allocative`.
- **Unions** aren't supported by the derive macro — implement `Allocative` by hand.
- **Custom ownership** where memory layout doesn't match actual ownership, such as arena-allocated data, borrowed buffers, or shared pointers. Write a manual implementation to express the right traversal.
- **Opaque or unsupported values** like raw pointers, callback captures, and V8 heap objects. Mark these fields with `#[allocative(skip)]` to exclude them.
- **Incorrect generic bounds** from the default derive. Use `#[allocative(bound = "...")]` to supply your own constraints.
- **Enum reflection edge cases.** The compiler driver's enum reflection needs `Self: 'static` and skips enums with lifetime parameters, const generics, or non-literal array types. These fall back to typed traversal and need explicit adapters.

The capture flow is:

```text
Binding build
  Include the allocative feature, cfg, and compiler driver
       ↓
Build or rebuild completes
  Finish cache processing; check RSPACK_ALLOCATIVE_DIR
       ↓
Root traversal
  Compiler → Compilation and other owned fields
  Registered globals + process-global Ustr string arena
       ↓
Ownership traversal
  Custom Allocative::visit or reflected fields
  Deduplicate shared payloads; record coverage warnings
       ↓
Snapshot output
  Byte profile + entry counts + metadata + warnings
```

The capture points are in [`compiler/mod.rs`](https://github.com/web-infra-dev/rspack/blob/main/crates/rspack_core/src/compiler/mod.rs) and [`compiler/rebuild.rs`](https://github.com/web-infra-dev/rspack/blob/main/crates/rspack_core/src/compiler/rebuild.rs). [`snapshot_allocative`](https://github.com/web-infra-dev/rspack/blob/main/crates/rspack_core/src/utils/mod.rs) visits the roots and writes the files. This measures the represented memory at that point, with the limits described in [Interpret the results](#interpret-the-results).

### Build a local binding

After setting up the [development prerequisites](https://rspack.rs/contribute/development/prerequisites.md), run these commands from the Rspack repository root:

```sh
pnpm install
pnpm run build:binding:debug
pnpm run build:js
```

**The debug build includes allocative support by default; snapshot collection is still off by default.** The `build:binding:debug` command uses the `release-debug` Cargo profile. Standard release builds do not include allocative support. The profiling build also includes this support by default:

```sh
pnpm run build:binding:profiling
```

To include allocative support in the development build (`build:binding:dev`) or another binding build profile, set `ALLOCATIVE=1` when building:

```sh
ALLOCATIVE=1 pnpm run build:binding:dev
```

| Environment variable    | When to set it                                         | Purpose                                                    |
| ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------- |
| `ALLOCATIVE=1`          | Building a binding that does not include it by default | Include allocative instrumentation in the binding.         |
| `RSPACK_ALLOCATIVE_DIR` | Running a local build or test                          | Enable snapshot collection and select an output directory. |

Setting `ALLOCATIVE=1` at runtime alone does not add instrumentation to an existing binding. Debug and profiling builds do not collect snapshots unless `RSPACK_ALLOCATIVE_DIR` is set.

### Profile a local build or test

Run the built CLI from the target project's working directory:

```sh
RSPACK_ALLOCATIVE_DIR=/absolute/path/profiles/local-build-1 \
  node /path/to/rspack/packages/rspack-cli/bin/rspack.js build \
  --config ./rspack.config.mjs
```

Tests using the local binding can also collect snapshots via `RSPACK_ALLOCATIVE_DIR`. Each build or rebuild produces a snapshot automatically; the test framework does not need a separate capture integration. After building the binding and JavaScript packages, run a focused HMR case:

```sh
cd /path/to/rspack/tests/rspack-test
RSPACK_ALLOCATIVE_DIR=/absolute/path/profiles/hmr-test-1 \
  pnpm test "\"$PWD/HotNode.test.mjs\"" --project base \
  -t hotCases/source-map/disable --maxWorkers 1
```

The literal double quotes in the positional argument select the exact `HotNode.test.mjs` runner; `-t` selects the fixture. Use one worker and a fresh output directory. For other runners or concurrent processes, give each process its own directory to avoid overwriting snapshots. See [Testing](https://rspack.rs/contribute/development/testing.md) for the repository's test conventions.

### Prepare count reports

For manually captured snapshots, run the report helper with a new output directory:

```sh
pnpm --dir /path/to/rspack exec zx \
  /path/to/rspack/.agents/skills/rspack-allocative/scripts/render-profile.mjs \
  /absolute/path/profiles/build-1/0-build.allocative \
  --out-dir /absolute/path/profiles/build-1/report-0
```

It discovers matching sidecars and generates the summary and count reports described in [Read entry counts](#read-entry-counts). The skill runs this step automatically.
