# Webpack bundle recipe

> End-to-end workflow using webpack4 and webpack5 testcases: build fixtures, run wakaru --unpack, compare against dist/*.pretty.js reference output, and multi-chunk inputs.

- 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

- `testcases/webpack4/README.md`
- `testcases/webpack4/webpack.config.js`
- `testcases/webpack4/dist/index.js`
- `testcases/webpack5/README.md`
- `testcases/webpack5/webpack.config.mjs`
- `testcases/webpack5/dist/index.js`

---

---
title: "Webpack bundle recipe"
description: "End-to-end workflow using webpack4 and webpack5 testcases: build fixtures, run wakaru --unpack, compare against dist/*.pretty.js reference output, and multi-chunk inputs."
---

Wakaru ships two committed webpack fixtures under `testcases/webpack4` and `testcases/webpack5`. Pipeline tests read `dist/index.js`, unpack with `BundleFormat::Webpack4` or `BundleFormat::Webpack5`, and pin every extracted module in `crates/core/tests/snapshots/`. The CLI path is `wakaru <bundle> --unpack -o <dir>`.

## Fixture layout

:::files
testcases/
├── webpack4/
│   ├── src/                  # React 16 + Redux + TypeScript sources
│   ├── webpack.config.js     # production build, babel-loader for .tsx
│   ├── package.json          # `pnpm build` → dist/index.js
│   └── dist/
│       ├── index.js          # minified webpack4 IIFE array bundle (test input)
│       └── index.pretty.js   # formatted copy of index.js for inspection
└── webpack5/
    ├── src/                  # small ESM app (6 modules + entry)
    ├── webpack.config.mjs    # development + source-map
    ├── package.json          # `pnpm build` → dist/index.js
    └── dist/
        ├── index.js          # webpack5 __webpack_modules__ bundle (test input)
        ├── index.js.map      # embedded source map
        ├── index.pretty.js   # formatted copy of index.js
        ├── modules.js        # extra artifact; not used by wakaru tests
        └── output.js         # partial bundle variant; not used by wakaru tests
:::

| Fixture | Webpack version | Bundle shape | Module IDs in output | Typical module count |
|---------|-----------------|--------------|--------------------|----------------------|
| `testcases/webpack4` | 4.4 (React starter fork) | `!function(modules){...}(["module",...])` | Numeric → `module-N.js` | ≥ 50 (+ `entry.js`) |
| `testcases/webpack5` | 5.88 | `(() => { var __webpack_modules__ = { "./src/…": … } })()` | String paths → `src/*.js` | 7 (`entry.js` + 6 `src/` files) |

<Note>
Only `dist/index.js` from each testcase is wired into `webpack4_unpack`, `webpack4_unpack_raw`, and `bundle_unpack` tests. The other files in `testcases/webpack5/dist/` are committed for manual comparison but are not CI inputs.
</Note>

## Webpack4 vs webpack5 detection markers

| Signal | Webpack4 (`testcases/webpack4/dist/index.js`) | Webpack5 (`testcases/webpack5/dist/index.js`) |
|--------|-----------------------------------------------|-----------------------------------------------|
| Bootstrap | Single IIFE + numeric `n(s=51)` entry | `/******/` banner + `__webpack_modules__` object |
| Module table | Array of factory functions | Object keyed by `"./src/…"` strings |
| Harmony helpers | `n.d`, `n.r`, `n.n` on runtime `n` | `__webpack_require__.d`, `.r`, `.n` |
| Output filenames | `module-0.js` … `module-50.js`, `entry.js` | Preserved paths: `src/a.js`, `src/b.js`, … |
| Source map | Not committed | `index.js.map` available (`devtool: 'source-map'`) |

Webpack4 is a full production React/Redux/Saga stack (Babel 6 + TypeScript 2.9). Webpack5 is a minimal ESM exercise focused on harmony exports, default interop, and duplicate binding names (`A` from `d.js` vs `e.js`).

## Build fixtures

<Steps>
<Step title="Install Node dependencies">

From the repo root:

<CodeGroup>
```bash title="Webpack4"
cd testcases/webpack4
pnpm install
```

```bash title="Webpack5"
cd testcases/webpack5
pnpm install
```
</CodeGroup>

Both packages pin `pnpm@8.15.2` and use a placeholder `pnpm-workspace.yaml` to avoid pnpm workspace detection issues.

</Step>

<Step title="Produce dist bundles">

<CodeGroup>
```bash title="Webpack4 (production)"
cd testcases/webpack4
pnpm build
# runs: cross-env NODE_OPTIONS="--openssl-legacy-provider" webpack --mode production
```

```bash title="Webpack5 (development + source map)"
cd testcases/webpack5
pnpm build
# runs: webpack (mode: development, devtool: source-map)
```
</CodeGroup>

Webpack4 writes a single minified `dist/index.js`. Webpack5 writes `dist/index.js` plus `dist/index.js.map`.

</Step>

<Step title="Verify committed artifacts">

After a local rebuild, confirm the primary inputs still exist:

```bash
ls -l testcases/webpack4/dist/index.js testcases/webpack5/dist/index.js
```

Wakaru tests read these paths relative to `crates/core/tests/` (`../../testcases/webpack4/dist/index.js`). If `pnpm build` changes bundle hashes, expect snapshot drift in the core test suite.

</Step>
</Steps>

## Unpack a single bundle

<ParamField body="--unpack" type="flag" required>
Unpack mode. Requires `-o/--output` as a directory. Default mode is `auto` (structural detection + heuristic fallback).
</ParamField>

<ParamField body="-o / --output" type="path" required>
Output directory. Wakaru refuses to overwrite a non-empty directory unless `--force` is set.
</ParamField>

<ParamField body="--raw" type="flag">
Write extractor output before the decompile rule pipeline. Useful for bisecting webpack normalization vs rule regressions.
</ParamField>

<ParamField body="-m / --source-map" type="path">
Source map for identifier recovery. **Single input only** — CLI rejects `--source-map` with multiple bundle files.
</ParamField>

<ParamField body="--force" type="flag">
Overwrite existing output files or non-empty output directories.
</ParamField>

<CodeGroup>
```bash title="Webpack4 full unpack"
cargo run -p wakaru-cli -- \
  testcases/webpack4/dist/index.js \
  --unpack -o /tmp/wp4-out
