Community JavaScript Snippet
Resolving a Production Stack Trace Against a Source Map
When a minified Sentry stack only points at `bundle.js:1:140183`, this is the zero-dep VLQ decoder I drop in to map every frame back to a real source line.
Resolving a Production Stack Trace Against a Source Map
When a minified Sentry stack only points at `bundle.js:1:140183`, this is the zero-dep VLQ decoder I drop in to map every frame back to a real source line.
By @kwamehenderson
January 20, 2026
·
Updated May 20, 2026
1,146 views
17
4.3 (11)
Source maps are a JSON file with a mappings field: a string of base64 VLQ groups separated by commas (one segment) and semicolons (one generated line). Each segment is up to five signed integers (genCol delta, sourceIdx delta, sourceLine delta, sourceCol delta, nameIdx delta). The decoder unpacks the bits five at a time, peels the sign bit, and accumulates deltas across segments. I keep this around because every "add a sourcemap library" PR I have reviewed pulled in source-map (300 KB) for what is genuinely a 30-line job. The output is one array of segments per generated line, ready for binary search.
Given decoded segments and a (line, column) from a stack trace, this picks the largest segment whose generatedColumn is at-or-before the target column. That is the standard sourcemap resolver behavior, because mappings are sparse (compilers only emit a segment at meaningful positions). Binary search is overkill for a 50-segment line but starts to matter once your bundle is 15 MB. The returned record gives the original source path, line, and column, plus an optional name if the compiler recorded one.
This is the part that actually sees production: parse a Node Error.stack, identify which frames live in your bundle, and rewrite just those. The regex tolerates both at fn (file:L:C) and at file:L:C forms; everything else (anonymous lambdas, node internals) passes through untouched. In our CI we pipe Sentry's raw stack through this in a Lambda so engineers see src/handler.ts:42:5 instead of bundle.js:1:140183. The whole script is under 100 lines and has zero npm dependencies.
