Run minified or bundled JavaScript with OneEcma and turn its error report back into one about the TypeScript you wrote: the location, the source line and the whole stack move to the original files through the bundle’s ECMA-426 source map.
The TypeScript, src/cart.ts and src/app.ts:
interface Line { sku: string; qty: number; price: number } interface Cart { id: string; lines: Line[] } export function cartTotal(cart: Cart): number { return cart.lines.reduce((sum, line) => sum + line.qty * line.price, 0); }
import { cartTotal } from "./cart"; const saved = JSON.parse('{"id":"c1","items":[{"sku":"pen","qty":3,"price":1.5}]}'); (globalThis as any).total = cartTotal(saved);
Bundled and minified with esbuild 0.25.11 (esbuild src/app.ts --bundle --minify --format=iife --sourcemap --outfile=dist/app.js), dist/app.js is:
(()=>{function r(t){return t.lines.reduce((i,e)=>i+e.qty*e.price,0)}var n=JSON.parse('{"id":"c1","items":[{"sku":"pen","qty":3,"price":1.5}]}');globalThis.total=r(n);})(); //# sourceMappingURL=app.js.map
The host runs the bundle and remaps the report:
using RecursiveAI.OneEcma; using RecursiveAI.OneEcma.Diagnostics; using RecursiveAI.OneEcma.SourceMaps; using RecursiveAI.OneEcma.Text; string code = File.ReadAllText("web/dist/app.js"); var engine = new ScriptEngine(); try { engine.Evaluate(code, "dist/app.js"); } catch (JsException e) { Console.WriteLine("--- as it ran"); Console.WriteLine(DiagnosticRenderer.Render(e.Report!)); // The engine reads no files: the host fetches the map the sourceMappingURL comment names. var generated = new SourceText(code, "dist/app.js"); SourceMapParseResult? loaded = SourceMapLoader.Load(generated, url => File.ReadAllText(Path.Combine("web", url))); if (loaded?.Map is SourceMap map) { Diagnostic original = SourceMapReports.Remap(e.Report!, map); Console.WriteLine("--- remapped to the TypeScript"); Console.WriteLine(DiagnosticRenderer.Render(original)); Console.WriteLine($"generated {original.Mapping!.Generated.Line}:{original.Mapping.Generated.Column} -> original {original.Mapping.Original.Source}:{original.Mapping.Original.Line}:{original.Mapping.Original.Column}"); } }
--- as it ran error[TypeError]: Cannot read properties of undefined (reading 'reduce') --> dist/app.js:1:30 | 1 | …{function r(t){return t.lines.reduce((i,e)=>i+e.qty*e.price,0)}var n=JSON.parse('{"id":"c1","items":[{"sku":"pen","qty":3,"price":1.5}]}');g… | ^^^^^ `t.lines` is undefined | = help: did you mean `t.items`? (Array, 1 item) = stack: r (dist/app.js:1) ← <anonymous> (dist/app.js:1) ← <top-level> (dist/app.js:1) --- remapped to the TypeScript error[TypeError]: Cannot read properties of undefined (reading 'reduce') --> src/cart.ts:5:15 | 5 | return cart.lines.reduce((sum, line) => sum + line.qty * line.price, 0); | ^^^^^ `t.lines` is undefined | = help: did you mean `t.items`? (Array, 1 item) (in the generated code) = note: generated position: dist/app.js:1:30 = stack: r (src/cart.ts:5) ← <anonymous> (src/app.ts:4) ← <top-level> (dist/app.js:1) generated 1:30 -> original src/cart.ts:5:15
SourceMapReports.Remap moved the primary span and every stack frame it could map: r is in src/cart.ts and its caller in src/app.ts. The suggestion and the label keep the generated names (t.lines, t.items), because they were computed on the code that ran, so the help line says “(in the generated code)”. The map has sourcesContent, so the report quotes the TypeScript line itself.
How the map is found
SourceMapLoader.Loadreads the//# sourceMappingURL=comment at the end of the generated code. Adata:URL is decoded on the spot. Any other URL is resolved against the generated file’s name and passed to your fetcher, because the engine reads no files and opens no connections.SourceMap.Parse(json)takes a map you already have. It never throws on bad input: problems come back as warnings you can render like any report, and a half-broken map still places most errors.- Without
sourcesContent, pass a third argument toRemapthat returns an original file’s text by URL. When no text is available the report keeps the generated source and addsoriginal position: file:line:col.
Good to know
Diagnostic.Mappingrecords both places, and the JSON report carries them asmapping(see the JSON report).oneecma checkfollows a map comment on its own, or takes--source-map <file>, for syntax errors in generated code.- Index maps with
sections,sourceRoot,ignoreListand the decoding limits are covered in source maps.