Code Splitting, Tree Shaking ve Bundle Analizi: Bundle'ı Neyin Şişirdiğini Bulmak
Bundle'ı neyin şişirdiğini tahmin etme, ölç: ng build --stats-json, source-map-explorer, rollup-plugin-visualizer ve webpack-bundle-analyzer paket bazında dağılımı gösterir; tree shaking ise yalnız ESM ve side effect'siz module'lerde çalışır. Bookstore'un Angular uygulamasında lodash'tan named import'un hiçbir şey kazandırmadığını, barrel dosyasının side effect taşıdığını, moment ve lodash'ı çıkarınca main'in 359 kB'tan 224 kB'a indiğini ve Vite'ta vendor chunk kararını görüyoruz.
Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (Angular CLI ve @angular/build 22.2.7), TypeScript 6.0 (Angular projesi) ve 7.0 (Vite widget'ı), Vite 8.3, webpack 5.112, rollup-plugin-visualizer 7.1, webpack-bundle-analyzer 5.4, source-map-explorer 2.5 ve Node.js 24 ile denenmiştir; tarayıcı davranışı Playwright 1.65'in Chromium'uyla gözlenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Angular istemcisi bookstore-web'in katalog sayfası, kitapları yazara göre gruplamak için lodash kullanıyor:
import _ from 'lodash';
const byAuthor = _.groupBy(_.sortBy(matches, 'title'), 'author');
Code review'da biri "tüm lodash'ı import ediyoruz, yalnız kullandıklarımızı alalım" diyor ve import'u değiştiriyoruz:
import { groupBy, sortBy } from 'lodash';
const byAuthor = groupBy(sortBy(matches, 'title'), 'author');
Yönetim sayfasındaki _.map, _.groupBy ve _.sumBy da aynı şekilde named import oluyor. ng build sonrasında 359.02 kB olan main bundle'ı kaç kB olur? Aynı değişikliği lodash yerine lodash-es ile yapsaydık ne değişirdi?
Bu yazı Build Araçları serisinin altıncı yazısı. Önceki yazıda Angular CLI'ın build sistemini incelerken CommonJS uyarılarını bir kenara bırakmıştık; şimdi o uyarılara dönüyoruz: bundle'ı neyin şişirdiğini ölçmek, tree shaking'in çalışması için gereken koşullar, code splitting ve vendor chunk kararı. Küçülen bundle'ın sayfa hızına etkisini ölçmek ilerideki Web Performansı serisinin konusu; bu yazı bundle'ın kendisiyle ilgileniyor.
Önce Ölç: stats.json ve Source Map
Bir bundle'ı küçültmeye tahminle başlama; önce içinde ne olduğuna bak. Angular CLI bunun için --stats-json flag'ini sunuyor. Build, esbuild'in metafile biçiminde bir dosya yazıyor; 22.2'den beri browser ve server için ayrı dosyalar var, bu yüzden dosyanın adı browser-stats.json. Bu dosyadaki baytları paket bazında gruplayan küçük bir script yazdık:
// Kullanım: ng build --stats-json && node scripts/bundle-report.mjs
// main bundle'ının baytlarını npm paketine (kendi kodumuz için "src") göre gruplar.
import { readFileSync } from 'node:fs';
const stats = JSON.parse(readFileSync('dist/bookstore-web/browser-stats.json', 'utf8'));
function packageOf(input) {
const index = input.lastIndexOf('node_modules/');
if (index < 0) {
return 'src';
}
const parts = input.slice(index + 'node_modules/'.length).split('/');
return parts[0].startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0];
}
for (const [file, output] of Object.entries(stats.outputs)) {
if (output.entryPoint !== 'src/main.ts') {
continue;
}
const byPackage = new Map();
for (const [input, { bytesInOutput }] of Object.entries(output.inputs)) {
byPackage.set(packageOf(input), (byPackage.get(packageOf(input)) ?? 0) + bytesInOutput);
}
const total = [...byPackage.values()].reduce((sum, bytes) => sum + bytes, 0);
console.log(`${file}: ${(output.bytes / 1000).toFixed(2)} kB`);
for (const [name, bytes] of [...byPackage].sort((a, b) => b[1] - a[1]).slice(0, 8)) {
const share = ((bytes / total) * 100).toFixed(1).padStart(5);
console.log(` ${name.padEnd(28)} ${share}%`);
}
}
package.json'a "analyze": "ng build --stats-json && node scripts/bundle-report.mjs" ekledik:
npm run analyze
main-KUFRJQIS.js: 359.02 kB
@angular/core 29.2%
lodash 22.2%
@angular/router 20.3%
moment 17.4%
rxjs 4.5%
@angular/platform-browser 3.2%
@angular/common 1.5%
src 1.2%
Kendi kodumuz main'in yüzde 1.2'si; lodash ve moment birlikte yaklaşık yüzde 40. Script'te yüzde gösterip bayt göstermemizin bir sebebi var. @angular/build'in kaynak kodunda görüldüğü gibi, chunk optimization açıkken (bir önceki yazıda gördüğümüz Rolldown adımı) stats dosyasındaki bayt değerleri Rolldown'ın ürettiği chunk'tan oranlanarak hesaplanıyor. Toplamları son dosyayla birebir tutmuyor: bizde input'ların toplamı 466 kB, dosyanın kendisi 359 kB. Oranlar doğru fikir veriyor, kesin bayt için source map'e dayanan bir araç gerekiyor:
npx ng build --source-map
npx source-map-explorer dist/bookstore-web/browser/main-*.js --no-border-checks
source-map-explorer, son dosyadaki her baytı source map üzerinden kaynak dosyasına bağlıyor ve tarayıcıda bir treemap açıyor. Bizim build'de lodash 71.6 kB, moment 62.8 kB çıktı; stats'taki oranlarla uyumlu. --no-border-checks olmadan araç şu hatayla duruyor, 2022'den beri güncellenmeyen source-map-explorer, Angular'ın source map'indeki bir eşlemeyi geçersiz sayıyor; --no-border-checks bu kontrolü kapatıyor:
Your source map refers to generated column Infinity on line 1, but the source only contains 7436 column(s) on that line.
Tree Shaking'in Koşulları ve CommonJS
Tahminin cevabı: named import'a geçince main yine 359.02 kB. Değişen hiçbir şey yok. lodash-es'e geçince ise 310.03 kB'a iniyor. Sebep, tree shaking'in ESM (ECMAScript Modules; import/export ile çalışan standart module sistemi) üzerinde çalışması. Bundler, ESM'de bir module'ün neyi export ettiğini ve kimin neyi import ettiğini kodu çalıştırmadan bilir; kullanılmayan export'ları güvenle atabilir. lodash paketi ise CommonJS: module.exports'a çalışma zamanında property ekleyen, yaklaşık 546 kB'lık tek bir dosya. Hangi property'nin kullanıldığını statik olarak bilmek mümkün değil; esbuild de dosyanın tamamını alıyor. import { groupBy } from 'lodash' yazmak bu dosyayı değiştirmiyor, yalnız import'un yazılışını değiştiriyor.
Angular'ın build sırasında verdiği uyarı tam olarak bunu söylüyor:
▲ [WARNING] Module 'lodash' used by 'src/app/admin/admin-page.ts' is not ESM
CommonJS or AMD dependencies can cause optimization bailouts.
Uyarıyı angular.json'daki allowedCommonJsDependencies listesine paketin adını ekleyerek susturabilirsin, ama Angular dokümanı bu option'ı yalnız uyarıyı kapatmanın yolu olarak anlatıyor; bundle'a hiçbir etkisi yok. Bilerek kabul ettiğin bir CommonJS bağımlılığı için kullan, bir sorunu gizlemek için değil.
lodash-es ise her fonksiyonu ayrı bir ESM dosyası olarak sunuyor. Ama source-map-explorer'a baktığımızda lodash-es'ten 144 dosya ve 19.4 kB geldiğini gördük; bizim kullandığımız yalnız dört fonksiyondu. Sebep groupBy(items, 'author') gibi string kısaltmaları: lodash bu kısaltmaları "iteratee" mekanizmasıyla çözüyor ve bu mekanizma property yolu, matches ve derin eşitlik karşılaştırması gibi bütün iteratee türlerini getiriyor. Tree shaking çalıştı, ama kullandığın fonksiyonun bağımlılıkları sandığından büyük olabiliyor.
sideEffects ve Barrel Dosyaları
Tree shaking'in ikinci koşulu side effect. Bir module'ün yalnız export'ları kullanılmıyorsa bile, module'ün en üst seviyesinde çalışan bir kod (bir global'e yazmak, bir event listener eklemek) varsa bundler o module'ü atamaz. Bunu bir barrel dosyasıyla, yani bir klasördeki module'leri tek yerden yeniden export eden index.ts ile denedik. Bookstore'un shared klasörüne geçici olarak bir hata raporlama module'ü ve barrel ekledik:
// Module çalıştırıldığı anda devreye girer: en üst seviyede bir side effect
window.addEventListener('error', (event) => {
console.warn('[bookstore] unhandled error reported', event.message);
});
export function reportError(error: unknown): void {
console.warn('[bookstore] reported', error);
}
export * from './cart-store';
export * from './collections';
export * from './dates';
export * from './error-reporting';
export * from './format';
Yönetim sayfası yalnız formatPrice'ı ../shared/format yerine ../shared'dan import edecek şekilde değişti. reportError hiçbir yerde kullanılmıyor. Sonuç: yönetim sayfasının lazy chunk'ı 631 byte'tan 723 byte'a çıktı ve içinde unhandled error reported metni var. Bundler, barrel'ın export ettiği her module'ü yüklemek zorunda kaldı ve error-reporting.ts'in side effect'ini koruyabilmek için onu chunk'a koydu. Üstelik bu listener artık yalnız yönetim sayfası açılınca ekleniyor; barrel, bir side effect'in ne zaman çalışacağını da değiştirdi.
package.json'a "sideEffects": false eklediğimizde chunk tekrar 631 byte'a indi ve main'in hash'i barrel'dan önceki hâline döndü. Bu alan bundler'a "bu paketteki dosyaların hiçbirinin side effect'i yok, kullanılmayanı at" sözü veriyor. Söz yanlışsa bundler'ı uyarmadan yanıltırsın: bizim örnekte hata raporlama listener'ı sessizce bundle'dan çıktı. Kütüphane yazarları bu alanı "sideEffects": ["*.css", "./src/polyfills.ts"] gibi bir listeyle doğru yazmalı; uygulama kodunda ise barrel yerine doğrudan dosya yolundan import etmek daha güvenli. Deneyden sonra barrel'ı ve sideEffects satırını geri aldık.
Bookstore Vakası: moment ve lodash'tan Çıkmak
moment'in yerini tarayıcıların kendi Intl API'leri alabiliyor. Intl.RelativeTimeFormat 2020'den beri bütün büyük tarayıcılarda var. Temporal ise TC39 (JavaScript standardını yöneten komite) sürecini 2026'da tamamladı ve Chrome 144 ile Firefox 139'da kullanılabiliyor, ama Safari'nin kararlı sürümünde henüz yok; Baseline'da değil. Bu yüzden tarih hesabını Date ile yapıp biçimlendirmeyi Intl'e bıraktık:
const relative = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const longDate = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeZone: 'UTC' });
const units: readonly (readonly [Intl.RelativeTimeFormatUnit, number])[] = [
['year', 365 * 24 * 60 * 60],
['month', 30 * 24 * 60 * 60],
['day', 24 * 60 * 60],
['hour', 60 * 60],
['minute', 60],
];
export function fromNow(iso: string, now: number = Date.now()): string {
const seconds = Math.round((Date.parse(iso) - now) / 1000);
for (const [unit, size] of units) {
if (Math.abs(seconds) >= size) {
return relative.format(Math.round(seconds / size), unit);
}
}
return relative.format(seconds, 'second');
}
export function formatLongDate(iso: string): string {
return longDate.format(new Date(iso));
}
lodash için ilk akla gelen Object.groupBy. Ama MDN'in (Mozilla'nın web platformu dokümantasyonu) uyumluluk verisine göre Safari onu 17.4'te ekledi; bir önceki yazıda gördüğümüz gibi Angular 22 Safari 17.0'ı destekliyor. Baseline'da "widely available" olması da Mart 2024'ten 30 ay sonrasına, yani Angular 22'nin seçtiği 2026-05-07 tarihinden sonraya denk geliyor. Bir build aracı bu farkı yakalamaz: esbuild syntax'ı hedefe göre dönüştürür ama eksik bir API için polyfill eklemez. Bu yüzden on satırlık bir yardımcı yazdık:
// Object.groupBy Baseline'a ancak Mart 2024'te girdi (Safari 17.4); Angular 22 ise Safari 17.0'ı da destekliyor
export function groupBy<T, K>(items: readonly T[], key: (item: T) => K): Map<K, T[]> {
const groups = new Map<K, T[]>();
for (const item of items) {
const groupKey = key(item);
const group = groups.get(groupKey);
if (group) {
group.push(item);
} else {
groups.set(groupKey, [item]);
}
}
return groups;
}
const byAuthor = groupBy(
[...matches].sort((a, b) => a.title.localeCompare(b.title)),
(book) => book.author,
);
return [...byAuthor.keys()]
.sort((a, b) => a.localeCompare(b))
.map((author) => ({ author, books: byAuthor.get(author) ?? [] }));
Her adımda ng build çıktısındaki main satırı:
| Adım | main | Tahmini transfer |
|---|---|---|
Başlangıç: import _ from 'lodash' ve moment | 359.02 kB | 102.14 kB |
import { groupBy, sortBy } from 'lodash' | 359.02 kB | 102.23 kB |
lodash-es named import | 310.03 kB | 85.83 kB |
moment yerine Intl, lodash yerine groupBy | 223.73 kB | 60.91 kB |

