Mert Özen Her Satırda Daha İleri
Frontend Build Araçları — #05

Angular CLI'ın Build Sistemi: application Builder, esbuild + Vite, Rolldown'a Geçiş ve webpack'ten Migration

Mert Özen 14 Kas 2026 15 dk 17 görüntülenme
Angular CLI'ın Build Sistemi: application Builder, esbuild + Vite, Rolldown'a Geçiş ve webpack'ten Migration

Angular CLI'ın application builder'ı uygulamayı esbuild ile bundle'lar, lazy chunk sayısı eşiği aşınca chunk'ları Rolldown ile yeniden düzenler ve Vite'ı yalnız dev server olarak kullanır. Bookstore'un Angular uygulamasını webpack tabanlı browser builder'dan taşıyıp browser klasörüne kayan çıktıyı, moment'in dil dosyaları yüzünden yarıya inen main bundle'ı, HMR'ın sınırlarını, define/loader option'larını, Baseline tarayıcı hedefini ve TypeScript 6.0 zorunluluğunu görüyoruz.

Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (Angular CLI ve @angular/build 22.2.7; içinde esbuild 0.28, Vite 8.3 ve Rolldown 1.2), TypeScript 6.0 ve Node.js 24 ile denenmiştir; Angular 22.2 TypeScript 7.0'ı desteklemediği için Angular projesinde TypeScript 6.0 kullanıyoruz. Tarayıcı davranışı Playwright 1.65'in Chromium'uyla gözlenmiştir. Süreler yazarın kendi makinesinden alındı; mutlak değerlere değil, aradaki orana bak.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Angular istemcisi bookstore-web birkaç yıl önce kurulmuş ve hâlâ webpack tabanlı browser builder'la derleniyor. Katalog sayfası tarih için moment, gruplama için lodash kullanıyor; kitap detayı, sepet, siparişlerim ve yönetim sayfaları lazy route. Deploy script'i yıllardır şunu yapıyor:

npx ng build
Copy-Item -Recurse dist/bookstore-web/* \\web01\bookstore\

Angular 22'ye yükselttikten sonra CLI'ın önerdiği migration'ı çalıştırıyoruz ve builder @angular/build:application oluyor. Kaynak kodda tek satır değişmiyor. İki soru:

  • Deploy script'i aynen çalışmaya devam eder mi?
  • main bundle'ının boyutu ne olur: aynı kalır mı, büyür mü, küçülür mü?

Bu yazı Build Araçları serisinin beşinci yazısı. Önceki dört yazıda webpack, esbuild ve Vite'ı ayrı ayrı inceledik; şimdi Angular CLI'ın bu araçları nasıl bir araya getirdiğine bakıyoruz: application builder'ın içi, esbuild'in çıktısını Rolldown'ın yeniden düzenlemesi, Vite'ın yalnız dev server olarak kullanılması, webpack'ten migration ve tarayıcı hedefi. Application builder'ın Angular 17'deki duyurusunu Angular serisinin "Angular 17 Yenilikleri: @if, @for, @defer ve Yeni Application Builder" yazısında, budgets ve environment dosyalarını "Angular Build ve Deploy" yazısında anlattık; burada tekrar etmiyoruz.

application Builder'ın İçi: Kim Ne Yapıyor?

Yeni bir proje kurduğunda angular.json'daki build target'ı zaten yeni builder'ı kullanıyor:

npx @angular/cli@22.2 new bookstore-web --directory web --style=css --ssr=false --ai-config=none
"build": {
  "builder": "@angular/build:application",
  "options": {
    "browser": "src/main.ts",
    "tsConfig": "tsconfig.app.json",
    "assets": [{ "glob": "**/*", "input": "public" }],
    "styles": ["src/styles.css"]
  }
}

@angular/build paketinin package.json'ı builder'ın hangi parçalardan oluştuğunu açıkça gösteriyor: esbuild 0.28.2, Vite 8.3.5, Rolldown 1.2.16, oxc-parser, sass ve Babel. Bir ng build sırasında işler şöyle dağılıyor:

  • Angular compiler (ngtsc), bir esbuild plugin'i olarak çalışıyor: template'leri derliyor, type check yapıyor ve TypeScript'i JavaScript'e çeviriyor. Type check, serinin esbuild yazısında ayrı bir adım olarak koştuğumuz tsc --noEmit'in buradaki karşılığı; builder bunu kendisi yapıyor.
  • esbuild bundle'lıyor: module graph, tree shaking, lazy route'lar için code splitting ve minification.
  • Rolldown, esbuild'in ürettiği chunk'ları ikinci bir geçişte yeniden düzenliyor (bir sonraki bölüm).
  • Oxc, Angular'a özel dönüşümlerde Babel'in yerini alıyor: 22.1'de advanced optimization dönüşümleri, 22.2'de Angular linker (önceden derlenmiş Angular kütüphanelerini son hâline getiren adım) oxc-parser'a taşındı.
  • Vite yalnız ng serve sırasında devreye giriyor.

