@master/css-runtime 2.0.0-rc.58 → 2.0.0-rc.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.d.ts +61 -1
- package/dist/global.min.js +1 -1
- package/dist/index.cjs +168 -35
- package/dist/index.d.ts +2 -0
- package/dist/index.development.cjs +172 -35
- package/dist/index.development.mjs +173 -36
- package/dist/index.mjs +169 -36
- package/dist/layer.d.ts +15 -0
- package/dist/syntax-layer.d.ts +19 -0
- package/dist/types/hydrate-result.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -10,6 +10,110 @@ function registerGlobal(CSSRuntime) {
|
|
|
10
10
|
cssDevtoolsHook.installHook();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function findNativeCSSRuleIndex(cssRules, nativeRule) {
|
|
14
|
+
for(let i = 0; i < cssRules.length; i++){
|
|
15
|
+
if (cssRules[i] === nativeRule) {
|
|
16
|
+
return i;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return -1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class RuntimeLayer extends css.Layer {
|
|
23
|
+
name;
|
|
24
|
+
cssRuntime;
|
|
25
|
+
rules;
|
|
26
|
+
tokenCounts;
|
|
27
|
+
native;
|
|
28
|
+
constructor(name, cssRuntime){
|
|
29
|
+
super(name, cssRuntime), this.name = name, this.cssRuntime = cssRuntime, this.rules = [], this.tokenCounts = new Map(), this.native = null;
|
|
30
|
+
}
|
|
31
|
+
attach() {
|
|
32
|
+
super.attach();
|
|
33
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
34
|
+
if (nativeSheet && !this.native?.parentStyleSheet) {
|
|
35
|
+
const insertedIndex = nativeSheet.insertRule(this.text, nativeSheet.cssRules.length);
|
|
36
|
+
this.native = nativeSheet.cssRules.item(insertedIndex);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
insert(rule, index = this.rules.length) {
|
|
40
|
+
const insertedIndex = super.insert(rule, index);
|
|
41
|
+
if (insertedIndex === undefined) return;
|
|
42
|
+
if (this.native) {
|
|
43
|
+
let cssRuleIndex = 0;
|
|
44
|
+
const lastCssRule = function getLastCssRule(layer, index) {
|
|
45
|
+
let lastCssRule;
|
|
46
|
+
const previouRule = layer.rules[index];
|
|
47
|
+
if (previouRule && 'nodes' in previouRule) {
|
|
48
|
+
if (!previouRule.nodes.length) return getLastCssRule(layer, index - 1);
|
|
49
|
+
const lastNativeRule = previouRule.nodes[previouRule.nodes.length - 1];
|
|
50
|
+
lastCssRule = lastNativeRule.native;
|
|
51
|
+
}
|
|
52
|
+
return lastCssRule;
|
|
53
|
+
}(this, insertedIndex - 1);
|
|
54
|
+
if (lastCssRule) {
|
|
55
|
+
for(let i = 0; i < this.native.cssRules.length; i++){
|
|
56
|
+
if (this.native.cssRules[i] === lastCssRule) {
|
|
57
|
+
cssRuleIndex = i + 1;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for(let i = 0; i < rule.nodes.length;){
|
|
63
|
+
try {
|
|
64
|
+
const node = rule.nodes[i];
|
|
65
|
+
const insertedIndex = this.native.insertRule(node.text, cssRuleIndex);
|
|
66
|
+
node.native = this.native.cssRules.item(insertedIndex);
|
|
67
|
+
cssRuleIndex++;
|
|
68
|
+
i++;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(error, rule.nodes[i]);
|
|
71
|
+
rule.nodes.splice(i, 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return insertedIndex;
|
|
76
|
+
}
|
|
77
|
+
detach() {
|
|
78
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
79
|
+
if (nativeSheet && this.native?.parentStyleSheet) {
|
|
80
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
81
|
+
if (foundIndex !== -1) {
|
|
82
|
+
nativeSheet.deleteRule(foundIndex);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
delete(key) {
|
|
87
|
+
const deletedRule = super.delete(key);
|
|
88
|
+
if (!deletedRule) return;
|
|
89
|
+
if (this.native?.cssRules && 'nodes' in deletedRule) {
|
|
90
|
+
for (const node of deletedRule.nodes){
|
|
91
|
+
if (node.native) {
|
|
92
|
+
const foundIndex = findNativeCSSRuleIndex(this.native.cssRules, node.native);
|
|
93
|
+
if (foundIndex !== -1) {
|
|
94
|
+
// todo: Firefox throw "Uncaught NS_ERROR_FAILURE". Reproduce: Add '@fade|1s @fade|2s' and remove '@fade|1s @fade|2s'
|
|
95
|
+
this.native.deleteRule(foundIndex);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return deletedRule;
|
|
101
|
+
}
|
|
102
|
+
reset() {
|
|
103
|
+
super.reset();
|
|
104
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
105
|
+
if (this.native && nativeSheet) {
|
|
106
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
107
|
+
if (foundIndex !== -1) {
|
|
108
|
+
nativeSheet.deleteRule(foundIndex);
|
|
109
|
+
}
|
|
110
|
+
this.native = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const RuntimeSyntaxLayer = css.withSyntaxLayer(RuntimeLayer);
|
|
116
|
+
|
|
13
117
|
class CSSRuntime extends css.MasterCSS {
|
|
14
118
|
root;
|
|
15
119
|
customConfig;
|
|
@@ -19,8 +123,14 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
19
123
|
progressive;
|
|
20
124
|
container;
|
|
21
125
|
observer;
|
|
126
|
+
baseLayer;
|
|
127
|
+
themeLayer;
|
|
128
|
+
presetLayer;
|
|
129
|
+
componentsLayer;
|
|
130
|
+
generalLayer;
|
|
131
|
+
classCounts;
|
|
22
132
|
constructor(root = document, customConfig = css.config){
|
|
23
|
-
super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false;
|
|
133
|
+
super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false, this.baseLayer = new RuntimeSyntaxLayer('base', this), this.themeLayer = new RuntimeLayer('theme', this), this.presetLayer = new RuntimeSyntaxLayer('preset', this), this.componentsLayer = new RuntimeSyntaxLayer('components', this), this.generalLayer = new RuntimeSyntaxLayer('general', this), this.classCounts = new Map();
|
|
24
134
|
const rootConstructorName = root?.constructor.name;
|
|
25
135
|
if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
|
|
26
136
|
this.root.defaultView.globalThis.cssRuntime = this;
|
|
@@ -47,6 +157,7 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
47
157
|
* @returns this
|
|
48
158
|
*/ observe() {
|
|
49
159
|
if (this.observing) return this;
|
|
160
|
+
// 1. Check if the stylesheet is progressive.
|
|
50
161
|
if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
|
|
51
162
|
const { ownerNode } = sheet;
|
|
52
163
|
if (ownerNode && ownerNode.id === 'master') {
|
|
@@ -56,31 +167,35 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
56
167
|
break;
|
|
57
168
|
}
|
|
58
169
|
}
|
|
170
|
+
// 2. Count the classes in the host and its children.
|
|
171
|
+
const connectedNames = new Set();
|
|
172
|
+
const increaseClassCount = (className)=>{
|
|
173
|
+
const count = this.classCounts.get(className) || 0;
|
|
174
|
+
if (!count) {
|
|
175
|
+
connectedNames.add(className);
|
|
176
|
+
}
|
|
177
|
+
this.classCounts.set(className, count + 1);
|
|
178
|
+
};
|
|
179
|
+
this.host.classList.forEach(increaseClassCount);
|
|
180
|
+
(this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
|
|
181
|
+
// 3. Hydrate the existing CSS rules or create a new one.
|
|
59
182
|
if (this.progressive) {
|
|
60
|
-
this.hydrate(this.style.sheet.cssRules);
|
|
183
|
+
const hydrateResult = this.hydrate(this.style.sheet.cssRules);
|
|
184
|
+
// Add the connected class names that are not hydrated yet.
|
|
185
|
+
for (const eachConnectedName of connectedNames){
|
|
186
|
+
if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
|
|
187
|
+
this.add(eachConnectedName);
|
|
188
|
+
}
|
|
61
189
|
} else {
|
|
62
190
|
this.style = document.createElement('style');
|
|
63
191
|
this.style.id = 'master';
|
|
64
192
|
this.container.append(this.style);
|
|
65
193
|
this.style.sheet.insertRule(this.layerStatementRule.text);
|
|
66
194
|
this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
|
|
195
|
+
for (const eachConnectedName of connectedNames){
|
|
196
|
+
this.add(eachConnectedName);
|
|
197
|
+
}
|
|
67
198
|
}
|
|
68
|
-
const addClasses = (element)=>{
|
|
69
|
-
element.classList.forEach((className)=>{
|
|
70
|
-
if (this.classCounts.has(className)) {
|
|
71
|
-
this.classCounts.set(className, this.classCounts.get(className) + 1);
|
|
72
|
-
} else {
|
|
73
|
-
this.classCounts.set(className, 1);
|
|
74
|
-
this.add(className);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
addClasses(this.host);
|
|
79
|
-
/**
|
|
80
|
-
* 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
|
|
81
|
-
*/ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
|
|
82
|
-
addClasses(element);
|
|
83
|
-
});
|
|
84
199
|
// @ts-expect-error readonly
|
|
85
200
|
this.observer = new MutationObserver((mutationRecords)=>{
|
|
86
201
|
// console.clear()
|
|
@@ -217,6 +332,9 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
217
332
|
hydrate(nativeLayerRules) {
|
|
218
333
|
const cssLayerRules = [];
|
|
219
334
|
const checkSheet = new CSSStyleSheet();
|
|
335
|
+
const result = {
|
|
336
|
+
allSyntaxRules: []
|
|
337
|
+
};
|
|
220
338
|
for(let i = 0; i < nativeLayerRules.length; i++){
|
|
221
339
|
const eachNativeCSSRule = nativeLayerRules[i];
|
|
222
340
|
if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
|
|
@@ -233,7 +351,7 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
233
351
|
lastVariableName = variableName;
|
|
234
352
|
variableRule = new css.Rule(variableName);
|
|
235
353
|
this.themeLayer.rules.push(variableRule);
|
|
236
|
-
this.themeLayer.
|
|
354
|
+
this.themeLayer.tokenCounts.set(variableRule.name, 0);
|
|
237
355
|
}
|
|
238
356
|
variableRule?.nodes.push({
|
|
239
357
|
native: cssRule,
|
|
@@ -256,11 +374,10 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
256
374
|
]);
|
|
257
375
|
this.animationsNonLayer.rules.push(animationRule);
|
|
258
376
|
this.rules.push(animationRule);
|
|
259
|
-
this.animationsNonLayer.
|
|
377
|
+
this.animationsNonLayer.tokenCounts.set(animationRule.name, 0);
|
|
260
378
|
}
|
|
261
379
|
}
|
|
262
380
|
for (const eachCSSLayerRule of cssLayerRules){
|
|
263
|
-
const nativeLayerRules = Array.from(eachCSSLayerRule.cssRules);
|
|
264
381
|
let layer;
|
|
265
382
|
switch(eachCSSLayerRule.name){
|
|
266
383
|
case 'base':
|
|
@@ -272,15 +389,23 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
272
389
|
case 'components':
|
|
273
390
|
layer = this.componentsLayer;
|
|
274
391
|
break;
|
|
275
|
-
|
|
392
|
+
case 'general':
|
|
276
393
|
layer = this.generalLayer;
|
|
277
394
|
break;
|
|
395
|
+
default:
|
|
396
|
+
console.error(`Cannot recognize the layer \`${eachCSSLayerRule.name}\`. (https://rc.css.master.co/messages/hydration-errors)`);
|
|
397
|
+
continue;
|
|
278
398
|
}
|
|
279
399
|
layer.native = eachCSSLayerRule;
|
|
280
|
-
|
|
281
|
-
|
|
400
|
+
const unresolvedCSSRules = new Map();
|
|
401
|
+
for (const rule of eachCSSLayerRule.cssRules){
|
|
402
|
+
unresolvedCSSRules.set(rule.cssText, rule);
|
|
403
|
+
}
|
|
404
|
+
for (const eachNativeLayerRule of eachCSSLayerRule.cssRules){
|
|
405
|
+
if (!unresolvedCSSRules.has(eachNativeLayerRule.cssText)) continue;
|
|
406
|
+
const selectorText = this.getSelectorText(eachNativeLayerRule);
|
|
282
407
|
if (!selectorText) {
|
|
283
|
-
console.error(`Cannot get the selector text from \`${
|
|
408
|
+
console.error(`Cannot get the selector text from \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
284
409
|
continue;
|
|
285
410
|
}
|
|
286
411
|
const createdRules = this.createFromSelectorText(selectorText);
|
|
@@ -289,34 +414,36 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
289
414
|
layer.rules.push(createdRule);
|
|
290
415
|
layer.insertVariables(createdRule);
|
|
291
416
|
layer.insertAnimations(createdRule);
|
|
417
|
+
result.allSyntaxRules.push(createdRule);
|
|
292
418
|
createdRule.nodes.forEach((node)=>{
|
|
293
419
|
const createdNodeNativeRule = checkSheet.cssRules.item(checkSheet.insertRule(node.text));
|
|
294
420
|
if (createdNodeNativeRule) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
421
|
+
const match = unresolvedCSSRules.get(createdNodeNativeRule.cssText);
|
|
422
|
+
if (match) {
|
|
423
|
+
node.native = match;
|
|
424
|
+
unresolvedCSSRules.delete(createdNodeNativeRule.cssText);
|
|
425
|
+
return;
|
|
301
426
|
}
|
|
302
427
|
}
|
|
303
428
|
console.error(`Cannot retrieve CSS rule for \`${node.selectorText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
304
429
|
});
|
|
305
430
|
}
|
|
306
431
|
} else {
|
|
307
|
-
console.error(`Cannot recognize \`${
|
|
432
|
+
console.error(`Cannot recognize \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
308
433
|
}
|
|
309
434
|
}
|
|
310
435
|
if (layer.rules.length) this.rules.push(layer);
|
|
311
436
|
}
|
|
312
437
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:hydrated', {
|
|
313
|
-
cssRuntime: this
|
|
438
|
+
cssRuntime: this,
|
|
439
|
+
result
|
|
314
440
|
});
|
|
441
|
+
return result;
|
|
315
442
|
}
|
|
316
443
|
getSelectorText(cssRule) {
|
|
317
|
-
if (
|
|
444
|
+
if (cssRule instanceof CSSStyleRule) {
|
|
318
445
|
return cssRule.selectorText;
|
|
319
|
-
} else if (
|
|
446
|
+
} else if (cssRule instanceof CSSGroupingRule) {
|
|
320
447
|
return this.getSelectorText(cssRule.cssRules.item(0));
|
|
321
448
|
}
|
|
322
449
|
}
|
|
@@ -346,6 +473,12 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
346
473
|
this.style.sheet.deleteRule(i);
|
|
347
474
|
}
|
|
348
475
|
super.refresh(customConfig);
|
|
476
|
+
/**
|
|
477
|
+
* 拿當前所有的 classNames 按照最新的 colors, config.rules 匹配並生成新的 style
|
|
478
|
+
* 所以 refresh 過後 rules 可能會變多也可能會變少
|
|
479
|
+
*/ this.classCounts.forEach((_, className)=>{
|
|
480
|
+
this.add(className);
|
|
481
|
+
});
|
|
349
482
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:refreshed', {
|
|
350
483
|
cssRuntime: this,
|
|
351
484
|
customConfig
|
package/dist/index.d.ts
CHANGED
|
@@ -100,6 +100,110 @@ function registerGlobal(CSSRuntime) {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function findNativeCSSRuleIndex(cssRules, nativeRule) {
|
|
104
|
+
for(let i = 0; i < cssRules.length; i++){
|
|
105
|
+
if (cssRules[i] === nativeRule) {
|
|
106
|
+
return i;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return -1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class RuntimeLayer extends css.Layer {
|
|
113
|
+
name;
|
|
114
|
+
cssRuntime;
|
|
115
|
+
rules;
|
|
116
|
+
tokenCounts;
|
|
117
|
+
native;
|
|
118
|
+
constructor(name, cssRuntime){
|
|
119
|
+
super(name, cssRuntime), this.name = name, this.cssRuntime = cssRuntime, this.rules = [], this.tokenCounts = new Map(), this.native = null;
|
|
120
|
+
}
|
|
121
|
+
attach() {
|
|
122
|
+
super.attach();
|
|
123
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
124
|
+
if (nativeSheet && !this.native?.parentStyleSheet) {
|
|
125
|
+
const insertedIndex = nativeSheet.insertRule(this.text, nativeSheet.cssRules.length);
|
|
126
|
+
this.native = nativeSheet.cssRules.item(insertedIndex);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
insert(rule, index = this.rules.length) {
|
|
130
|
+
const insertedIndex = super.insert(rule, index);
|
|
131
|
+
if (insertedIndex === undefined) return;
|
|
132
|
+
if (this.native) {
|
|
133
|
+
let cssRuleIndex = 0;
|
|
134
|
+
const lastCssRule = function getLastCssRule(layer, index) {
|
|
135
|
+
let lastCssRule;
|
|
136
|
+
const previouRule = layer.rules[index];
|
|
137
|
+
if (previouRule && 'nodes' in previouRule) {
|
|
138
|
+
if (!previouRule.nodes.length) return getLastCssRule(layer, index - 1);
|
|
139
|
+
const lastNativeRule = previouRule.nodes[previouRule.nodes.length - 1];
|
|
140
|
+
lastCssRule = lastNativeRule.native;
|
|
141
|
+
}
|
|
142
|
+
return lastCssRule;
|
|
143
|
+
}(this, insertedIndex - 1);
|
|
144
|
+
if (lastCssRule) {
|
|
145
|
+
for(let i = 0; i < this.native.cssRules.length; i++){
|
|
146
|
+
if (this.native.cssRules[i] === lastCssRule) {
|
|
147
|
+
cssRuleIndex = i + 1;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for(let i = 0; i < rule.nodes.length;){
|
|
153
|
+
try {
|
|
154
|
+
const node = rule.nodes[i];
|
|
155
|
+
const insertedIndex = this.native.insertRule(node.text, cssRuleIndex);
|
|
156
|
+
node.native = this.native.cssRules.item(insertedIndex);
|
|
157
|
+
cssRuleIndex++;
|
|
158
|
+
i++;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(error, rule.nodes[i]);
|
|
161
|
+
rule.nodes.splice(i, 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return insertedIndex;
|
|
166
|
+
}
|
|
167
|
+
detach() {
|
|
168
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
169
|
+
if (nativeSheet && this.native?.parentStyleSheet) {
|
|
170
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
171
|
+
if (foundIndex !== -1) {
|
|
172
|
+
nativeSheet.deleteRule(foundIndex);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
delete(key) {
|
|
177
|
+
const deletedRule = super.delete(key);
|
|
178
|
+
if (!deletedRule) return;
|
|
179
|
+
if (this.native?.cssRules && 'nodes' in deletedRule) {
|
|
180
|
+
for (const node of deletedRule.nodes){
|
|
181
|
+
if (node.native) {
|
|
182
|
+
const foundIndex = findNativeCSSRuleIndex(this.native.cssRules, node.native);
|
|
183
|
+
if (foundIndex !== -1) {
|
|
184
|
+
// todo: Firefox throw "Uncaught NS_ERROR_FAILURE". Reproduce: Add '@fade|1s @fade|2s' and remove '@fade|1s @fade|2s'
|
|
185
|
+
this.native.deleteRule(foundIndex);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return deletedRule;
|
|
191
|
+
}
|
|
192
|
+
reset() {
|
|
193
|
+
super.reset();
|
|
194
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
195
|
+
if (this.native && nativeSheet) {
|
|
196
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
197
|
+
if (foundIndex !== -1) {
|
|
198
|
+
nativeSheet.deleteRule(foundIndex);
|
|
199
|
+
}
|
|
200
|
+
this.native = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const RuntimeSyntaxLayer = css.withSyntaxLayer(RuntimeLayer);
|
|
206
|
+
|
|
103
207
|
class CSSRuntime extends css.MasterCSS {
|
|
104
208
|
root;
|
|
105
209
|
customConfig;
|
|
@@ -109,8 +213,14 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
109
213
|
progressive;
|
|
110
214
|
container;
|
|
111
215
|
observer;
|
|
216
|
+
baseLayer;
|
|
217
|
+
themeLayer;
|
|
218
|
+
presetLayer;
|
|
219
|
+
componentsLayer;
|
|
220
|
+
generalLayer;
|
|
221
|
+
classCounts;
|
|
112
222
|
constructor(root = document, customConfig = css.config){
|
|
113
|
-
super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false;
|
|
223
|
+
super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false, this.baseLayer = new RuntimeSyntaxLayer('base', this), this.themeLayer = new RuntimeLayer('theme', this), this.presetLayer = new RuntimeSyntaxLayer('preset', this), this.componentsLayer = new RuntimeSyntaxLayer('components', this), this.generalLayer = new RuntimeSyntaxLayer('general', this), this.classCounts = new Map();
|
|
114
224
|
const rootConstructorName = root?.constructor.name;
|
|
115
225
|
if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
|
|
116
226
|
this.root.defaultView.globalThis.cssRuntime = this;
|
|
@@ -137,6 +247,7 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
137
247
|
* @returns this
|
|
138
248
|
*/ observe() {
|
|
139
249
|
if (this.observing) return this;
|
|
250
|
+
// 1. Check if the stylesheet is progressive.
|
|
140
251
|
if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
|
|
141
252
|
const { ownerNode } = sheet;
|
|
142
253
|
if (ownerNode && ownerNode.id === 'master') {
|
|
@@ -146,31 +257,39 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
146
257
|
break;
|
|
147
258
|
}
|
|
148
259
|
}
|
|
260
|
+
// 2. Count the classes in the host and its children.
|
|
261
|
+
const connectedNames = new Set();
|
|
262
|
+
const increaseClassCount = (className)=>{
|
|
263
|
+
const count = this.classCounts.get(className) || 0;
|
|
264
|
+
if (!count) {
|
|
265
|
+
connectedNames.add(className);
|
|
266
|
+
}
|
|
267
|
+
this.classCounts.set(className, count + 1);
|
|
268
|
+
};
|
|
269
|
+
this.host.classList.forEach(increaseClassCount);
|
|
270
|
+
(this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
|
|
271
|
+
// 3. Hydrate the existing CSS rules or create a new one.
|
|
149
272
|
if (this.progressive) {
|
|
150
|
-
this.hydrate(this.style.sheet.cssRules);
|
|
273
|
+
const hydrateResult = this.hydrate(this.style.sheet.cssRules);
|
|
274
|
+
// Add the connected class names that are not hydrated yet.
|
|
275
|
+
for (const eachConnectedName of connectedNames){
|
|
276
|
+
if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
|
|
277
|
+
this.add(eachConnectedName);
|
|
278
|
+
{
|
|
279
|
+
// Basically, the style#master should have all the prerendered CSS rules that are connected in the DOM.
|
|
280
|
+
console.debug(`The class \`${eachConnectedName}\` was added via script before calling hydrate, or the corresponding CSS rule were not properly pre-rendered.`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
151
283
|
} else {
|
|
152
284
|
this.style = document.createElement('style');
|
|
153
285
|
this.style.id = 'master';
|
|
154
286
|
this.container.append(this.style);
|
|
155
287
|
this.style.sheet.insertRule(this.layerStatementRule.text);
|
|
156
288
|
this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
|
|
289
|
+
for (const eachConnectedName of connectedNames){
|
|
290
|
+
this.add(eachConnectedName);
|
|
291
|
+
}
|
|
157
292
|
}
|
|
158
|
-
const addClasses = (element)=>{
|
|
159
|
-
element.classList.forEach((className)=>{
|
|
160
|
-
if (this.classCounts.has(className)) {
|
|
161
|
-
this.classCounts.set(className, this.classCounts.get(className) + 1);
|
|
162
|
-
} else {
|
|
163
|
-
this.classCounts.set(className, 1);
|
|
164
|
-
this.add(className);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
};
|
|
168
|
-
addClasses(this.host);
|
|
169
|
-
/**
|
|
170
|
-
* 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
|
|
171
|
-
*/ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
|
|
172
|
-
addClasses(element);
|
|
173
|
-
});
|
|
174
293
|
// @ts-expect-error readonly
|
|
175
294
|
this.observer = new MutationObserver((mutationRecords)=>{
|
|
176
295
|
// console.clear()
|
|
@@ -307,6 +426,9 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
307
426
|
hydrate(nativeLayerRules) {
|
|
308
427
|
const cssLayerRules = [];
|
|
309
428
|
const checkSheet = new CSSStyleSheet();
|
|
429
|
+
const result = {
|
|
430
|
+
allSyntaxRules: []
|
|
431
|
+
};
|
|
310
432
|
for(let i = 0; i < nativeLayerRules.length; i++){
|
|
311
433
|
const eachNativeCSSRule = nativeLayerRules[i];
|
|
312
434
|
if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
|
|
@@ -323,7 +445,7 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
323
445
|
lastVariableName = variableName;
|
|
324
446
|
variableRule = new css.Rule(variableName);
|
|
325
447
|
this.themeLayer.rules.push(variableRule);
|
|
326
|
-
this.themeLayer.
|
|
448
|
+
this.themeLayer.tokenCounts.set(variableRule.name, 0);
|
|
327
449
|
}
|
|
328
450
|
variableRule?.nodes.push({
|
|
329
451
|
native: cssRule,
|
|
@@ -346,11 +468,10 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
346
468
|
]);
|
|
347
469
|
this.animationsNonLayer.rules.push(animationRule);
|
|
348
470
|
this.rules.push(animationRule);
|
|
349
|
-
this.animationsNonLayer.
|
|
471
|
+
this.animationsNonLayer.tokenCounts.set(animationRule.name, 0);
|
|
350
472
|
}
|
|
351
473
|
}
|
|
352
474
|
for (const eachCSSLayerRule of cssLayerRules){
|
|
353
|
-
const nativeLayerRules = Array.from(eachCSSLayerRule.cssRules);
|
|
354
475
|
let layer;
|
|
355
476
|
switch(eachCSSLayerRule.name){
|
|
356
477
|
case 'base':
|
|
@@ -362,15 +483,23 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
362
483
|
case 'components':
|
|
363
484
|
layer = this.componentsLayer;
|
|
364
485
|
break;
|
|
365
|
-
|
|
486
|
+
case 'general':
|
|
366
487
|
layer = this.generalLayer;
|
|
367
488
|
break;
|
|
489
|
+
default:
|
|
490
|
+
console.error(`Cannot recognize the layer \`${eachCSSLayerRule.name}\`. (https://rc.css.master.co/messages/hydration-errors)`);
|
|
491
|
+
continue;
|
|
368
492
|
}
|
|
369
493
|
layer.native = eachCSSLayerRule;
|
|
370
|
-
|
|
371
|
-
|
|
494
|
+
const unresolvedCSSRules = new Map();
|
|
495
|
+
for (const rule of eachCSSLayerRule.cssRules){
|
|
496
|
+
unresolvedCSSRules.set(rule.cssText, rule);
|
|
497
|
+
}
|
|
498
|
+
for (const eachNativeLayerRule of eachCSSLayerRule.cssRules){
|
|
499
|
+
if (!unresolvedCSSRules.has(eachNativeLayerRule.cssText)) continue;
|
|
500
|
+
const selectorText = this.getSelectorText(eachNativeLayerRule);
|
|
372
501
|
if (!selectorText) {
|
|
373
|
-
console.error(`Cannot get the selector text from \`${
|
|
502
|
+
console.error(`Cannot get the selector text from \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
374
503
|
continue;
|
|
375
504
|
}
|
|
376
505
|
const createdRules = this.createFromSelectorText(selectorText);
|
|
@@ -379,34 +508,36 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
379
508
|
layer.rules.push(createdRule);
|
|
380
509
|
layer.insertVariables(createdRule);
|
|
381
510
|
layer.insertAnimations(createdRule);
|
|
511
|
+
result.allSyntaxRules.push(createdRule);
|
|
382
512
|
createdRule.nodes.forEach((node)=>{
|
|
383
513
|
const createdNodeNativeRule = checkSheet.cssRules.item(checkSheet.insertRule(node.text));
|
|
384
514
|
if (createdNodeNativeRule) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
515
|
+
const match = unresolvedCSSRules.get(createdNodeNativeRule.cssText);
|
|
516
|
+
if (match) {
|
|
517
|
+
node.native = match;
|
|
518
|
+
unresolvedCSSRules.delete(createdNodeNativeRule.cssText);
|
|
519
|
+
return;
|
|
391
520
|
}
|
|
392
521
|
}
|
|
393
522
|
console.error(`Cannot retrieve CSS rule for \`${node.selectorText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
394
523
|
});
|
|
395
524
|
}
|
|
396
525
|
} else {
|
|
397
|
-
console.error(`Cannot recognize \`${
|
|
526
|
+
console.error(`Cannot recognize \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
398
527
|
}
|
|
399
528
|
}
|
|
400
529
|
if (layer.rules.length) this.rules.push(layer);
|
|
401
530
|
}
|
|
402
531
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:hydrated', {
|
|
403
|
-
cssRuntime: this
|
|
532
|
+
cssRuntime: this,
|
|
533
|
+
result
|
|
404
534
|
});
|
|
535
|
+
return result;
|
|
405
536
|
}
|
|
406
537
|
getSelectorText(cssRule) {
|
|
407
|
-
if (
|
|
538
|
+
if (cssRule instanceof CSSStyleRule) {
|
|
408
539
|
return cssRule.selectorText;
|
|
409
|
-
} else if (
|
|
540
|
+
} else if (cssRule instanceof CSSGroupingRule) {
|
|
410
541
|
return this.getSelectorText(cssRule.cssRules.item(0));
|
|
411
542
|
}
|
|
412
543
|
}
|
|
@@ -436,6 +567,12 @@ class CSSRuntime extends css.MasterCSS {
|
|
|
436
567
|
this.style.sheet.deleteRule(i);
|
|
437
568
|
}
|
|
438
569
|
super.refresh(customConfig);
|
|
570
|
+
/**
|
|
571
|
+
* 拿當前所有的 classNames 按照最新的 colors, config.rules 匹配並生成新的 style
|
|
572
|
+
* 所以 refresh 過後 rules 可能會變多也可能會變少
|
|
573
|
+
*/ this.classCounts.forEach((_, className)=>{
|
|
574
|
+
this.add(className);
|
|
575
|
+
});
|
|
439
576
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:refreshed', {
|
|
440
577
|
cssRuntime: this,
|
|
441
578
|
customConfig
|