Son durumda npm run analyze şunu gösteriyor:
main-Y3GQS3U4.js: 223.73 kB
@angular/core 48.0%
@angular/router 33.6%
rxjs 7.4%
@angular/platform-browser 5.3%
@angular/common 2.5%
src 2.3%
tslib 0.9%
CommonJS uyarıları kayboldu ve package.json'dan üç paket (lodash, moment ve @types/lodash) çıktı. Ama değişikliği yapmadan önce ve sonra katalog sayfasının metinlerini Playwright ile karşılaştırdık ve sekiz kitaptan üçünde fark çıktı: moment'in "a day ago" dediğine Intl.RelativeTimeFormat numeric: 'auto' ile "yesterday", "a month ago" dediğine "last month" diyor; 107 günlük bir farkı moment "3 months ago", bizim 30 günlük ay hesabımız "4 months ago" olarak gösteriyor. Kütüphane değiştirmek davranış değiştirmek demek; bu farkların kabul edilebilir olup olmadığına ürün tarafıyla birlikte karar ver ve kararı bir unit test ile sabitle.
Code Splitting: Ne Zaman Ayrı Chunk?
Code splitting'in temel aracı dinamik import(). Angular'da bunu route seviyesinde loadComponent ile yapıyoruz:
{ path: 'admin', loadComponent: () => import('./admin/admin-page'), title: 'Admin · Bookstore' },
Yönetim sayfası ayrı bir lazy chunk'ta ve yalnız /admin açılınca indiriliyor. Bir sayfanın içindeki ağır bir parçayı (bir grafik, bir editör) ertelemek için Angular'ın @defer bloğu da aynı mekanizmayı kullanıyor; @defer trigger'larını ve bunun sayfa hızına etkisini Web Performansı serisinde ele alacağız.
Bölmenin bir maliyeti var: her chunk bir istek, ve lazy chunk'ın ihtiyaç duyduğu ortak kodun nerede duracağına bundler karar vermek zorunda. Bir önceki yazıda gördüğümüz gibi esbuild bu kodu ince taneli ortak chunk'lara bölüyor, Angular ise lazy chunk sayısı üçü geçince bunları Rolldown ile tekrar birleştiriyor. Genel kural: kullanıcıların çoğunun hiç açmadığı sayfalar (yönetim, raporlar) ve ağır, nadir kullanılan bileşenler ayrı chunk olsun; her sayfada kullanılan kod main'de kalsın.
Vendor Chunk: HTTP/2 Sonrası Değişen Mantık
HTTP/1.1 döneminde tarayıcı aynı sunucuya ancak birkaç paralel bağlantı açabiliyordu ve her istek pahalıydı. O dönemde yaygın strateji, bütün node_modules'ı tek bir vendor dosyasına koymaktı: uygulama kodu her deploy'da değişse de vendor aynı kalır, tarayıcı onu cache'ten kullanır. HTTP/2 ve HTTP/3 aynı bağlantı üzerinden çok sayıda isteği paralel taşıyor; istek sayısının maliyeti düştü. Bu yüzden karar artık "kaç istek" sorusundan çok "deploy'lar arasında hangi dosyalar değişmeden kalacak" sorusuna bağlı.
Vite 8'de bu kararı Rolldown'ın codeSplitting option'ıyla veriyorsun. Widget'ın config'ine bir vendor mode'u ekledik:
// npx vite build --mode vendor: node_modules uzun ömürlü kendi chunk'ına gider
...(mode === "vendor" && {
build: {
rolldownOptions: {
output: {
codeSplitting: {
groups: [{ name: "vendor", test: /node_modules/ }],
},
},
},
},
}),
npx vite build --mode vendor
dist/assets/index-ovXL4q3W.js 2.10 kB │ gzip: 1.08 kB
dist/assets/vendor-BMhjzZUC.js 2.21 kB │ gzip: 1.04 kB
finder.ts'te placeholder metnini değiştirip tekrar build aldığımızda index-BFJAy8q9.js oldu, vendor-BMhjzZUC.js aynı kaldı. Tek dosyalı varsayılan build'de (index-BvaywwEr.js, gzip ile 2.02 kB) ise her değişiklik lodash-es'in kodunu da yeniden indirtir. Bedeli bir ek istek ve gzip'te 0.10 kB. Bu küçük widget'ta kazanç önemsiz; ama uygulama kodunun sık, bağımlılıkların seyrek değiştiği büyük bir projede vendor chunk'ı ayırmak hâlâ anlamlı.
Bunun tersi de doğru. Bir önceki yazıda Angular'ın lazy chunk'larının main'i hash'li adıyla import ettiğini, bu yüzden main değişince bütün lazy chunk'ların hash'inin değiştiğini görmüştük. Angular vendorChunk option'ını application builder'da kaldırdı ve chunk düzenini kendisi yönetiyor; Angular projesinde bu kararı sen vermiyorsun. Vite'ta veriyorsan ölç: gruplar side effect sırasını da değiştirebilir. Rolldown dokümanı, sıraya bağlı module'ler için output.strictExecutionOrder'ı öneriyor.
Analiz Araçları: Hangi Bundler'da Hangisi?
Widget'ta Vite ve webpack build'leri için de analiz kurduk. Vite tarafında rollup-plugin-visualizer 7.1 Rolldown'ı peer dependency olarak destekliyor ve Vite 8 ile sorunsuz çalıştı:
npx vite build --mode analyze
- `/node_modules/lodash-es/debounce.js`: 28.1% (2.51 KiB)
- ` vite/modulepreload-polyfill.js`: 13.0% (1.16 KiB)
- `/src/finder.ts`: 8.1% (737 B)
- `/node_modules/lodash-es/toNumber.js`: 7.7% (708 B)
Plugin'in markdown template'i bu listeyi stats/list.md'ye, treemap template'i etkileşimli bir HTML sayfasını stats/treemap.html'e yazıyor. Raporun "Rendered" değerleri minify öncesi boyutlar; dosyanın kendisi 4.26 kB iken rapor 8.93 KiB diyor. Listede ilginç bir satır var: Vite'ın modulepreload polyfill'i widget'ın yüzde 13'ü. Tarayıcı hedefin modulepreload'ı destekliyorsa build.modulePreload.polyfill: false ile bunu çıkarabilirsin; serinin Vite yazısında backend entegrasyonu için bunu zaten yapmıştık.
| Araç | Bundler | Girdi | Not |
|---|---|---|---|
ng build --stats-json + bir script ya da esbuild'in web'deki analyzer'ı | Angular CLI, esbuild | metafile | Chunk optimization açıkken baytlar oranlanmış tahmin |
rollup-plugin-visualizer | Vite, Rolldown, Rollup | build sırasında plugin | Treemap, sunburst, markdown; minify öncesi boyut |
Vite DevTools (@vitejs/devtools) | Vite 8.3+ | devtools: true | Module, chunk ve paket görünümü, iki build'i karşılaştırma; yazım anında 0.8.2, bu yazıda denemedik |
webpack-bundle-analyzer | webpack, Rspack | --json stats dosyası | stat, parsed ve gzip boyutları ayrı |
source-map-explorer | Hepsi | son dosya ve source map | Son dosyadaki gerçek bayt; Angular'da --no-border-checks gerekiyor |
webpack için widget'a npm run analyze:webpack ekledik: önce webpack --json=stats/webpack.json ile stats dosyası yazılıyor, sonra webpack-bundle-analyzer bu dosyadan stats/webpack.html'i üretiyor. İki HTML raporunu da Chromium'da açıp hatasız render edildiklerini kontrol ettik.
Üretimde Dikkat
Analizi tek seferlik bir iş olarak bırakma. npm run analyze'ın çıktısını pull request'lerde karşılaştırmak, büyük bir bağımlılığın sessizce eklendiği anı yakalar. Angular'da bunun hazır bir sınırı var: budgets. Budget'ların nasıl yazıldığını Angular serisinin "Angular Build ve Deploy" yazısında anlattık; burada önemli olan, budget'ın bir şey aştığında build'i durdurması, analizin ise neyin aştığını göstermesi. İkisi birlikte çalışır.
Bundle'ı küçültmek tek başına hedef değil. 359 kB'tan 224 kB'a inmek, kullanıcının daha az JavaScript indirip parse etmesi demek; ama bunun LCP (Largest Contentful Paint; sayfadaki en büyük içeriğin ekrana gelme süresi) ya da INP (Interaction to Next Paint; kullanıcı etkileşimine sayfanın tepki süresi) üzerindeki etkisini gerçek kullanıcı verisiyle ölçmek Web Performansı serisinin konusu.
Tarih ve gruplama gibi davranışı olan bir kütüphaneyi değiştirdiğinde ekranda görünen metinleri test et. Bizim örneğimizde build başarılıydı, uyarı yoktu ve bundle küçüktü; ama sekiz kitaptan üçünün metni değişmişti.
Sık Düşülen Tuzaklar
Birincisi, CommonJS bir paketten named import yapınca tree shaking'in çalıştığını sanmak. Paket CommonJS ise dosyanın tamamı gelir; import'un yazılışı bunu değiştirmez.
İkincisi, allowedCommonJsDependencies'i bir çözüm sanmak. Yalnız uyarıyı susturur, bundle'a dokunmaz.
Üçüncüsü, "sideEffects": false'u bundle'ı küçülttüğü için her yere eklemek. Side effect'i olan bir dosya (bir polyfill, global bir stil, kendini kaydeden bir custom element) bu durumda uyarı vermeden bundle'dan çıkar.
Dördüncüsü, bir kütüphanenin yerine yerleşik API koyarken tarayıcı hedefini kontrol etmemek. Object.groupBy Chrome'da çalışır ama Angular 22'nin desteklediği Safari 17.0'da yoktur; build aracı polyfill eklemez.
Beşincisi, analiz aracının gösterdiği sayıyı sorgulamadan almak. stats dosyası oranlanmış tahmin, visualizer minify öncesi boyut, source-map-explorer ise son dosyadaki gerçek baytı gösteriyor. Karşılaştırma yaparken hep aynı aracı ve aynı ölçüyü kullan.
Küçük Bir Deneme
★☆☆ snapshot/06/web'de npm run analyze çalıştır. Sonra app.routes.ts'te sepet sayfasını lazy route olmaktan çıkarıp doğrudan import et ve main'deki src payının nasıl değiştiğine bak.
★★☆ catalog-page.ts'e bir tarih aralığı filtresi ekle ve bunu bir tarih kütüphanesinin ESM sürümüyle (örneğin date-fns) yaz. npm run analyze ile kütüphaneden kaç module geldiğini ve main'in ne kadar büyüdüğünü ölç; aynı işi Intl ve Date ile yazıp karşılaştır.
★★★ Widget'ta vendor grubunu lodash-es için ayrı, geri kalan node_modules için ayrı iki gruba böl (priority kullanarak). İki ardışık build arasında yalnız finder.ts'i değiştirip hangi dosyaların hash'inin değiştiğini otomatik raporlayan bir script yaz.
Bir sonraki yazıda bu yazıda analiz için kullandığımız source map'lerin kendisine bakıyoruz: formatın standardı (ECMA-426), production'da hidden source map, error tracking servisleriyle eşleştirme ve source map'in kaynak kodunu sızdırma riski.