PikaCSS 如何產生 CSS
從一次 pika() 呼叫到產生出來的樣式表之間,引擎做了哪些事。了解這些規則,就能解釋 pika.gen.css 裡的每一個位元組。
管線
- 建置外掛會掃描納入的檔案,找出
pika()呼叫。 - 每一次呼叫的引數都會在建置時期求值,並傳給引擎。
- 引擎會把每一組
[selector, property, value]三元組擷取成一筆原子樣式,並給它一個簡短的 class ID(pk-a、pk-b、……)。 - 外掛會把你原始碼裡的呼叫替換成最終產生的 class 名稱字串常值。
- 所有蒐集到的原子樣式都會渲染進產生出來的 CSS 檔案,也就是
import 'pika.css'會解析到的那個檔案。
去除重複
原子樣式是以它的 [selector, property, value] 內容作為鍵值。同一條宣告不論用在你專案裡的哪個地方(不管是不是同一個檔案),都會解析到同一個 class:
const cardClass = pika({ display: 'flex', padding: '1rem' })
const bannerClass = pika({ display: 'flex', color: 'white' })
export { bannerClass, cardClass }@layer utilities {
.pk-a {
display: flex;
}
.pk-b {
padding: 1rem;
}
.pk-c {
color: white;
}
}兩次呼叫共用 .pk-a { display: flex; }。輸出的大小取決於不重複宣告的數量,而不是呼叫地點的數量。
每個屬性由最後宣告勝出
在同一次 pika() 呼叫裡(涵蓋它的所有引數,包含展開後的 shortcut),對同一組 [selector, property] 配對,較後面的定義會取代較前面的定義;最終只會輸出最後那個值:
const overriddenClass = pika(
{ color: 'red', padding: '1rem' },
{ color: 'blue' },
)
export { overriddenClass }@layer utilities {
.pk-a {
padding: 1rem;
}
.pk-b {
color: blue;
}
}color: 'red' 永遠不會出現在樣式表裡。正是這一點,讓 pika('btn', { backgroundColor: 'purple' }) 這類組合行為表現得像覆寫,而不是衝突。
null 會移除一個屬性
把 null(或 undefined)當作值傳入,會移除同一次呼叫裡對該屬性較早的任何定義;這在你想從 shortcut 或共用基礎樣式裡減去某一條宣告時很有用:
const quietCardClass = pika(
{ padding: '1rem', boxShadow: '0 1px 2px rgb(0 0 0 / 0.2)' },
{ boxShadow: null },
)
export { quietCardClass }@layer utilities {
.pk-a {
padding: 1rem;
}
}引擎會完全捨棄 boxShadow 這條宣告,不會為它產生任何原子 class。
備用值
[value, fallbacks] 元組會在同一條規則裡先渲染備用值(fallback)、最後才渲染主要值,這樣支援主要值的瀏覽器就會使用主要值,較舊的瀏覽器則沿用備用值:
const fullHeightClass = pika({
height: ['100dvh', ['100vh']],
})
export { fullHeightClass }@layer utilities {
.pk-a {
height: 100vh;
height: 100dvh;
}
}輸出順序
產生出來的樣式表是決定性的:
- 每一筆原子樣式都會得到一個渲染權重:當它只用到預設選擇器(也就是單純的 class 規則)時為
0,否則就是巢狀選擇器區段的數量(一條包在@media裡、又包在偽選擇器裡的規則,權重會比單純規則高)。樣式會依權重由小到大排序,因此較簡單的規則一定排在前面。 - 在相同權重下,樣式會保持它們的註冊順序(排序是穩定的)。
- 簡寫屬性/個別屬性(longhand)的衝突受到順序保護:當同一次呼叫裡某個屬性與較早的屬性效果重疊時(例如先
padding再paddingTop),引擎會把較後面的那個標記為順序敏感,並且只有在既有 class 已經排在它必須覆寫的那些 class 後面時,才會重複使用該 class;否則就會建立一個新的 class。這就是為什麼無論 class 在別處如何重複使用,paddingTop都能可靠地勝過padding。
一個實際的結果是:當同一個元素上有兩個獨立的原子 class 設定了同一個屬性時,決定勝負的是樣式表裡的位置,而不是你 class 屬性裡的順序。請優先使用單次呼叫的組合(最後者勝出),而不要把互相衝突的 class 疊在一起。
Layer 分組
原子樣式會包在一個 CSS @layer 區塊裡。引擎預設會宣告兩個 layer,也就是 preflights(權重 1)與 utilities(權重 10),並輸出一段依權重排序的順序宣告:
@layer preflights, utilities;- 除非某個定義把
__layer設成另一個已設定的 layer,否則原子樣式都會進入預設的 utilities layer。 - 沒有明確指定 layer 的 preflight,當該 layer 名稱存在於已設定的 layers 裡時,會包進預設的 preflights layer。
- 若某個
__layer值不在已設定的 layers 裡,引擎不會默默採用它:它會輸出一則警告,並把該樣式以不分 layer 的方式渲染。請注意,不分 layer 的 CSS 其層疊優先權高於任何 layer,因此請務必在layers設定裡註冊自訂的 layer。 - 指派到未宣告 layer 的 preflight,會渲染成一個尾端的
@layer區塊,且不會出現在順序宣告裡;依照@layer的語意,它接著會排在所有已宣告 layer 的後面,因此它會勝過所有 layer,而這通常與原本的意圖相反。請改為用明確的權重來註冊該 layer。
關於設定 layer 名稱與權重,請見 Layers。