Üretim build'i:

npx ng build
Initial chunk files | Names            |  Raw size | Estimated transfer size
main-MB2FDNUA.js    | main             | 358.68 kB |               101.98 kB
styles-WBKGN4YZ.css | styles           |  78 bytes |                78 bytes

                    | Initial total    | 358.75 kB |               102.06 kB

Lazy chunk files    | Names            |  Raw size | Estimated transfer size
chunk-DgJXKc3X.js   | book-detail-page |   1.04 kB |               596 bytes
chunk-q36WD529.js   | orders-page      | 759 bytes |               759 bytes
chunk-D5_26l1o.js   | cart-page        | 728 bytes |               728 bytes
chunk-CTpOvIBR.js   | admin-page       | 669 bytes |               669 bytes

Application bundle generation complete. [1.413 seconds]

▲ [WARNING] Module 'lodash' used by 'src/app/admin/admin-page.ts' is not ESM
▲ [WARNING] Module 'moment' used by 'src/app/orders/orders-page.ts' is not ESM

CommonJS uyarılarını şimdilik bir kenara bırakıyoruz; bundle'ı neyin şişirdiğini bulmak serinin bir sonraki yazısının konusu. Dosya adlarına dikkat: main-MB2FDNUA.js esbuild'in büyük harfli hash biçiminde, lazy chunk'lar ise chunk-D5_26l1o.js gibi küçük-büyük harf karışık. İkinci biçim Rolldown'ın; esbuild'in çıktısına sonradan dokunulduğunun ilk işareti bu.

Chunk Optimization: esbuild'in Chunk'larını Rolldown Neden Yeniden Bundle'lıyor?

esbuild'in code splitting'i doğru ama ince taneli: iki entry'nin (burada main ve lazy route'lar) paylaştığı her module grubu ayrı bir chunk'a gider. Gerçek bir uygulamada bu, ilk açılışta tarayıcının indirmek zorunda olduğu çok sayıda küçük dosya demek. Angular 22.0 bu yüzden esbuild'den sonra çalışan bir "chunk optimization" adımını varsayılan yaptı; 22.1'de bu adımı yapan araç Rollup'tan Rolldown'a geçti. Adımın ne yaptığını kapatıp açarak görebiliriz. Windows'ta PowerShell ile:

$env:NG_BUILD_OPTIMIZE_CHUNKS = "false"
npx ng build
Remove-Item Env:NG_BUILD_OPTIMIZE_CHUNKS
Initial chunk files | Names            |  Raw size | Estimated transfer size
chunk-BV4774W7.js   | -                | 121.89 kB |                36.73 kB
chunk-QJSI765M.js   | -                |  95.14 kB |                23.99 kB
chunk-ONXP5GQA.js   | -                |  72.81 kB |                23.43 kB
chunk-CTHDJKAH.js   | -                |  62.92 kB |                18.34 kB
main-MB2FDNUA.js    | main             |   3.07 kB |                 1.25 kB
chunk-ZUTRIRL5.js   | -                | 918 bytes |               918 bytes
chunk-VRM7G5WT.js   | -                | 542 bytes |               542 bytes
chunk-DKPVJ5LR.js   | -                | 120 bytes |               120 bytes
styles-WBKGN4YZ.css | styles           |  78 bytes |                78 bytes

                    | Initial total    | 357.49 kB |               105.39 kB

esbuild'in kendi çıktısında ilk açılış için 8 dosya var; main yalnız 3 kB ve geri kalanı paylaşılan chunk'ları import ediyor. Optimization açıkken aynı kod tek bir main dosyasında toplanıyor ve tahmini transfer boyutu 105.39 kB'tan 102.06 kB'a iniyor, çünkü tek büyük dosya, birçok küçük dosyadan daha iyi sıkışıyor. Tarayıcı açısından daha önemli olan istek sayısı: 8 yerine 1 JavaScript isteği.

