When you write TypeScript, the compiler converts (transpiles) it into JavaScript. This makes debugging a bit tricky, since you're running JS but wrote TS. That’s where source maps help.
✅ What is a Source Map?
A source map is a file that maps the compiled JavaScript code back to the original TypeScript source code. This allows browsers and debuggers (like VS Code or Chrome DevTools) to show the original .ts code while debugging.
🔧 How to Enable Source Maps
You can enable it in tsconfig.json:
{
"compilerOptions": {
"sourceMap": true
}
}When this is enabled, for every .ts file compiled to .js, a corresponding .js.map file is created.
📂 Example Output
For a file app.ts, TypeScript generates:
app.js– compiled JavaScriptapp.js.map– source map file
At the end of app.js, you'll see a comment like:
//# sourceMappingURL=app.js.mapThis tells the browser to use the source map for debugging.
<div class="infographic-ts" id="infographic-ts">
<style>
.infographic-ts {
--body-font: system-ui, -apple-system, sans-serif;
--mono-font: ui-monospace, SFMono-Regular, Consolas, "Courier New", monospace;
--ed-bg: #1e1e1e;
--ed-bar: #2a2a2a;
--ed-out: #d4d4d4;
--ed-muted: #8a8a8a;
--ed-ok: #4ec9b0;
--run-bg: #2e7d46;
--run-bg-hover: #35924f;
--tok-kw: #c586c0;
--tok-type: #4ec9b0;
--tok-str: #ce9178;
--tok-cmt: #808080;
--tok-var: #ffffff;
--arrow: #6b7280;
max-width: 640px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
font-family: var(--body-font);
}
.infographic-ts * { box-sizing: border-box; }
.ts-header { font-family: 'Georgia', 'Fraunces', serif; font-weight: 700;
font-size: 1.9rem; letter-spacing: 0.01em; color: inherit; text-align: center;
margin: 0 0 1.4rem; }
.code-window { background: var(--ed-bg); border: 1px solid #2f2f2f; border-radius: 8px;
overflow: hidden; transition: opacity 0.4s ease, max-height 0.4s ease; }
.code-window.hidden { opacity: 0; max-height: 0; }
.code-window.revealed { opacity: 1; max-height: 400px; }
.code-topbar { display: flex; align-items: center; justify-content: space-between;
padding: 0.5rem 0.75rem; background: var(--ed-bar); }
.code-dots { display: flex; gap: 6px; }
.code-dot { width: 9px; height: 9px; border-radius: 50%; }
.code-dot.r { background: #ff5f57; }
.code-dot.y { background: #febc2e; }
.code-dot.g { background: #28c840; }
.code-filename { font-family: var(--mono-font); font-size: 0.74rem; color: var(--ed-muted); }
.run-btn { display: inline-flex; align-items: center; gap: 0.35rem; background: var(--run-bg);
color: #fff; border: none; border-radius: 4px; padding: 0.32rem 0.7rem; font-size: 0.74rem;
font-family: var(--body-font); font-weight: 500; cursor: pointer; }
.run-btn:hover:not(:disabled) { background: var(--run-bg-hover); }
.run-btn:disabled { opacity: 0.5; cursor: default; }
.run-btn svg { width: 10px; height: 10px; }
.code-body { padding: 0.85rem 1.1rem; font-family: var(--mono-font); font-size: 0.78rem;
line-height: 1.6; color: var(--ed-out); white-space: pre; overflow-x: auto; }
.tok-kw { color: var(--tok-kw); }
.tok-type { color: var(--tok-type); }
.tok-str { color: var(--tok-str); }
.tok-cmt { color: var(--tok-cmt); }
.tok-var { color: var(--tok-var); }
.tok-punc { color: var(--tok-var); }
.highlight-map { background: rgba(78, 201, 176, 0.16); border-radius: 3px; padding: 0 2px; }
.console { padding: 0 1.1rem; max-height: 0; overflow: hidden; font-family: var(--mono-font);
font-size: 0.78rem; line-height: 1.65; transition: max-height 0.35s ease, padding 0.35s ease; }
.console.show { max-height: 100px; padding: 0 1.1rem 0.85rem; }
.console-line { color: var(--ed-ok); }
.flow-arrow { display: flex; justify-content: center; margin: 0.5rem 0; }
.flow-arrow svg { width: 18px; height: 18px; color: var(--arrow); }
.note-line { font-family: var(--body-font); font-size: 0.85rem; color: var(--ed-out);
background: var(--ed-bg); border: 1px solid #2f2f2f; border-radius: 8px;
padding: 0.85rem 1.1rem; margin: 0.9rem 0 0; line-height: 1.55; }
</style>
<h2 class="ts-header">How source maps get generated</h2>
<div class="code-window" id="w1">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">tsconfig.json</span>
</div>
<div class="code-body"><span class="tok-punc">{</span>
<span class="tok-str">"compilerOptions"</span><span class="tok-punc">:</span> <span class="tok-punc">{</span>
<span class="tok-str">"sourceMap"</span><span class="tok-punc">:</span> <span class="tok-kw">true</span>
<span class="tok-punc">}</span>
<span class="tok-punc">}</span></div>
</div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
</div>
<div class="code-window" id="w2">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">terminal</span>
<button class="run-btn" id="compileBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span style="color:#4ec9b0">$</span> tsc app.ts</div>
<div class="console" id="compileConsole"></div>
</div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
</div>
<div class="code-window hidden" id="w3">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">app.js</span>
</div>
<div class="code-body"><span class="tok-str">"use strict"</span><span class="tok-punc">;</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-var">log</span><span class="tok-punc">(</span><span class="tok-str">"Hello, TypeScript!"</span><span class="tok-punc">);</span>
<span class="highlight-map tok-cmt">//# sourceMappingURL=app.js.map</span></div>
</div>
<div class="code-window hidden" id="w4" style="margin-top: 1rem;">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">app.js.map</span>
</div>
<div class="code-body"><span class="tok-cmt">{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC"}</span></div>
</div>
<p class="note-line" id="noteBox" style="display:none">That last comment line is what lets a debugger read app.js.map and show you app.ts instead — breakpoints, stack traces, and step-through all land on your original TypeScript, not the compiled output.</p>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-ts')
|| document.querySelector('.infographic-ts');
var root = wrapper;
function line(consoleEl, text) {
var el = document.createElement('div');
el.className = 'console-line';
el.textContent = text;
consoleEl.appendChild(el);
}
var compileBtn = root.querySelector('#compileBtn');
var compileConsole = root.querySelector('#compileConsole');
var w3 = root.querySelector('#w3');
var w4 = root.querySelector('#w4');
var noteBox = root.querySelector('#noteBox');
compileBtn.addEventListener('click', function () {
compileBtn.disabled = true;
compileConsole.innerHTML = '';
compileConsole.classList.add('show');
setTimeout(function () {
line(compileConsole, '\u2713 app.js and app.js.map written');
w3.classList.remove('hidden');
w3.classList.add('revealed');
w4.classList.remove('hidden');
w4.classList.add('revealed');
noteBox.style.display = 'block';
compileBtn.disabled = false;
setTimeout(function () {
w4.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 450);
}, 600);
});
})();
</script>
</div>🐞 Debugging in the Browser
If you're using tools like Chrome DevTools:
Compile your TypeScript with
sourceMap: true.Open DevTools → Sources.
You’ll see the original
.tsfiles.You can set breakpoints in TypeScript and debug like it's native code.
<div class="infographic-ts" id="infographic-ts">
<style>
.infographic-ts {
--body-font: system-ui, -apple-system, sans-serif;
--mono-font: ui-monospace, SFMono-Regular, Consolas, "Courier New", monospace;
--ed-bg: #1e1e1e;
--ed-bar: #2a2a2a;
--ed-out: #d4d4d4;
--ed-muted: #8a8a8a;
--ed-ok: #4ec9b0;
--run-bg: #2e7d46;
--run-bg-hover: #35924f;
--tok-kw: #c586c0;
--tok-type: #4ec9b0;
--tok-str: #ce9178;
--tok-cmt: #808080;
--tok-var: #ffffff;
--arrow: #6b7280;
max-width: 640px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
font-family: var(--body-font);
}
.infographic-ts * { box-sizing: border-box; }
.ts-header { font-family: 'Georgia', 'Fraunces', serif; font-weight: 700;
font-size: 1.9rem; letter-spacing: 0.01em; color: inherit; text-align: center;
margin: 0 0 1.4rem; }
.code-window { background: var(--ed-bg); border: 1px solid #2f2f2f; border-radius: 8px;
overflow: hidden; transition: opacity 0.4s ease, max-height 0.4s ease; }
.code-window.hidden { opacity: 0; max-height: 0; }
.code-window.revealed { opacity: 1; max-height: 400px; }
.code-topbar { display: flex; align-items: center; justify-content: space-between;
padding: 0.5rem 0.75rem; background: var(--ed-bar); }
.code-dots { display: flex; gap: 6px; }
.code-dot { width: 9px; height: 9px; border-radius: 50%; }
.code-dot.r { background: #ff5f57; }
.code-dot.y { background: #febc2e; }
.code-dot.g { background: #28c840; }
.code-filename { font-family: var(--mono-font); font-size: 0.74rem; color: var(--ed-muted); }
.run-btn { display: inline-flex; align-items: center; gap: 0.35rem; background: var(--run-bg);
color: #fff; border: none; border-radius: 4px; padding: 0.32rem 0.7rem; font-size: 0.74rem;
font-family: var(--body-font); font-weight: 500; cursor: pointer; }
.run-btn:hover:not(:disabled) { background: var(--run-bg-hover); }
.run-btn:disabled { opacity: 0.5; cursor: default; }
.run-btn svg { width: 10px; height: 10px; }
.code-body { padding: 0.85rem 1.1rem; font-family: var(--mono-font); font-size: 0.78rem;
line-height: 1.6; color: var(--ed-out); white-space: pre; overflow-x: auto; }
.tok-kw { color: var(--tok-kw); }
.tok-type { color: var(--tok-type); }
.tok-str { color: var(--tok-str); }
.tok-cmt { color: var(--tok-cmt); }
.tok-var { color: var(--tok-var); }
.tok-punc { color: var(--tok-var); }
.highlight-map { background: rgba(78, 201, 176, 0.16); border-radius: 3px; padding: 0 2px; }
.console { padding: 0 1.1rem; max-height: 0; overflow: hidden; font-family: var(--mono-font);
font-size: 0.78rem; line-height: 1.65; transition: max-height 0.35s ease, padding 0.35s ease; }
.console.show { max-height: 100px; padding: 0 1.1rem 0.85rem; }
.console-line { color: var(--ed-ok); }
.flow-arrow { display: flex; justify-content: center; margin: 0.5rem 0; }
.flow-arrow svg { width: 18px; height: 18px; color: var(--arrow); }
.note-line { font-family: var(--body-font); font-size: 0.85rem; color: var(--ed-out);
background: var(--ed-bg); border: 1px solid #2f2f2f; border-radius: 8px;
padding: 0.85rem 1.1rem; margin: 0.9rem 0 0; line-height: 1.55; }
.devtools-window { background: #fff; border-radius: 8px; overflow: hidden;
box-shadow: 0 6px 18px rgba(0,0,0,0.18); border: 1px solid #dadce0; margin-top: 1rem;
opacity: 0; max-height: 0; transition: opacity 0.4s ease, max-height 0.4s ease; }
.devtools-window.revealed { opacity: 1; max-height: 460px; }
.devtools-tabs { display: flex; background: #f1f3f4; border-bottom: 1px solid #dadce0;
font-family: system-ui, sans-serif; }
.devtools-tab { padding: 0.5rem 0.9rem; font-size: 0.72rem; color: #5f6368; cursor: default;
border-bottom: 2px solid transparent; }
.devtools-tab.active { color: #1a73e8; border-bottom-color: #1a73e8; background: #fff; }
.devtools-body { display: flex; height: 230px; }
.devtools-files { width: 140px; background: #f8f9fa; border-right: 1px solid #dadce0;
font-size: 0.72rem; padding: 0.5rem 0; overflow-y: auto; font-family: system-ui, sans-serif; }
.devtools-file-row { padding: 0.28rem 0.7rem; display: flex; align-items: center; gap: 0.35rem;
color: #3c4043; cursor: default; }
.devtools-file-row.active { background: #e8f0fe; color: #1a73e8; font-weight: 600; }
.devtools-code { flex: 1; overflow: auto; background: #fff; }
.devtools-line { display: flex; font-family: var(--mono-font); font-size: 0.74rem; line-height: 1.7; }
.devtools-gutter { width: 30px; text-align: right; padding-right: 0.5rem; color: #80868b;
user-select: none; flex-shrink: 0; position: relative; }
.devtools-gutter.has-bp::before { content: ""; position: absolute; right: 4px; top: 5px;
width: 8px; height: 8px; border-radius: 2px; background: #1a73e8; }
.devtools-line.is-paused { background: #fff3c4; }
.devtools-code-text { padding-left: 0.4rem; white-space: pre; color: #202124; }
.devtools-status { display: none; align-items: center; justify-content: space-between;
background: #fff3c4; border-top: 1px solid #f0d264; padding: 0.45rem 0.8rem;
font-family: system-ui, sans-serif; font-size: 0.72rem; color: #7a5b00; }
.devtools-status.show { display: flex; }
.devtools-resume-btn { font: inherit; font-size: 0.7rem; font-weight: 600; background: #1a73e8;
color: #fff; border: none; border-radius: 4px; padding: 0.3rem 0.7rem; cursor: pointer; }
.devtools-run-row { display: flex; justify-content: center; margin-top: 0.9rem; }
</style>
<div class="code-window" id="w1">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">tsconfig.json</span>
</div>
<div class="code-body"><span class="tok-punc">{</span>
<span class="tok-str">"compilerOptions"</span><span class="tok-punc">:</span> <span class="tok-punc">{</span>
<span class="tok-str">"sourceMap"</span><span class="tok-punc">:</span> <span class="tok-kw">true</span>
<span class="tok-punc">}</span>
<span class="tok-punc">}</span></div>
</div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
</div>
<div class="code-window" id="w2">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">terminal</span>
<button class="run-btn" id="compileBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span style="color:#4ec9b0">$</span> tsc app.ts</div>
<div class="console" id="compileConsole"></div>
</div>
<div class="flow-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
</div>
<div class="code-window hidden" id="w3">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">app.js</span>
</div>
<div class="code-body"><span class="tok-str">"use strict"</span><span class="tok-punc">;</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-var">log</span><span class="tok-punc">(</span><span class="tok-str">"Hello, TypeScript!"</span><span class="tok-punc">);</span>
<span class="highlight-map tok-cmt">//# sourceMappingURL=app.js.map</span></div>
</div>
<div class="code-window hidden" id="w4" style="margin-top: 1rem;">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">app.js.map</span>
</div>
<div class="code-body"><span class="tok-cmt">{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC"}</span></div>
</div>
<p class="note-line" id="noteBox" style="display:none">That last comment line is what lets a debugger read app.js.map and show you app.ts instead — breakpoints, stack traces, and step-through all land on your original TypeScript, not the compiled output.</p>
<div class="devtools-window" id="devtoolsWindow">
<div class="devtools-tabs">
<span class="devtools-tab">Elements</span>
<span class="devtools-tab">Console</span>
<span class="devtools-tab active">Sources</span>
<span class="devtools-tab">Network</span>
</div>
<div class="devtools-body">
<div class="devtools-files">
<div class="devtools-file-row active">📄 app.ts</div>
</div>
<div class="devtools-code">
<div class="devtools-line"><span class="devtools-gutter">1</span><span class="devtools-code-text">function greet(): void {</span></div>
<div class="devtools-line" id="dtLine2"><span class="devtools-gutter has-bp">2</span><span class="devtools-code-text"> console.log("Hello, TypeScript!");</span></div>
<div class="devtools-line"><span class="devtools-gutter">3</span><span class="devtools-code-text">}</span></div>
<div class="devtools-line"><span class="devtools-gutter">4</span><span class="devtools-code-text"></span></div>
<div class="devtools-line"><span class="devtools-gutter">5</span><span class="devtools-code-text">greet();</span></div>
</div>
</div>
<div class="devtools-status" id="devtoolsStatus">
<span>Paused on breakpoint — app.ts:2</span>
<button class="devtools-resume-btn" id="resumeBtn" type="button">Resume</button>
</div>
</div>
<div class="devtools-run-row">
<button class="run-btn" id="reloadBtn" type="button" style="display:none">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
reload & run app.ts
</button>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-ts')
|| document.querySelector('.infographic-ts');
var root = wrapper;
function line(consoleEl, text) {
var el = document.createElement('div');
el.className = 'console-line';
el.textContent = text;
consoleEl.appendChild(el);
}
var compileBtn = root.querySelector('#compileBtn');
var compileConsole = root.querySelector('#compileConsole');
var w3 = root.querySelector('#w3');
var w4 = root.querySelector('#w4');
var noteBox = root.querySelector('#noteBox');
var devtoolsWindow = root.querySelector('#devtoolsWindow');
var reloadBtn = root.querySelector('#reloadBtn');
var dtLine2 = root.querySelector('#dtLine2');
var devtoolsStatus = root.querySelector('#devtoolsStatus');
var resumeBtn = root.querySelector('#resumeBtn');
compileBtn.addEventListener('click', function () {
compileBtn.disabled = true;
compileConsole.innerHTML = '';
compileConsole.classList.add('show');
setTimeout(function () {
line(compileConsole, '\u2713 app.js and app.js.map written');
w3.classList.remove('hidden');
w3.classList.add('revealed');
w4.classList.remove('hidden');
w4.classList.add('revealed');
noteBox.style.display = 'block';
devtoolsWindow.classList.add('revealed');
reloadBtn.style.display = 'inline-flex';
compileBtn.disabled = false;
setTimeout(function () {
devtoolsWindow.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 450);
}, 600);
});
reloadBtn.addEventListener('click', function () {
dtLine2.classList.add('is-paused');
devtoolsStatus.classList.add('show');
});
resumeBtn.addEventListener('click', function () {
dtLine2.classList.remove('is-paused');
devtoolsStatus.classList.remove('show');
});
})();
</script>
</div>🧠 Bonus Tips
Use
inlineSourceMap: trueto embed the source map into the.jsfile itself. This avoids a separate.mapfile.{ "compilerOptions": { "inlineSourceMap": true } }Use
sourceRootormapRootif your project structure is more complex.
✅ Why It's Useful
Debug the original TypeScript source, not the compiled JavaScript.
Set breakpoints, inspect variables, and trace errors more easily.
Great developer experience in tools like VS Code, WebStorm, and Chrome DevTools.