Sass 2026'da Hâlâ Gerekli mi? Native CSS ile Karşılaştırma ve Dart Sass Kurulumu
Evet, ama eskisi kadar her yerde değil: değişken, nesting ve renk/matematik artık native CSS'te var; mixin, döngüyle kural üretme, map'ler, module sistemi ve derleme zamanı hataları ise hâlâ yalnız Sass'ta. Ham SCSS'in tarayıcıda neden tek kural bırakmadığını ölçüyor, sass ile sass-embedded'i karşılaştırıyor ve Sass'ı Angular, Vite ve ASP.NET Core'a ekliyoruz.
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 kendi içinde Sass 1.104.1 getiriyor), Vite 8.3, .NET 10 ve Playwright 1.65'in Chromium ve WebKit build'leriyle denenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için kitap kartının stilini yazan bir ekip arkadaşı, "tarayıcılar artık nesting'i biliyor" diye düşünüp SCSS (Sass'ın CSS'e benzeyen syntax'ı) dosyasını derlemeden doğrudan sayfaya koydu:
$brand: #b45309;
.book-card {
padding: 16px;
border: 1px solid $brand;
&:hover {
border-color: #1d4ed8;
}
&__title {
color: $brand;
}
.price {
font-weight: 700;
}
}
Kartın 16px padding'i gelir mi? Başlık kahverengi olur mu? Fiyat kalın görünür mü? Bir tahmin yap; cevap, Sass'ın 2026'da hâlâ neden var olduğunu anlatmanın en kısa yolu.
Bu serinin ilk yazısında Sass'ın ne olduğunu, native CSS'in son yıllarda hangi alanları kapattığını, Sass'ın hangi işleri hâlâ daha iyi yaptığını ve Angular, Vite ve ASP.NET Core projelerine nasıl ekleneceğini konuşuyoruz. Native CSS özelliklerinin kendisi (nesting, custom property, @layer, renk fonksiyonları) "CSS3: Flexbox, Grid ve Modern CSS" serisinde anlatıldı; burada yalnız Sass karşılığıyla farklarına bakıyoruz.
Sass Bir Derleme Adımıdır: Tarayıcı SCSS Görmez
Tahminin cevabını tarayıcıya sordum. Küçük bir Playwright script'i aynı dosyayı Chromium ve WebKit'e üç şekilde veriyor: ham SCSS, ilk satırı ($brand) silinmiş ham SCSS ve Sass'ın derlediği CSS. Her seferinde style sheet'te kaç kural kaldığını ve üç element'in computed değerlerini yazıyor:
node scripts/01-raw-scss.mjs
chromium raw .scss rules=0 padding=0px title=rgb(0, 0, 0) price=400
chromium raw, no $ rules=1 padding=16px title=rgb(0, 0, 0) price=700
chromium compiled rules=4 padding=16px title=rgb(180, 83, 9) price=700
webkit raw .scss rules=0 padding=0px title=rgb(0, 0, 0) price=400
webkit raw, no $ rules=1 padding=16px title=rgb(0, 0, 0) price=700
webkit compiled rules=4 padding=16px title=rgb(180, 83, 9) price=700
Ham SCSS'te sayfaya hiçbir kural ulaşmıyor. Sebep ilk satır: CSS parser'ı için en üstteki $brand: #b45309; bir değişken tanımı değil, bir selector'ün başı. Parser ilk {'ye kadar her şeyi aynı selector'e katıyor, ortaya $brand: #b45309; .book-card gibi geçersiz bir selector çıkıyor ve kural bütün bloğuyla birlikte atılıyor. Tek bir Sass satırı, kartın bütün stilini sessizce yok etti; konsolda hata da yok.
İlk satırı silince tablo değişiyor ve native nesting'in nereye kadar Sass'a benzediği görünüyor. &:hover ve .price tarayıcıda da çalışıyor, çünkü native nesting'de & ebeveyn selector'ü temsil ediyor. &__title ise çalışmıyor: tarayıcı için & bir metin parçası değil, bir selector; ona __title yapıştırılamaz. color: $brand de geçersiz bir değer olduğu için atılıyor. Bu farkın ayrıntısı CSS3 serisinin "Native CSS Nesting ve @scope" yazısında ölçüldü.
Sass'ın derlediği CSS'te ise dört kural var ve hepsi beklendiği gibi çalışıyor:
npx sass 01/book-card.scss
.book-card {
padding: 16px;
border: 1px solid #b45309;
}
.book-card:hover {
border-color: #1d4ed8;
}
.book-card__title {
color: #b45309;
}
.book-card .price {
font-weight: 700;
}
Sass'ı bir derleme zamanı aracı olarak düşünmek gerekiyor; TypeScript nasıl JavaScript'e derleniyorsa SCSS de CSS'e derleniyor. Tarayıcıya giden dosyada $brand, mixin, döngü ya da @use kalmaz; hepsi derleme sırasında çözülür. Bu iki şey demek: Sass değişkeni runtime'da değiştirilemez, ama Sass'ın yaptığı her işin tarayıcıda maliyeti de sıfırdır. Hata da derleme anında çıkar.
SCSS ve Indented Syntax
Sass'ın iki syntax'ı var. Yukarıdaki .scss dosyası SCSS syntax'ı: CSS'in süslü parantezlerini ve noktalı virgüllerini kullanıyor, bu yüzden geçerli her CSS dosyası aynı zamanda geçerli bir SCSS dosyası. Daha eski olan indented syntax (.sass uzantılı) ise parantez ve noktalı virgül yerine girintiye bakıyor:
$brand: #b45309
.book-card
padding: 16px
border: 1px solid $brand
&:hover
border-color: #1d4ed8
&__title
color: $brand
.price
font-weight: 700
İki dosyayı derleyip çıktıları diff ile karşılaştırdım; fark yok. Hangi syntax'ı seçtiğin yalnız yazım tercihi. Bu seride SCSS kullanıyoruz: Angular CLI, Vite ve hemen her kütüphane (Angular Material dahil) SCSS ile geliyor, mevcut bir CSS dosyasını .scss yapmak da uzantıyı değiştirmek kadar kolay.
Native CSS Neyi Kapattı, Sass Neyi Hâlâ Daha İyi Yapıyor?
2015'te Sass kullanmanın üç büyük sebebi değişken, nesting ve renk/matematik fonksiyonlarıydı. 2026'da üçünün de native karşılığı var. Aşağıdaki tabloda her satırın "native" sütunu CSS3 serisinde anlatılan özelliklere karşılık geliyor:
| İhtiyaç | Sass | Native CSS (Kasım 2026) |
|---|---|---|
| Değişken | $brand, derleme zamanı | --brand ve var(), runtime; cascade ve inheritance'a katılır |
| Nesting | &__title dahil metin birleştirme | Baseline; & selector'dür, metin birleştirme yok |
| Renk | sass:color module'ü | color-mix(), relative color, oklch() |
| Matematik | sass:math | calc(), round(), mod(), clamp() |
| Mixin ve parametreli tekrar | @mixin, @include, @content | Yok; CSS mixin'leri taslak spesifikasyonda, Baseline değil |
| Kullanıcı fonksiyonu | @function | @function yalnız Chromium'da, Baseline değil |
| Döngüyle kural üretme | @each, @for, map'ler | Yok |
| Dosyalara bölme | @use/@forward, namespace, private member | @import (ek istek, namespace yok) |
| Hata yakalama | @error, derleme kırılır | Geçersiz kural sessizce atılır |
Tablonun üst yarısında karar artık çoğu zaman native taraftan yana. Runtime'da değişmesi gereken her değer (tema renkleri, kullanıcının seçtiği yazı boyutu) custom property olmalı; Sass değişkeni bunu yapamaz. Basit nesting için de derleme adımına gerek yok. Alt yarıda ise Sass'ın hâlâ rakibi yok. Aşağıdaki dosya Bookstore'un katalog sayfasından; dört şeyi aynı anda yapıyor:
@use "sass:map";
// derleme zamanı verisi: tarayıcıya hiç gitmez
$breakpoints: (
"md": 48rem,
"lg": 64rem,
);
@mixin from($name) {
@if not map.has-key($breakpoints, $name) {
@error "Unknown breakpoint '#{$name}'. Expected one of: #{map.keys($breakpoints)}";
}
@media (width >= map.get($breakpoints, $name)) {
@content;
}
}
// döngüyle üretim: 4 stok durumu için 4 badge class'ı
$stock-states: (
"in-stock": #15803d,
"low": #b45309,
"preorder": #1d4ed8,
"out": #6b7280,
);
@each $state, $color in $stock-states {
.stock-badge--#{$state} {
border-color: $color;
color: $color;
}
}
.book-list {
display: grid;
gap: 16px;
@include from("md") {
grid-template-columns: repeat(2, 1fr);
}
@include from("lg") {
grid-template-columns: repeat(4, 1fr);
}
}
Breakpoint'ler tek bir map'te duruyor ve from() mixin'i onları isimle kullanıyor; native CSS'te media query içinde var() kullanılamadığı için breakpoint değerlerini her media query'ye elle yazmak gerekirdi. @each dört badge class'ını tek döngüyle üretiyor. Derleme çıktısının ilgili kısmı:
.stock-badge--in-stock {
border-color: #15803d;
color: #15803d;
}
...
@media (width >= 48rem) {
.book-list {
grid-template-columns: repeat(2, 1fr);
}
}
@media (width >= 64rem) {
.book-list {
grid-template-columns: repeat(4, 1fr);
}
}
Dördüncü iş en az konuşulanı ama ekipte en çok işe yarayanı: derleme zamanı hatası. Biri from("xl") yazarsa build kırılıyor:
Error: "Unknown breakpoint 'xl'. Expected one of: md, lg"
╷
41 │ @include from("xl") {
│ ^^^^^^^^^^^^^^^^^^^
╵
01/typo.scss 41:5 root stylesheet
Aynı yazım hatası native CSS'te sessizce atılan bir kural olurdu ve ancak biri sayfayı geniş ekranda açınca fark edilirdi. Module sistemi ve private member'lar (serinin dördüncü yazısı) da bu listeye giriyor: büyük bir stil codebase'te hangi değişkenin kime ait olduğunu ve neyin dışarı açıldığını Sass kontrol ediyor.
Kurulum: sass mı, sass-embedded mi?
Sass'ın tek resmî implementasyonu Dart Sass. C++ ile yazılmış LibSass'ın 23 Ekim 2025'te end-of-life olduğu Sass blogunda duyuruldu, ona dayanan node-sass paketi npm'de zaten "artık desteklenmiyor, sass ya da sass-embedded kullan" uyarısıyla duruyor. Eski bir projede node-sass görüyorsan ilk iş onu değiştirmek; module sistemi ve yeni renk fonksiyonları orada hiç yok.
npm'de Dart Sass'ın iki paketi var. sass, Dart kodunun JavaScript'e derlenmiş hâli; hiçbir native bağımlılığı yok, her yerde çalışıyor. sass-embedded aynı JavaScript API'sini sunuyor ama derlemeyi platformuna uygun native bir Dart Sass sürecine yaptırıyor. İkisinin farkını Bookstore'a benzemeyen, kasıtlı olarak ağır bir dosyayla ölçtüm: @for ile 3000 kural üreten ve her kuralda renk ve matematik fonksiyonu çağıran 300 KB'lık bir çıktı. Script dosyayı her paketle önce bir kez ısınmak için, sonra 30 kez derleyip ortalamayı yazıyor:
npm install --save-dev --save-exact sass@1.108.2 sass-embedded@1.108.2
node scripts/01-bench.mjs
sass (pure JS) 43 ms per compile
sass-embedded (Dart VM) 26 ms per compile
Ölçümü birkaç kez tekrarladım; mutlak sayılar makinenin o anki yüküne göre 38 ile 75 ms arasında oynadı ama sass-embedded her seferinde yüzde 30 ile 45 daha hızlıydı. Fark, binlerce dosyalı bir Angular projesinde ya da Angular Material temasını her build'de yeniden derleyen bir pipeline'da hissediliyor. CLI'dan tek dosya derlerken ya da native binary'nin çalışamadığı bir ortamda (ör. tarayıcıda çalışan bir playground) sass paketi yeterli.
Angular, Vite ve ASP.NET Core'da Sass
Angular'da Sass seçimi proje oluşturulurken yapılıyor. Bookstore'un Angular istemcisini bu seri için şu komutla oluşturdum:
npx @angular/cli@22.2.7 new bookstore-web --style=scss --ssr=false
CLI src/styles.scss ile her component için bir .scss dosyası oluşturuyor ve angular.json'a iki ayar yazıyor: yeni component'lerin .scss ile üretilmesi ve template içindeki inline stillerin de SCSS olarak okunması.
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
...
"options": {
"inlineStyleLanguage": "scss",
"styles": ["src/styles.scss"]
}
Ayrıca sass paketi kurmaya gerek yok; @angular/build onu kendi bağımlılığı olarak getiriyor ve varsayılan olarak sass-embedded ile derliyor:
npm ls sass sass-embedded
bookstore-web@0.0.0
+-- @angular/build@22.2.7
| +-- sass-embedded@1.104.1
| +-- sass@1.104.1
...
Buradaki sürüm önemli: Angular projesinde stil kodunu @angular/build'in sabitlediği Sass sürümü derliyor, senin package.json'ına yazdığın değil. Bu ayrıntıya serinin 07. yazısında, Dart Sass 2.0'a hazırlanırken geri döneceğiz. Bookstore'un ilk hâli bir header, dört kitaplık bir katalog ve BookCard component'inden oluşuyor; component'in stil dosyası şimdilik yukarıdaki kart stiline çok benziyor ve ng build ile ng test temiz geçiyor.
Vite'ta durum daha da basit: .scss dosyası import edildiğinde Vite derleyiciyi kendisi arıyor. Paket yoksa build şu hatayla duruyor:
[plugin vite:css] /home/user/bookstore-vite/src/book-card.scss
Error: Preprocessor dependency "sass-embedded" not found. Did you install it? Try `npm install -D sass-embedded`.
npm install -D sass-embedded yeterli; Vite 8 config dosyasına dokunmadan aynı CSS'i üretiyor. Hata mesajının sass yerine sass-embedded önermesi de yukarıdaki ölçümle uyumlu.
ASP.NET Core tarafında bir frontend build zinciri yoksa en sade yol Sass CLI'ını npm script'i olarak tanımlayıp MSBuild'den çağırmak. Bookstore.Web projesinin package.json'ı:
{
"name": "bookstore-web-styles",
"private": true,
"scripts": {
"css:build": "sass Styles/site.scss wwwroot/css/site.css --style=compressed --no-source-map",
"css:watch": "sass Styles/site.scss wwwroot/css/site.css --watch"
},
"devDependencies": {
"sass": "1.108.2"
}
}
Proje dosyasına iki target ekledim. Birincisi node_modules yoksa paketleri kuruyor; ikincisi yalnız bir .scss dosyası site.css'ten yeniyse Sass'ı çalıştırıyor:
<ItemGroup>
<SassSource Include="Styles/**/*.scss" />
</ItemGroup>
<!-- paketler ilk Sass derlemesinden önce bir kez kurulur -->
<Target Name="NpmInstall" BeforeTargets="CompileSass" Condition="!Exists('node_modules')">
<Exec Command="npm ci" />
</Target>
<!-- 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:build" />
<ItemGroup>
<Content Include="wwwroot/css/site.css" Exclude="@(Content)" />
</ItemGroup>
</Target>
Target içindeki Content satırı önemli. MSBuild wwwroot altındaki dosyaları target'lar çalışmadan önce listeliyor; temiz bir checkout'ta site.css o anda henüz yok. Bu satır olmadan ilk build'de dosya MapStaticAssets'in manifest'ine girmiyor. Temiz bir klasörde dotnet build çıktısının son satırları:
dotnet build
> css:build
> sass Styles/site.scss wwwroot/css/site.css --style=compressed --no-source-map
Bookstore.Web -> C:\src\bookstore\aspnet\Bookstore.Web\bin\Debug\net10.0\Bookstore.Web.dll
Build succeeded.
0 Warning(s)
0 Error(s)
İkinci dotnet build'de css:build satırı görünmüyor; bir .scss dosyasını değiştirince yeniden çalışıyor. Uygulamayı başlatıp /css/site.css'i istediğimde 200 ve text/css döndü. MapStaticAssets'in fingerprint ve sıkıştırma tarafı ASP.NET Core MVC serisinin "Static Asset'ler ve UI Altyapısı" yazısında anlatıldı. NuGet'ten Dart Sass'ı paketleyen topluluk paketleri de var; Node.js'i build makinesinden tamamen çıkarmak istiyorsan onlara bakabilirsin, ama Sass sürümünü artık paketin bakımcısının takvimi belirler.
Ne Zaman Sass Eklememeli?
Sass bir bağımlılık, derleme adımı ve ekibe öğretilecek bir dil daha demek. Yeni bir projede şu durumlarda eklemem:
- Stil kodu birkaç yüz satır ve ortak breakpoint, döngüyle üretilen class ya da paylaşılan mixin ihtiyacı yok: native nesting ve custom property yetiyor.
- Proje Tailwind CSS v4 kullanıyor: Tailwind'in dokümanı v4'ün Sass, Less ya da Stylus ile birlikte kullanılmak için tasarlanmadığını açıkça söylüyor; Tailwind kendisi import, prefix ve nesting'i hallediyor (ayrıntısı Tailwind CSS serisinde).
- Tüm değerler runtime'da değişiyor (kullanıcı teması, white-label renkler): bunlar zaten custom property olmak zorunda; Sass olsa olsa onları üretmeye yardım eder.
- Derleme adımı olmayan bir ortam (CMS'e yapıştırılan CSS, e-posta şablonu): ya Sass'ı ayrı bir build'de çalıştırırsın ya da hiç kullanmazsın.
Buna karşılık Angular projesinde Angular Material kullanıyorsan, design token'lardan çok sayıda class ya da custom property üretiyorsan, ya da yüzlerce stil dosyasını birden fazla ekip paylaşıyorsa Sass kendini hızla amorti ediyor. Bookstore'un Angular istemcisi bu ikinci gruba giriyor.
Üretimde Dikkat
Sass sürümünü sabitle. Yukarıdaki package.json'da "sass": "1.108.2" yazmamın sebebi bu: Dart Sass 1.x içinde bile "potentially breaking bug fix" notuyla çıkan sürümler var; 1.106.0'ın CHANGELOG'unda bu notla altı madde var. Lock dosyası olmadan CI'da bir sabah farklı bir CSS üretebilirsin.
Derleme çıktısını ölç. Sass'ın yazdığın kodu çoğalttığı yerler (mixin, @each, @extend) üretilen CSS'i beklenmedik şekilde büyütebilir; benchmark için yazdığım on satırlık dosyanın 300 KB'lık çıktı vermesi bunun abartılı bir örneği. Build'den sonra dist'teki CSS boyutunu izle; Angular'ın yeni projeyle gelen anyComponentStyle bütçesi (production'da 4 kB'ta uyarı, 8 kB'ta hata) component başına bu kontrolü zaten yapıyor.
Deprecation uyarılarını görmezden gelme. Dart Sass 2.0 Aralık 2026'ya hedefleniyor ve bugünkü uyarıların çoğu orada hataya dönüşecek. Build log'unda DEPRECATION WARNING satırları görüyorsan serinin 07. yazısını okumadan önce bile sayılarını not et.
Sık Düşülen Tuzaklar
SCSS dosyasını derlemeden yayınlamak. Bu yazının başındaki deneyde gördüğün gibi hata vermiyor, sadece kuralları yok ediyor. ASP.NET Core'da wwwroot altına .scss koyup <link> ile çağırmak bunun en yaygın şekli.
Sass değişkenini runtime değeri sanmak. $brand'ı JavaScript'ten değiştiremezsin, media query'ye göre de değiştiremezsin; derleme bitince ortada değişken kalmıyor. Runtime'da değişecek her şey custom property olmalı. İkisini birlikte kullanmayı bir sonraki yazıda göreceğiz.
Native nesting ile Sass nesting'inin aynı şey olduğunu varsaymak. Sass'tan native CSS'e geçerken &__title gibi BEM (Block, Element, Modifier; class isimlendirme yöntemi) birleştirmeleri çalışmayı bırakır ve bunu sana kimse söylemez.
Tailwind v4 ile Sass'ı aynı dosyada kullanmaya çalışmak. İkisi de stil dosyasını işleyen bir araç; sıralarını ve birbirlerinin syntax'ını bilmedikleri için sonuç tahmin edilemez.
Küçük Bir Deneme
★☆☆ 01/book-card.scss'teki $brand satırını .book-card bloğunun içine taşı ve node scripts/01-raw-scss.mjs'i yeniden çalıştır. Ham SCSS'te kaç kuralın hayatta kaldığını ve sebebini açıkla.
★★☆ 01/what-sass-adds.scss'e "xl": 80rem breakpoint'ini ve beşinci bir stok durumu ekle. Derleme çıktısında kaç kural ve media query olduğunu say, sonra aynı CSS'i elle yazmanın kaç satır tutacağını hesapla.
★★★ Bookstore.Web'e ikinci bir giriş dosyası (Styles/admin.scss) ekle. css:build script'ini Sass'ın Styles:wwwroot/css şeklindeki klasör-klasöre derleme modunu kullanacak şekilde değiştir ve MSBuild target'ının Outputs'unu iki dosyayı da kapsayacak şekilde güncelle. Temiz bir checkout'ta ilk build'den sonra iki CSS dosyasının da manifest'e girdiğini doğrula.
Bir sonraki yazıda Sass'ın temel syntax'ına giriyoruz: $variable ile custom property'yi birlikte kullanmak, nesting ve & ile BEM, interpolation, placeholder'lar ve fazla nesting'in derlenmiş CSS'te nasıl bir specificity ve boyut sorununa dönüştüğü.