@angular/build'in kaynak kodundaki mantık sade. Optimization yalnız script optimization açıkken ve lazy chunk sayısı bir eşiği geçtiğinde çalışıyor; eşik varsayılan olarak 3 ve NG_BUILD_OPTIMIZE_CHUNKS ile değiştirilebiliyor (false kapatır, true her durumda çalıştırır, sayı eşiği değiştirir). Rolldown, esbuild'in ürettiği chunk'ları girdi olarak alıp main'den başlayarak yeniden bundle'lıyor ve minify'ı tekrar yapmıyor, çünkü esbuild zaten yapmış. Bizim projede dört lazy route olduğu için eşik aşılıyor. NG_BUILD_DEBUG_PERF=1 ile aldığımız ölçümde bu adım toplam build süresinin yaklaşık yüzde 6'sını tuttu (DURATION[OPTIMIZE_CHUNKS]: 0.054410625s).

Rolldown'a geçişte sorun yaşarsan NG_BUILD_CHUNKS_ROLLDOWN=false eski Rollup yoluna döndürüyor; ama Rollup artık @angular/build'in bağımlılığı değil ve build açık bir hatayla duruyor:

✘ [ERROR] Chunk optimization failed

  Rollup is required when 'NG_BUILD_CHUNKS_ROLLDOWN' is set to false. Please install 'rollup' manually (e.g. 'npm install rollup --save-dev') to use this fallback.

ng serve: Vite Burada Yalnız Dev Server

Serinin Vite yazısında Vite'ın dev modda kaynak kodu bundle'lamadığını, her dosyayı istendiği anda çevirdiğini görmüştük. Angular'ın dev server'ı farklı çalışıyor. ng serve, uygulama kodunu esbuild ile bellekte derleyip bundle'lıyor ve çıktıyı Vite'a veriyor; Vite yalnız sunucu, HMR (Hot Module Replacement; sayfayı yenilemeden yalnız değişen kodu değiştirme) kanalı ve bağımlılık pre-bundling'i olarak kullanılıyor. Angular dokümanı Vite'ın doğrudan yapılandırılamayacağını da açıkça yazıyor. Uygulamayı Chromium'da açtığımızda yapılan istekler (yollar kısaltıldı, port varsayılan 4200 olarak yazıldı):

http://localhost:4200/@vite/client
http://localhost:4200/styles.css
http://localhost:4200/main.js
http://localhost:4200/chunk-SB5DMEEX.js
http://localhost:4200/chunk-VSGMQDDN.js
http://localhost:4200/@fs/.../web/.angular/cache/22.2.7/bookstore-web/vite/deps/@angular_core.js?v=3f8e8d8d
http://localhost:4200/@fs/.../web/.angular/cache/22.2.7/bookstore-web/vite/deps/lodash.js?v=3f8e8d8d
http://localhost:4200/@fs/.../web/.angular/cache/22.2.7/bookstore-web/vite/deps/moment.js?v=3f8e8d8d
http://localhost:4200/@fs/.../web/.angular/cache/22.2.7/bookstore-web/vite/deps/rolldown-runtime-BcuH8Uup.js?v=f35d19b7
http://localhost:4200/@ng/component?c=src%2Fapp%2Fcatalog%2Fcatalog-page.ts%40CatalogPage&t=...
http://localhost:4200/@ng/component?c=src%2Fapp%2Fapp.ts%40App&t=...

Kaynak dosyalar tek tek gelmiyor; main.js ve chunk'lar esbuild'in dev build'i. Angular paketleri, lodash ve moment ise Vite'ın pre-bundle ettiği dosyalar: .angular/cache altında duruyorlar ve dosya adındaki rolldown-runtime bu işi Vite 8'in Rolldown ile yaptığını gösteriyor. CommonJS olan moment ve lodash da bu adımda ESM'e (ECMAScript Modules; import/export ile çalışan standart module sistemi) çevrildiği için tarayıcıda çalışıyor. Pre-bundling'i dev-server builder'ının prebundle option'ıyla yönetiyorsun: sorunlu bir paketi prebundle.exclude ile dışarıda bırakabilirsin.

@ng/component istekleri HMR'ın parçası. Angular'ın HMR'ı component template'lerini ve stillerini kapsıyor, genel JavaScript değişikliklerini kapsamıyor. Sayfayı Playwright ile açık tutup sepete Emma'yı ekledik, arama kutusuna "austen" yazdık ve üç değişiklik yaptık:

