# Use source maps

> Provide --source-map for identifier recovery and import dedup, emit decompiled maps with --emit-source-map, extract embedded sources via wakaru extract, and pipeline ordering constraints.

- Repository: pionxzh/wakaru
- GitHub: https://github.com/pionxzh/wakaru
- Human docs: https://grok-wiki.com/public/docs/pionxzh-wakaru-77a438a6cc6b
- Complete Markdown: https://grok-wiki.com/public/docs/pionxzh-wakaru-77a438a6cc6b/llms-full.txt

## Source Files

- `README.md`
- `crates/core/src/sourcemap_rename.rs`
- `crates/cli/src/main.rs`
- `docs/architecture.md`
- `crates/core/src/rules/import_dedup.rs`

---

---
title: Use source maps
description: Provide --source-map for identifier recovery and import dedup, emit decompiled maps with --emit-source-map, extract embedded sources via wakaru extract, and pipeline ordering constraints.
---

Wakaru supports source maps in three distinct ways: **input maps** that improve decompiled readability, **output maps** that link decompiled code back to the minified input, and **`wakaru extract`** to recover original source files embedded in a map. Input and output maps are independent — you can use either, both, or neither.

## When to use each feature

| Feature | Flag / command | Purpose |
|---------|----------------|---------|
| Input source map | `--source-map` / `-m` | Recover original identifier names; deduplicate and merge imports before rename |
| Output source map | `--emit-source-map` | Emit a v3 `.map` alongside each decompiled output file |
| Extract embedded sources | `wakaru extract` | Write `sourcesContent` entries from a map to disk |

The strongest results come from **single-file decompile** with a companion `.map` from the same build that produced the minified JavaScript. Identifier positions in the AST must align with the map's generated positions.

## Provide an input source map

Pass the v3 source map that corresponds to the minified or bundled file you are decompiling.

<Steps>
<Step title="Locate the companion map">

Find the `.map` file emitted by your bundler or minifier alongside the JavaScript you feed to Wakaru. The map must describe the **same generated file** as the input — not a parent bundle when you are decompiling an already-extracted module.

</Step>

<Step title="Run decompile with --source-map">

<CodeGroup>

```bash title="Single file"
wakaru input.js --source-map input.js.map -o output.js
```

```bash title="Short alias"
wakaru input.js -m input.js.map -o output.js
```

```bash title="Unpack a single bundle"
wakaru bundle.js --unpack --source-map bundle.js.map -o out/
```

</CodeGroup>

</Step>

<Step title="Verify improved names">

Compare output with and without the map. With `--source-map`, minified single-letter bindings should recover readable names where mappings and `sourcesContent` are present. Duplicate imports from scope-hoisted bundles should collapse into canonical bindings.

</Step>
</Steps>

<ParamField body="--source-map" type="path" required={false}>
Path to a v3 source map (`.map`). Aliases: `-m`, `--sourcemap`. Read as raw bytes and passed to `DecompileOptions::sourcemap`.
</ParamField>

### CLI constraints

- **Single input file only.** Wakaru rejects `--source-map` when multiple explicit inputs are passed (for example `entry.js chunk.js --unpack`). Directory scans that resolve to multiple bundle files also conflict with this flag.
- **Works with `--unpack`** when exactly one bundle file is the input. The same bundle-level map is applied during Phase 2 of each extracted module. Rename quality depends on whether identifier spans in the extracted module still align with bundle positions; single-file decompile is the reliable path.
- **`debug trace` accepts `--source-map` but does not run the source-map rename pass.** Rule tracing covers the normal `apply_rules` pipeline only, not the post-pipeline `ImportDedup` → rename → `UnImportRename` sequence.

## What --source-map enables

When `DecompileOptions::sourcemap` is set, Wakaru runs a dedicated post-rules pass that does two things:

1. **Import deduplication** — merges repeated imports of the same specifier from the same module and consolidates multiple `import` statements targeting one source into a single declaration.
2. **Identifier recovery** — looks up each binding's generated position in the map, reads the original name from the `names` array or from `sourcesContent`, and renames bindings via `BindingRenamer`.

