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