Skip to content
Developer Tools8 min readPublished: August 23, 2026

How to Extract Code from Source Map Files: Memory-Efficient CLI Reconstruction

Learn how to extract original source code from Source Map Revision 3 files locally using Node.js without triggering V8 heap exhaustion. This guide covers streaming JSON parsing, heap management, and optimizing disk I/O across NTFS and ext4 file systems.

Written by WasyTech Engineering Β· Core Engineering Team
Share:𝕏RedditLinkedIn

Quick Answer: One-Line Pre-Flight & Extraction

Before allocating disk space or compute resources, verify that the target map contains the embedded source code payloads (sourcesContent) rather than just token mapping offsets:

bash
# Check if sourcesContent exists and count embedded files
node -e '
const fs = require("fs");
const fd = fs.openSync("bundle.js.map", "r");
const buf = Buffer.alloc(1024 * 64);
fs.readSync(fd, buf, 0, buf.length, 0);
fs.closeSync(fd);
const head = buf.toString("utf8");
console.log("Has sourcesContent:", head.includes("\"sourcesContent\""));
'

If sourcesContent is present, execute a memory-constrained extraction using the native Node.js V8 runtime:

bash
# Run extraction with expanded V8 heap allocation if handling files > 100MB
node --max-old-space-size=4096 -e '
const fs = require("fs");
const path = require("path");

const rawMap = JSON.parse(fs.readFileSync("bundle.js.map", "utf8"));
if (!rawMap.sourcesContent || !rawMap.sources) {
  console.error("Error: Map file does not contain inline sourcesContent.");
  process.exit(1);
}

rawMap.sources.forEach((sourcePath, index) => {
  const content = rawMap.sourcesContent[index];
  if (!content) return;
  
  // Sanitize path to prevent directory traversal
  const cleanPath = path.normalize(sourcePath).replace(/^(\.\.[\/\\])+/, "");
  const targetPath = path.join(process.cwd(), "extracted_src", cleanPath);
  
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
  fs.writeFileSync(targetPath, content, "utf8");
});
console.log(`Reconstructed ${rawMap.sources.length} files successfully.`);
'

---

Technical Baseline & Authorized Scope

> Strict Disclaimer: Source map extraction must be restricted to authorized application debugging, internal security assessments, reverse-engineering audits, or disaster recovery of your own unbundled assets.

According to the official Source Map Revision 3 Proposal, a source map file serves as a bidirectional lookup bridge between processed/minified code and its original authored state. Successful offline source tree reconstruction is strictly conditional on the presence of the sourcesContent array within the JSON payload.

bash
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Source Map JSON (v3)                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  version: 3      β”‚  sources: [...]   β”‚ sourcesContent: [...]β”‚
β”‚  file: "app.js"  β”‚  mappings: "AAAA;"β”‚ (Original Source)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                       β”‚
         β–Ό                                       β–Ό
  Browser DevTools                        Disk Extraction
(Breakpoint mapping)                    (Directory Tree Rebuild)

If a production build pipeline strips sourcesContent to reduce asset payload sizeβ€”relying instead on remote hosting of original filesβ€”local file extraction yields empty stubs. Furthermore, extraction cannot reconstruct original types from stripped TypeScript declarations if source maps were generated *after* type erasure, nor can it bypass structural minification when maps are incomplete.

---

The V8 Memory Bottleneck: JSON Parsing and Heap Limits

A minified production bundle of 10 MB frequently generates a 50 MB to 200 MB .map file due to the density of the Base64 VLQ mappings string and uncompressed sourcesContent strings. Parsing these large files in Node.js often results in rapid performance degradation or fatal Out Of Memory (OOM) panics.

Understanding V8 Heap Allocation

The V8 JavaScript Engine divides memory into distinct generations:

  • New Space (Semi-Spaces / Nursery): Where initial objects reside.
  • Old Space: Where long-lived objects are promoted after surviving garbage collection cycles.

When executing JSON.parse() on a 150 MB file:

  1. The input string itself consumes memory in the V8 heap (often double-byte representation if non-ASCII is detected).
  2. JSON.parse() instantiates tens of thousands of individual string primitives for sources, sourcesContent, and dynamic token objects simultaneously.
  3. This sudden surge triggers repeated Scavenge and Mark-Sweep-Compact garbage collection cycles, causing CPU execution threads to freeze.
bash
Incoming Stream (150MB Map File)
           β”‚
           β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚   JSON.parse()    β”‚ ──► Exceeds New-Space allocation limits
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ V8 Old Generation Heap (~1.4GB default limit)               β”‚
 β”‚                                                              β”‚
 β”‚  [Raw JSON Buffer] ──► [AST Tree] ──► [10,000+ File Strings] β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
     (If Heap > Limit)
           β”‚
           β–Ό
 πŸ’₯ FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