```

```bash title="Webpack5 full unpack"
cargo run -p wakaru-cli -- \
  testcases/webpack5/dist/index.js \
  --unpack -o /tmp/wp5-out
```

```bash title="Webpack5 with source map"
cargo run -p wakaru-cli -- \
  testcases/webpack5/dist/index.js \
  -m testcases/webpack5/dist/index.js.map \
  --unpack -o /tmp/wp5-mapped-out
```

```bash title="Raw extraction (pre-pipeline)"
cargo run -p wakaru-cli -- \
  testcases/webpack4/dist/index.js \
  --unpack --raw -o /tmp/wp4-raw-out
```
</CodeGroup>

### Expected output layout

:::files
/tmp/wp4-out/                    # webpack4: flat numeric modules
├── entry.js
├── module-0.js
├── module-1.js
└── …                            # 52 files total (entry + 51 modules)

/tmp/wp5-out/                    # webpack5: source-tree paths
├── entry.js
└── src/
    ├── 1.js
    ├── a.js
    ├── b.js
    ├── c.js
    ├── d.js
    └── e.js
:::

Webpack4 `entry.js` recovers the React mount (imports from `./module-N.js`). Webpack5 `entry.js` mirrors `testcases/webpack5/src/index.js` structure with `import` statements and aliased bindings (`A as A_1`, `A as A_2`).

<RequestExample>
```bash
cargo run -p wakaru-cli -- testcases/webpack5/dist/index.js --unpack -o /tmp/wp5-out
head -5 /tmp/wp5-out/entry.js
```
</RequestExample>

<ResponseExample>
```javascript
import _1_js__WEBPACK_IMPORTED_MODULE_0__ from "./src/1.js";
import { A } from "./src/a.js";
import _b_js__WEBPACK_IMPORTED_MODULE_2__ from "./src/b.js";
import { getC } from "./src/c.js";
import { A as A_1 } from "./src/d.js";
```
</ResponseExample>

## Reference outputs and comparison

Three layers serve different comparison goals:

| Layer | Path | What it represents | How to use it |
|-------|------|--------------------|---------------|
| Pretty bundle | `dist/index.pretty.js` | Human-readable **input** bundle (formatted `dist/index.js`) | Inspect webpack runtime, module boundaries, and `__webpack_require__` wiring before unpacking |
| Original sources | `testcases/webpack5/src/` | Pre-bundle authoring files | Spot-check recovered imports/exports against known `src/` for webpack5 |
| Insta snapshots | `crates/core/tests/snapshots/webpack4_unpack__*.snap` | Pinned **decompiled** module output | Automated regression gate; authoritative for CI |

<Warning>
`dist/*.pretty.js` is not wakaru output. Do not diff it against unpack results. It exists so you can read the committed bundle without a formatter. Automated verification compares against insta snapshots, not pretty files.
</Warning>

### Manual diff workflow

1. Unpack to a scratch directory (`--unpack -o /tmp/out --force`).
2. For webpack5, compare `out/src/*.js` and `out/entry.js` against `testcases/webpack5/src/`.
3. For webpack4, use `out/entry.js` and representative `out/module-N.js` files; original TypeScript lives under `testcases/webpack4/src/` but module numbering does not map 1:1 to source paths.
4. When rule changes alter output, run focused tests and review snapshot diffs (see below).

### Snapshot-backed verification

```bash
# Webpack4: structural checks + per-module snapshots
cargo test -p wakaru-core --test webpack4_unpack
cargo test -p wakaru-core --test webpack4_unpack_raw

# Webpack5 single-file unpack
cargo test -p wakaru-core --test bundle_unpack

# Webpack5 JSONP chunk format (inline fixtures)
cargo test -p wakaru-core --test webpack5_chunk_unpack

# Entry + separate chunk files (multi-input)
cargo test -p wakaru-core --test multi_file_unpack
```

Snapshot drift fails the test and writes `.snap.new` (configured via `INSTA_UPDATE=new` in `.cargo/config.toml`). Review diffs, then accept intentional improvements:

```bash
cargo insta review
# or
cargo insta accept
```

<Check>
Success signals from `webpack4_unpack.rs`: unpack succeeds with no error warnings, ≥ 50 modules extracted, every module non-empty, and `entry.js` present.
</Check>

## Raw vs full unpack (webpack4)

Webpack4 has two regression layers:

| Mode | API / CLI | Output stage | Test binary |
|------|-----------|--------------|-------------|
| Raw | `unpack_raw` / `--unpack --raw` | Post-extraction normalization only; ESM markers remain | `webpack4_unpack_raw` |
| Full | `unpack` / `--unpack` | Two-phase pipeline through ~60 rewrite rules | `webpack4_unpack` |

`webpack4_unpack_raw.rs` pins pre-pipeline code via `unpack_webpack4_raw`. `webpack4_unpack.rs` asserts raw output differs from decompiled output for at least one module — confirming the rule pipeline adds value beyond extraction.

Use `--raw` when a regression could be extraction vs rules. Use full unpack for end-user-readable modules.

## Multi-chunk and multi-file inputs

Single-file testcases cover monolithic bundles. Split-chunk workflows use separate entry and chunk artifacts merged by `unpack_files`.

```mermaid
sequenceDiagram
    participant CLI as wakaru CLI
    participant Detect as is_detected_unpack_input
    participant Merge as unpack_files
    participant P1 as Phase 1 (facts)
    participant P2 as Phase 2 (cross-module rules)

    CLI->>Detect: scan each input file
    Detect-->>CLI: bundle/chunk candidates only
    CLI->>Merge: UnpackInput[] (entry + chunks)
    Merge->>P1: par_iter modules through UnEsm
    P1->>P2: ModuleFactsMap barrier
    P2-->>CLI: merged module set → output dir
```

### Inline JSONP chunks

`webpack5_chunk_unpack.rs` exercises the `(self.webpackChunk… \|\| []).push([[id], { numericId: factory }])` format. Covered behaviors:

- Numeric module IDs become `module-11111.js`, `module-200.js`, etc.
- `require(N)` inside a chunk rewrites to `./module-N.js` imports
- Arrow and method factory forms unpack
- `window["webpackJsonp"]` chunk bases are accepted
- Webpack4-style `require.d(exports, "name", getter)` inside chunks normalizes

These tests use inline source strings, not the `testcases/` tree.

### Entry + external chunk files

`multi_file_unpack.rs` loads generated fixtures from `crates/core/tests/bundles/webpack-gen/dist/`:

| Test | Inputs | Key assertion |
|------|--------|---------------|
| `webpack5_dynamic_entry_and_chunk_unpack_together` | `bundle.js` + `src_greet_js.bundle.js` | `entry.js` imports `./src/greet.js`; chunk module extracted |
| `webpack5_dynamic_min_runtime_entry_and_chunk_unpack_together` | `bundle.js` + `529.bundle.js` | `entry.js` references `./module-529.js` |
| `webpack5_multi_file_rewrites_unambiguous_numeric_chunk_id` | synthetic entry + `529.bundle.js` | Numeric `529` rewritten; no raw `, 529,` left in entry |

CLI equivalent — pass multiple detected files:

```bash
cargo run -p wakaru-cli -- \
  path/to/bundle.js path/to/529.bundle.js \
  --unpack -o /tmp/multi-out
```

Or scan a directory (skips `node_modules`, hidden paths, and non-bundle `.js` files):

```bash
cargo run -p wakaru-cli -- \
  path/to/dist/ \
  --unpack -o /tmp/scanned-out
```

<Info>
`unpack_files` stabilizes filenames across inputs and rewrites **unambiguous** numeric webpack IDs to final module paths. Duplicate numeric IDs across unrelated runtimes in the same scan are left untouched to avoid merging incompatible bundles.
</Info>

## Definition-of-done checklist

Before merging webpack unpack changes, run the pipeline matrix from `docs/testing.md`:

```bash
cargo test -p wakaru-core --test noop_pipeline
cargo test -p wakaru-core --test webpack4_unpack
cargo test -p wakaru-core --test webpack4_unpack_raw
cargo test -p wakaru-core --test bundle_unpack
cargo test -p wakaru-core --test esbuild_unpack
cargo test -p wakaru-core --test webpack5_chunk_unpack
cargo test -p wakaru-core --test multi_file_unpack
cargo fmt --check
cargo clippy -p wakaru-core --all-targets -- -D warnings
```

Inspect every snapshot diff. Accept only when output is semantically better, not merely different. Ensure `git status --short` shows no stale `.snap.new` files.

<AccordionGroup>
<Accordion title="Overwrite protection on repeat runs">

If the output directory already contains files, wakaru exits unless `--force` is passed. Use a fresh `/tmp/…` path or add `--force` when re-running locally.

</Accordion>

<Accordion title="webpack4 build fails on OpenSSL 3">

The webpack4 `package.json` build script sets `NODE_OPTIONS="--openssl-legacy-provider"` for webpack 4 on modern Node. Keep that flag when rebuilding locally.

</Accordion>

<Accordion title="Snapshot update workflow">

1. Run the failing test binary.
2. Open the `.snap.new` diff (`cargo insta review` or your diff tool).
3. Confirm the change is an improvement (e.g., cleaner imports, correct `unresolved_mark` behavior).
4. `cargo insta accept` and re-run the test binary to confirm green.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Bundle formats and unpacking" href="/bundle-formats-and-unpacking">
Detection order for `BundleFormat::Webpack4` and `Webpack5`, raw vs full unpack, and directory scan semantics.
</Card>
<Card title="Unpack bundles" href="/unpack-bundles">
Operational guide for `--unpack` modes, `--raw`, multi-file inputs, and `--force`.
</Card>
<Card title="Testing and snapshots" href="/testing-and-snapshots">
Insta workflow, required pipeline test binaries, and pre-commit verification matrix.
</Card>
<Card title="Debug regressions" href="/debugging-regressions">
Raw vs final webpack4 layers, rule trace bisection, and snapshot drift investigation.
</Card>
<Card title="Cross-module facts" href="/cross-module-facts">
Two-phase unpack barrier that enables cross-chunk import/export recovery.
</Card>
</CardGroup>
