wakaru 1.12.0

Public Rust API for the Wakaru JavaScript decompiler
Documentation

Wakaru: a JavaScript decompiler exposed as a compiler-like service.

The stable crate root offers two end-to-end operations:

  • [decompile] — rewrite one minified/transpiled source into readable, modern JavaScript.
  • [unpack] — split one or more bundle/chunk inputs into modules and decompile each; [UnpackJob] is the incremental intake form of the same operation for directory walkers.

Optional workflows live in cohesive namespaces: [bun] (single-file executable extraction), [debug] (normalization, rule tracing, rule metadata), [sourcemap] (embedded-source extraction), and [vue] (experimental standalone SFC recovery).

The API is deliberately not an SWC transformation toolkit: it never exposes SWC AST types, individual rewrite visitors, detector objects, prepared ASTs, or cross-module fact structures. Partial recovery is represented structurally ([ModuleStatus], [Diagnostic]) instead of being hidden in warning strings.

Design decisions, compatibility boundaries, and the internal processing invariants behind this surface are recorded in docs/public-api.md.

Publishing model

The façade is published as wakaru. Cargo cannot package a façade with an unpublished path dependency, so wakaru-core is also published as a lockstep, exact-version implementation dependency. It remains explicitly unsupported as an integration surface and may change whenever the façade version changes.

Examples

Single-file decompile:

use wakaru::{decompile, DecompileOptions, Source};

# fn main() -> wakaru::Result<()> {
let minified = String::from("var a=!0;");
let output = decompile(Source::new("input.js", minified), DecompileOptions::default())?;

println!("{}", output.module.code);
for diagnostic in output.diagnostics {
    eprintln!("{}", diagnostic.message);
}
# Ok(())
# }

Bundle/chunk set:

use wakaru::{
    unpack, DceMode, ModuleMode, RewriteLevel, RewriteOptions, Source, UnmatchedInput,
    UnpackMode, UnpackOptions,
};

# fn main() -> wakaru::Result<()> {
# let (entry, chunk) = (String::new(), String::new());
let output = unpack(
    vec![Source::new("entry.js", entry), Source::new("chunk.js", chunk)],
    UnpackOptions::default()
        .with_modules(ModuleMode::Decompile(
            RewriteOptions::default()
                .with_level(RewriteLevel::Standard)
                .with_dce(DceMode::TransformOnly),
        ))
        .with_mode(UnpackMode::Auto)
        .with_unmatched(UnmatchedInput::Process)
        .with_diagnostics(true),
)?;

for module in output.modules {
    println!("{}: {} bytes", module.filename, module.code.len());
}
# Ok(())
# }

Directory-style detected-only processing with bounded intake memory:

use wakaru::{InputAction, Source, UnmatchedInput, UnpackJob, UnpackOptions};

# fn main() -> anyhow::Result<()> {
# let javascript_candidate_paths: Vec<std::path::PathBuf> = Vec::new();
let mut job = UnpackJob::new(UnpackOptions::default().with_unmatched(UnmatchedInput::Skip))?;

for path in javascript_candidate_paths {
    let code = std::fs::read_to_string(&path)?;
    job.push(Source::new(path.to_string_lossy(), code))?;
}

let output = job.finish()?;
let skipped = output
    .inputs
    .iter()
    .filter(|input| input.action == InputAction::Skipped)
    .count();
# let _ = skipped;
# Ok(())
# }