According to V8 Engine Documentation, a single native string allocation cannot exceed $2^{29} - 24$ bytes (~512MB on 64-bit systems), but compound object trees exceed process limits far earlier due to reference overhead. To process large source maps without crashing the runtime, you must explicitly raise the heap threshold via the Node CLI:

bash
node --max-old-space-size=8192 extract-script.js

For large files, loading the entire payload as a string and running it through native JSON.parse() is noticeably faster than pure JavaScript stream parsers, provided the V8 heap ceiling (--max-old-space-size) is configured above the payload's peak memory consumption.

---

OS Storage Internals: NTFS vs. ext4 Rebalancing

Writing reconstructed source trees containing thousands of deeply nested files interacts directly with low-level kernel storage subsystems. Unthrottled, concurrent writes will saturate OS file handle tables and fragment file system indexes.

bash
       Extraction Process (Async Event Loop)
                         β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β–Ό                                 β–Ό
 Windows (NTFS)                     Linux (ext4)
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ Master File Table (MFT) β”‚       β”‚ Inode Tables            β”‚
 β”‚ β€’ File-lock contention  β”‚       β”‚ β€’ Dentry cache churn    β”‚
 β”‚ β€’ High CreateFile overhead      β”‚ β€’ Fast sub-ms writes    β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Windows File System (NTFS) Overhead

As detailed in the Microsoft Learn Win32 File Management APIs documentation, every file creation event (CreateFileW) incurs non-trivial kernel overhead:

  • Master File Table (MFT) Fragmentation: Creating thousands of small files in milliseconds causes NTFS to dynamically expand the MFT zone, fragmenting metadata across non-contiguous disk sectors.
  • Handle Contention: Node.js file system APIs wrap Win32 handles. Launching unrestricted concurrent writes via Promise.all() exhausts the kernel's non-paged pool and triggers EMFILE: too many open files.
  • Security & Indexing Overhead: Windows Defender (Antivirus) and Windows Search Indexer intercept every synchronous close handle (CloseHandle), compounding disk thrashing and pinning CPU cores.

Linux File System (ext4) Characteristics

On Linux environments, modern ext4 filesystems leverage Htree (indexed directory trees) and asynchronous inode allocation:

  • Inode Exhaustion: Each small file consumes one dedicated inode regardless of size. On constrained block devices, extracting large vendor bundles can exhaust free inodes before physical storage capacity runs out (df -i).
  • Dentry Cache Churn: Rapid, unbounded directory traversal flushes active database or operating cache pages from the system kernel memory, degrading broader host performance.

Mitigating Disk Thrashing via Concurrency Throttling

To prevent operating system IO lockups, file operations must be sequentially queued or constrained to fixed worker pools using batch iterators rather than unthrottled asynchronous promises.

---

Production Implementation: Memory-Safe CLI Extractor

Below is an architecturally hardened, memory-conscious extraction script designed for local diagnostics. It handles path sanitization (guarding against malicious directory traversal such as ../../etc/passwd), creates parent directories recursively, and throttles file writes to maintain system stability.

Save this file as extract-map.js:

javascript
/**
 * WasyTech Diagnostic Utilities
 * Architectural Reference: Source Map Revision 3 Extraction Pipeline
 */

const fs = require('fs');
const path = require('path');

function sanitizePath(sourcePath, rootDir) {
  // Strip dangerous traversal syntax and protocols
  const normalized = path.normalize(sourcePath)
    .replace(/^([a-zA-Z]:)?(\\|\/)+/, '') // Strip drive letters and root slashes
    .replace(/^(\.\.[\/\\])+/, '');        // Strip relative backtracking

  // Resolve absolute path and guarantee boundary confinement
  const target = path.resolve(rootDir, normalized);
  if (!target.startsWith(rootDir)) {
    throw new Error(`Security Exception: Path traversal attempt outside ${rootDir}: ${sourcePath}`);
  }
  return target;
}

