How SourceMaps Work: Decoding VLQ and Babel’s Generation Process

This article explains the purpose and format of JavaScript SourceMaps, details the VLQ‑based encoding of the mappings field, and walks through how tools like webpack and Babel generate SourceMaps to map transformed code back to the original source for effective debugging.

ELab Team
ELab Team
ELab Team
How SourceMaps Work: Decoding VLQ and Babel’s Generation Process

SourceMap Usage

After bundling, the generated code no longer matches the original source, making debugging difficult; SourceMap files map the transformed code back to the original source, allowing browsers to locate errors in the original files when the .map file is deployed.

SourceMap Format

A typical SourceMap generated by webpack contains fields such as "version", "file", "mappings", "sources", "sourcesContent", "names", and "sourceRoot". The "mappings" field encodes position information using a compact VLQ‑base64 representation.

Mappings Field Definition

The "mappings" string is divided into lines separated by semicolons; each line contains comma‑separated segments. A segment consists of 1‑5 variable‑length fields: generated column, optional source index, original line, original column, and optional name index. All numbers are stored as relative values and encoded with VLQ‑base64.

VLQ Principle

VLQ (Variable‑length quantity) encodes an integer as a series of 6‑bit groups. Each group uses the most‑significant bit as a continuation flag and the remaining five bits for data. The integer is first transformed to a signed form, then split into 5‑bit chunks, each prefixed with the continuation bit, and finally mapped to a base64 character.

Encoding Example (value 29 → “6B”)

Decimal 29 → binary 11101. The first character encodes the lower four bits 1101 with a continuation flag, yielding 6. The second character encodes the remaining bits 00001 without continuation, yielding B. The result is 6B.

VLQ Encoding Implementation (Node.js)

const base64 = require("./base64");
const VLQ_BASE_SHIFT = 5;
const VLQ_BASE = 1 << VLQ_BASE_SHIFT;
const VLQ_BASE_MASK = VLQ_BASE - 1;
const VLQ_CONTINUATION_BIT = VLQ_BASE;

function toVLQSigned(value) {
  return value < 0 ? (-value << 1) + 1 : (value << 1) + 0;
}

function base64VLQ_encode(value) {
  let encoded = "";
  let vlq = toVLQSigned(value);
  do {
    let digit = vlq & VLQ_BASE_MASK;
    vlq >>>= VLQ_BASE_SHIFT;
    if (vlq > 0) digit |= VLQ_CONTINUATION_BIT;
    encoded += base64.encode(digit);
  } while (vlq > 0);
  return encoded;
}
exports.encode = base64VLQ_encode;

VLQ Decoding Implementation

export function decode(string) {
  let result = [];
  let shift = 0;
  let value = 0;
  for (let i = 0; i < string.length; i++) {
    const integer = char_to_integer[string[i]];
    if (integer === undefined) throw new Error('Invalid character (' + string[i] + ')');
    const hasContinuation = integer & 100000;
    const digit = integer & 11111;
    value += digit << shift;
    if (hasContinuation) {
      shift += 5;
    } else {
      const negative = value & 1;
      value >>>= 1;
      result.push(negative ? -value : value);
      value = shift = 0;
    }
  }
  return result;
}

Overall Conversion Process Example

The original src/index.js file is transformed by webpack into dist/main-145900df.js. The accompanying .map file contains the encoded "mappings" string that links generated columns back to the original source lines and columns.

How Babel Generates SourceMaps

Babel’s pipeline consists of three stages: parse (producing an AST with location data), transform (applying plugins while preserving loc), and generate (traversing the AST to emit code). During generation, methods such as word, space, and token call append, which updates a Buffer that records the generated line/column and forwards the mapping to a SourceMap object.

// Simplified excerpt from Babel’s generator
export default function generate(ast, opts, code) {
  const gen = new Generator(ast, opts, code);
  return gen.generate();
}
class Generator extends Printer {
  generate() {
    return super.generate(this.ast);
  }
}

The Buffer tracks line breaks and column offsets, calling SourceMap.mark for each segment. The SourceMap then stores the raw mappings and encodes them using the VLQ‑base64 scheme described earlier.

Storage Optimizations in SourceMap

Relative positions keep numbers small.

VLQ‑base64 encoding reduces the size of each numeric value.

Line information is represented by semicolons, eliminating redundant data.

VLQ encoding illustration
VLQ encoding illustration
JavaScriptBabelWebpackSourceMapVLQ
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.