start -> loads: 1 | cart: Cart (1) | nav1: My orders | input: "austen"
template change -> loads: 1 | cart: Cart (1) | nav1: Orders | input: ""
component style change -> loads: 1 | cart: Cart (1) | nav1: Orders | header border: rgb(245, 158, 11)
ts change -> loads: 2 | cart: Cart (0) | nav1: Orders

ng serve'ün terminalinde ilk ikisi için Component update sent to client(s)., üçüncüsü için Page reload sent to client(s). yazdı. app.html ve app.css değişikliklerinde sayfa yenilenmedi ve sepet sayısı korundu, çünkü sepet bir root service'te (CartStore) duruyor. Ama arama kutusu boşaldı: HMR, App component'inin view'ını yeniden oluşturdu ve router-outlet içindeki katalog sayfası da onunla birlikte yeniden yaratıldı. Yani HMR, değişen component'in ve altındaki component'lerin kendi state'ini korumuyor; service'teki state ise kalıyor. format.ts gibi düz bir TypeScript dosyası değişince tam yeniden yükleme var ve sepet sıfırlanıyor. Template HMR'ı ng serve --no-hmr ile kapatabilirsin.

browser Builder'dan Migration

Tahmin sorusundaki eski projeyi kurmak için angular.json'ı Angular 16 dönemindeki hâline getirdik: @angular-devkit/build-angular:browser, main, vendorChunk, buildOptimizer. Angular 22.2'de bu builder hâlâ çalışıyor ama her build'de uyarı veriyor; webpack builder'ları 22.0'da resmen deprecated oldu:

The "@angular-devkit/build-angular:browser" builder is deprecated as part of Angular's Webpack support deprecation. Use "@angular/build:application" instead.
Initial chunk files         | Names                    |  Raw size | Estimated transfer size
main.7660d202c1f8464c.js    | main                     | 842.59 kB |               191.68 kB
runtime.c180557d932b4ee4.js | runtime                  |   3.05 kB |                 1.41 kB
styles.438ac3e8faf16184.css | styles                   |  78 bytes |                55 bytes

                            | Initial total            | 845.73 kB |               193.14 kB

Migration için Angular dokümanının önerdiği komut:

npx ng update @angular/cli --name use-application-builder
The output location of the browser build has been updated from "dist/bookstore-web" to "dist/bookstore-web/browser". You might need to adjust your deployment pipeline or, as an alternative, set outputPath.browser to "" in order to maintain the previous functionality.
UPDATE angular.json (1677 bytes)
UPDATE package.json (872 bytes)

git diff'teki değişiklikler (kısaltıldı):

-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular/build:application",
-            "outputPath": "dist/bookstore-web",
+            "outputPath": {
+              "base": "dist/bookstore-web"
+            },
-            "main": "src/main.ts",
+            "browser": "src/main.ts"
-              "buildOptimizer": false,
-              "vendorChunk": true,
-          "builder": "@angular-devkit/build-angular:dev-server",
+          "builder": "@angular/build:dev-server",

-    "@angular-devkit/build-angular": "^22.2.7",
+    "@angular/build": "^22.2.7",

Tahminin ilk cevabı migration'ın kendi mesajında: çıktı artık dist/bookstore-web/browser altında. Eski deploy script'i browser klasörünü kopyalar, index.html sunucunun kökünde olmaz ve site açılmaz. Ya script'i dist/bookstore-web/browser/* olarak düzeltirsin ya da outputPath.browser'ı "" yaparsın. Ayrıca buildOptimizer ve vendorChunk kaldırıldı; yeni builder'da bunların karşılığı yok, optimization zaten her şeyi kapsıyor. package.json'da webpack'i getiren @angular-devkit/build-angular gitti, yerine @angular/build geldi; bu projede node_modules 410 MB'tan 300 MB'a indi.

