Skip to content

CONCEPT Cited by 1 source

Source-map composition

Definition

A source map is a file that maps locations in compiled output (JS, WASM, minified bundle) back to locations in the source the developer wrote. Browser DevTools reverse the map so a breakpoint on a TypeScript line pauses at the equivalent point in the shipped JS. Conventionally .map files.

Source-map composition is the discipline, required by any multi-stage compilation pipeline, of combining N single-stage maps into one end-to-end map so that breakpoints set in the first-stage source resolve correctly in the final output.

Why it matters

In a single-compile pipeline (TS → JS via tsc/esbuild) the compiler emits the source map for free. In a two-stage pipeline — which is the shape of any transpiler-driven language migration — there are two inputs and two maps, and the browser can only consume one map for the final bundle:

file.sk  ──[ transpiler ]──→  file.ts  ──[ esbuild ]──→  bundle.js
                 ↓                              ↓
             file.map                      ts-to-js.map
                 └──────── compose ────────────┘
                        bundle.sk.map

The composed map is built by iterating every entry E in the outer (ts-to-js) map: look up the TS file and TS code location from E, open the inner (.sk → .ts) map for that TS file, read off the originating Skew file and Skew code location, emit (JS location from E, Skew location) into the composed map.

Why it's load-bearing

Without composition, breakpoints set in the first-stage source (the language developers actually write, especially during a gradual migration) either don't fire, fire at the wrong location, or report a different symbol than the one the developer was debugging. This is a monitoring-paradox class of invisible regression in developer tooling — the symptom is "my debugger is lying," which engineers often attribute to their own mistake before suspecting the build.

At codebase scale this compounds: multiple generations of developers land code with subtle debugger-visibility bugs that no one files a JIRA for, because the fallback (console.log) does work. Migration velocity erodes invisibly.

Where it appears

  • Transpiler-driven language migrations — Figma's Skew → TypeScript → JS (canonical). Any migration of the shape A → B → bundled-output must compose.
  • Cross-language WASM builds — Rust → WASM with debug info, then bundler processing.
  • Template/DSL preprocessors that emit a high-level language that's then compiled (e.g. Vue SFC → JS/TS → bundle).
  • Minification on top of an already-generated bundle — less common today because bundlers do both in one pass, but any legacy pipeline that runs a separate uglify step needs composition.

Mechanics (summary)

A source map is a list of mappings (generated location) → (source file, source location, optional symbol name). Composition is:

for each mapping E in outer_map:
    gen_loc = E.generated
    ts_loc  = E.source
    ts_file = E.source_file
    inner = load_map(ts_file + ".map")
    sk_loc  = lookup(inner, ts_loc)       # reverse inner map
    sk_file = inner.source_file_at(ts_loc)
    emit (gen_loc → sk_file, sk_loc) into composed_map

Corner cases: one-to-many (a TS range emitted by several JS fragments), many-to-one (an optimiser collapsed two TS statements into one JS expression), and inlined helpers with no first-stage origin. Tooling is thin — most pipelines roll the composition logic by hand inside the build, as Figma did.

Seen in

  • sources/2024-05-03-figma-typescript-migration — Figma rebuilt the source-map composition explicitly as part of the Skew→TS migration so developers could set breakpoints in Skew or TS and have the browser resolve them in the final JS bundle. Named in the blog as a three-step pipeline: (1) esbuild emits ts-to-js.map, (2) the Skew→TS transpiler emits one .sk → .ts map per file (borrowed logic from the Skew→JS backend's map generator), (3) the build composes them per-entry into the shipped map.
  • patterns/gradual-transpiler-migration — source-map composition is the hidden developer-experience requirement this pattern places on the migration team.
  • systems/esbuild — provides the second-stage .ts → .js map; Evan Wallace (Skew's author, later Figma's CTO) authored esbuild partly on this project's learnings.
Last updated · 200 distilled / 1,178 read