Wakaru Documentation

repository·main·Indexed 21 days ago

https://github.com/pionxzh/wakaru

Wakaru is a JavaScript decompiler written in Rust designed to unpack bundles and reverse the effects of minifiers and transpilers (such as Babel, SWC, and esbuild). It restores readable, modern JavaScript code from production bundles, supporting various formats including webpack, esbuild, Bun, and Metro. The tool provides different rewrite levels (minimal, standard, aggressive) to balance semantic safety and readability, and includes specialized support for Vue SFC recovery and Bun standalone executables.

Tokens
73.9K
Snippets
182
Records
318
Agent score
72%

What's inside Wakaru

  1. Understand the Test262 Round-Trip Summary report

    main

    The Test262 Round-Trip Summary provides a report of how a specific pipeline (e.g., terser-light) handles the Test262 suite. It details the configuration used for the run, the total number of tests processed, and the specific reasons why certain tests were skipped, rejected, or unsupported.

    Report Sections

    • Options: The configuration parameters used for the test run, including the pipeline, transform engine, nodeMajor version, and specific paths being tested.
    • Totals: A summary table showing the distribution of tests across states: Discovered, Runnable, Skipped, Unsupported, Rejected, Passed, and Failed.
    • Reasons: A breakdown of why tests were categorized as rejected or unsupported. This is useful for identifying specific language features or transformations that are causing issues (e.g., transform-reject or swc-parse-async-ident).
    • Failures: Lists any correctness failures found by Wakaru. If no failures are present, it will state "No Wakaru correctness failures.".
  2. Understanding Webpack Async Context Recovery limitations

    main

    Wakaru currently does not automatically recover Webpack context modules (dynamic import() implementations over a static request map) into standard import() statements. This is because these modules rely on complex cross-module and chunk-loading semantics that cannot be safely simplified to local expression rewrites without losing metadata or runtime behavior.

    Key characteristics of the pattern that prevent recovery:

    • Async Chunk Boundaries: Uses require.e(chunkId) to manage chunk loading.
    • Mode-based Loading: Uses require.t(moduleId, mode) where mode & 1 triggers a module load after the chunk resolves.
    • Context Map Lookups: Resolves request strings through a map (e.g., request -> [moduleId, chunkId]).
    • Runtime API Properties: The generated context function exposes specific properties like .keys() and .id.
    • Observable Error Behavior: Returns a rejected promise with a MODULE_NOT_FOUND error code if a lookup fails.
  3. Understand Rewrite Assumptions in Wakaru

    main

    Wakaru uses RewriteLevel to control how aggressively it recovers original source code. However, RewriteLevel alone does not explain the safety of a transformation. Transformations often rely on specific properties of the input code that cannot be proven from the AST alone. These properties are called Assumptions.

    Rule authors use a shared vocabulary of named assumptions to indicate what they are assuming about the code. Users can use these names to understand what a specific RewriteLevel (like standard or aggressive) is actually promising regarding code safety and behavior preservation.

  4. Understand the status of Unified Helper Detection

    main

    The proposal for a 'Unified helper detection' engine (which aimed to replace hand-written Rust AST matchers with a JavaScript-based pattern engine) has been CLOSED and mostly rejected.

    While Phase 1 (consolidating inline-vs-declaration detection) was successfully implemented and resulted in a ~260-line reduction in code, the subsequent phases involving a pattern-matching engine were reverted. The investigation found that the number of helpers suitable for a shared matcher (~10–14) was too small to justify the engine's complexity, and the engine actually increased the total lines of code (LOC).

    Do not attempt to implement or use the pattern-matching engine or the corpus matcher, as they have been removed from the codebase. If you need to see the historical implementation, you must reconstruct it from the git history.

  5. Debug Webpack4 snapshot regressions

    main

    Webpack4 uses two snapshot layers. When a snapshot changes unexpectedly, compare them to locate the failure point:

    1. webpack4_unpack__*.snap: The final decompiled output.
    2. webpack4_unpack_raw__*.snap: The raw module output after extraction and bundler-coupled normalization, but before the decompile pipeline.

    Diagnostic Logic:

    • If the raw snapshot is unchanged but the final snapshot moved: The issue is in the decompile pipeline.
    • If the raw output changed: Inspect the unpacker or bundler-coupled normalization first.
  6. Understand Rule Safety Metadata

    main

    Rules in Wakaru carry internal metadata describing their safety profile. This is used by the engine to determine the risk of a rewrite and is distinct from the user-facing RewriteLevel:

    • safe: The rewrite is guaranteed to be semantics-preserving.
    • heuristic: The rule uses high-confidence pattern matching.
    • aggressive: The rule may change semantics to achieve a specific output shape.
  7. Understand the Rule Dependency Model

    main

    Wakaru uses a rule-based pipeline for rewrites and decompilation. The system's execution order and dependencies are managed by a central registry in crates/core/src/rules/pipeline.rs.

    There are two layers of dependency:

    1. Registry-enforced dependencies: The registry owns the full rule list, execution order, stage membership, and enforced ordering edges via RuleDescriptor::requires. This is the source of truth for what executes and in what order.
    2. Rule-level dependencies: These are documented rationale for why certain orderings exist (e.g., safety, fragile orderings, or experiment results).

    If you encounter a regression, refer to the Debugging guide to trace which rule caused it.

  8. How UnDestructuring and UnSlicedToArray work

    main

    Wakaru uses strict structural matching to restore destructuring patterns from transpiler helpers.

    UnDestructuring

    Accepts a mangled arrayLikeToArray declaration only if the function body proves the complete helper contract:

    1. A canonical null/length guard.
    2. An unresolved Array(length) allocation.
    3. A bounded element-for-element copy loop.
    4. Returning that exact allocation.

    Near-matches with different guards, source indices, output bindings, or shadowed Array declarations are ignored to prevent incorrect transformations.

    UnSlicedToArray

    Restores callback-local destructuring when a proven helper is applied directly to one callback parameter. It supports:

    • Unconditional leading declarations: const value = sliced(entry, 2)[1]
    • Direct equality comparisons of the indexed result.

    Requirements for restoration:

    • The parameter must have no other uses.
    • The limit and index must be bounded literals.
    • The recovered array pattern must retain trailing elisions to consume the exact number of iterator values requested by the helper.

    Exclusions: Conditional/deferred access, arguments, direct eval, with, and minimal rewrite mode will preserve the lowered form.

  9. Understand the Wakaru rule pipeline and dependency chains

    main

    Wakaru's transformation pipeline is composed of specialized rules grouped by functional areas (Syntax Normalization, Transpiler Helper Unwrapping, Structural Restoration, etc.). These rules are highly interdependent; many rules require specific 'shapes' (e.g., flat statement lists, dot notation, or specific identifier patterns) produced by preceding rules.

    Key Pipeline Concepts:

    • Dependency Chains: Rules often form 'hard chains' where one rule's output is the required input for the next (e.g., ArgRest $\rightarrow$ UnRestArrayCopy).
    • Level Gating: Rules are gated by execution levels (minimal, standard, standard+, aggressive). Higher levels assume more properties about the code (like pure_getters or stable_builtins) to perform more transformative optimizations.
    • Safety Constraints: Many rules are designed to be conservative. For example, UnArgumentSpread skips certain .apply() calls to avoid breaking semantics if the receiver isn't a plain imported function.
  10. Understand the Helper Detection Architecture

    main

    Wakaru uses a three-layer architecture to detect and recover transpiler helpers (like Babel or TypeScript helpers) to simplify code during transformation. This structure avoids complex AST pattern DSLs while maintaining high precision.

    1. Binding-aware matching (MatchContext): Used within body-shape matchers to ensure multiple identifiers refer to the same binding. This prevents false positives caused by shadowing or swapped operands.
    2. Helper lifecycle utilities (helper_matcher.rs): Provides low-level, scope-sensitive primitives for tracking, rewriting, or removing helper declarations once they are identified.
    3. Rule-local matching: Each specific helper rule (e.g., for esbuild, webpack, or Closure Compiler) owns its own domain-specific logic for recognizing the "shape" of a helper's implementation.
  11. Handling transpiler version drift

    main

    To account for changes in how transpilers (like Babel) implement helpers across different versions, Wakaru uses relaxed matching. Instead of checking for exact AST equality, it verifies the essential semantic structure.

    For example, when matching interopRequireDefault, the system looks for the presence of __esModule and default in the correct structural positions. It tolerates variations such as:

    • Different conditional forms (e.g., ternary vs if/else).
    • Different property access styles (e.g., .default vs ["default"]).
    • Extra Object.defineProperty calls for non-configurable exports.
    • Additional null checks.

    If a future transpiler version fundamentally changes the behavior of a helper, it is treated as a new helper requiring a new matcher.

  12. Understand why helper detection is not a generic pattern matching problem

    main

    A common misconception is that wakaru's helper detection code is unnecessarily large due to poor abstraction. In reality, the detection logic is large because the problem is irreducibly semantic.

    Most detection in wakaru falls into three categories that generic pattern matchers (like ast-grep) cannot handle effectively:

    1. Marker / Signal Accumulation: Detecting if certain identifiers or method calls exist anywhere within a body (e.g., slicedToArray, objectSpread). These are variance-tolerant and do not follow a fixed structural skeleton.
    2. State Machines: Complex recognition that requires a stateful traversal of the AST structure, such as un_regenerator or un_async_await.
    3. Recursive/Compositional Helpers: Helpers that inline other sub-helpers, creating a combinatorial number of possible body shapes that cannot be captured by a single fixed pattern.

    Only a small minority (~10-14 out of ~209 functions) are "fixed-shape" helpers that could be expressed via a pattern engine. Attempting to unify these into a single engine increases maintenance overhead without significantly reducing the total codebase size.