{"id":51094838,"url":"https://github.com/tracewayapp/sourcemap-demo","last_synced_at":"2026-06-24T05:31:06.206Z","repository":{"id":362974227,"uuid":"1261491832","full_name":"tracewayapp/sourcemap-demo","owner":"tracewayapp","description":"Companion demo for the Traceway deep dive into JS/TS source maps.","archived":false,"fork":false,"pushed_at":"2026-06-06T19:04:02.000Z","size":24,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T21:06:08.747Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tracewayapp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T19:00:26.000Z","updated_at":"2026-06-06T19:06:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tracewayapp/sourcemap-demo","commit_stats":null,"previous_names":["tracewayapp/sourcemap-demo"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tracewayapp/sourcemap-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tracewayapp%2Fsourcemap-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tracewayapp%2Fsourcemap-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tracewayapp%2Fsourcemap-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tracewayapp%2Fsourcemap-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tracewayapp","download_url":"https://codeload.github.com/tracewayapp/sourcemap-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tracewayapp%2Fsourcemap-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34719092,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-24T05:31:05.363Z","updated_at":"2026-06-24T05:31:06.191Z","avatar_url":"https://github.com/tracewayapp.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"Traceway%20Logo%20White.png\" /\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"Traceway%20Logo.png\" /\u003e\n    \u003cimg src=\"Traceway Logo.png\" alt=\"Traceway Logo\" width=\"200\" /\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003eHow source maps fall short where it matters most\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eDemo project for the Traceway deep dive into the JS/TS toolchain\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://tracewayapp.com/blog/deep-dive-into-the-sourcemaps\"\u003eRead the blog post\u003c/a\u003e · \u003ca href=\"https://tracewayapp.com\"\u003eWebsite\u003c/a\u003e · \u003ca href=\"https://github.com/tracewayapp/traceway\"\u003eTraceway\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nThis is a **demo project**. It shows, fully reproducibly, that a source map alone can recover the original **file, line, and column** of every frame in a minified stack trace, but **not the function names**. For the names you also need the minified bundle itself, parsed.\n\nThe whole story (the VLQ decoding by hand, the failure modes, the fix, and how Node gets away with it) is in the blog post: **[Deep dive into the JS/TS toolchain: How source maps fall short where it matters most](https://tracewayapp.com/blog/deep-dive-into-the-sourcemaps)**.\n\n## Run it\n\n```bash\nnpm install\nnpm run build        # bundle + minify the demo program into dist/\nnpm run crash        # run the minified bundle, see the raw production stack trace\nnpm run decode       # dump the source map's decoded mapping table\nnpm run demo         # resolve the trace using ONLY the map: locations work, names don't\nnpm run symbolicate  # the fix: the full 3-step algorithm (map + bundle parsed with acorn)\nnpm run heuristic    # catch the caller-site heuristic lying (indirect call + async)\nnpm run compare      # minified vs non-minified vs node --enable-source-maps vs the target\nnpm run quirk        # raw `node --enable-source-maps` trace: a name leaks onto a global frame\nnpm run no-quirk     # same, but the call sits in a named test(): the leak renames from handleSignup to test\n```\n\n## What's in here\n\n```\nsrc/                          the tiny TypeScript program that crashes\nsrc-heuristic/                variant program with an indirect call and an async caller\ndist/                         bundles and source maps (npm run build regenerates them)\nscripts/vlq.mjs               minimal VLQ mappings decoder, no dependencies\nscripts/decode-map.mjs        prints the decoded mapping table\nscripts/map-only-resolve.mjs  resolves the stack trace with the map only\nscripts/symbolicate.mjs       the correct algorithm: location -\u003e enclosure -\u003e name\nscripts/caller-heuristic.mjs  shows when \"steal the name from the frame below\" lies\nscripts/compare-traces.mjs    four traces side by side\nscripts/quirk.mjs             runs `node --enable-source-maps` on the demo, raw leaked trace\nscripts/node-no-quirk.mjs     same, on src/no-quirk.ts (call wrapped in a named test())\n```\n\n## The leak: `quirk` vs `no-quirk`\n\nBoth commands run a real `node --enable-source-maps`, and **both still leak**, because the bug lives in Node's source-map decoder, not in the program. Node stamps a phantom function name onto the map's last (nameless) segment, and the bottom global frame of the trace floor-looks-up into it. **The leaked name is always whatever function esbuild named last before the bundle's tail**, which is exactly why the two runs below leak different names.\n\n`npm run quirk` runs the original program (`src/index.ts`, a bare top-level call):\n\n```\nError: user has no name\n    at validateUser (src/user.ts:8:11)\n    at handleSignup (src/index.ts:4:10)   real\n    at handleSignup (src/index.ts:7:1)    leak: line 7 is top-level code, not handleSignup\n    at \u003canonymous\u003e  (src/index.ts:7:29)\n```\n\n`handleSignup` prints **twice**: once where it really runs, once leaked onto the global frame right below it. `\u003canonymous\u003e` prints once.\n\n`npm run no-quirk` runs the same crash, but the top-level call now sits inside a named `test()` function (`src/no-quirk.ts`):\n\n```\nError: user has no name\n    at validateUser (src/user.ts:8:11)\n    at handleSignup (src/no-quirk.ts:4:10)   handleSignup, now correct and printed once\n    at test (src/no-quirk.ts:7:3)            real test (its call to handleSignup)\n    at test (src/no-quirk.ts:10:1)           leak: line 10 is `test();`, module-level code\n    at \u003canonymous\u003e  (src/no-quirk.ts:10:6)\n```\n\nNow `test()` is the last named call before the bundle's tail, so **the leak renames itself.** `handleSignup` drops from twice to **once**, and `test` shows up **twice**: the real one (its body calling `handleSignup`) and the phantom stamped onto the module-level `test()` frame. The name `no-quirk` is ironic: the quirk did not go away, adding a layer just changed which name gets leaked. You can move the leak onto a different function by reshaping the program, but you cannot make it disappear, because the phantom name lives on the map's last segment regardless.\n\nThe only thing that actually removes it is a one-line fix in Node's decoder (guard the name read with `hasNext()`), which is the wider point of [the blog post](https://tracewayapp.com/blog/deep-dive-into-the-sourcemaps).\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eBuilt in the open by \u003ca href=\"https://tracewayapp.com\"\u003eTraceway\u003c/a\u003e, an MIT-licensed, OpenTelemetry-native observability platform with exception tracking · \u003ca href=\"https://github.com/tracewayapp/traceway\"\u003egithub.com/tracewayapp/traceway\u003c/a\u003e\u003c/sub\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftracewayapp%2Fsourcemap-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftracewayapp%2Fsourcemap-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftracewayapp%2Fsourcemap-demo/lists"}