Sass 效能與產出品質:讓編譯結果不要失控
Sass 的效能問題幾乎不在編譯速度,Dart Sass 編譯幾千行只要幾百毫秒。問題全在產出的那份 CSS。
原因很單純:Sass 寫起來太省力。一個迴圈三行,產出三百條規則;巢狀縮排看起來很整齊,攤平後選取器有五段。你在原始碼看到的份量,與實際產出的份量嚴重脫節。
坑一:巢狀過深
.page .sidebar .menu .item .link { color: #333; }三個代價:優先權 高到難以覆蓋、檔案帶著重複的祖先路徑、樣式綁死 DOM 結構。
對策: 巢狀不超過三層,用 BEM 讓選取器維持一層。用 Stylelint 的 max-nesting-depth 自動把關,見 source map 與 Stylelint。
坑二:@extend 的選取器爆炸
.btn { }
.card .btn { }
.modal .btn:hover { }
.btn-primary { @extend .btn; } // 一次繼承,三處合併繼承鏈一長,選取器數量成長得很快,而且產出的順序不由你決定。
對策: 只 @extend %placeholder,不要繼承真實類別;不確定就 改用 混入。理由見 @extend。
坑三:迴圈的組合爆炸
// 20 類 × 8 個值 × 4 個斷點 = 640 條
@each $prop in $properties {
@each $val in $values {
@each $bp in $breakpoints { }
}
}而專案裡真正用到的可能不到一成。
對策: 只產生會用到的組合、把變體做成可關閉的開關、或改用會按需產生的原子化框架。見 工具類別。
坑四:abstracts 裡誤放 CSS 規則
// abstracts/_mixins.scss
.debug-outline { outline: 1px solid red; } // ❌模組系統 保證只編譯一次,所以不會像 @import 時代那樣重複輸出十份,但這條規則的輸出位置會變得無法控制。
對策: abstracts 只放定義。見 abstracts 該放什麼。
混入的重複不必擔心
.a { display: flex; align-items: center; }
.b { display: flex; align-items: center; }看起來浪費,但這種重複字串正是 gzip 最擅長壓縮的東西,實際傳輸量增加有限。
優化的順序
- 刪掉沒用到的規則 ← 這裡有最大的收穫
- 減少產生的組合數
- 開啟壓縮與 gzip
- 合併重複的媒體查詢 ← 收益最小
多數人跳過第一步直接做第四步,方向錯了。
壓縮
npx sass src/main.scss dist/main.css --style=compressed --no-source-mapVite 與 Webpack 在正式建置時預設就會壓縮。
壓縮不會幫你刪東西
compressed 只去掉空白與換行,重複的規則、沒用到的類別它全部保留。
移除未使用的樣式
// postcss.config.js
module.exports = {
plugins: [
require("@fullhuman/postcss-purgecss")({
content: ["./src/**/*.html", "./src/**/*.vue", "./src/**/*.js"],
safelist: [/^is-/, /^has-/], // JavaScript 動態加的類別要保住
}),
],
};safelist 一定要設
由 JavaScript 動態加上的類別(is-active、has-error)不會出現在靜態掃描裡,沒列進白名單就會被誤刪。
想知道有多少沒用到:Chrome DevTools 的 Coverage 面板,實際操作一遍網站,它會標出每個檔案的未使用比例。
合併媒體查詢
把 @media 寫在元件裡會讓同一個斷點在產出裡出現幾十次。真的想合併:
require("postcss-sort-media-queries")()不要因此改變寫法
「媒體查詢寫在元件裡」的維護價值遠高於那點體積。要合併就交給工具,不要退回把所有斷點樣式集中在檔案尾端的舊做法。見 響應式 mixin。
編譯真的變慢時
| 症狀 | 檢查 |
|---|---|
| 每次存檔都要好幾秒 | 是不是還在用 node-sass?換成 sass |
| 只改一個元件卻全部重編 | 建置工具的 HMR 設定 |
| 產出檔案異常大 | 上面那四個坑 |
| 記憶體吃很兇 | 有沒有巢狀三層以上的迴圈 |
一份檢查清單
- [ ] 巢狀不超過三層
- [ ] 沒有
@extend真實類別 - [ ]
abstracts裡沒有任何 CSS 規則 - [ ] 迴圈產生的組合都真的用得到
- [ ] 沒有用
node-sass - [ ] 沒有殘留的
@debug - [ ] 正式建置有壓縮
- [ ] 用 Coverage 看過未使用比例
CSS 本身的效能主題見 CSS 效能優化。
常見問題
Sass 專案的 CSS 為什麼特別容易肥大?
因為寫起來太省力。一個迴圈就能產生幾百條規則,巢狀讓選取器不知不覺變長,你在原始碼裡看到的份量與實際產出的份量嚴重脫節。
混入的重複輸出需要優化嗎?
通常不需要。重複的宣告字串正是 gzip 最擅長壓縮的東西,實際傳輸增加有限。真正該優化的是那些從來沒被用到的規則。
重複的媒體查詢要合併嗎?
壓縮後影響很小,通常不必特別處理。真的想合併可以在編譯後接一個 PostCSS 外掛,但不要為此改變把媒體查詢寫在元件裡的習慣,那個維護價值更高。
怎麼知道產出的 CSS 有多少沒用到?
用 Chrome DevTools 的 Coverage 面板實際跑一遍網站,它會標示每個檔案有多少比例未被使用。比例太高就該考慮接上移除未使用樣式的工具。