Import dedup also runs inside the normal rule pipeline (Stage 6), but the source-map pass runs **`ImportDedup` again** immediately before rename so duplicates are collapsed to one canonical binding before names are recovered.

### Pipeline ordering

Source-map work runs **after** the full transformation pipeline and **before** the fixer and emitter. Order is fixed for correctness:

```mermaid
flowchart TD
    A[parse + resolver] --> B[apply_rules — full rule pipeline]
    B --> C{sourcemap provided?}
    C -->|yes| D[ImportDedup]
    D --> E[apply_sourcemap_renames]
    E --> F[UnImportRename]
    C -->|no| G[strip Sentry markers — standard+]
    F --> G
    G --> H[fixer]
    H --> I{emit_source_map?}
    I -->|yes| J[print with srcmap + build v3 map]
    I -->|no| K[print_js]
```

**Why rename must come last among transforms:**

- Rules match patterns by **minified names** (`require`, `__generator`, `__esModule`). Renaming first would break helper detection and module-system reconstruction.
- `ImportDedup` needs **`UnEsm`** to have converted `require()` calls into `import` declarations. That happens in Stage 2 of the main pipeline.
- Dedup must run **before** rename so five duplicate imports of `lodash` become one binding that receives a single recovered name instead of five separate renames.

During **unpack**, the same source-map trio (`ImportDedup` → `apply_sourcemap_renames` → `UnImportRename`) runs at the end of Phase 2, after cross-module rules (`namespace_decomposition`, re-export consolidation) and the `UnTemplateLiteral` through `UnReturn` range. Supplying a source map or requesting `--emit-source-map` forces Phase 2 to **re-parse** each module rather than reuse the Phase 1 AST, because rename lookup depends on the Phase 2 parser `SourceMap` and emitted mappings depend on a fresh parse.

### How identifier recovery works

For each non-global identifier (bindings where `ctxt.outer() != unresolved_mark`):

1. Convert the identifier's `span` to a generated `(line, col)` in the parsed input file.
2. Call `lookup_token` on the v3 map to find the original source file, line, and column.
3. Prefer the map's `names` entry when present; otherwise extract the identifier text from `sourcesContent` at the original position (works when `names` is empty, as in esbuild output).
4. **Vote** per `(sym, SyntaxContext)` binding — plurality wins.
5. Apply renames with scope-aware disambiguation:
   - **Local bindings** (params, block-scoped locals): all claimants receive the bare recovered name; nested scopes shadow as in original source.
   - **Module-level bindings** (imports, top-level declarations): names must be unique in the merged flat namespace; collisions get suffixes (`name_2`, `name_3`, …).

Globals and unresolved references (`Object`, `Symbol`, etc.) are never renamed. Identifiers with `DUMMY_SP` spans are skipped so synthetically inserted bindings are not voted on.

After rename, **`UnImportRename`** removes redundant import aliases (`import { foo as bar }` → `import { foo }` when safe).

## Emit a decompiled source map

`--emit-source-map` generates a v3 source map mapping **decompiled output** positions back to the **parsed input** (the minified or extracted module code Wakaru received), not to original TypeScript sources.

<RequestExample>

```bash
wakaru input.js --emit-source-map -o output.js
```

</RequestExample>

This writes two files:

- `output.js` — decompiled JavaScript
- `output.js.map` — v3 map with `sourcesContent` embedding the input source

<ParamField body="--emit-source-map" type="boolean" required={false}>
Off by default. When set, each output file gets a companion `.map` (for example `foo.js` → `foo.js.map`). With `--unpack`, one map is emitted per kept module. The map's `file` field uses the final module filename, including names recovered from provenance markers.
</ParamField>

### Output map contents

Wakaru collects byte-position mappings during `JsWriter` emission, then builds a v3 map via `SourceMapBuilder`:

- **`file`** — output filename (`DecompileOptions::filename` or recovered module name).
- **`sources`** — the parsed input source name from SWC's `SourceMap`.
- **`sourcesContent`** — full text of the input as Wakaru parsed it.
- **Mappings** — tokens linking decompiled output lines/columns to input lines/columns.

<ResponseExample>

