@master/css-runtime 2.0.0-rc.69 → 2.0.0-rc.70
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/README.md +89 -7
- package/dist/class-tracker.d.ts +12 -0
- package/dist/core.d.ts +51 -66
- package/dist/css-runtime.d.ts +17 -0
- package/dist/debuggers/log.d.ts +17 -0
- package/dist/default-manifest.json +1 -0
- package/dist/generated-rule.d.ts +26 -0
- package/dist/global.min.js +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.development.mjs +980 -268
- package/dist/index.mjs +972 -263
- package/dist/layer.d.ts +3 -2
- package/dist/native-declaration.d.ts +2 -0
- package/dist/register-global.d.ts +2 -5
- package/dist/theme-layer.d.ts +27 -0
- package/dist/types/hydrate-result.d.ts +3 -2
- package/dist/utility-layer.d.ts +13 -0
- package/dist/utils/find-native-css-rule-index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/index.cjs +0 -481
- package/dist/index.development.cjs +0 -608
- package/dist/init.d.ts +0 -10
- package/dist/syntax-layer.d.ts +0 -19
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Layer,
|
|
1
|
+
import { Layer, AnimationRule, VariableRule, compareRulePriority, ThemeLayer, MasterCSS } from '@master/css-engine';
|
|
2
|
+
import { MASTER_CSS_HYDRATION_MANIFEST_ATTR, MASTER_CSS_HYDRATION_MANIFEST_SCRIPT_ID } from '@master/css-schema/hydration-manifest';
|
|
3
|
+
import { MASTER_CSS_RUNTIME_STYLE_ID } from '@master/css-schema/runtime-style';
|
|
2
4
|
import { installHook } from '@master/css-devtools-hook';
|
|
3
5
|
|
|
4
6
|
const createLogger = (type)=>{
|
|
@@ -105,8 +107,8 @@ function registerLifecycleDebugger() {
|
|
|
105
107
|
log.info('Runtime rendering is adopted.');
|
|
106
108
|
}
|
|
107
109
|
});
|
|
108
|
-
hook.on('runtime:refreshed', ({ cssRuntime,
|
|
109
|
-
log.debug('refreshed', cssRuntime,
|
|
110
|
+
hook.on('runtime:refreshed', ({ cssRuntime, manifest })=>{
|
|
111
|
+
log.debug('refreshed', cssRuntime, manifest);
|
|
110
112
|
});
|
|
111
113
|
hook.on('runtime:disconnected', ({ cssRuntime })=>{
|
|
112
114
|
log.debug('disconnected', cssRuntime);
|
|
@@ -123,7 +125,7 @@ function startDebuggers() {
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
function registerGlobal(CSSRuntime) {
|
|
126
|
-
if (!globalThis.
|
|
128
|
+
if (!globalThis.MasterCSSRuntime) globalThis.MasterCSSRuntime = CSSRuntime;
|
|
127
129
|
installHook();
|
|
128
130
|
{
|
|
129
131
|
startDebuggers();
|
|
@@ -139,6 +141,10 @@ function findNativeCSSRuleIndex(cssRules, nativeRule) {
|
|
|
139
141
|
return -1;
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
function getRuleNodes(rule) {
|
|
145
|
+
const nodes = rule.nodes;
|
|
146
|
+
return Array.isArray(nodes) ? nodes : undefined;
|
|
147
|
+
}
|
|
142
148
|
class RuntimeLayer extends Layer {
|
|
143
149
|
name;
|
|
144
150
|
cssRuntime;
|
|
@@ -157,11 +163,15 @@ class RuntimeLayer extends Layer {
|
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
insert(rule, index = this.rules.length) {
|
|
166
|
+
const hadNative = !!this.native?.parentStyleSheet;
|
|
167
|
+
const wasEmpty = this.rules.length === 0;
|
|
160
168
|
const insertedIndex = super.insert(rule, index);
|
|
161
169
|
if (insertedIndex === undefined || !this.native) return;
|
|
170
|
+
const nativeInsertIndex = this.getNativeInsertIndex(insertedIndex);
|
|
171
|
+
const wasAttachedWithRule = !hadNative && wasEmpty;
|
|
162
172
|
const insertRuleSafely = (text, position)=>{
|
|
163
173
|
// Checks if the rule is inserted in a native CSS rule with this.attach()
|
|
164
|
-
if (
|
|
174
|
+
if (wasAttachedWithRule) {
|
|
165
175
|
return this.native.cssRules.item(position);
|
|
166
176
|
} else {
|
|
167
177
|
try {
|
|
@@ -177,17 +187,26 @@ class RuntimeLayer extends Layer {
|
|
|
177
187
|
}
|
|
178
188
|
}
|
|
179
189
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
const nodes = getRuleNodes(rule);
|
|
191
|
+
if (nodes) {
|
|
192
|
+
let currentIndex = nativeInsertIndex;
|
|
193
|
+
nodes.forEach((node)=>{
|
|
183
194
|
node.native = insertRuleSafely(node.text, currentIndex);
|
|
184
195
|
if (node.native) currentIndex++;
|
|
185
196
|
});
|
|
186
197
|
} else {
|
|
187
|
-
rule.native = insertRuleSafely(rule.text,
|
|
198
|
+
rule.native = insertRuleSafely(rule.text, nativeInsertIndex);
|
|
188
199
|
}
|
|
189
200
|
return insertedIndex;
|
|
190
201
|
}
|
|
202
|
+
getNativeInsertIndex(index) {
|
|
203
|
+
let nativeIndex = 0;
|
|
204
|
+
for(let i = 0; i < index; i++){
|
|
205
|
+
const rule = this.rules[i];
|
|
206
|
+
nativeIndex += getRuleNodes(rule)?.length ?? 1;
|
|
207
|
+
}
|
|
208
|
+
return nativeIndex;
|
|
209
|
+
}
|
|
191
210
|
detach() {
|
|
192
211
|
super.detach();
|
|
193
212
|
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
@@ -212,8 +231,9 @@ class RuntimeLayer extends Layer {
|
|
|
212
231
|
}
|
|
213
232
|
}
|
|
214
233
|
};
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
const nodes = getRuleNodes(deletedRule);
|
|
235
|
+
if (nodes) {
|
|
236
|
+
nodes.forEach((node)=>deleteRuleSafely(node.native));
|
|
217
237
|
} else {
|
|
218
238
|
deleteRuleSafely(deletedRule.native);
|
|
219
239
|
}
|
|
@@ -232,82 +252,615 @@ class RuntimeLayer extends Layer {
|
|
|
232
252
|
}
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
|
|
255
|
+
function findUtilityInsertIndex(rules, utility) {
|
|
256
|
+
let low = 0;
|
|
257
|
+
let high = rules.length;
|
|
258
|
+
while(low < high){
|
|
259
|
+
const mid = low + high >> 1;
|
|
260
|
+
const rule = rules[mid];
|
|
261
|
+
if (!('priority' in rule)) {
|
|
262
|
+
return findUtilityInsertIndexLinear(rules, utility);
|
|
263
|
+
}
|
|
264
|
+
if (compareRulePriority(utility, rule) < 0) {
|
|
265
|
+
high = mid;
|
|
266
|
+
} else {
|
|
267
|
+
low = mid + 1;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return low;
|
|
271
|
+
}
|
|
272
|
+
function findUtilityInsertIndexLinear(rules, utility) {
|
|
273
|
+
for(let i = 0; i < rules.length; i++){
|
|
274
|
+
const rule = rules[i];
|
|
275
|
+
if (!('priority' in rule)) continue;
|
|
276
|
+
if (compareRulePriority(utility, rule) < 0) {
|
|
277
|
+
return i;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return rules.length;
|
|
281
|
+
}
|
|
282
|
+
class RuntimeUtilityLayer extends RuntimeLayer {
|
|
283
|
+
rules = [];
|
|
284
|
+
insert(utility) {
|
|
285
|
+
if (this.get(utility.key)) return;
|
|
286
|
+
if ('valid' in utility && !utility.valid) return;
|
|
287
|
+
let index = this.rules.length;
|
|
288
|
+
if ('priority' in utility) {
|
|
289
|
+
index = findUtilityInsertIndex(this.rules, utility);
|
|
290
|
+
}
|
|
291
|
+
const insertedIndex = super.insert(utility, index);
|
|
292
|
+
if (insertedIndex === undefined) return;
|
|
293
|
+
this.insertVariables(utility);
|
|
294
|
+
this.insertAnimations(utility);
|
|
295
|
+
return insertedIndex;
|
|
296
|
+
}
|
|
297
|
+
delete(key) {
|
|
298
|
+
const utility = super.delete(key);
|
|
299
|
+
if (!utility) return;
|
|
300
|
+
const deleteLayerToken = (layerToken, layer, visited = new Set())=>{
|
|
301
|
+
if (visited.has(layerToken)) return;
|
|
302
|
+
visited.add(layerToken);
|
|
303
|
+
const count = layer.tokenCounts.get(layerToken) ?? 0;
|
|
304
|
+
const variable = layer === this.css.themeLayer ? this.css.variables.get(layerToken) : undefined;
|
|
305
|
+
if (count <= 1) {
|
|
306
|
+
const deletedRule = layer.delete(layerToken);
|
|
307
|
+
layer.tokenCounts.delete(layerToken);
|
|
308
|
+
variable?.dependencies?.forEach((dependency)=>deleteLayerToken(dependency, layer, visited));
|
|
309
|
+
return deletedRule;
|
|
310
|
+
} else {
|
|
311
|
+
layer.tokenCounts.set(layerToken, count - 1);
|
|
312
|
+
variable?.dependencies?.forEach((dependency)=>deleteLayerToken(dependency, layer, visited));
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
if ('variableNames' in utility) utility.variableNames?.forEach((eachVariableName)=>{
|
|
316
|
+
deleteLayerToken(eachVariableName, this.css.themeLayer);
|
|
317
|
+
});
|
|
318
|
+
if ('animationNames' in utility) utility.animationNames?.forEach((eachAnimationName)=>{
|
|
319
|
+
const deletedAnimationRule = deleteLayerToken(eachAnimationName, this.css.animationsNonLayer);
|
|
320
|
+
const variableNames = deletedAnimationRule && 'variableNames' in deletedAnimationRule ? deletedAnimationRule.variableNames : undefined;
|
|
321
|
+
if (variableNames) {
|
|
322
|
+
variableNames.forEach((eachVariableName)=>{
|
|
323
|
+
deleteLayerToken(eachVariableName, this.css.themeLayer);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return utility;
|
|
328
|
+
}
|
|
329
|
+
insertVariables(utility) {
|
|
330
|
+
if (!('variableNames' in utility)) return;
|
|
331
|
+
const insertVariable = (eachVariableName, visited = new Set())=>{
|
|
332
|
+
if (visited.has(eachVariableName)) return;
|
|
333
|
+
visited.add(eachVariableName);
|
|
334
|
+
const variable = this.css.variables.get(eachVariableName);
|
|
335
|
+
if (!variable || variable.inline) return;
|
|
336
|
+
if (this.css.themeLayer.rules.find(({ name })=>name === eachVariableName) || this.css.isEmittedGlobalsVariable(eachVariableName)) {
|
|
337
|
+
const count = this.css.themeLayer.tokenCounts.get(eachVariableName) || 0;
|
|
338
|
+
this.css.themeLayer.tokenCounts.set(eachVariableName, count + 1);
|
|
339
|
+
} else {
|
|
340
|
+
const newRule = new VariableRule(eachVariableName, variable, this.css);
|
|
341
|
+
this.css.themeLayer.insert(newRule);
|
|
342
|
+
this.css.themeLayer.tokenCounts.set(eachVariableName, 1);
|
|
343
|
+
}
|
|
344
|
+
variable.dependencies?.forEach((dependency)=>insertVariable(dependency, visited));
|
|
345
|
+
};
|
|
346
|
+
utility.variableNames?.forEach((eachVariableName)=>insertVariable(eachVariableName));
|
|
347
|
+
}
|
|
348
|
+
insertAnimations(utility) {
|
|
349
|
+
if (!('animationNames' in utility)) return;
|
|
350
|
+
utility.animationNames?.forEach((eachAnimationName)=>{
|
|
351
|
+
const animationRule = this.css.animationsNonLayer.rules.find(({ name })=>name === eachAnimationName);
|
|
352
|
+
if (animationRule || this.css.isEmittedGlobalsAnimation(eachAnimationName)) {
|
|
353
|
+
const count = this.css.animationsNonLayer.tokenCounts.get(eachAnimationName) || 0;
|
|
354
|
+
this.css.animationsNonLayer.tokenCounts.set(eachAnimationName, count + 1);
|
|
355
|
+
if (animationRule) this.insertVariables(animationRule);
|
|
356
|
+
} else {
|
|
357
|
+
const keyframes = this.css.animations.get(eachAnimationName);
|
|
358
|
+
if (!keyframes) return;
|
|
359
|
+
const newRule = new AnimationRule(eachAnimationName, keyframes, this.css);
|
|
360
|
+
this.css.animationsNonLayer.insert(newRule);
|
|
361
|
+
this.css.animationsNonLayer.tokenCounts.set(eachAnimationName, 1);
|
|
362
|
+
this.insertVariables(newRule);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
236
367
|
|
|
368
|
+
class RuntimeThemeLayer extends ThemeLayer {
|
|
369
|
+
name;
|
|
370
|
+
cssRuntime;
|
|
371
|
+
native = null;
|
|
372
|
+
nativeBuckets = new Map();
|
|
373
|
+
constructor(name, cssRuntime){
|
|
374
|
+
super(name, cssRuntime), this.name = name, this.cssRuntime = cssRuntime;
|
|
375
|
+
}
|
|
376
|
+
attach() {
|
|
377
|
+
super.attach();
|
|
378
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
379
|
+
if (nativeSheet && !this.native?.parentStyleSheet) {
|
|
380
|
+
const insertedIndex = nativeSheet.insertRule(this.text, nativeSheet.cssRules.length);
|
|
381
|
+
this.native = nativeSheet.cssRules.item(insertedIndex);
|
|
382
|
+
this.syncNativeBuckets();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
getBucketKey(mediaText, selectorText) {
|
|
386
|
+
const normalizedMediaText = mediaText.replace(/\s*:\s*/g, ':').replace(/\s+/g, ' ').trim();
|
|
387
|
+
const normalizedSelectorText = selectorText.replace(/\s*,\s*/g, ',').replace(/\s+/g, ' ').trim();
|
|
388
|
+
return normalizedMediaText + '\n' + normalizedSelectorText;
|
|
389
|
+
}
|
|
390
|
+
getStyleRule(nativeRule) {
|
|
391
|
+
if (nativeRule instanceof CSSStyleRule) return nativeRule;
|
|
392
|
+
if (nativeRule instanceof CSSGroupingRule) {
|
|
393
|
+
return nativeRule.cssRules.item(0);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
syncNativeBuckets() {
|
|
397
|
+
this.nativeBuckets.clear();
|
|
398
|
+
if (!this.native) return;
|
|
399
|
+
for (const nativeRule of this.native.cssRules){
|
|
400
|
+
const styleRule = this.getStyleRule(nativeRule);
|
|
401
|
+
if (!styleRule) continue;
|
|
402
|
+
const mediaText = nativeRule instanceof CSSMediaRule ? nativeRule.conditionText : '';
|
|
403
|
+
const normalizedMediaText = mediaText ? `@media ${mediaText}` : '';
|
|
404
|
+
const key = this.getBucketKey(normalizedMediaText, styleRule.selectorText);
|
|
405
|
+
this.nativeBuckets.set(key, {
|
|
406
|
+
key,
|
|
407
|
+
mediaText: normalizedMediaText,
|
|
408
|
+
selectorText: styleRule.selectorText,
|
|
409
|
+
nativeRule,
|
|
410
|
+
styleRule
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
ensureNativeBucket(mediaText, selectorText) {
|
|
415
|
+
if (!this.native) return;
|
|
416
|
+
const key = this.getBucketKey(mediaText, selectorText);
|
|
417
|
+
let bucket = this.nativeBuckets.get(key);
|
|
418
|
+
if (bucket) return bucket;
|
|
419
|
+
const text = mediaText ? `${mediaText}{${selectorText}{}}` : `${selectorText}{}`;
|
|
420
|
+
const insertedIndex = this.native.insertRule(text, this.getNativeBucketInsertIndex(mediaText, selectorText));
|
|
421
|
+
const nativeRule = this.native.cssRules.item(insertedIndex);
|
|
422
|
+
if (!nativeRule) return;
|
|
423
|
+
const styleRule = this.getStyleRule(nativeRule);
|
|
424
|
+
if (!styleRule) return;
|
|
425
|
+
const newBucket = {
|
|
426
|
+
key,
|
|
427
|
+
mediaText,
|
|
428
|
+
selectorText,
|
|
429
|
+
nativeRule,
|
|
430
|
+
styleRule
|
|
431
|
+
};
|
|
432
|
+
this.nativeBuckets.set(key, newBucket);
|
|
433
|
+
return newBucket;
|
|
434
|
+
}
|
|
435
|
+
getNativeBucketInsertIndex(mediaText, selectorText) {
|
|
436
|
+
if (!this.native) return 0;
|
|
437
|
+
const bucket = {
|
|
438
|
+
mediaText,
|
|
439
|
+
selectorText
|
|
440
|
+
};
|
|
441
|
+
for(let index = 0; index < this.native.cssRules.length; index++){
|
|
442
|
+
const nativeRule = this.native.cssRules.item(index);
|
|
443
|
+
if (!nativeRule) continue;
|
|
444
|
+
const styleRule = this.getStyleRule(nativeRule);
|
|
445
|
+
if (!styleRule) continue;
|
|
446
|
+
const nativeMediaText = nativeRule instanceof CSSMediaRule ? `@media ${nativeRule.conditionText}` : '';
|
|
447
|
+
if (this.shouldInsertBucketBefore(bucket, {
|
|
448
|
+
mediaText: nativeMediaText,
|
|
449
|
+
selectorText: styleRule.selectorText
|
|
450
|
+
})) {
|
|
451
|
+
return index;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return this.native.cssRules.length;
|
|
455
|
+
}
|
|
456
|
+
insert(rule, index = this.rules.length) {
|
|
457
|
+
const wasEmpty = this.rules.length === 0;
|
|
458
|
+
const insertedIndex = super.insert(rule, index);
|
|
459
|
+
if (insertedIndex === undefined || !this.native || wasEmpty) return insertedIndex;
|
|
460
|
+
for (const node of rule.nodes){
|
|
461
|
+
const bucket = this.ensureNativeBucket(node.mediaText, node.selectorText);
|
|
462
|
+
bucket?.styleRule.style.setProperty(node.declarationName, node.declarationValue);
|
|
463
|
+
node.native = bucket?.styleRule;
|
|
464
|
+
}
|
|
465
|
+
return insertedIndex;
|
|
466
|
+
}
|
|
467
|
+
delete(key) {
|
|
468
|
+
const rule = this.rules.find((rule)=>rule.key === key);
|
|
469
|
+
const deletedRule = super.delete(key);
|
|
470
|
+
if (!rule || !deletedRule || !this.native) return deletedRule;
|
|
471
|
+
for (const node of rule.nodes){
|
|
472
|
+
const bucketKey = this.getBucketKey(node.mediaText, node.selectorText);
|
|
473
|
+
const bucket = this.nativeBuckets.get(bucketKey);
|
|
474
|
+
if (!bucket) continue;
|
|
475
|
+
bucket.styleRule.style.removeProperty(node.declarationName);
|
|
476
|
+
if (bucket.styleRule.style.length === 0) {
|
|
477
|
+
const foundIndex = findNativeCSSRuleIndex(this.native.cssRules, bucket.nativeRule);
|
|
478
|
+
if (foundIndex !== -1) {
|
|
479
|
+
this.native.deleteRule(foundIndex);
|
|
480
|
+
}
|
|
481
|
+
this.nativeBuckets.delete(bucketKey);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return deletedRule;
|
|
485
|
+
}
|
|
486
|
+
detach() {
|
|
487
|
+
super.detach();
|
|
488
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
489
|
+
if (nativeSheet && this.native?.parentStyleSheet) {
|
|
490
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
491
|
+
if (foundIndex !== -1) {
|
|
492
|
+
nativeSheet.deleteRule(foundIndex);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
this.native = null;
|
|
496
|
+
this.nativeBuckets.clear();
|
|
497
|
+
}
|
|
498
|
+
reset() {
|
|
499
|
+
super.reset();
|
|
500
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
501
|
+
if (this.native && nativeSheet) {
|
|
502
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
503
|
+
if (foundIndex !== -1) {
|
|
504
|
+
nativeSheet.deleteRule(foundIndex);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this.native = null;
|
|
508
|
+
this.nativeBuckets.clear();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function updateClassDelta(deltaCounts, className, delta) {
|
|
513
|
+
const nextDelta = (deltaCounts.get(className) || 0) + delta;
|
|
514
|
+
if (nextDelta) {
|
|
515
|
+
deltaCounts.set(className, nextDelta);
|
|
516
|
+
} else {
|
|
517
|
+
deltaCounts.delete(className);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function traverseElementSubtree(root, visited, callback) {
|
|
521
|
+
const stack = [
|
|
522
|
+
root
|
|
523
|
+
];
|
|
524
|
+
while(stack.length){
|
|
525
|
+
const el = stack.pop();
|
|
526
|
+
if (visited.has(el)) continue;
|
|
527
|
+
visited.add(el);
|
|
528
|
+
callback(el);
|
|
529
|
+
for(let child = el.lastElementChild; child; child = child.previousElementSibling){
|
|
530
|
+
stack.push(child);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
class RuntimeClassTracker {
|
|
535
|
+
snapshots = new WeakMap();
|
|
536
|
+
reset() {
|
|
537
|
+
this.snapshots = new WeakMap();
|
|
538
|
+
}
|
|
539
|
+
collectConnected(root, classCounts) {
|
|
540
|
+
this.reset();
|
|
541
|
+
const connectedNames = new Set();
|
|
542
|
+
for (const el of root.querySelectorAll('[class]')){
|
|
543
|
+
const classNames = this.snapshotElement(el);
|
|
544
|
+
if (!classNames) continue;
|
|
545
|
+
for (const className of classNames){
|
|
546
|
+
const count = classCounts.get(className) || 0;
|
|
547
|
+
if (!count) connectedNames.add(className);
|
|
548
|
+
classCounts.set(className, count + 1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return connectedNames;
|
|
552
|
+
}
|
|
553
|
+
collectMutations(records) {
|
|
554
|
+
const deltaCounts = new Map();
|
|
555
|
+
const nodeCounts = new Map();
|
|
556
|
+
const attrRecords = new Set();
|
|
557
|
+
const visited = new WeakSet();
|
|
558
|
+
const updateNodeCount = (el, delta)=>{
|
|
559
|
+
const nextCount = (nodeCounts.get(el) || 0) + delta;
|
|
560
|
+
if (nextCount) {
|
|
561
|
+
nodeCounts.set(el, nextCount);
|
|
562
|
+
} else {
|
|
563
|
+
nodeCounts.delete(el);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
for (const record of records){
|
|
567
|
+
if (record.type === 'childList') {
|
|
568
|
+
for (const node of record.addedNodes){
|
|
569
|
+
if (node instanceof Element && node.isConnected) updateNodeCount(node, 1);
|
|
570
|
+
}
|
|
571
|
+
for (const node of record.removedNodes){
|
|
572
|
+
if (node instanceof Element && !node.isConnected) updateNodeCount(node, -1);
|
|
573
|
+
}
|
|
574
|
+
} else if (record.type === 'attributes' && record.attributeName === 'class') {
|
|
575
|
+
const target = record.target;
|
|
576
|
+
if (target instanceof Element) attrRecords.add(target);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (const [node, count] of nodeCounts){
|
|
580
|
+
if (count > 0) {
|
|
581
|
+
traverseElementSubtree(node, visited, (el)=>this.diffElement(el, deltaCounts));
|
|
582
|
+
} else if (count < 0 && !node.isConnected) {
|
|
583
|
+
traverseElementSubtree(node, visited, (el)=>this.removeElement(el, deltaCounts));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
for (const el of attrRecords){
|
|
587
|
+
if (!visited.has(el)) this.diffElement(el, deltaCounts);
|
|
588
|
+
}
|
|
589
|
+
return deltaCounts;
|
|
590
|
+
}
|
|
591
|
+
snapshotElement(el) {
|
|
592
|
+
const classList = el.classList;
|
|
593
|
+
if (!classList.length) {
|
|
594
|
+
this.snapshots.delete(el);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const classNames = new Set();
|
|
598
|
+
for(let i = 0; i < classList.length; i++){
|
|
599
|
+
classNames.add(classList.item(i));
|
|
600
|
+
}
|
|
601
|
+
this.snapshots.set(el, classNames);
|
|
602
|
+
return classNames;
|
|
603
|
+
}
|
|
604
|
+
diffElement(el, deltaCounts) {
|
|
605
|
+
const previousClassNames = this.snapshots.get(el);
|
|
606
|
+
const classList = el.classList;
|
|
607
|
+
let nextClassNames;
|
|
608
|
+
for(let i = 0; i < classList.length; i++){
|
|
609
|
+
const className = classList.item(i);
|
|
610
|
+
if (!previousClassNames?.has(className)) updateClassDelta(deltaCounts, className, 1);
|
|
611
|
+
(nextClassNames ??= new Set()).add(className);
|
|
612
|
+
}
|
|
613
|
+
if (previousClassNames) {
|
|
614
|
+
for (const className of previousClassNames){
|
|
615
|
+
if (!nextClassNames?.has(className)) updateClassDelta(deltaCounts, className, -1);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (nextClassNames) {
|
|
619
|
+
this.snapshots.set(el, nextClassNames);
|
|
620
|
+
} else {
|
|
621
|
+
this.snapshots.delete(el);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
removeElement(el, deltaCounts) {
|
|
625
|
+
const previousClassNames = this.snapshots.get(el);
|
|
626
|
+
if (!previousClassNames) return;
|
|
627
|
+
for (const className of previousClassNames){
|
|
628
|
+
updateClassDelta(deltaCounts, className, -1);
|
|
629
|
+
}
|
|
630
|
+
this.snapshots.delete(el);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function collectVariableNames(text) {
|
|
635
|
+
const variableNames = new Set();
|
|
636
|
+
for (const match of text.matchAll(/var\(\s*--([_a-zA-Z0-9-]+)/g)){
|
|
637
|
+
variableNames.add(match[1]);
|
|
638
|
+
}
|
|
639
|
+
return variableNames;
|
|
640
|
+
}
|
|
641
|
+
class HydratedGeneratedRuleNode {
|
|
642
|
+
text;
|
|
643
|
+
native;
|
|
644
|
+
constructor(text){
|
|
645
|
+
this.text = text;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
class HydratedGeneratedRule {
|
|
649
|
+
ir;
|
|
650
|
+
layer;
|
|
651
|
+
native;
|
|
652
|
+
name;
|
|
653
|
+
key;
|
|
654
|
+
layerName;
|
|
655
|
+
type;
|
|
656
|
+
sortTier;
|
|
657
|
+
priority;
|
|
658
|
+
text;
|
|
659
|
+
valid = true;
|
|
660
|
+
fixedClass;
|
|
661
|
+
nodes;
|
|
662
|
+
variableNames;
|
|
663
|
+
animationNames;
|
|
664
|
+
selectorText;
|
|
665
|
+
constructor(ir, layer){
|
|
666
|
+
this.ir = ir;
|
|
667
|
+
this.layer = layer;
|
|
668
|
+
this.name = ir.className;
|
|
669
|
+
this.key = ir.key;
|
|
670
|
+
this.layerName = ir.layer;
|
|
671
|
+
this.type = ir.type;
|
|
672
|
+
this.sortTier = ir.sortTier;
|
|
673
|
+
this.priority = ir.priority;
|
|
674
|
+
this.text = ir.text;
|
|
675
|
+
this.selectorText = ir.selectorText;
|
|
676
|
+
if (ir.nodes?.length) {
|
|
677
|
+
this.nodes = ir.nodes.map((node)=>new HydratedGeneratedRuleNode(node.text));
|
|
678
|
+
}
|
|
679
|
+
const variableNames = collectVariableNames(ir.text);
|
|
680
|
+
ir.nodes?.forEach((node)=>{
|
|
681
|
+
collectVariableNames(node.text).forEach((variableName)=>variableNames.add(variableName));
|
|
682
|
+
});
|
|
683
|
+
ir.variableNames?.forEach((variableName)=>variableNames.add(variableName));
|
|
684
|
+
if (variableNames.size) {
|
|
685
|
+
this.variableNames = variableNames;
|
|
686
|
+
}
|
|
687
|
+
if (ir.animationNames?.length) {
|
|
688
|
+
this.animationNames = new Set(ir.animationNames);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const supportsCache = new Map();
|
|
694
|
+
const browserNativeDeclarationMatcher = ({ property, value })=>{
|
|
695
|
+
if (property.startsWith('--')) return true;
|
|
696
|
+
const cacheKey = property + '\0' + value;
|
|
697
|
+
const cached = supportsCache.get(cacheKey);
|
|
698
|
+
if (cached !== undefined) return cached;
|
|
699
|
+
const supported = Boolean(globalThis.CSS?.supports(property, value));
|
|
700
|
+
supportsCache.set(cacheKey, supported);
|
|
701
|
+
return supported;
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
const MASTER_CSS_RUNTIME_STYLE_SELECTOR = `style#${MASTER_CSS_RUNTIME_STYLE_ID}`;
|
|
705
|
+
function isDocumentRoot(root) {
|
|
706
|
+
const rootConstructorName = root?.constructor.name;
|
|
707
|
+
return rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document';
|
|
708
|
+
}
|
|
709
|
+
function findElementById(root, id) {
|
|
710
|
+
return isDocumentRoot(root) ? root.getElementById(id) : 'querySelector' in root ? root.querySelector(`#${id}`) : undefined;
|
|
711
|
+
}
|
|
712
|
+
function parseHydrationManifest(source) {
|
|
713
|
+
try {
|
|
714
|
+
const hydrationManifest = JSON.parse(source);
|
|
715
|
+
return hydrationManifest?.version === 1 && Array.isArray(hydrationManifest.rules) ? hydrationManifest : undefined;
|
|
716
|
+
} catch (error) {
|
|
717
|
+
{
|
|
718
|
+
console.debug('Cannot parse Master CSS hydration manifest.', error);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function readInlineHydrationManifest(root) {
|
|
723
|
+
const element = findElementById(root, MASTER_CSS_HYDRATION_MANIFEST_SCRIPT_ID);
|
|
724
|
+
const source = element?.textContent?.trim();
|
|
725
|
+
if (!source) return;
|
|
726
|
+
return parseHydrationManifest(source);
|
|
727
|
+
}
|
|
728
|
+
function readExternalHydrationManifestSource(root) {
|
|
729
|
+
const styleElement = findElementById(root, MASTER_CSS_RUNTIME_STYLE_ID);
|
|
730
|
+
const HTMLStyleElementConstructor = globalThis.HTMLStyleElement;
|
|
731
|
+
return HTMLStyleElementConstructor && styleElement instanceof HTMLStyleElementConstructor ? styleElement.getAttribute(MASTER_CSS_HYDRATION_MANIFEST_ATTR) : undefined;
|
|
732
|
+
}
|
|
237
733
|
class CSSRuntime extends MasterCSS {
|
|
238
734
|
root;
|
|
239
|
-
|
|
240
|
-
baseConfig;
|
|
735
|
+
hydrationManifest;
|
|
241
736
|
static instances = new WeakMap();
|
|
242
737
|
host;
|
|
243
738
|
container;
|
|
244
|
-
baseLayer = new
|
|
245
|
-
themeLayer = new
|
|
246
|
-
|
|
247
|
-
componentsLayer = new
|
|
248
|
-
|
|
739
|
+
baseLayer = new RuntimeUtilityLayer('base', this);
|
|
740
|
+
themeLayer = new RuntimeThemeLayer('theme', this);
|
|
741
|
+
defaultsLayer = new RuntimeUtilityLayer('defaults', this);
|
|
742
|
+
componentsLayer = new RuntimeUtilityLayer('components', this);
|
|
743
|
+
utilitiesLayer = new RuntimeUtilityLayer('utilities', this);
|
|
249
744
|
classCounts = new Map();
|
|
745
|
+
classTracker = new RuntimeClassTracker();
|
|
746
|
+
hydrationFailureReason;
|
|
250
747
|
observer;
|
|
251
748
|
progressive = false;
|
|
252
749
|
observing = false;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
750
|
+
static create(options) {
|
|
751
|
+
const { manifest, root = document, emittedGlobals, hydrationManifest } = options;
|
|
752
|
+
const resolvedHydrationManifest = hydrationManifest === undefined ? readInlineHydrationManifest(root) : hydrationManifest;
|
|
753
|
+
const current = globalThis.MasterCSSRuntime.instances.get(root);
|
|
754
|
+
if (current) {
|
|
755
|
+
current.registerEmittedGlobals(emittedGlobals);
|
|
756
|
+
if (resolvedHydrationManifest !== undefined) current.setHydrationManifest(resolvedHydrationManifest);
|
|
757
|
+
return current;
|
|
758
|
+
}
|
|
759
|
+
return new CSSRuntime(root, manifest, emittedGlobals, resolvedHydrationManifest).register();
|
|
760
|
+
}
|
|
761
|
+
constructor(root = document, manifest, emittedGlobals, hydrationManifest){
|
|
762
|
+
super(manifest, emittedGlobals, {
|
|
763
|
+
nativeDeclarationMatcher: browserNativeDeclarationMatcher
|
|
764
|
+
}), this.root = root, this.hydrationManifest = hydrationManifest;
|
|
765
|
+
if (isDocumentRoot(root)) {
|
|
766
|
+
this.container = root.head;
|
|
767
|
+
this.host = root.documentElement;
|
|
261
768
|
} else {
|
|
262
769
|
this.container = this.root;
|
|
263
770
|
this.host = this.root.host;
|
|
264
771
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
cssRuntime: this
|
|
772
|
+
this.applyEmittedGlobalsCounts({
|
|
773
|
+
variables: this.emittedGlobals.variables
|
|
268
774
|
});
|
|
269
775
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
776
|
+
register() {
|
|
777
|
+
const registered = globalThis.MasterCSSRuntime.instances.get(this.root) === this;
|
|
778
|
+
globalThis.MasterCSSRuntime.instances.set(this.root, this);
|
|
779
|
+
if (isDocumentRoot(this.root)) {
|
|
780
|
+
this.root.defaultView.globalThis.masterCSSRuntime = this;
|
|
781
|
+
}
|
|
782
|
+
if (!registered) {
|
|
783
|
+
__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:created', {
|
|
784
|
+
cssRuntime: this
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
unregister() {
|
|
790
|
+
globalThis.MasterCSSRuntime.instances.delete(this.root);
|
|
791
|
+
if (isDocumentRoot(this.root) && this.root.defaultView.globalThis.masterCSSRuntime === this) {
|
|
792
|
+
this.root.defaultView.globalThis.masterCSSRuntime = undefined;
|
|
793
|
+
}
|
|
794
|
+
return this;
|
|
795
|
+
}
|
|
796
|
+
setHydrationManifest(hydrationManifest) {
|
|
797
|
+
this.hydrationManifest = hydrationManifest;
|
|
798
|
+
return this;
|
|
799
|
+
}
|
|
800
|
+
needsHydrationManifest() {
|
|
801
|
+
return this.hydrationManifest === undefined && Boolean(readExternalHydrationManifestSource(this.root));
|
|
802
|
+
}
|
|
803
|
+
async loadHydrationManifest() {
|
|
804
|
+
const inlineHydrationManifest = readInlineHydrationManifest(this.root);
|
|
805
|
+
if (inlineHydrationManifest) {
|
|
806
|
+
this.setHydrationManifest(inlineHydrationManifest);
|
|
807
|
+
return this;
|
|
808
|
+
}
|
|
809
|
+
const source = readExternalHydrationManifestSource(this.root);
|
|
810
|
+
if (!source) return this;
|
|
811
|
+
try {
|
|
812
|
+
const response = await fetch(source, {
|
|
813
|
+
credentials: 'same-origin'
|
|
814
|
+
});
|
|
815
|
+
if (!response.ok) return this;
|
|
816
|
+
this.setHydrationManifest(parseHydrationManifest(await response.text()));
|
|
817
|
+
} catch (error) {
|
|
818
|
+
{
|
|
819
|
+
console.debug('Cannot load Master CSS hydration manifest.', error);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return this;
|
|
823
|
+
}
|
|
824
|
+
createRuntimeStyle() {
|
|
825
|
+
const ownerDocument = 'createElement' in this.root ? this.root : this.root.ownerDocument;
|
|
826
|
+
this.style = ownerDocument.createElement('style');
|
|
827
|
+
this.style.id = MASTER_CSS_RUNTIME_STYLE_ID;
|
|
828
|
+
this.style.setAttribute('blocking', 'render');
|
|
829
|
+
this.container.append(this.style);
|
|
830
|
+
}
|
|
831
|
+
warnHydrationFallback(reason) {
|
|
832
|
+
console.warn(`Master CSS progressive hydration requires a matching hydration manifest. ${reason} Rebuilding ${MASTER_CSS_RUNTIME_STYLE_SELECTOR} with the runtime.`);
|
|
833
|
+
}
|
|
834
|
+
useRuntimeStyle(connectedNames, reason) {
|
|
835
|
+
if (reason) this.warnHydrationFallback(reason);
|
|
836
|
+
this.style?.remove();
|
|
837
|
+
this.style = null;
|
|
838
|
+
this.progressive = false;
|
|
839
|
+
this.createRuntimeStyle();
|
|
840
|
+
this.insertStaticResources();
|
|
841
|
+
connectedNames.forEach((cls)=>this.add(cls));
|
|
842
|
+
}
|
|
843
|
+
detectRuntimeStyle() {
|
|
277
844
|
if (this.root.styleSheets) {
|
|
278
845
|
for (const sheet of this.root.styleSheets){
|
|
279
846
|
const { ownerNode } = sheet;
|
|
280
|
-
if (ownerNode instanceof HTMLStyleElement && ownerNode.id ===
|
|
847
|
+
if (ownerNode instanceof HTMLStyleElement && ownerNode.id === MASTER_CSS_RUNTIME_STYLE_ID) {
|
|
281
848
|
this.style = ownerNode;
|
|
282
|
-
|
|
283
|
-
this.progressive = true;
|
|
284
|
-
}
|
|
849
|
+
this.progressive = true;
|
|
285
850
|
break;
|
|
286
851
|
}
|
|
287
852
|
}
|
|
288
853
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
const elementsWithClass = this.root.querySelectorAll('[class]');
|
|
299
|
-
elementsWithClass.forEach((el)=>{
|
|
300
|
-
const clsList = el.classList;
|
|
301
|
-
if (clsList) {
|
|
302
|
-
el.classList.forEach(increaseClassCount);
|
|
303
|
-
}
|
|
304
|
-
elementClasses.set(el, new Set(clsList));
|
|
305
|
-
});
|
|
306
|
-
// Hydration or style creation
|
|
307
|
-
if (this.progressive) {
|
|
308
|
-
const hydrateResult = this.hydrate(this.style.sheet.cssRules);
|
|
854
|
+
}
|
|
855
|
+
collectConnectedClasses() {
|
|
856
|
+
return this.classTracker.collectConnected(this.root, this.classCounts);
|
|
857
|
+
}
|
|
858
|
+
hydrateRuntimeStyle(connectedNames) {
|
|
859
|
+
const hydrateResult = this.style?.sheet && this.hydrate(this.style.sheet.cssRules);
|
|
860
|
+
if (hydrateResult) {
|
|
861
|
+
const hydratedClassNames = new Set(hydrateResult.allUtilities.map(({ fixedClass, name })=>fixedClass || name));
|
|
309
862
|
for (const cls of connectedNames){
|
|
310
|
-
if (!
|
|
863
|
+
if (!hydratedClassNames.has(cls)) {
|
|
311
864
|
this.add(cls);
|
|
312
865
|
{
|
|
313
866
|
console.debug(`Missing prerendered rule for class \`${cls}\``);
|
|
@@ -315,214 +868,343 @@ class CSSRuntime extends MasterCSS {
|
|
|
315
868
|
}
|
|
316
869
|
}
|
|
317
870
|
} else {
|
|
318
|
-
this.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const removeSnapshot = (el)=>{
|
|
340
|
-
for (const c of elementClasses.get(el) || [])updateDelta(c, -1);
|
|
341
|
-
elementClasses.delete(el);
|
|
342
|
-
};
|
|
343
|
-
for (const record of records){
|
|
344
|
-
if (record.type === 'childList') {
|
|
345
|
-
for (const node of record.addedNodes)if (node instanceof Element && node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) + 1);
|
|
346
|
-
for (const node of record.removedNodes)if (node instanceof Element && !node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) - 1);
|
|
347
|
-
} else if (record.type === 'attributes' && record.attributeName === 'class') {
|
|
348
|
-
attrRecords.add(record.target);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
const traverseIncludingSelf = (el, fn, visited)=>{
|
|
352
|
-
if (!visited.has(el)) {
|
|
353
|
-
visited.add(el);
|
|
354
|
-
fn(el);
|
|
355
|
-
for (const child of el.children){
|
|
356
|
-
traverseIncludingSelf(child, fn, visited);
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
nodeMap.delete(el);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
for (const [node, count] of nodeMap){
|
|
363
|
-
if (count > 0) {
|
|
364
|
-
traverseIncludingSelf(node, diffAndSnapshot, visited);
|
|
365
|
-
} else if (count < 0 && !node.isConnected) {
|
|
366
|
-
traverseIncludingSelf(node, removeSnapshot, visited);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
for (const el of attrRecords){
|
|
370
|
-
if (!visited.has(el)) diffAndSnapshot(el);
|
|
371
|
-
}
|
|
372
|
-
for (const [cls, change] of deltaCounts){
|
|
373
|
-
const current = this.classCounts.get(cls) || 0;
|
|
374
|
-
const next = current + change;
|
|
375
|
-
if (next > 0) {
|
|
376
|
-
this.classCounts.set(cls, next);
|
|
377
|
-
if (current === 0) this.add(cls);
|
|
378
|
-
} else {
|
|
379
|
-
this.classCounts.delete(cls);
|
|
380
|
-
this.remove(cls);
|
|
381
|
-
}
|
|
871
|
+
this.useRuntimeStyle(connectedNames, this.hydrationFailureReason || `Cannot read ${MASTER_CSS_RUNTIME_STYLE_SELECTOR} CSS rules.`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
renderRuntimeStyle(connectedNames) {
|
|
875
|
+
this.createRuntimeStyle();
|
|
876
|
+
this.insertStaticResources();
|
|
877
|
+
connectedNames.forEach((cls)=>this.add(cls));
|
|
878
|
+
}
|
|
879
|
+
handleMutationRecords(records) {
|
|
880
|
+
const deltaCounts = this.classTracker.collectMutations(records);
|
|
881
|
+
const addedClassNames = [];
|
|
882
|
+
const removedClassNames = [];
|
|
883
|
+
for (const [cls, change] of deltaCounts){
|
|
884
|
+
const current = this.classCounts.get(cls) || 0;
|
|
885
|
+
const next = current + change;
|
|
886
|
+
if (next > 0) {
|
|
887
|
+
this.classCounts.set(cls, next);
|
|
888
|
+
if (current === 0) addedClassNames.push(cls);
|
|
889
|
+
} else {
|
|
890
|
+
this.classCounts.delete(cls);
|
|
891
|
+
removedClassNames.push(cls);
|
|
382
892
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
893
|
+
}
|
|
894
|
+
if (addedClassNames.length) this.add(...addedClassNames);
|
|
895
|
+
if (removedClassNames.length) this.remove(...removedClassNames);
|
|
896
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:mutated', {
|
|
897
|
+
records,
|
|
898
|
+
classCounts: deltaCounts,
|
|
899
|
+
cssRuntime: this
|
|
388
900
|
});
|
|
901
|
+
}
|
|
902
|
+
startMutationObserver() {
|
|
903
|
+
this.observer = new MutationObserver((records)=>this.handleMutationRecords(records));
|
|
389
904
|
this.observer.observe(this.root, {
|
|
390
905
|
childList: true,
|
|
391
906
|
attributes: true,
|
|
392
907
|
attributeFilter: [
|
|
393
908
|
'class'
|
|
394
909
|
],
|
|
395
|
-
attributeOldValue: true,
|
|
396
910
|
subtree: true
|
|
397
911
|
});
|
|
912
|
+
}
|
|
913
|
+
revealHostIfNeeded() {
|
|
398
914
|
if (!this.progressive) this.host.removeAttribute('hidden');
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Observe the DOM for changes and update the running stylesheet. (browser only)
|
|
918
|
+
* @param options mutation observer options
|
|
919
|
+
* @returns this
|
|
920
|
+
*/ observe() {
|
|
921
|
+
if (this.observing) return this;
|
|
922
|
+
this.detectRuntimeStyle();
|
|
923
|
+
const connectedNames = this.collectConnectedClasses();
|
|
924
|
+
if (this.progressive) {
|
|
925
|
+
this.hydrateRuntimeStyle(connectedNames);
|
|
926
|
+
} else {
|
|
927
|
+
this.renderRuntimeStyle(connectedNames);
|
|
928
|
+
}
|
|
929
|
+
this.startMutationObserver();
|
|
930
|
+
this.revealHostIfNeeded();
|
|
399
931
|
this.observing = true;
|
|
400
932
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:observed', {
|
|
401
933
|
cssRuntime: this
|
|
402
934
|
});
|
|
403
935
|
return this;
|
|
404
936
|
}
|
|
937
|
+
failHydration(reason) {
|
|
938
|
+
this.hydrationFailureReason = reason;
|
|
939
|
+
return undefined;
|
|
940
|
+
}
|
|
941
|
+
getHydrationManifestLayerRules() {
|
|
942
|
+
const layers = new Map();
|
|
943
|
+
if (!this.hydrationManifest?.rules?.length) return layers;
|
|
944
|
+
for (const rule of this.hydrationManifest.rules){
|
|
945
|
+
const layerRules = layers.get(rule.layer);
|
|
946
|
+
if (layerRules) {
|
|
947
|
+
layerRules.push(rule);
|
|
948
|
+
} else {
|
|
949
|
+
layers.set(rule.layer, [
|
|
950
|
+
rule
|
|
951
|
+
]);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return layers;
|
|
955
|
+
}
|
|
956
|
+
registerHydratedClassRule(className, rule) {
|
|
957
|
+
const classUtilities = this.classUtilities;
|
|
958
|
+
const rules = classUtilities.get(className);
|
|
959
|
+
if (rules) {
|
|
960
|
+
rules.push(rule);
|
|
961
|
+
} else {
|
|
962
|
+
classUtilities.set(className, [
|
|
963
|
+
rule
|
|
964
|
+
]);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
getUtilityLayerByName(name) {
|
|
968
|
+
switch(name){
|
|
969
|
+
case 'base':
|
|
970
|
+
return this.baseLayer;
|
|
971
|
+
case 'defaults':
|
|
972
|
+
return this.defaultsLayer;
|
|
973
|
+
case 'components':
|
|
974
|
+
return this.componentsLayer;
|
|
975
|
+
case 'utilities':
|
|
976
|
+
return this.utilitiesLayer;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
collectHydrationManifestVariableNames() {
|
|
980
|
+
const variableNames = new Set();
|
|
981
|
+
const collectVariableReferences = (text)=>{
|
|
982
|
+
for (const match of text.matchAll(/var\(\s*--([_a-zA-Z0-9-]+)/g)){
|
|
983
|
+
collectVariable(match[1]);
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
const collectVariable = (variableName, visited = new Set())=>{
|
|
987
|
+
if (visited.has(variableName)) return;
|
|
988
|
+
visited.add(variableName);
|
|
989
|
+
const variable = this.variables.get(variableName);
|
|
990
|
+
if (!variable) return;
|
|
991
|
+
variableNames.add(variableName);
|
|
992
|
+
variable.dependencies?.forEach((dependency)=>collectVariable(dependency, visited));
|
|
993
|
+
};
|
|
994
|
+
for (const rule of this.hydrationManifest?.rules || []){
|
|
995
|
+
rule.variableNames?.forEach((variableName)=>collectVariable(variableName));
|
|
996
|
+
collectVariableReferences(rule.text);
|
|
997
|
+
rule.nodes?.forEach((node)=>collectVariableReferences(node.text));
|
|
998
|
+
rule.animationNames?.forEach((animationName)=>{
|
|
999
|
+
const keyframes = this.animations.get(animationName);
|
|
1000
|
+
if (!keyframes) return;
|
|
1001
|
+
const animationRule = new AnimationRule(animationName, keyframes, this);
|
|
1002
|
+
animationRule.variableNames?.forEach((variableName)=>collectVariable(variableName));
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
for (const [variableName, variable] of this.variables){
|
|
1006
|
+
if (variable.static) collectVariable(variableName);
|
|
1007
|
+
}
|
|
1008
|
+
return variableNames;
|
|
1009
|
+
}
|
|
1010
|
+
collectHydrationManifestAnimationNames() {
|
|
1011
|
+
const animationNames = new Set();
|
|
1012
|
+
for (const rule of this.hydrationManifest?.rules || []){
|
|
1013
|
+
rule.animationNames?.forEach((animationName)=>animationNames.add(animationName));
|
|
1014
|
+
}
|
|
1015
|
+
for (const animationName of this.animations.keys()){
|
|
1016
|
+
if (this.manifest.animationOptions?.[animationName]?.static) animationNames.add(animationName);
|
|
1017
|
+
}
|
|
1018
|
+
return animationNames;
|
|
1019
|
+
}
|
|
1020
|
+
nativeThemeLayerHasOnlyEmittedGlobalsVariables(nativeThemeLayer) {
|
|
1021
|
+
if (!nativeThemeLayer) return false;
|
|
1022
|
+
let found = false;
|
|
1023
|
+
for (const nativeRule of nativeThemeLayer.cssRules){
|
|
1024
|
+
const styleRule = this.themeLayer.getStyleRule(nativeRule);
|
|
1025
|
+
if (!styleRule) return false;
|
|
1026
|
+
for(let index = 0; index < styleRule.style.length; index++){
|
|
1027
|
+
const propertyName = styleRule.style.item(index);
|
|
1028
|
+
if (!propertyName.startsWith('--')) return false;
|
|
1029
|
+
found = true;
|
|
1030
|
+
if (!this.isEmittedGlobalsVariable(propertyName.slice(2))) return false;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return found;
|
|
1034
|
+
}
|
|
1035
|
+
getVariableRuleBuckets(variableRules) {
|
|
1036
|
+
const buckets = new Map();
|
|
1037
|
+
for (const rule of variableRules){
|
|
1038
|
+
for (const node of rule.nodes){
|
|
1039
|
+
const key = this.themeLayer.getBucketKey(node.mediaText, node.selectorText);
|
|
1040
|
+
let bucket = buckets.get(key);
|
|
1041
|
+
if (!bucket) {
|
|
1042
|
+
bucket = {
|
|
1043
|
+
mediaText: node.mediaText,
|
|
1044
|
+
selectorText: node.selectorText,
|
|
1045
|
+
nodes: []
|
|
1046
|
+
};
|
|
1047
|
+
buckets.set(key, bucket);
|
|
1048
|
+
}
|
|
1049
|
+
bucket.nodes.push(node);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return Array.from(buckets.values());
|
|
1053
|
+
}
|
|
1054
|
+
getNativeThemeRuleBucketKey(nativeRule) {
|
|
1055
|
+
const styleRule = this.themeLayer.getStyleRule(nativeRule);
|
|
1056
|
+
if (!styleRule) return;
|
|
1057
|
+
const mediaText = nativeRule instanceof CSSMediaRule ? `@media ${nativeRule.conditionText}` : '';
|
|
1058
|
+
return {
|
|
1059
|
+
key: this.themeLayer.getBucketKey(mediaText, styleRule.selectorText),
|
|
1060
|
+
styleRule
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
hydrateHydrationManifestVariables(nativeThemeLayer) {
|
|
1064
|
+
const variableRules = [
|
|
1065
|
+
...this.collectHydrationManifestVariableNames()
|
|
1066
|
+
].filter((variableName)=>!this.isEmittedGlobalsVariable(variableName)).map((variableName)=>{
|
|
1067
|
+
const variable = this.variables.get(variableName);
|
|
1068
|
+
return variable && !variable.inline ? new VariableRule(variableName, variable, this) : undefined;
|
|
1069
|
+
}).filter((rule)=>Boolean(rule));
|
|
1070
|
+
const expectedBuckets = this.getVariableRuleBuckets(variableRules);
|
|
1071
|
+
const nativeRuleCount = nativeThemeLayer?.cssRules.length || 0;
|
|
1072
|
+
if (expectedBuckets.length !== nativeRuleCount) {
|
|
1073
|
+
return !variableRules.length && this.nativeThemeLayerHasOnlyEmittedGlobalsVariables(nativeThemeLayer);
|
|
1074
|
+
}
|
|
1075
|
+
if (!variableRules.length) return true;
|
|
1076
|
+
if (!nativeThemeLayer) return false;
|
|
1077
|
+
this.themeLayer.native = nativeThemeLayer;
|
|
1078
|
+
const nativeBuckets = new Map();
|
|
1079
|
+
for (const nativeRule of nativeThemeLayer.cssRules){
|
|
1080
|
+
const nativeBucket = this.getNativeThemeRuleBucketKey(nativeRule);
|
|
1081
|
+
if (!nativeBucket || nativeBuckets.has(nativeBucket.key)) return false;
|
|
1082
|
+
nativeBuckets.set(nativeBucket.key, nativeBucket.styleRule);
|
|
1083
|
+
}
|
|
1084
|
+
for (const bucket of expectedBuckets){
|
|
1085
|
+
const nativeStyleRule = nativeBuckets.get(this.themeLayer.getBucketKey(bucket.mediaText, bucket.selectorText));
|
|
1086
|
+
if (!nativeStyleRule) return false;
|
|
1087
|
+
for (const node of bucket.nodes){
|
|
1088
|
+
node.native = nativeStyleRule;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
this.themeLayer.rules.push(...variableRules);
|
|
1092
|
+
this.themeLayer.syncNativeBuckets();
|
|
1093
|
+
if (this.themeLayer.rules.length && !this.rules.includes(this.themeLayer)) {
|
|
1094
|
+
this.rules.push(this.themeLayer);
|
|
1095
|
+
}
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
hydrateHydrationManifestAnimations(nativeKeyframesRules) {
|
|
1099
|
+
const animationNames = this.collectHydrationManifestAnimationNames();
|
|
1100
|
+
const hydratedAnimationNames = new Set();
|
|
1101
|
+
for (const animationName of animationNames){
|
|
1102
|
+
if (this.isEmittedGlobalsAnimation(animationName)) {
|
|
1103
|
+
if (nativeKeyframesRules.has(animationName)) hydratedAnimationNames.add(animationName);
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
const keyframes = this.animations.get(animationName);
|
|
1107
|
+
if (!keyframes) continue;
|
|
1108
|
+
const nativeRule = nativeKeyframesRules.get(animationName);
|
|
1109
|
+
if (!nativeRule) return false;
|
|
1110
|
+
const animationRule = new AnimationRule(animationName, keyframes, this);
|
|
1111
|
+
animationRule.native = nativeRule;
|
|
1112
|
+
this.animationsNonLayer.rules.push(animationRule);
|
|
1113
|
+
this.rules.push(animationRule);
|
|
1114
|
+
hydratedAnimationNames.add(animationName);
|
|
1115
|
+
}
|
|
1116
|
+
for (const animationName of nativeKeyframesRules.keys()){
|
|
1117
|
+
if (!hydratedAnimationNames.has(animationName)) return false;
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
hydrateHydrationManifestLayer(layer, nativeLayerRule, manifestRules, result) {
|
|
1122
|
+
const expectedRuleCount = manifestRules.reduce((count, rule)=>count + (rule.nodes?.length || 1), 0);
|
|
1123
|
+
if (expectedRuleCount !== nativeLayerRule.cssRules.length) return false;
|
|
1124
|
+
layer.native = nativeLayerRule;
|
|
1125
|
+
let nativeIndex = 0;
|
|
1126
|
+
for (const manifestRule of manifestRules){
|
|
1127
|
+
const hydratedRule = new HydratedGeneratedRule(manifestRule, layer);
|
|
1128
|
+
const nodes = hydratedRule.nodes;
|
|
1129
|
+
if (nodes?.length) {
|
|
1130
|
+
for (const node of nodes){
|
|
1131
|
+
node.native = nativeLayerRule.cssRules.item(nativeIndex++) || undefined;
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
hydratedRule.native = nativeLayerRule.cssRules.item(nativeIndex++) || undefined;
|
|
1135
|
+
}
|
|
1136
|
+
layer.rules.push(hydratedRule);
|
|
1137
|
+
layer.insertVariables(hydratedRule);
|
|
1138
|
+
layer.insertAnimations(hydratedRule);
|
|
1139
|
+
this.registerHydratedClassRule(manifestRule.className, hydratedRule);
|
|
1140
|
+
result.allUtilities.push(hydratedRule);
|
|
1141
|
+
}
|
|
1142
|
+
if (layer.rules.length && !this.rules.includes(layer)) {
|
|
1143
|
+
this.rules.push(layer);
|
|
1144
|
+
}
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
405
1147
|
hydrate(nativeLayerRules) {
|
|
406
|
-
|
|
407
|
-
|
|
1148
|
+
this.hydrationFailureReason = undefined;
|
|
1149
|
+
if (this.hydrationManifest?.version !== 1 || !Array.isArray(this.hydrationManifest.rules)) {
|
|
1150
|
+
return this.failHydration('Missing or invalid hydration manifest.');
|
|
1151
|
+
}
|
|
1152
|
+
if (!this.hydrationManifest.rules.length) {
|
|
1153
|
+
return this.failHydration(`Hydration manifest has no generated rules for ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
1154
|
+
}
|
|
408
1155
|
const result = {
|
|
409
|
-
|
|
1156
|
+
allUtilities: []
|
|
410
1157
|
};
|
|
1158
|
+
const manifestLayerRules = this.getHydrationManifestLayerRules();
|
|
1159
|
+
const nativeUtilityLayerRules = new Map();
|
|
1160
|
+
const nativeKeyframesRules = new Map();
|
|
1161
|
+
let nativeThemeLayer;
|
|
411
1162
|
for(let i = 0; i < nativeLayerRules.length; i++){
|
|
412
1163
|
const eachNativeCSSRule = nativeLayerRules[i];
|
|
413
1164
|
if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
|
|
414
1165
|
const eachCSSLayerRule = eachNativeCSSRule;
|
|
415
|
-
if (
|
|
416
|
-
this.
|
|
417
|
-
|
|
418
|
-
const unresolvedCSSRules = new Map();
|
|
419
|
-
for (const rule of eachCSSLayerRule.cssRules){
|
|
420
|
-
// trim() for fix the firefox bug that the cssText ends with \n\n
|
|
421
|
-
unresolvedCSSRules.set(rule.cssText.trim(), rule);
|
|
422
|
-
}
|
|
423
|
-
for (const cssRule of eachCSSLayerRule.cssRules){
|
|
424
|
-
if (!unresolvedCSSRules.has(cssRule.cssText)) continue;
|
|
425
|
-
const variableCSSRule = cssRule.constructor.name === 'CSSMediaRule' ? cssRule.cssRules[0] : cssRule;
|
|
426
|
-
const variableName = variableCSSRule.style[0].slice(2);
|
|
427
|
-
const variable = this.variables.get(variableName);
|
|
428
|
-
if (!variable) continue;
|
|
429
|
-
variableRule = new VariableRule(variableName, variable, this);
|
|
430
|
-
this.themeLayer.rules.push(variableRule);
|
|
431
|
-
this.themeLayer.tokenCounts.set(variableRule.name, 0);
|
|
432
|
-
variableRule.nodes.forEach((node)=>{
|
|
433
|
-
const checkRuleIndex = checkSheet.insertRule(node.text);
|
|
434
|
-
const checkNodeNativeRule = checkSheet.cssRules.item(checkRuleIndex);
|
|
435
|
-
if (checkNodeNativeRule) {
|
|
436
|
-
const checkNodeNativeRuleText = checkNodeNativeRule.cssText.trim();
|
|
437
|
-
const match = unresolvedCSSRules.get(checkNodeNativeRuleText);
|
|
438
|
-
if (match) {
|
|
439
|
-
node.native = match;
|
|
440
|
-
unresolvedCSSRules.delete(checkNodeNativeRuleText);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
if (this.themeLayer.rules.length) this.rules.push(this.themeLayer);
|
|
447
|
-
} else {
|
|
448
|
-
cssLayerRules.push(eachCSSLayerRule);
|
|
449
|
-
}
|
|
450
|
-
} else if (eachNativeCSSRule.constructor.name === 'CSSLayerStatementRule') {
|
|
451
|
-
this.layerStatementRule.native = eachNativeCSSRule;
|
|
452
|
-
} else if (eachNativeCSSRule.constructor.name === 'CSSKeyframesRule') {
|
|
453
|
-
const nativeKeyframsRule = eachNativeCSSRule;
|
|
454
|
-
const keyframes = this.animations.get(nativeKeyframsRule.name);
|
|
455
|
-
if (!keyframes) continue;
|
|
456
|
-
const animationRule = new AnimationRule(nativeKeyframsRule.name, keyframes, this);
|
|
457
|
-
animationRule.native = nativeKeyframsRule;
|
|
458
|
-
this.animationsNonLayer.rules.push(animationRule);
|
|
459
|
-
this.rules.push(animationRule);
|
|
460
|
-
this.animationsNonLayer.tokenCounts.set(animationRule.name, 0);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
for (const eachCSSLayerRule of cssLayerRules){
|
|
464
|
-
let layer;
|
|
465
|
-
switch(eachCSSLayerRule.name){
|
|
466
|
-
case 'base':
|
|
467
|
-
layer = this.baseLayer;
|
|
468
|
-
break;
|
|
469
|
-
case 'preset':
|
|
470
|
-
layer = this.presetLayer;
|
|
471
|
-
break;
|
|
472
|
-
case 'components':
|
|
473
|
-
layer = this.componentsLayer;
|
|
474
|
-
break;
|
|
475
|
-
case 'general':
|
|
476
|
-
layer = this.generalLayer;
|
|
477
|
-
break;
|
|
478
|
-
default:
|
|
479
|
-
console.error(`Cannot recognize the layer \`${eachCSSLayerRule.name}\`. (https://rc.css.master.co/messages/hydration-errors)`);
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
layer.native = eachCSSLayerRule;
|
|
483
|
-
const unresolvedCSSRules = new Map();
|
|
484
|
-
for (const rule of eachCSSLayerRule.cssRules){
|
|
485
|
-
// trim() for fix the firefox bug that the cssText ends with \n\n
|
|
486
|
-
unresolvedCSSRules.set(rule.cssText.trim(), rule);
|
|
487
|
-
}
|
|
488
|
-
for (const eachNativeLayerRule of eachCSSLayerRule.cssRules){
|
|
489
|
-
if (!unresolvedCSSRules.has(eachNativeLayerRule.cssText)) continue;
|
|
490
|
-
const selectorText = this.getSelectorText(eachNativeLayerRule);
|
|
491
|
-
if (!selectorText) {
|
|
492
|
-
console.error(`Cannot get the selector text from \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
1166
|
+
if (eachCSSLayerRule.name === 'theme') {
|
|
1167
|
+
if (nativeThemeLayer) return this.failHydration(`Duplicate theme layer in ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
1168
|
+
nativeThemeLayer = eachCSSLayerRule;
|
|
493
1169
|
continue;
|
|
494
1170
|
}
|
|
495
|
-
const
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
layer.insertVariables(createdRule);
|
|
500
|
-
layer.insertAnimations(createdRule);
|
|
501
|
-
result.allSyntaxRules.push(createdRule);
|
|
502
|
-
try {
|
|
503
|
-
const checkRuleIndex = checkSheet.insertRule(createdRule.text);
|
|
504
|
-
const checkNodeNativeRule = checkSheet.cssRules.item(checkRuleIndex);
|
|
505
|
-
if (checkNodeNativeRule) {
|
|
506
|
-
const checkNodeNativeRuleText = checkNodeNativeRule.cssText.trim();
|
|
507
|
-
const match = unresolvedCSSRules.get(checkNodeNativeRuleText);
|
|
508
|
-
if (match) {
|
|
509
|
-
createdRule.native = match;
|
|
510
|
-
unresolvedCSSRules.delete(checkNodeNativeRuleText);
|
|
511
|
-
continue;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
console.error(`Cannot retrieve CSS rule for \`${createdRule.text}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
515
|
-
} catch (error) {
|
|
516
|
-
{
|
|
517
|
-
console.debug(`Cannot insert CSS rule for \`${createdRule.text}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
} else {
|
|
522
|
-
console.error(`Cannot recognize \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
1171
|
+
const layer = this.getUtilityLayerByName(eachCSSLayerRule.name);
|
|
1172
|
+
if (!layer) return this.failHydration(`Unknown layer \`${eachCSSLayerRule.name}\` in ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
1173
|
+
if (nativeUtilityLayerRules.has(layer.name)) {
|
|
1174
|
+
return this.failHydration(`Duplicate layer \`${eachCSSLayerRule.name}\` in ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
523
1175
|
}
|
|
1176
|
+
nativeUtilityLayerRules.set(layer.name, eachCSSLayerRule);
|
|
1177
|
+
} else if (eachNativeCSSRule.constructor.name === 'CSSKeyframesRule') {
|
|
1178
|
+
const nativeKeyframesRule = eachNativeCSSRule;
|
|
1179
|
+
nativeKeyframesRules.set(nativeKeyframesRule.name, nativeKeyframesRule);
|
|
1180
|
+
} else {
|
|
1181
|
+
return this.failHydration(`Unknown top-level rule \`${eachNativeCSSRule.cssText}\` in ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
for (const [layerName, manifestRules] of manifestLayerRules){
|
|
1185
|
+
const nativeLayerRule = nativeUtilityLayerRules.get(layerName);
|
|
1186
|
+
if (!nativeLayerRule) return this.failHydration(`Missing layer \`${layerName}\` in ${MASTER_CSS_RUNTIME_STYLE_SELECTOR}.`);
|
|
1187
|
+
const expectedRuleCount = manifestRules.reduce((count, rule)=>count + (rule.nodes?.length || 1), 0);
|
|
1188
|
+
if (expectedRuleCount !== nativeLayerRule.cssRules.length) {
|
|
1189
|
+
return this.failHydration(`Layer \`${layerName}\` does not match the hydration manifest.`);
|
|
524
1190
|
}
|
|
525
|
-
|
|
1191
|
+
}
|
|
1192
|
+
for (const [layerName, nativeLayerRule] of nativeUtilityLayerRules){
|
|
1193
|
+
if (!manifestLayerRules.has(layerName) && nativeLayerRule.cssRules.length) {
|
|
1194
|
+
return this.failHydration(`Layer \`${layerName}\` has no hydration manifest rules.`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (!this.hydrateHydrationManifestVariables(nativeThemeLayer)) {
|
|
1198
|
+
return this.failHydration('Theme layer does not match the hydration manifest.');
|
|
1199
|
+
}
|
|
1200
|
+
if (!this.hydrateHydrationManifestAnimations(nativeKeyframesRules)) {
|
|
1201
|
+
return this.failHydration('Keyframes do not match the hydration manifest.');
|
|
1202
|
+
}
|
|
1203
|
+
for (const [layerName, nativeLayerRule] of nativeUtilityLayerRules){
|
|
1204
|
+
const manifestRules = manifestLayerRules.get(layerName);
|
|
1205
|
+
if (!manifestRules?.length) continue;
|
|
1206
|
+
const layer = this.getUtilityLayerByName(layerName);
|
|
1207
|
+
this.hydrateHydrationManifestLayer(layer, nativeLayerRule, manifestRules, result);
|
|
526
1208
|
}
|
|
527
1209
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:hydrated', {
|
|
528
1210
|
cssRuntime: this,
|
|
@@ -530,13 +1212,6 @@ class CSSRuntime extends MasterCSS {
|
|
|
530
1212
|
});
|
|
531
1213
|
return result;
|
|
532
1214
|
}
|
|
533
|
-
getSelectorText(cssRule) {
|
|
534
|
-
if (cssRule instanceof CSSStyleRule) {
|
|
535
|
-
return cssRule.selectorText;
|
|
536
|
-
} else if (cssRule instanceof CSSGroupingRule) {
|
|
537
|
-
return this.getSelectorText(cssRule.cssRules.item(0));
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
1215
|
disconnect() {
|
|
541
1216
|
if (!this.observing) return;
|
|
542
1217
|
if (this.observer) {
|
|
@@ -546,7 +1221,9 @@ class CSSRuntime extends MasterCSS {
|
|
|
546
1221
|
// @ts-ignore
|
|
547
1222
|
this.observing = false;
|
|
548
1223
|
this.reset();
|
|
1224
|
+
this.loadManifest(this.manifest);
|
|
549
1225
|
this.classCounts.clear();
|
|
1226
|
+
this.classTracker.reset();
|
|
550
1227
|
if (!this.progressive) {
|
|
551
1228
|
this.style?.remove();
|
|
552
1229
|
this.style = null;
|
|
@@ -556,27 +1233,28 @@ class CSSRuntime extends MasterCSS {
|
|
|
556
1233
|
});
|
|
557
1234
|
return this;
|
|
558
1235
|
}
|
|
559
|
-
refresh(
|
|
1236
|
+
refresh(manifest = this.manifest) {
|
|
560
1237
|
if (!this.observing || !this.style.sheet) return this;
|
|
561
|
-
|
|
1238
|
+
const cssRules = this.style.sheet.cssRules;
|
|
1239
|
+
for(let i = cssRules.length - 1; i >= 0; i--){
|
|
562
1240
|
this.style.sheet.deleteRule(i);
|
|
563
1241
|
}
|
|
564
|
-
super.refresh(
|
|
1242
|
+
super.refresh(manifest);
|
|
565
1243
|
/**
|
|
566
|
-
*
|
|
1244
|
+
* Recreate rules from the current class names against the latest manifest.
|
|
567
1245
|
* 所以 refresh 過後 rules 可能會變多也可能會變少
|
|
568
1246
|
*/ this.classCounts.forEach((_, className)=>{
|
|
569
1247
|
this.add(className);
|
|
570
1248
|
});
|
|
571
1249
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:refreshed', {
|
|
572
1250
|
cssRuntime: this,
|
|
573
|
-
|
|
1251
|
+
manifest
|
|
574
1252
|
});
|
|
575
1253
|
return this;
|
|
576
1254
|
}
|
|
577
1255
|
destroy() {
|
|
578
1256
|
this.disconnect();
|
|
579
|
-
|
|
1257
|
+
this.unregister();
|
|
580
1258
|
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:destroyed', {
|
|
581
1259
|
cssRuntime: this
|
|
582
1260
|
});
|
|
@@ -587,18 +1265,52 @@ class CSSRuntime extends MasterCSS {
|
|
|
587
1265
|
registerGlobal(CSSRuntime);
|
|
588
1266
|
})(CSSRuntime);
|
|
589
1267
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1268
|
+
function resolveManifest(host, manifest) {
|
|
1269
|
+
return typeof manifest === 'function' ? manifest(host) : manifest;
|
|
1270
|
+
}
|
|
1271
|
+
function resolveRoot(host, root) {
|
|
1272
|
+
return typeof root === 'function' ? root(host) : root || host.shadowRoot;
|
|
1273
|
+
}
|
|
1274
|
+
function resolveEmittedGlobals(host, emittedGlobals) {
|
|
1275
|
+
return typeof emittedGlobals === 'function' ? emittedGlobals(host) : emittedGlobals;
|
|
1276
|
+
}
|
|
1277
|
+
function resolveHydrationManifest(host, hydrationManifest) {
|
|
1278
|
+
return typeof hydrationManifest === 'function' ? hydrationManifest(host) : hydrationManifest;
|
|
1279
|
+
}
|
|
1280
|
+
function cssRuntime(options) {
|
|
1281
|
+
return function(target) {
|
|
1282
|
+
const connectedCallback = target.prototype.connectedCallback;
|
|
1283
|
+
const disconnectedCallback = target.prototype.disconnectedCallback;
|
|
1284
|
+
return class extends target {
|
|
1285
|
+
cssRuntime;
|
|
1286
|
+
connectedCallback() {
|
|
1287
|
+
connectedCallback?.call(this);
|
|
1288
|
+
const root = resolveRoot(this, options.root);
|
|
1289
|
+
if (!root) {
|
|
1290
|
+
throw new Error('`@cssRuntime()` requires a shadow root. Provide `options.root` or create a shadow root before `connectedCallback()` finishes.');
|
|
1291
|
+
}
|
|
1292
|
+
const cssRuntime = CSSRuntime.create({
|
|
1293
|
+
manifest: resolveManifest(this, options.manifest),
|
|
1294
|
+
root,
|
|
1295
|
+
emittedGlobals: resolveEmittedGlobals(this, options.emittedGlobals),
|
|
1296
|
+
hydrationManifest: resolveHydrationManifest(this, options.hydrationManifest)
|
|
1297
|
+
});
|
|
1298
|
+
this.cssRuntime = cssRuntime;
|
|
1299
|
+
if (cssRuntime.needsHydrationManifest()) {
|
|
1300
|
+
void cssRuntime.loadHydrationManifest().then(()=>{
|
|
1301
|
+
if (this.cssRuntime === cssRuntime) cssRuntime.observe();
|
|
1302
|
+
});
|
|
1303
|
+
} else {
|
|
1304
|
+
cssRuntime.observe();
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
disconnectedCallback() {
|
|
1308
|
+
disconnectedCallback?.call(this);
|
|
1309
|
+
this.cssRuntime?.destroy();
|
|
1310
|
+
this.cssRuntime = undefined;
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
};
|
|
602
1314
|
}
|
|
603
1315
|
|
|
604
|
-
export { CSSRuntime,
|
|
1316
|
+
export { CSSRuntime, RuntimeUtilityLayer, cssRuntime };
|