İkinci cevap daha şaşırtıcı. Migration sonrası main 842.59 kB'tan 358.68 kB'a, tahmini transfer boyutu 191.68 kB'tan 101.98 kB'a indi. Fark esbuild'in daha iyi minify etmesinden gelmiyor. moment, dil dosyalarını require('./locale/' + name) gibi bir dinamik require ile yüklüyor. webpack bu ifadeyi görünce locale klasöründeki 139 dosyanın hepsini bundle'a koyuyor; Türkçe ay adı "Ocak" eski main dosyasında var, yenisinde yok. esbuild ise dinamik require'ı çözmeye çalışmıyor ve dil dosyalarını almıyor. Bu iyi bir haber ama bir uyarıyla: uygulaman moment.locale('tr') çağırıyor ve dil dosyalarına güveniyorsa, migration sonrası tarihler sessizce İngilizce'ye döner. Migration'dan sonra build çıktısını eskisiyle karşılaştırmak bu yüzden gerekli.

Süre tarafında, aynı projede soğuk build (cache'siz) webpack'le yaklaşık 4 kat uzun sürdü; cache'li ikinci build'de fark yaklaşık 1,6 kata indi. Küçük bir projede bu saniyeler önemsiz, ama oran büyük projelerde de benzer kalıyor ve CI süresine doğrudan yansıyor.

Custom webpack Config'lerinin Karşılıkları

Migration'ın en zor kısmı genelde @angular-builders/custom-webpack ile eklenmiş bir webpack.config.js. Application builder'ın bir plugin API'si yok; bunun yerine webpack ile yapılan yaygın işler için yerleşik option'lar var. Bookstore'da iki tanesini kullandık: uygulamanın sürümünü build zamanında koda gömmek ve logoyu dosya olarak import etmek.

"options": {
  "browser": "src/main.ts",
  "define": {
    "BOOKSTORE_VERSION": "'1.5.0'"
  },
  "loader": {
    ".svg": "file"
  }
}
// angular.json'daki "define" option'ı bu değeri build zamanında yerleştirir
declare const BOOKSTORE_VERSION: string;

// "loader": { ".svg": "file" } bir SVG import'unu çıktıdaki dosyanın URL'ine çevirir
declare module '*.svg' {
  const url: string;
  export default url;
}
import logoUrl from './logo.svg';

export class App {
  protected readonly cart = inject(CartStore);
  protected readonly logoUrl = logoUrl;
  protected readonly version = BOOKSTORE_VERSION;
}

Build sonrası main içinde BOOKSTORE_VERSION yerine doğrudan 1.5.0 string'i duruyor, logo ise media/logo-X3XO3UBL.svg olarak kopyalanıyor ve sayfa bu URL'i kullanıyor. define, ng build --define BOOKSTORE_VERSION="'1.6.0'" gibi komut satırından da verilebiliyor ve komut satırındaki değer önce geliyor; CI'da commit hash'ini gömmek için kullanışlı. Yaygın webpack ihtiyaçlarının karşılıkları:

webpack'teapplication builder'da
DefinePlugindefine option'ı
raw-loader, file-loader, url-loaderloader option'ı (text, file, dataurl, base64, binary, empty) ya da with { loader: 'text' } import attribute'u
externalsexternalDependencies
NormalModuleReplacementPluginfileReplacements ya da package.json imports ile production/development condition'ları
webpack-bundle-analyzerng build --stats-json (serinin bir sonraki yazısında)
Kendi webpack plugin'inYerleşik karşılığı yok; build'den önce ya da sonra çalışan ayrı bir script

Son satır önemli: webpack plugin'iyle build'in ortasına girip çıktıyı değiştiriyorsan, bunu application builder'da aynı şekilde yapamazsın. Çoğu zaman bu iş build'den sonra dist klasörü üzerinde çalışan küçük bir Node.js script'ine taşınabiliyor. Topluluğun esbuild plugin'lerine izin veren builder'ları var, ama Angular ekibinin desteklediği yol değil ve her Angular sürümünde uyumluluğu ayrıca kontrol etmen gerekir.

Tarayıcı Hedefi ve TypeScript 6.0

Angular, desteklediği tarayıcıları Baseline'ın "widely available" tanımıyla belirliyor: her major sürüm, çıkış tarihine yakın bir Baseline tarihi seçiyor. Angular 22 için bu tarih 2026-05-07. @angular/build'in kaynak kodunda bu tarih sabit olarak duruyor ve browserslist'e baseline widely available on 2026-05-07 sorgusu olarak veriliyor. Sonuç: Chrome ve Edge 119, Firefox 119, Safari 17 ve üstü. Bu listeyi .browserslistrc ile daraltabilirsin (örneğin yalnız son iki sürümü hedeflemek), ama genişletemezsin. Eski bir Safari'yi eklemeyi denedik:

Chrome >= 100
Safari >= 15
One or more browsers which are configured in the project's Browserslist configuration fall outside Angular's browser support for this version.
Unsupported browsers:
chrome 118, chrome 117, ..., safari 15.1, safari 15

Build durmuyor, sadece uyarı veriyor; esbuild bu tarayıcılar için de syntax dönüşümü yapmaya çalışıyor ama Angular'ın kendisi bu tarayıcılarda test edilmiyor. ECMAScript module desteklemeyen bir tarayıcı eklersen o tarayıcı tamamen yok sayılıyor, çünkü Angular CLI ES5 çıktısı üretmiyor.

TypeScript tarafında Angular 22.x >=6.0.0 <6.1.0 istiyor. TypeScript 7.0'ın Go ile yazılan derleyicisini TypeScript İleri serisinin "TypeScript 7 ve Sonrası: Go ile Yazılan Derleyici ve Yeni Varsayılanlar" yazısında anlattık; Angular compiler ise TypeScript'in JavaScript API'sini kullandığı için henüz ona geçemedi. Projeye typescript@7.0.2 kurduğumuzda npm yalnız peer dependency uyarısı verdi, build ise anlaşılması zor bir hatayla durdu:

✘ [ERROR] Angular compilation initialization failed. [plugin angular-compiler]

  TypeError: Cannot read properties of undefined (reading 'Error')
      at readConfiguration (.../@angular/compiler-cli/bundles/chunk-WPHJIKYE.js:507:42)

Bu hatayı görürsen ilk bakacağın yer package.json'daki TypeScript sürümü. Aynı repoda TypeScript 7 kullanan başka bir proje (örneğin serinin Vite widget'ı) varsa, her projenin kendi typescript bağımlılığı olsun; Angular projesinde ~6.0.2 kalsın.