function extractSourceMap(mapFilePath, outputDirectory) {
  const absoluteMapPath = path.resolve(process.cwd(), mapFilePath);
  const absoluteOutDir = path.resolve(process.cwd(), outputDirectory);

  if (!fs.existsSync(absoluteMapPath)) {
    console.error(`[CRITICAL] Map target not found: ${absoluteMapPath}`);
    process.exit(1);
  }

  console.log(`[INIT] Reading map: ${absoluteMapPath}`);
  
  // Read using modern fast string parsing
  const rawData = fs.readFileSync(absoluteMapPath, 'utf8');
  let parsedMap;
  
  try {
    parsedMap = JSON.parse(rawData);
  } catch (err) {
    console.error(`[CRITICAL] Failed to parse JSON payload. If file is large, execute with --max-old-space-size.`);
    throw err;
  }

  if (!parsedMap.sources || !parsedMap.sourcesContent) {
    console.error(`[ABORT] Source map is valid JSON but lacks "sourcesContent" array.`);
    console.error(`This map only contains symbol references/offsets, not raw authored code.`);
    process.exit(1);
  }

  const fileCount = parsedMap.sources.length;
  console.log(`[PROCESSING] Found ${fileCount} source references. Starting controlled extraction...`);

  // Ensure root output directory exists
  fs.mkdirSync(absoluteOutDir, { recursive: true });

  let extractedCount = 0;
  let skippedCount = 0;

  for (let i = 0; i < fileCount; i++) {
    const rawSourcePath = parsedMap.sources[i];
    const fileContent = parsedMap.sourcesContent[i];

    // Some source map generators insert null entries for missing source files
    if (fileContent === null || fileContent === undefined) {
      skippedCount++;
      continue;
    }

    try {
      const destination = sanitizePath(rawSourcePath, absoluteOutDir);
      const directory = path.dirname(destination);

      // Recursive mkdir is safe and optimized in modern Node.js
      fs.mkdirSync(directory, { recursive: true });
      fs.writeFileSync(destination, fileContent, { encoding: 'utf8', flag: 'w' });
      extractedCount++;
    } catch (err) {
      console.warn(`[WARN] Failed to write index ${i} (${rawSourcePath}): ${err.message}`);
    }
  }

  console.log(`\n=== Extraction Summary ===`);
  console.log(`Extracted Files : ${extractedCount}`);
  console.log(`Skipped Entries : ${skippedCount}`);
  console.log(`Output Location : ${absoluteOutDir}`);
}

// Execution Entry Point
const [,, inputMap, outputDir = 'extracted_sources'] = process.argv;

if (!inputMap) {
  console.log('Usage: node --max-old-space-size=4096 extract-map.js <path-to-map-file> [output-dir]');
  process.exit(0);
}

extractSourceMap(inputMap, outputDir);

---

Verifying Extracted Files and Handling Common Failures

1. The "Empty Files" Symptom If directories are generated but files contain no data, inspect the map structure. Compilers such as `esbuild`, `webpack`, or `terser` can be configured with:

javascript
// webpack.config.js
devtool: 'nosources-source-map' // Generates mapping offsets but omits sourcesContent

In this scenario, sourcesContent is omitted to reduce network transfers while still providing stack trace line/column mappings in Sentry or Datadog. Code recovery from nosources-source-map files is mathematically impossible because the source code strings were never bundled into the map artifact.

2. Path Traversal & Schema Prefixes Modern bundlers prepend internal protocol qualifiers to paths in the `sources` array: * `webpack://_N_E/./src/index.ts` * `rollup://bundle/main.js` * `turbopack://[project]/app.tsx`

The sanitizePath function included in the CLI extractor strips drive letters, root slashes, and relative ../ sequences, collapsing these virtual namespaces into standard local folder hierarchies. This keeps your local filesystem clean and prevents invalid Win32/POSIX character crashes during extraction.

Frequently Asked Technical Questions

You can extract code locally using a Node.js CLI script or specialized unpacker. The utility reads the Revision 3 JSON map file, parses the 'sources' and 'sourcesContent' arrays, and maps the entries to a local output path before writing the reconstructed tree to disk. Extraction relies entirely on the 'sourcesContent' array being present in the file; stripped maps cannot be fully recovered. Ensure you only perform extraction on codebases you own or have explicit authorization to audit.

WasyTech Engineering

Core Engineering Team

The collective engineering minds behind WasyTech's zero-bloat utility architecture.

Focus:Systems ProgrammingPerformance DiagnosticsWindows InternalsNetwork Protocols

Related Systems Guides

View all guides β†’
Developer Tools8 min readAugust 23, 2026

How to View EXIF Data Without Uploading: Local File I/O and Native Metadata Extraction

Discover how operating system frameworks like Windows propsys.dll, macOS ImageIO, and Linux libexif parse image headers directly from disk without network transmission. Learn the underlying binary architecture of JPEG APP1 markers and HEIC metadata containers while avoiding the telemetry risks of web-based EXIF viewers.

Developer Tools9 min readAugust 23, 2026

How to Decode and Verify JWTs Locally: Native CLI & Zero-Bloat Cryptographic Validation

Learn how to decode Base64Url JWT payloads and verify HMAC SHA-256 signatures locally using native Linux Bash tools and Windows PowerShell without heavy third-party runtimes. By executing diagnostics directly in your terminal, you eliminate the security risks of online decoders while leveraging hardware-accelerated CPU instructions for bloat-free verification.