Sass'ı Build'e Bağlamak: Source Map'ler, Angular, Vite ve ASP.NET Core Ayarları, Load Path'ler ve Derleme Performansı
Source map'i geliştirmede aç, production'da hidden yap ve .map dosyasını yayından ayır; Vite'ta sass-embedded kur, additionalData'ya yalnız CSS üretmeyen @use satırı koy. Bookstore'da Debug sonrası Release build'in Sass'ı hiç çalıştırmadığını ve map'i Brotli ve gzip kopyalarıyla yayına gönderdiğini gösteriyor, MSBuild target'ını düzeltiyor, DevTools'un .scss satırını bulduğunu ve pkg: importer'lı kendi build script'imizi deniyoruz.
Sürüm notu: Bu yazıdaki örnekler Dart Sass 1.108.2 (sass ve sass-embedded), Angular 22.2 (TypeScript 6.0; Angular CLI'ın içindeki Sass 1.104.1), Vite 8.3, .NET 10 ve Playwright 1.65'in Chromium build'iyle denenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) ASP.NET Core projesi Bookstore.Web'de Sass'ı serinin ilk yazısında bir MSBuild target'ına bağlamıştık. Bir ekip arkadaşı Debug build'lerde source map istedi ve target'ı şöyle değiştirdi:
<!-- yalnız bir .scss dosyası site.css'ten yeniyse çalışır -->
<Target Name="CompileSass" BeforeTargets="BeforeBuild" Inputs="@(SassSource)" Outputs="wwwroot/css/site.css">
<Exec Command="npm run css:dev" Condition="'$(Configuration)' == 'Debug'" />
<Exec Command="npm run css:build" Condition="'$(Configuration)' != 'Debug'" />
<ItemGroup>
<Content Include="wwwroot/css/site.css" Exclude="@(Content)" />
</ItemGroup>
</Target>
css:dev source map'li ve okunabilir CSS üretiyor, css:build ise --style=compressed --no-source-map. Geliştirici gün boyu dotnet build ile çalıştı, akşam dotnet publish -c Release ile yayın paketi hazırladı. Yayına giden site.css hangisi? wwwroot/css'te başka ne var?
Serinin son yazısında Sass'ın build'e nasıl bağlandığına bakıyoruz: source map'ler ve DevTools, Angular ve Vite ayarları, ASP.NET Core'da Debug ve Release ayrımı ve modern JS API ile kendi derleme script'in. Build araçlarının genel mimarisi Build Araçları serisinde anlatıldı; burada yalnız Sass'ın o zincire girdiği noktalar var.
Source Map: CLI'da Üretmek ve Gömmek
Source map, derlenmiş CSS'teki her konumun kaynakta hangi dosya ve satıra karşılık geldiğini tutan bir JSON dosyası. Tarayıcı CSS'in sonundaki sourceMappingURL yorumunu görünce map'i indiriyor ve DevTools'ta kuralın yanında .css satırı yerine .scss satırını gösteriyor. Aşağıdaki görsel zinciri ve ayarların girdiği yerleri özetliyor:

Sass CLI dosyaya yazarken source map'i varsayılan olarak üretiyor. Önceki yazının paketini kullanan küçük bir dosyayla:
npx sass --load-path=node_modules 12/src/main.scss 12/out/main.css
main.css
main.css.map
/*# sourceMappingURL=main.css.map */
{"version":3,"sourceRoot":"","sources":["../src/_card.scss"],"names":[],"mappings":"AAEA;EACI;;AAEA;EACI","file":"main.css"}
sources'ta yalnız CSS üreten dosya var; main.scss yalnız @use içerdiği, paketin dosyaları da CSS üretmediği için listede değiller. Diğer bayraklar:
--embed-sources:.scssdosyalarının içeriğini map'insourcesContentalanına koyar; DevTools kaynağı sunucudan istemeden gösterebilir.--embed-source-map: map'i ayrı dosya yerine base64 olarak CSS'in sonundaki yoruma gömer; tek dosya, ama CSS büyür.--no-source-map: map üretmez;--style=compressedile birlikte production çıktısı.
npx sass --load-path=node_modules --no-source-map --style=compressed 12/src/main.scss 12/out/prod.css
.book-card{padding:16px}.book-card__title{font-size:1.125rem}
DevTools'ta .scss Satırına Gitmek
DevTools'un map'i bulup bulmadığını Chrome DevTools Protocol (DevTools'un tarayıcıyla konuştuğu protokol) üzerinden kontrol ettim. Script bookstore-web'in development build'ini açıyor, tarayıcının stil dosyası için bildirdiği sourceMapURL'i okuyor ve DevTools'un yaptığı gibi birkaç kuralı map üzerinden kaynağa çeviriyor:
npx ng build --configuration development
node scripts/12-devtools-map.mjs
style sheet: /styles.css
sourceMapURL: styles.css.map
.page -> src/styles/layout/_page.scss:4
.md\:d-block -> ../packages/styles/src/_utilities.scss:12
İkinci satır öğretici: .md\:d-block bir utility generator'ın çıktısı ve map onu utilities/_index.scss'teki çağrıya değil, kuralı yazan mixin satırına, yani paketin içine götürüyor. Mixin'le üretilen bir kuralı DevTools'ta tıkladığında generator'ı göreceksin; hangi çağrının ürettiğini görmek için class adından geriye gitmek gerekiyor. Map'in sources listesinde Angular Material'ın _system.scss'i ve modern-normalize.css da var; Angular development build'de sourcesContent'i de dolduruyor.
Angular: sourceMap.styles, hidden ve Sass Ayarları
Angular'da Sass ile ilgili ayarların hepsi angular.json'daki build target'ında. @angular/build'in kendi şemasından doğruladığım liste:
styles: global stil dosyaları (src/styles.scss);inlineStyleLanguage: "scss": component'lerinstylesalanındaki inline stillerin dili.stylePreprocessorOptions.includePaths: load path'ler (serinin dördüncü yazısı);stylePreprocessorOptions.sass: yalnızfatalDeprecations,silenceDeprecationsvefutureDeprecations(ayrıntısı yedinci yazıda).sourceMap:true/falseya dascripts,styles,hidden,vendorvesourcesContentalanları olan bir object.
Yeni projede development konfigürasyonu sourceMap: true, production ise source map'siz geliyor. Production'da hata takip aracına map göndermek istiyorsan doğru ayar hidden source map: map üretilir ama CSS'e ve JavaScript'e sourceMappingURL yorumu yazılmaz. Bookstore'un production konfigürasyonuna ekledim:
"production": {
"sourceMap": {
"scripts": true,
"styles": true,
"hidden": true
},
...
}
main-4WYDE7X7.js
main-4WYDE7X7.js.map
styles-LOGIWG2J.css
styles-LOGIWG2J.css.map
Aynı script production build'de sourceMapURL: (none) yazdı: tarayıcı map'in varlığından haberdar değil. Ama dosyalar dist'te duruyor ve dist'i olduğu gibi sunucuya kopyalarsan herkes styles-LOGIWG2J.css.map'i isteyip Sass kaynağını okuyabilir; map'te sourcesContent varsayılan olarak açık. Hidden source map'in mantığı ve hata takip araçlarına yüklenmesi Build Araçları serisinin source map yazısında anlatıldı; Sass açısından kural aynı: .map dosyaları deploy adımında ayrılmalı.
Vite: sass-embedded, preprocessorOptions ve devSourcemap
Vite tarafını vite klasöründe ayrı küçük bir projeyle denedim: Vite 8.3, sass-embedded ve önceki yazının paketi. Derleyiciye biraz iş vermek için bir script 40 küçük component stil dosyası üretiyor:
import { NodePackageImporter } from "sass-embedded";
import { defineConfig } from "vite";
export default defineConfig({
css: {
// dev server'da CSS source map'leri (varsayılan kapalı)
devSourcemap: true,
preprocessorOptions: {
scss: {
// her .scss dosyası bu satırla başlar: CSS üretmemeli
additionalData: '@use "pkg:@bookstore/styles" as bs;\n',
loadPaths: ["src/styles"],
importers: [new NodePackageImporter()],
},
},
},
});
node scripts/generate-components.mjs
npx vite build
dist/index.html 0.32 kB │ gzip: 0.22 kB
dist/assets/index-RGmfAxUY.css 4.79 kB │ gzip: 0.63 kB
dist/assets/index-Bh-ertQ-.js 0.67 kB │ gzip: 0.38 kB
preprocessorOptions.scss, Sass'ın modern API option'larını olduğu gibi alıyor: loadPaths, importers, silenceDeprecations gibi. Vite 7'den beri legacy API yok, bu yüzden api: "modern" gibi bir ayar artık gerekmiyor. additionalData ise Vite'ın kendi eklemesi: verilen metni her stil dosyasının başına koyuyor. Component dosyalarındaki bs.space(3) çağrıları @use satırı olmadan bu sayede çalışıyor.
Derleyici seçimi: Vite sass-embedded kuruluysa onu, değilse sass'ı kullanıyor. Aynı build'i iki paketle altışar kez çalıştırdım. sass-embedded ile Vite'ın raporladığı süre 49–66 ms, komutun toplam süresi 0,16–0,20 saniye; sass ile 189–311 ms ve 0,33–0,58 saniye. Birinci yazıdaki tek büyük dosyalı ölçümde fark yüzde 30–45'ti; burada çok sayıda küçük dosya var ve her derlemenin sabit maliyeti öne çıkıyor. sass-embedded platformuna uygun native paketi (Windows x64'te ayrı bir paket) kendisi seçiyor. css.preprocessorMaxWorkers varsayılan olarak true, yani CPU sayısının bir eksiği kadar worker; çoğu projede dokunmaya gerek yok.
devSourcemap Vite dokümanında hâlâ "experimental" ve varsayılan olarak kapalı. Dev server'ı 127.0.0.1'de başlatıp bir component'in CSS'ini istedim: ayar kapalıyken yanıtta sourceMappingURL yok, açıkken CSS'in sonunda base64 bir map var. Map'i çözünce sources ["card-01.scss", "../../node_modules/@bookstore/styles/src/_breakpoints.scss"] ve x_google_ignoreList: [1] çıktı: Vite node_modules'tan gelen dosyayı DevTools'un ignore list'ine koyuyor, yani debugger adımlarında paketin içine girilmiyor.
ASP.NET Core: --watch, Debug/Release ve Yayına Giden Map
Tahminin cevabı. Temiz bir klasörde dotnet build, sonra dotnet build -c Release ve dotnet publish -c Release -o out:
## debug
> css:dev
> sass Styles/site.scss wwwroot/css/site.css --embed-sources
Build succeeded.
## release
Build succeeded.
## publish
site.css
site.css.br
site.css.gz
site.css.map
site.css.map.br
site.css.map.gz
Release build'de css:build hiç çalışmadı. Target'ın Outputs'u site.css; Debug build onu az önce ürettiği için MSBuild'e göre güncel. Yayına Debug'ın okunabilir CSS'i, sonundaki sourceMappingURL yorumu ve map gitti; MapStaticAssets map'in Brotli ve gzip sürümlerini de üretti. Sorun, iki konfigürasyonun aynı çıktı dosyasını paylaşması.
Düzeltmede her konfigürasyonun kendi stamp dosyası var ve her derleme diğerlerininkini siliyor; yani site.css'i en son hangi konfigürasyon ürettiyse onun stamp'i duruyor. Map ayrıca Debug dışında hiçbir zaman Content'e girmiyor:
<ItemGroup>
<SassSource Include="Styles/**/*.scss" />
<!-- source map yalnız Debug içindir: asla yayına gitmez -->
<Content Remove="wwwroot/css/**/*.map" Condition="'$(Configuration)' != 'Debug'" />
</ItemGroup>
<!-- her konfigürasyonun bir stamp'i var; site.css'i son derleyen konfigürasyon sahiplenir,
bu yüzden Debug ile Release arasında geçişte her zaman yeniden derlenir -->
<Target Name="CompileSass" BeforeTargets="BeforeBuild" Inputs="@(SassSource);package.json" Outputs="$(IntermediateOutputPath)sass.stamp">
<Exec Command="npm run css:dev" Condition="'$(Configuration)' == 'Debug'" />
<Exec Command="npm run css:build" Condition="'$(Configuration)' != 'Debug'" />
<Delete Files="wwwroot/css/site.css.map" Condition="'$(Configuration)' != 'Debug'" />
<ItemGroup>
<_SassStamp Include="$(BaseIntermediateOutputPath)**/sass.stamp" />
</ItemGroup>
<Delete Files="@(_SassStamp)" />
<MakeDir Directories="$(IntermediateOutputPath)" />
<Touch Files="$(IntermediateOutputPath)sass.stamp" AlwaysCreate="true" />
<ItemGroup>
<Content Include="wwwroot/css/site.css" Exclude="@(Content)" />
<Content Include="wwwroot/css/site.css.map" Exclude="@(Content)" Condition="'$(Configuration)' == 'Debug'" />
</ItemGroup>
</Target>
## debug
> css:dev
site.css site.css.map
## debug again
site.css site.css.map
## release
> css:build
site.css
## publish
site.css site.css.br site.css.gz
## debug
> css:dev
site.css site.css.map
Değişiklik olmadan ikinci Debug build'i Sass'ı çalıştırmıyor, konfigürasyon değişince her zaman çalışıyor ve yayın paketinde map yok. MakeDir satırı ilk build için gerekli: target BeforeBuild'den önce çalıştığı için obj/Debug/net10.0 henüz yok ve Touch MSB3371 hatasıyla duruyordu. Debug'da uygulamayı başlatıp istediğimde /css/site.css 200 text/css, /css/site.css.map 200 döndü.
Geliştirirken her değişiklikte dotnet build beklemek yerine Sass'ı ayrı bir terminalde izleme modunda çalıştırmak daha hızlı:
npm run css:watch
Sass is watching for changes. Press Ctrl-C to stop.
[2026-11-15 00:54] Compiled Styles/site.scss to wwwroot/css/site.css.
Kendi Script'in: compileAsync, pkg: Importer ve Source Map'i Dosyaya Yazmak
Bir build aracı kullanmıyorsan ya da birkaç tema dosyasını ayrı derlemen gerekiyorsa modern JS API yeterli. Önceki yazının paketini pkg: URL'iyle yüklüyorum; NodePackageImporter (Sass 1.71.0) bu URL'i Node.js'in paket çözümleme kurallarına ve paketin package.json'daki sass export'una göre çözüyor:
// pkg: node_modules ve paketin "sass" export'u üzerinden çözülür
@use "pkg:@bookstore/styles" as bs;
.book-list {
display: grid;
gap: bs.space(3);
@include bs.up("md") {
grid-template-columns: repeat(3, 1fr);
}
}
CLI'da importer'ı açmak gerekiyor; açılmazsa Error: Can't find stylesheet to import. alıyorsun, npx sass --pkg-importer=node 12/src/app.scss ise derliyor. Script:
// modern JS API ile küçük bir build script'i: pkg: importer, CSS'in yanına yazılan source map
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, relative } from "node:path";
import { fileURLToPath } from "node:url";
import * as sass from "sass";
const input = "12/src/app.scss";
const output = "12/out/app.css";
const result = await sass.compileAsync(input, {
importers: [new sass.NodePackageImporter()],
style: "compressed",
sourceMap: true,
sourceMapIncludeSources: false,
});
console.log(`first source as returned: ${result.sourceMap.sources[0]}`);
// API mutlak file: URL'leri döndürür; onları CSS dosyasına göre göreli yap
const map = {
...result.sourceMap,
file: "app.css",
sources: result.sourceMap.sources.map((url) => relative(dirname(output), fileURLToPath(url))),
};
await mkdir(dirname(output), { recursive: true });
await writeFile(output, `${result.css}\n/*# sourceMappingURL=app.css.map */\n`);
await writeFile(`${output}.map`, JSON.stringify(map));
console.log(`${output}: ${result.css.length} bytes, ${result.loadedUrls.length} files loaded`);
console.log(`sources: ${map.sources.join(", ")}`);
first source as returned: file:///home/user/bookstore/lab/12/src/app.scss
12/out/app.css: 105 bytes, 7 files loaded
sources: ../src/app.scss, ../../node_modules/@bookstore/styles/src/_breakpoints.scss
İki ayrıntı CLI'dan farklı. Birincisi API'nin dosya yazmaması: CSS'i, map'i ve sourceMappingURL yorumunu sen yazıyorsun. İkincisi sources: API derleyen makinedeki mutlak file: URL'lerini döndürüyor. Map'i olduğu gibi yazarsan hem başka makinede çalışmıyor hem de build sunucusunun klasör yapısını, bazen kullanıcı adını herkese açıyor. Script bu yüzden yolları CSS dosyasına göre göreli yapıyor. loadedUrls watch modu kurmak isteyene hangi dosyaların izleneceğini veriyor.
Üretimde Dikkat
Map'i yayından ayır. Angular'ın hidden source map'i yalnız yorumu kaldırıyor; .map dosyaları dist'te. Deploy script'i *.map dosyalarını sunucuya kopyalamamalı, hata takip aracına ayrı bir adımda yüklemeli. ASP.NET Core'da yukarıdaki Content Remove aynı işi yapıyor.
Production CSS'ini doğrula. Yayın paketindeki CSS'in compressed olduğunu ve sourceMappingURL içermediğini CI'da tek satırla kontrol edebilirsin: grep -L sourceMappingURL out/wwwroot/css/*.css her dosyayı listelemeli. Bu yazının başındaki hata ancak böyle bir kontrolle yakalanıyordu; build her seferinde "succeeded" diyordu.
Derleyiciyi bilinçli seç. Angular sass-embedded'i kendisi kullanıyor; Vite'ta ve kendi script'lerinde sass-embedded kurmak çok dosyalı projelerde build süresini belirgin şekilde kısaltıyor. Native binary'nin çalışamadığı bir ortamda sass paketi aynı CSS'i üretiyor.
Sık Düşülen Tuzaklar
additionalData ile CSS üreten bir dosyayı enjekte etmek. additionalData'ya @use "theme"; eklediğimde (tema dosyası :root custom property'leri üretiyor) Vite'ın minify edilmemiş çıktısında 41 tane :root bloğu oluştu ve CSS 7,55 kB'tan 10,71 kB'a çıktı; her stil dosyası temanın kendi kopyasını aldı. Production build'de Vite'ın varsayılan CSS minifier'ı Lightning CSS bu birebir aynı blokları birleştirdi ve boyut değişmedi. Bu bir garanti değil, minifier'ın yaptığı bir optimizasyon: dev server'da 41 kopya duruyor, Sass derlemesi 41 kez yapılıyor ve serinin dördüncü yazısında gördüğümüz gibi Angular'da component'e özel selector'ler yüzünden kopyalar birleştirilemiyor. additionalData'ya yalnız CSS üretmeyen @use satırları girer. Bir de hata mesajlarında satır numaraları kayıyor: eklenen satır yüzünden dosyanın ilk satırındaki bir hata 2:1 olarak raporlanıyor.
Source map'i production'a public olarak koymak. Map, sourcesContent açıksa Sass kaynağının tamamını, değilse en azından dosya ve klasör yapısını açıyor. Yazının başındaki ASP.NET Core örneğinde map'in Brotli ve gzip sürümleri bile yayına gitti.
webpack döneminden kalan ~ önekli import'lar. Eski projelerde @use "~@bookstore/styles" gibi satırlar webpack'in sass-loader'ına "bunu node_modules'ta ara" diyordu. Vite 8'de de Angular 22.2'de de bu satır Can't find stylesheet to import. hatası veriyor. Çözüm öneki kaldırmak: Angular paketi önek olmadan buluyor, Vite'ta ve kendi script'inde ise pkg: importer'ı ya da node_modules'u load path olarak vermek gerekiyor.
Küçük Bir Deneme
★☆☆ 12/src/main.scss'i --embed-sources ile ve onsuz derle, iki .map dosyasının boyutunu karşılaştır. Hangi durumda DevTools kaynağı göstermek için sunucudan .scss dosyasını istemek zorunda kalır?
★★☆ Vite projesinde additionalData'yı bir function'a çevir: (source, filename) => … imzasıyla yalnız src/components altındaki dosyalara @use satırını eklesin. main.scss'te paket gerektiğinde ne olduğunu ve hata mesajındaki satır numarasının nasıl değiştiğini gözle.
★★★ bookstore-web için bir deploy adımı yaz: dist'teki .map dosyalarını ayrı bir klasöre taşısın, kalan CSS ve JS dosyalarında sourceMappingURL geçmediğini doğrulasın ve bir tane bile bulursa sıfırdan farklı bir exit code ile dursun. Hidden source map'i kapatıp adımın gerçekten kırıldığını göster.
Bu yazıyla Sass/SCSS serisi tamamlanıyor. İlk yedi yazıda Sass'ın hâlâ neden gerekli olduğunu, module sistemini, theming'i, stil mimarisini ve Dart Sass 2.0'a hazırlığı gördük; son beş yazıda built-in module'leri, responsive mixin ve utility üretimini, test edilen bir iç kütüphaneyi ve build entegrasyonunu ekledik. Utility-first yaklaşımı Tailwind CSS serisinde, bundler'ların geri kalanını Build Araçları serisinde bulabilirsin.