Skip to content

Sass abstracts 目錄:變數、mixin 與 function 的分層

Sass abstracts 目錄的分層與載入順序

7-1 架構 裡,abstracts 是唯一一個 不產生任何 CSS 的目錄。它裡面全是定義(變數、混入函式),也就是整個專案共用的「樣式詞彙」。

檔案清單

abstracts/
├── _index.scss        入口,全是 @forward
├── _tokens.scss       設計代幣:色彩、間距、字級、圓角
├── _breakpoints.scss  斷點 map 與 respond-to
├── _functions.scss    rem()、space()、色彩取值
├── _mixins.scss       truncate、visually-hidden 等工具
└── _placeholders.scss %placeholder(用 @extend 才需要)

一條規則都不要放

這裡放進 CSS 規則會出問題

scss
// abstracts/_mixins.scss
@mixin center { display: flex; }

.debug-outline { outline: 1px solid red; }   // ❌ 不該在這裡

模組系統會保證每個模組只編譯一次,所以不會像 @import 時代那樣重複輸出十份。但這條規則的 輸出位置 會變成「第一個載入 abstracts 的檔案那裡」,完全不受你控制,也違反這個目錄的職責。

想要除錯用的樣式,放 base 或獨立的 _debug.scss

相依順序

abstracts 內部有先後:

_tokens.scss        ← 純資料,不依賴任何東西

_functions.scss     ← 用到 tokens 的 map

_breakpoints.scss   ← 用到 functions

_mixins.scss        ← 用到上面全部

入口檔照這個順序轉發:

scss
// abstracts/_index.scss
@forward "tokens";
@forward "functions";
@forward "breakpoints";
@forward "mixins";

每個檔案還是要自己載入相依

scss
// abstracts/_functions.scss
@use "sass:map";
@use "tokens";        // ← 不能省

@function space($key) {
  @return map.get(tokens.$spacing, $key);
}

不要依賴入口檔的順序,模組系統要求每個檔案明確宣告自己用到什麼,見 @use

_tokens.scss:純資料

scss
// abstracts/_tokens.scss
$spacing: (0: 0, 1: 4px, 2: 8px, 3: 16px, 4: 24px, 5: 32px) !default;

$radius: (sm: 4px, md: 8px, full: 999px) !default;

$colors: (
  brand:   #2965f1,
  danger:  #e03131,
  text:    #212529,
  surface: #ffffff,
) !default;

$font-size: (sm: 14px, base: 16px, lg: 20px, xl: 24px) !default;

全部加 !default,這樣使用這套樣式的專案就能用 @use ... with 換掉,見 變數旗標

什麼該加、什麼不該加

預期會被調整的設定都加:斷點、間距、主色。純粹內部推導的中間值不要加,加了反而讓人以為那是對外的設定項。

_functions.scss:安全取值

scss
// abstracts/_functions.scss
@use "sass:map";
@use "sass:math";
@use "tokens";

@function space($key) {
  @if not map.has-key(tokens.$spacing, $key) {
    @error "間距 #{$key} 不存在。可用的有:#{map.keys(tokens.$spacing)}。";
  }
  @return map.get(tokens.$spacing, $key);
}

@function color($key) {
  @if not map.has-key(tokens.$colors, $key) {
    @error "色彩 #{$key} 不存在。可用的有:#{map.keys(tokens.$colors)}。";
  }
  @return map.get(tokens.$colors, $key);
}

@function rem($px, $base: 16px) {
  @return math.div($px, $base) * 1rem;
}

每個 map 都配一個取值函式,打錯字就在編譯期爆炸,而不是安靜地少一行樣式。理由見 sass:map

_mixins.scss:真正共用的才放

scss
// abstracts/_mixins.scss
@mixin truncate($lines: 1) {
  @if $lines == 1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  } @else {
    display: -webkit-box;
    -webkit-line-clamp: $lines;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

@mixin visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

只有一個元件會用的混入不要放這裡

@mixin card-shadow 就放在 _card.scss 裡。abstracts跨元件共用 的東西,什麼都往這裡塞會變成新的垃圾桶。

使用端

scss
// components/_card.scss
@use "../abstracts" as a;

.card {
  padding: a.space(3);
  border-radius: a.radius(md);
  background: a.color(surface);

  &__title {
    font-size: a.rem(20px);
    @include a.truncate(2);
  }

  @include a.respond-to(md) {
    padding: a.space(4);
  }
}

一行 @use 就有全部工具可用。

載入整個目錄,不要個別載入

個別載入雖然更精確,但 abstracts 不產生任何 CSS,省不下任何體積,只換來更多要維護的載入語句。

前綴用什麼

寫法評價
@use "../abstracts" as a;最短,團隊統一即可
@use "../abstracts" as t;t 代表 token,語意好一點
@use "../abstracts" as *;省事,但看不出來源

as * 在這裡風險最高

abstracts 的成員最多,全部倒進全域最容易撞名。保留一個字母的前綴,成本很低。@use

常見問題

為什麼 abstracts 裡不能寫 CSS 規則?

因為每個元件檔都會載入它。雖然模組系統保證每個模組只編譯一次,不會像舊寫法那樣重複輸出,但把會產生樣式的東西放在這裡會讓載入順序影響產出,違反這個目錄的單一職責。

abstracts 裡的檔案之間有順序嗎?

有相依順序。函式會用到變數、混入會用到函式,所以入口檔的轉發要照這個順序排。同時每個檔案也要自己載入它用到的模組,不能依賴入口檔的順序。

哪些變數該加 !default?

所有預期會被專案調整的設定都該加,例如斷點、間距刻度、主色。純粹內部推導出來的中間值則不必,加了反而讓人以為那是對外的設定項。

元件檔要載入整個 abstracts 還是個別檔案?

載入整個目錄比較實用,一行就有全部工具可用,也不必在新增相依時改載入語句。個別載入雖然更精確,但因為 abstracts 不產生 CSS,省不下任何體積。

延伸閱讀