A tangled glowing cable being unwound into neat strands on a workbench, with a large red maine coon cat watching nearby.

Wakaru: Turning Minified JavaScript Back Into Code You Can Actually Read

The Orange Cat
The Orange Cat

Open the network tab on almost any modern web app and you will find a single main.[hash].js file: thousands of lines of single-letter variable names, !0 where true used to be, and helper functions with names like _classCallCheck. That file is the output of a long assembly line. A bundler collapsed dozens of modules into one and wrapped them in runtime glue. A transpiler downgraded modern syntax and injected polyfills. A minifier erased every meaningful name. Wakaru is the tool that runs that assembly line in reverse.

Wakaru is a JavaScript decompiler and bundle splitter. Feed it a minified, transpiled, bundled file and it gives you back readable, modern JavaScript: split into its original modules, with classes, JSX, and async/await restored, and with sensible names recovered where possible. It is useful when you have lost the source for a shipped bundle, when you are security-auditing a third-party frontend, when you are chasing a production-only bug and only have the minified build, or when you simply want to learn how a real application is structured.

A quick but important note before anything else: the tool ships on npm as @wakaru/cli, not as the bare wakaru package. The unscoped name is an empty placeholder. Always install @wakaru/cli.

What Makes It Tick

Wakaru was rewritten from TypeScript into Rust for its 1.x line, built on top of SWC (the Rust-based JavaScript and TypeScript compiler). That choice pays off in two ways: raw speed, and parallelism via rayon so that large bundles are decompiled module-by-module across cores. The headline capabilities are:

  • Bundle unpacking for webpack 4 and 5, esbuild, Bun, Browserify, and SystemJS. Wakaru detects the bundle format, then extracts each module back into its own file.
  • Decompilation through roughly sixty transformation rules, each implemented as an SWC AST visitor and applied in a fixed order across six stages. These unwrap transpiler helpers, rebuild ES2015+ syntax, and restore higher-level constructs.
  • JSX, class, and async/await restoration, turning React.createElement calls back into JSX, prototype-assignment patterns back into class declarations, and state-machine generators back into async/await.
  • Source-map name recovery, which uses an attached map to vote on the best original name for each binding, even when the map's names array is empty.
  • Rewrite levels (minimal, standard, aggressive) that dial the tradeoff between behavioral fidelity and readability.

There is also a zero-install browser playground at wakaru.vercel.app/playground where you can paste a snippet and watch it transform live. It is the fastest way to get a feel for the tool before committing to an install.

Getting It Onto Your Machine

Wakaru is distributed as @wakaru/cli. Install it globally, or run it on demand with npx:

# Global install (npm)
npm install -g @wakaru/cli

# Global install (yarn)
yarn global add @wakaru/cli

# Or run without installing
npx @wakaru/cli input.js -o output.js

Pre-built binaries are also published on the project's GitHub Releases page, so if you would rather not involve Node at all, you can grab a standalone binary for your platform. A quick legal reminder that the project itself makes: using Wakaru against targets without prior mutual consent is illegal, and you are responsible for complying with applicable law.

Reading a Single File Again

The simplest job is taking one minified file and making it legible. Point Wakaru at the file and give it an output path:

# Decompile a single file to disk
wakaru input.js -o output.js

# No -o prints to stdout, so you can pipe it
cat input.js | wakaru > output.js

Under the hood this runs the decompile pipeline: parse the source, resolve scopes, apply the rule set, run a final fixer pass, and print. To see what that actually buys you, imagine a minifier-flattened snippet like this:

function f(a) {
  return a["length"] > 0 ? !0 : !1;
}
var g = function () {
  return Math.pow(2, 10);
};

After a standard decompile pass, the boolean tricks, bracket access, and Math.pow are all rewritten into their readable equivalents:

function f(a) {
  return a.length > 0 ? true : false;
}
const g = () => 2 ** 10;

Those are small examples, but they stand in for the whole first stages of the pipeline: undoing !0/!1 booleans, flipping reversed comparisons, converting bracket notation to dot notation, normalizing numeric literals, and rewriting var to let/const. By the time those stages finish, the deeper structural rules have something they can actually pattern-match against.

Splitting a Bundle Into Modules

A single file is one thing, but most real targets are bundles. The --unpack flag tells Wakaru to detect the bundle format, extract each module as its own file, and then decompile every one of them:

# Unpack a bundle into modules, then decompile each
wakaru bundle.js --unpack -o out/

# Just split the modules, skip the readability transforms
wakaru bundle.js --unpack --raw -o out/

# Pass an entry file and a lazy chunk together
wakaru entry.js chunk.js --unpack -o out/

When unpacking, decompilation runs as a two-phase parallel pipeline. The first phase parses every module and collects cross-module facts about which things are imported and exported. The second phase re-runs the rules with a late pass that uses those facts, which is what lets Wakaru perform rewrites that span module boundaries, like consolidating re-exports or turning a namespace access such as ns.foo into a proper import { foo }. The --raw flag is handy when you only want the module split and intend to read the code as-is, or feed it into another tool.

You can also hand Wakaru a whole build directory. It recursively scans for .js, .mjs, and .cjs files, skips hidden files and node_modules, and processes only the files it actually detects as bundles or chunks:

# Scan a dist directory; only detected bundles/chunks are processed
wakaru dist/ --unpack -o out/

One thing worth internalizing: a directory scan is detect-only. Files that are not recognized as bundles are silently skipped rather than copied, so the output is not a one-to-one mirror of the folder. By default Wakaru also refuses to overwrite existing files; pass --force when you genuinely want to replace them.

Recovering Real Names With Source Maps

Minification destroys names, but a source map remembers them. If you have the map for a bundle, hand it to Wakaru and the output improves dramatically:

# Use a source map for better names and import dedup
wakaru input.js --source-map input.js.map -o output.js

The renaming runs as an extra pipeline that fires after the rules. The ordering matters: the rules detect patterns by their minified shapes, so renaming earlier would hide those shapes and break detection. Once the structure is recovered, Wakaru looks up each generated identifier's original position through the map, reads the original name from the embedded sources, and votes on the best name per binding when there are conflicts. Crucially, this works even when the map's names array is empty, which is the common case for esbuild output. Note that source maps currently apply to a single input file only.

If all you want is the original source that a map already carries, you can extract it directly without decompiling anything:

# Dump the sourcesContent embedded in a map to disk
wakaru extract input.js.map -o src/

Dialing Fidelity Against Readability

Not every job has the same priorities. Auditing a security-sensitive bundle demands that the output behave identically to the input. Reading a competitor's UI to understand it just wants the cleanest possible code. Wakaru exposes that tradeoff as an explicit --level flag:

# Only the safest, most obvious transforms
wakaru input.js --level minimal

# Balanced default
wakaru input.js --level standard

# Maximum readability, may alter edge-case behavior
wakaru input.js --level aggressive

Use minimal when you are diffing or auditing and behavioral fidelity matters most. Use the default standard for everyday work. Reach for aggressive when you just want to read the code and are willing to accept that stronger intent-recovery heuristics might change edge-case behavior. The levels do not add or remove whole rules; instead each rule internally gates its riskier subpatterns by level, and the project documents a "semantic contract" listing which assumptions each level is allowed to rely on.

This is where Wakaru's heaviest restorations live. At standard and above you will see React.createElement and _jsx calls turned back into JSX, prototype-assignment chains rebuilt into class declarations, regenerator and state-machine generators collapsed into clean async/await, and the usual modern syntax such as optional chaining, nullish coalescing, template literals, and for...of reconstructed from their downleveled forms. It is best-effort, not a perfect inversion. Minification permanently destroys comments, types, and some intent, so think of Wakaru as recovering the shape and intent of the original code rather than its exact bytes.

Where Wakaru Fits

Wakaru is purpose-built for legitimately built but minified modern frontends: code that went through Terser, Babel, SWC, TypeScript, and a mainstream bundler. Its strengths are Rust-backed speed, broad bundler coverage including esbuild and Bun, high-quality JSX, class, and async restoration, the source-map name voting, and that explicit fidelity dial.

It is deliberately not a deobfuscator for hostile code. If you are facing control-flow flattening or string-array obfuscation from something like obfuscator.io, a tool such as webcrack is the more targeted choice, and tools like debundle only split bundles without decompiling. Wakaru's niche is making real, shipped, modern JavaScript readable again, in a single command, fast, with a no-install playground to try it first. The next time you are staring at an inscrutable main.[hash].js, point Wakaru at it and watch the assembly line run backward.