Open 1aron opened 6 months ago
🚧 Under internal discussion
開發 Master UI 第一個 Button 元件後我意識到沒有透過 CSS Layers 管理優先級會導致未預期的樣式覆蓋並使 config.styles 無法支援選取器及媒體查詢。
config.styles
假設 master.css.js:
export default { styles: { btn: 'inline-flex p:4x bg:white:focus bg:black:hover h:32@sm' } }
預期重構後的 style.sheet:
style.sheet
@layer base, preset, theme, style, utility; @layer theme { :root { --text-strong: 0 0 0 } .light { --text-strong: 24 32 48 } .light { --text-light: 95 115 149 } .dark { --text-strong: 239 238 240 } .dark { --text-light: 137 136 138 } } @layer style { .btn { display: inline-flex } /* inline-flex */ .btn { padding: 1rem } /* p:4x */ .btn:hover { background-color: #000000 } /* bg:black:hover */ .btn:focus { background-color: #ffffff } /* bg:white:focus */ @media (min-width: 834px) { /* h:32@sm */ .btn { height: 2rem; } } } @layer utility { .p\:4x { padding: 1rem } .pt\:8x { padding-top: 2rem } } /* anonymous layer */ @keyframes fade-in {}
產生的 style.sheet.cssRules:
style.sheet.cssRules
[ CSSLayerStatementRule, CSSLayerBlockRule { name: 'theme' … }, CSSLayerBlockRule { name: 'style' … }, CSSLayerBlockRule { name: 'utility' … }, CSSKeyframesRule { name: 'fade' … }, CSSKeyframesRule { name: 'flash' … }, ]
@layer base
@master/normal.css
@layer theme
@layer style
@layer utility
CSSLayerBlockRule
.cssRules
@keyframes
css.hasKeyframesRule
enum Layer
enum SyntaxType
config.rules
config.syntaxes
@layer base, preset, theme, style, utility;
css.Rules
css.syntaxes
class LayerRule
現在引入 Layer 概念就必須創建 class Layer 形成多個封閉區來排序各自的 rules。
class Layer
rules
// core.ts class MasterCSS { layerStatementRule = new Rule('layer-statement', [ { text: '@layer base,preset,theme,style,utility' } ], this); themeLayer = new Layer('theme', this); styleLayer = new SyntaxLayer('style', this); utilityLayer = new SyntaxLayer('utility', this); keyframeLayer = new AnonymousLayer('keyframe', this); sheet = new AnonymousLayer('', this); constructor() { this.sheet.rules = [ this.layerStatementRule, this.themeLayer, this.styleLayer, this.utilityLayer, this.keyframeLayer ] } get text(): string { return this.sheet.text } }
// layer.ts class Layer { native?: CSSLayerBlockRule | CSSStyleSheet rules: Rule[] = [] usages: Record<string, number> constructor( public name: string, public css: MasterCSS ) {} insert(rule: Rule, index?: number) {} delete(rule: Rule) {} get text(): string { return '@layer ' + this.name + '{' + this.rules.map((eachRule) => eachRule.text).join('') + '}' } }
layer.insert()
this.rules.push(rule)
index
anonymousLayer
CSSLayerStatementRule
layer.delete()
this.rules.splice(this.rules.indexOf(rule), 1)
usages
themeLayer.usages
--text-purple
styleLayer.usages
btn
utilityLayer.usages
fg:white
keyframeLayer.usages
fade
// anonymous-layer.ts class AnonymousLayer extends Layer { native?: CSSStyleSheet rules: (Rule | Layer)[] = [] constructor( public name: string, public css: MasterCSS ) {} get text(): string { return this.rules.map((eachRule) => eachRule.text).join('') } }
CSSStyleSheet
// syntax-layer.ts class SyntaxLayer extends Layer { native?: CSSLayerBlockRule constructor( public name: string, public css: MasterCSS ) { super() } insert(rule: Rule) {} delete(rule: Rule) {} }
css.insert()
syntaxLayer.insert()
css.delete()
deleteRule
syntaxLayer.delete()
css.remove()
css.add()
style
utility
Rule
// rule.ts class Rule { constructor( public readonly name: string, public natives: NativeRule[] = [], public css: MasterCSS ) {} get text(): string { return this.natives.map((eachNative) => eachNative.text).join('') } }
// syntax-rule.ts class SyntaxRule extends Rule { constructor( public readonly name: string, public natives: NativeRule[] = [], public css: MasterCSS, public readonly RegisteredRule: RegisteredRule, ) { super(name, layer, natives) } }
SyntaxRule
VariableRule extends Rule
KeyframeRule extends Rule
@layer theme {}
--variable
.light
layers[].rules
rule.fixedClass
.bg\:black\:hover:hover {}
.btn:hover {}
!
btn font:semibold!
btn font:semibold
btn-sm@<sm
Description
開發 Master UI 第一個 Button 元件後我意識到沒有透過 CSS Layers 管理優先級會導致未預期的樣式覆蓋並使
config.styles
無法支援選取器及媒體查詢。假設 master.css.js:
預期重構後的
style.sheet
:產生的
style.sheet.cssRules
:@layer base
: 無需操作,通常給@master/normal.css
使用@layer theme
: variables@layer style
: styles@layer utility
: rulesCSSLayerBlockRule
有.cssRules
可以直接插入實作
@keyframes
隨意插入至匿名層css.hasKeyframesRule
相關邏輯不再需要,包含 variableenum Layer
應改為enum SyntaxType
,它壓根就不是 layer,相關變數也要跟著改config.rules
重新命名為config.syntaxes
@layer base, preset, theme, style, utility;
css.Rules
可以重命名為css.syntaxes
全新
class LayerRule
現在引入 Layer 概念就必須創建
class Layer
形成多個封閉區來排序各自的rules
。layer.insert()
實作簡易的this.rules.push(rule)
index
可以插入指定的位置,實際會用到的像是在anonymousLayer
的頂部插入CSSLayerStatementRule
layer.delete()
實作簡易的this.rules.splice(this.rules.indexOf(rule), 1)
usages
:themeLayer.usages
的 keys 可能是--text-purple
styleLayer.usages
的 keys 可能是btn
utilityLayer.usages
的 keys 可能是fg:white
keyframeLayer.usages
的 keys 可能是fade
CSSStyleSheet
並方便統一操縱css.insert()
應改寫為syntaxLayer.insert()
css.delete()
內的deleteRule
函數應改寫為syntaxLayer.delete()
css.delete()
重新命名為css.remove()
以對應css.add()
css.add()
時判斷該 className 屬於哪個style
還是utility
layer重構
Rule
Rule
重新命名為SyntaxRule
並調整 constructor 傳入參數Rule
改為一個共通類SyntaxRule
擴展VariableRule extends Rule
並將相關的 methods 整併KeyframeRule extends Rule
並將相關的 methods 整併@layer theme
@layer theme {}
,這樣可以更容易地與其他規則分開。--variable
以單個.light
包含所有變數,一個 class/host/media 只能包含一個--variable
,這才能對單一變數進行新增/刪除,而不是 PUT 整個規則導致額外的開銷。@layer style
layers[].rules
rule.fixedClass
用來固定為目標 class 名稱。像原本是.bg\:black\:hover:hover {}
要固定為.btn:hover {}
解決問題
!
。btn font:semibold!
->btn font:semibold
config.styles
支援 at-rules。btn-sm@<sm