Çıktı Klasörü ve outputHashing

Application builder'ın çıktısı:

dist/bookstore-web/3rdpartylicenses.txt
dist/bookstore-web/browser/index.html
dist/bookstore-web/browser/main-KUFRJQIS.js
dist/bookstore-web/browser/chunk-CcmlRWAg.js
dist/bookstore-web/browser/styles-WBKGN4YZ.css
dist/bookstore-web/browser/media/logo-X3XO3UBL.svg
dist/bookstore-web/browser/favicon.ico

browser klasörü SSR (Server-Side Rendering; sayfanın HTML'inin sunucuda üretilmesi) için yer açıyor. Aynı projeye ng add @angular/ssr ile SSR eklediğimizde yanına server klasörü (server.mjs, main.server.mjs), prerender edilen her route için browser/cart/index.html gibi dosyalar ve prerendered-routes.json geldi. index.html'e de bakmaya değer: builder global stillerin kritik kısmını (beasties ile) head'e inline yazıyor ve stil dosyasının geri kalanını media="print" hilesiyle sayfanın render'ını bloklamadan yüklüyor.

outputHashing hangi dosyaların adına içerik hash'i ekleneceğini belirliyor. Dört değeri sırayla denedik:

none    -> main.js, styles.css, media/logo.svg, chunk-CJoZx80H.js
media   -> main.js, styles.css, media/logo-X3XO3UBL.svg, chunk-CJoZx80H.js
bundles -> main-RFA2EUMN.js, styles-WBKGN4YZ.css, media/logo.svg, chunk-4bA6SRpc.js
all     -> main-KUFRJQIS.js, styles-WBKGN4YZ.css, media/logo-X3XO3UBL.svg, chunk-CcmlRWAg.js

İki ayrıntı var. Lazy chunk'lar none'da bile hash'li, çünkü esbuild ve Rolldown chunk adlarını hash'le üretiyor. İkincisi, define ile sürümü değiştirdiğimizde içeriği değişmeyen lazy chunk'ların da hash'i değişti. Sebep chunk'ın ilk satırında: import{...}from"./main-KUFRJQIS.js". Lazy chunk, main'i hash'li adıyla import ediyor; main'in adı değişince bu satır, dolayısıyla chunk'ın içeriği ve hash'i de değişiyor. Serinin ilk yazısında gördüğümüz hash zincirinin Angular'daki karşılığı bu: main'e dokunan her değişiklik, kullanıcıların lazy chunk'ları da yeniden indirmesi demek. Production'da varsayılan all kalsın; diğer değerler ancak sürümlemeyi başka bir araç yapıyorsa anlamlı, örneğin dosya adlarını kendisi sürümleyen bir CDN (dosyaları kullanıcıya yakın sunuculardan dağıtan ağ) kullanıyorsan.

Üretimde Dikkat

Migration'ı ayrı bir pull request olarak yap ve çıktıyı karşılaştır: dosya listesi, initial bundle boyutu, CommonJS uyarıları ve uygulamanın tarayıcıdaki davranışı. moment örneğindeki gibi bazı farklar ancak çalışan uygulamada görünür.

Deploy pipeline'ını browser klasörüne göre güncelle. CI'da dist/bookstore-web/browser/index.html'in varlığını kontrol eden tek satırlık bir adım, boş bir sitenin yayına çıkmasını engeller.

Ortam değişkenleriyle oynadığın build'leri (NG_BUILD_OPTIMIZE_CHUNKS, NG_BUILD_CHUNKS_ROLLDOWN) yalnız sorun ayıklarken kullan. CI'da kalıcı olarak bırakırsan, Angular güncellemelerinde bu değişkenlerin anlamı değişebilir ve fark etmeyebilirsin.

.angular/cache klasörü persistent build cache'i tutuyor; 22.1'den beri git worktree'ler arasında paylaşılıyor. CI'da bu klasörü job'lar arasında cache'lemek ikinci build'i hızlandırır, ama package-lock.json'ı cache key'e katmayı unutma.

Sık Düşülen Tuzaklar

Birincisi, migration'ın kodu değiştirmediği için davranışı da değiştirmediğini sanmak. Bundler değişti; dinamik require, side effect'li import'lar ve CommonJS paketleri farklı ele alınıyor. Angular dokümanı da lazy module'ler arasında sıraya bağlı side effect import'larının farklı sırada çalışabileceğini bilinen bir sorun olarak listeliyor.

İkincisi, Vite'ı ng serve'ün içinden yapılandırmaya çalışmak. vite.config.ts yazmak hiçbir şeyi değiştirmez; Angular'ın dev server'ı yalnız angular.json'daki option'ları (prebundle, hmr, proxyConfig, headers) okur.

Üçüncüsü, Angular 22'de ng serve'ün portunu --port ile verdiğini sanıp ortamdaki PORT değişkenini unutmak. 22.0'dan beri PORT ortam değişkeni hem angular.json'daki ayardan hem --port'tan önce geliyor; makinede başka bir araç için PORT tanımlıysa dev server beklemediğin portta açılır.

Dördüncüsü, .browserslistrc ile eski tarayıcıları destekleyebileceğini düşünmek. Liste yalnız daraltılabilir; Angular'ın tarayıcı politikasının dışına çıkan tarayıcılar için uyarı alırsın ve Angular'ın kendisi o tarayıcılarda test edilmez.

Beşincisi, Angular projesinde TypeScript'i diğer projelerle birlikte 7.0'a yükseltmek. npm yalnız uyarı verir, build ise yukarıdaki anlaşılması zor hatayla durur.

Küçük Bir Deneme

★☆☆ snapshot/05/web'de npx ng build çalıştır, sonra NG_BUILD_OPTIMIZE_CHUNKS'ı false yapıp tekrar dene. İki build'deki initial dosya sayısını ve tahmini transfer boyutunu karşılaştır. Eşiği 10 yapınca ne oluyor?

★★☆ Yönetim sayfasındaki admin-page.ts'i lazy route olmaktan çıkarıp doğrudan import et. Lazy chunk sayısı üçe indiğinde chunk optimization hâlâ çalışıyor mu? Kaynak koddaki eşiği hatırlayarak cevabını build çıktısıyla doğrula.

★★★ ng build --define BOOKSTORE_VERSION="'1.6.0'" ile iki build al ve iki dist klasörünü karşılaştıran küçük bir Node.js script'i yaz: adı değişen dosyaları ve içeriği gerçekten değişen dosyaları ayrı listelesin. Kullanıcının bir sürüm güncellemesinde gereksiz yere yeniden indireceği dosyaları bu farktan bul.

Bir sonraki yazıda bu yazıda bir kenara bıraktığımız CommonJS uyarılarına dönüyoruz: tree shaking'in koşulları, code splitting ve ng build --stats-json ile bundle analizi. moment ve lodash'ı Bookstore'dan çıkarıp initial bundle'ın ne kadar küçüldüğünü ölçeceğiz.