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