```json title="--json stdout (single file, no -o)"
{
  "code": "const greeting = \"hello\";\n",
  "source_map": "{\"version\":3,\"file\":\"input.js\",...}",
  "warnings": [],
  "elapsed_ms": 12
}
```

</ResponseExample>

With `-o`, the map is written to disk. With `--json` and no `-o`, the map JSON is included in the `source_map` field on stdout. Human-readable stdout mode (no `-o`, no `--json`) prints code only and discards the map.

## Extract embedded sources

When a bundler embeds original sources in `sourcesContent`, recover them without running the decompiler:

<Steps>
<Step title="Run extract">

```bash
wakaru extract input.js.map -o src/
```

</Step>

<Step title="Check output">

Wakaru prints a count to stderr when attached to a terminal:

```
extracted 42 source file(s) to src/
```

Only entries with **both** a source path and non-empty `sourcesContent` are written. Entries missing content are skipped silently.

</Step>
</Steps>

### Path sanitization

Source paths from webpack and other bundlers often carry prefixes or traversal segments. `resolve_source_path` normalizes them before writing:

- Strips `webpack://`, `webpack:///`, and leading `/`
- Resolves path components under the output directory
- **Drops `..` components** — source paths never escape the output root

Parent directories are created as needed. Overwrite protection follows the global `--force` flag.

## Core API and WASM

<ParamField body="DecompileOptions::sourcemap" type="Option<Vec<u8>>">
Raw v3 source map bytes. Enables the post-rules import dedup + rename pass.
</ParamField>

<ParamField body="DecompileOptions::emit_source_map" type="bool">
When `true`, populates `DecompileOutput::source_map` (single file) or `UnpackOutput::source_maps` (per-module `(filename, json)` pairs).
</ParamField>

The WASM `decompile` binding accepts `sourcemap?: Uint8Array` and `emitSourceMap?: boolean`; results surface in `WakaruDecompileResult.source_map`. The `unpack` binding supports `emitSourceMap` and returns `source_maps` per module.

Public helpers in `wakaru-core`:

- `parse_sourcemap(data)` — parse v3 bytes
- `extract_source_entries(sm, out_dir)` — list `(path, content)` pairs without writing
- `resolve_source_path(out_dir, source)` — sanitize a single source path

## Limitations and troubleshooting

| Situation | Behavior |
|-----------|----------|
| No `--source-map` | Skip import-dedup/rename pass; `ImportDedup` in the main pipeline still runs at Stage 6 |
| Map missing `sourcesContent` and empty `names` | Fewer identifiers recover; position fallback cannot read original text |
| Multiple CLI inputs + `--source-map` | Hard error |
| `debug trace` + `--source-map` | Map bytes accepted but rename pass not traced |
| stdin input (`<stdin>`) | Rename still works if map positions match; output map uses `<stdin>` as source name |
| Non-ASCII identifiers in source | Column lookup assumes ASCII-fast extraction; UTF-16 column offsets may mismatch for non-ASCII source |

If recovered names look wrong, confirm the map targets the exact file Wakaru parses — not an intermediate bundle when modules were pre-extracted. For regressions in the rename pass, use normal decompile output comparison; source-map steps are outside rule trace range.

## Related pages

<CardGroup>
<Card title="Decompile pipeline" href="/decompile-pipeline">
Parse, resolver marks, staged rules, optional source-map rename, fixer, and emit order.
</Card>

<Card title="CLI reference" href="/cli-reference">
Full flag list including `-m`, `--emit-source-map`, and the `extract` subcommand.
</Card>

<Card title="Core API reference" href="/core-api-reference">
`DecompileOptions` fields, `DecompileOutput::source_map`, and `UnpackOutput::source_maps`.
</Card>

<Card title="Unpack bundles" href="/unpack-bundles">
Using `--unpack` with single-file inputs and Phase 2 re-parse behavior when maps are involved.
</Card>

<Card title="Trace the rule pipeline" href="/trace-rule-pipeline">
Per-rule diffs for the main pipeline; source-map passes are not included in trace output.
</Card>

<Card title="JSON output and CI" href="/json-output-and-ci">
Machine-readable `source_map` field in `--json` decompile responses.
</Card>
</CardGroup>
