@master/css-runtime 2.0.0-rc.63 → 2.0.0-rc.65

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
@@ -36,12 +36,21 @@ class RuntimeLayer extends Layer {
36
36
  const insertedIndex = super.insert(rule, index);
37
37
  if (insertedIndex === undefined || !this.native) return;
38
38
  const insertRuleSafely = (text, position)=>{
39
- try {
40
- const insertedIndex = this.native.insertRule(text, position);
41
- return this.native.cssRules.item(insertedIndex);
42
- } catch (error) {
43
- console.error(error, rule);
44
- return;
39
+ // Checks if the rule is inserted in a native CSS rule with this.attach()
40
+ if (this.rules.length === 1) {
41
+ return this.native.cssRules.item(position);
42
+ } else {
43
+ try {
44
+ const insertedIndex = this.native.insertRule(text, position);
45
+ return this.native.cssRules.item(insertedIndex);
46
+ } catch (error) {
47
+ console.error(error, rule);
48
+ /**
49
+ * If the rule is invalid, remove it from the rules array.
50
+ * It's important to note that the rule may break the entire CSS runtime,
51
+ */ this.rules.splice(insertedIndex, 1);
52
+ return;
53
+ }
45
54
  }
46
55
  };
47
56
  if ('nodes' in rule) {
@@ -51,7 +60,7 @@ class RuntimeLayer extends Layer {
51
60
  if (node.native) currentIndex++;
52
61
  });
53
62
  } else {
54
- rule.native = insertRuleSafely(rule.text, index);
63
+ rule.native = insertRuleSafely(rule.text, insertedIndex);
55
64
  }
56
65
  return insertedIndex;
57
66
  }
@@ -106,20 +115,19 @@ class CSSRuntime extends MasterCSS {
106
115
  customConfig;
107
116
  static instances = new WeakMap();
108
117
  host;
109
- observing;
110
- progressive;
111
118
  container;
112
- observer;
113
119
  baseLayer;
114
120
  themeLayer;
115
121
  presetLayer;
116
122
  componentsLayer;
117
123
  generalLayer;
118
124
  classCounts;
125
+ observer;
126
+ progressive;
127
+ observing;
119
128
  constructor(root = document, customConfig = config){
120
- super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false, this.baseLayer = new RuntimeSyntaxLayer('base', this), this.themeLayer = new RuntimeLayer('theme', this), this.presetLayer = new RuntimeSyntaxLayer('preset', this), this.componentsLayer = new RuntimeSyntaxLayer('components', this), this.generalLayer = new RuntimeSyntaxLayer('general', this), this.classCounts = new Map();
121
- const rootConstructorName = root?.constructor.name;
122
- if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
129
+ super(customConfig), this.root = root, this.customConfig = customConfig, this.baseLayer = new RuntimeSyntaxLayer('base', this), this.themeLayer = new RuntimeLayer('theme', this), this.presetLayer = new RuntimeSyntaxLayer('preset', this), this.componentsLayer = new RuntimeSyntaxLayer('components', this), this.generalLayer = new RuntimeSyntaxLayer('general', this), this.classCounts = new Map(), this.progressive = false, this.observing = false;
130
+ if (this.root instanceof Document || this.root instanceof HTMLDocument) {
123
131
  this.root.defaultView.globalThis.cssRuntime = this;
124
132
  this.container = this.root.head;
125
133
  this.host = this.root.documentElement;
@@ -138,36 +146,44 @@ class CSSRuntime extends MasterCSS {
138
146
  * @returns this
139
147
  */ observe() {
140
148
  if (this.observing) return this;
141
- // 1. Check if the stylesheet is progressive.
142
- if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
143
- const { ownerNode } = sheet;
144
- if (ownerNode && ownerNode.id === 'master') {
145
- this.style = ownerNode;
146
- if (this.style.sheet?.cssRules.length) {
147
- // @ts-ignore
148
- this.progressive = true;
149
+ // Detect prerendered stylesheet
150
+ if (this.root.styleSheets) {
151
+ for (const sheet of this.root.styleSheets){
152
+ const { ownerNode } = sheet;
153
+ if (ownerNode instanceof HTMLStyleElement && ownerNode.id === 'master') {
154
+ this.style = ownerNode;
155
+ if (this.style.sheet?.cssRules.length) {
156
+ this.progressive = true;
157
+ }
158
+ break;
149
159
  }
150
- break;
151
160
  }
152
161
  }
153
- // 2. Count the classes in the host and its children.
162
+ // Prepare snapshot map
163
+ const elementClasses = new WeakMap();
164
+ // Initial scan and populate counts + snapshot
154
165
  const connectedNames = new Set();
155
166
  const increaseClassCount = (className)=>{
156
167
  const count = this.classCounts.get(className) || 0;
157
- if (!count) {
158
- connectedNames.add(className);
159
- }
168
+ if (!count) connectedNames.add(className);
160
169
  this.classCounts.set(className, count + 1);
161
170
  };
162
- this.host.classList.forEach(increaseClassCount);
163
- (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
164
- // 3. Hydrate the existing CSS rules or create a new one.
171
+ const rootEl = this.root instanceof Document || this.root instanceof HTMLDocument ? this.root : this.container;
172
+ const elementsWithClass = rootEl.querySelectorAll('[class]');
173
+ elementsWithClass.forEach((el)=>{
174
+ const clsList = el.classList;
175
+ if (clsList) {
176
+ el.classList.forEach(increaseClassCount);
177
+ }
178
+ elementClasses.set(el, new Set(clsList));
179
+ });
180
+ // Hydration or style creation
165
181
  if (this.progressive) {
166
182
  const hydrateResult = this.hydrate(this.style.sheet.cssRules);
167
- // Add the connected class names that are not hydrated yet.
168
- for (const eachConnectedName of connectedNames){
169
- if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
170
- this.add(eachConnectedName);
183
+ for (const cls of connectedNames){
184
+ if (!hydrateResult.allSyntaxRules.find((r)=>(r.fixedClass || r.name) === cls)) {
185
+ this.add(cls);
186
+ }
171
187
  }
172
188
  } else {
173
189
  this.style = document.createElement('style');
@@ -175,125 +191,81 @@ class CSSRuntime extends MasterCSS {
175
191
  this.container.append(this.style);
176
192
  this.style.sheet.insertRule(this.layerStatementRule.text);
177
193
  this.layerStatementRule.native = this.style.sheet.cssRules.item(0);
178
- for (const eachConnectedName of connectedNames){
179
- this.add(eachConnectedName);
180
- }
194
+ connectedNames.forEach((cls)=>this.add(cls));
181
195
  }
182
- // @ts-expect-error readonly
183
- this.observer = new MutationObserver((mutationRecords)=>{
184
- const eachClassCounts = new Map();
185
- const targetFirstAttrMutationRecord = new Map();
186
- const updateClassCount = (classes, isAdding = false)=>{
187
- const usage = isAdding ? 1 : -1;
188
- classes.forEach((className)=>{
189
- eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
190
- });
196
+ this.observer = new MutationObserver((records)=>{
197
+ const deltaCounts = new Map();
198
+ const nodeMap = new Map();
199
+ const attrRecords = new Set();
200
+ const visited = new WeakSet();
201
+ const updateDelta = (cls, delta)=>deltaCounts.set(cls, (deltaCounts.get(cls) || 0) + delta);
202
+ const diffAndSnapshot = (el)=>{
203
+ const prev = new Set(elementClasses.get(el) || []);
204
+ const next = new Set(el.classList);
205
+ for (const c of next)if (!prev.has(c)) updateDelta(c, 1);
206
+ for (const c of prev)if (!next.has(c)) updateDelta(c, -1);
207
+ elementClasses.set(el, next);
191
208
  };
192
- const connectedStatusMap = new Map();
193
- const disconnectedStatusMap = new Map();
194
- const recordStatus = (target, mutationRecord, map, adding)=>{
195
- const status = map.get(target);
196
- if (status) {
197
- status.change += adding ? 1 : -1;
198
- status.mutationRecord = mutationRecord;
199
- } else {
200
- map.set(target, {
201
- change: adding ? 1 : -1,
202
- mutationRecord
203
- });
204
- }
205
- for (const child of target.children){
206
- if ('classList' in child) {
207
- recordStatus(child, mutationRecord, map, adding);
208
- }
209
- }
209
+ const removeSnapshot = (el)=>{
210
+ for (const c of elementClasses.get(el) || [])updateDelta(c, -1);
211
+ elementClasses.delete(el);
210
212
  };
211
- mutationRecords.forEach((mutationRecord)=>{
212
- const target = mutationRecord.target;
213
- switch(mutationRecord.type){
214
- case 'attributes':
215
- if (!targetFirstAttrMutationRecord.has(target)) {
216
- targetFirstAttrMutationRecord.set(target, mutationRecord);
217
- }
218
- break;
219
- case 'childList':
220
- const targetStatusMap = target.isConnected ? connectedStatusMap : disconnectedStatusMap;
221
- mutationRecord.addedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, true));
222
- mutationRecord.removedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, false));
223
- break;
224
- }
225
- });
226
- const updateTarget = (target, adding)=>{
227
- const firstAttrMutationRecord = targetFirstAttrMutationRecord.get(target);
228
- if (firstAttrMutationRecord) {
229
- targetFirstAttrMutationRecord.delete(target);
213
+ for (const record of records){
214
+ if (record.type === 'childList') {
215
+ for (const node of record.addedNodes)if (node instanceof Element && node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) + 1);
216
+ for (const node of record.removedNodes)if (node instanceof Element && !node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) - 1);
217
+ } else if (record.type === 'attributes' && record.attributeName === 'class') {
218
+ attrRecords.add(record.target);
230
219
  }
231
- if (adding) {
232
- updateClassCount(target.classList, adding);
233
- } else {
234
- if (firstAttrMutationRecord) {
235
- updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
236
- } else {
237
- updateClassCount(target.classList, adding);
220
+ }
221
+ const traverseIncludingSelf = (el, fn, visited)=>{
222
+ if (!visited.has(el)) {
223
+ visited.add(el);
224
+ fn(el);
225
+ for (const child of el.children){
226
+ traverseIncludingSelf(child, fn, visited);
238
227
  }
239
- disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
240
- if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
241
- updateTarget(disconnectedTarget, disconnectedTargetStatus.change > 0);
242
- }
243
- });
228
+ } else {
229
+ nodeMap.delete(el);
244
230
  }
245
231
  };
246
- connectedStatusMap.forEach(({ change }, target)=>change !== 0 && updateTarget(target, change > 0));
247
- targetFirstAttrMutationRecord.forEach((mutation, target)=>{
248
- if (!target.isConnected) return;
249
- const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
250
- const newClassList = target.classList;
251
- const addedClasses = [];
252
- newClassList.forEach((c)=>{
253
- if (!oldClassList.includes(c)) addedClasses.push(c);
254
- });
255
- const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
256
- if (addedClasses.length) {
257
- updateClassCount(addedClasses, true);
258
- }
259
- if (removedClasses.length) {
260
- updateClassCount(removedClasses, false);
232
+ for (const [node, count] of nodeMap){
233
+ if (count > 0) {
234
+ traverseIncludingSelf(node, diffAndSnapshot, visited);
235
+ } else if (count < 0 && !node.isConnected) {
236
+ traverseIncludingSelf(node, removeSnapshot, visited);
261
237
  }
262
- });
263
- /**
264
- * Merge the class usage changes into the current class usage map.
265
- */ eachClassCounts.forEach((countChange, className)=>{
266
- let currentCount = this.classCounts.get(className) || 0;
267
- let newCount = currentCount + countChange;
268
- if (newCount > 0) {
269
- this.classCounts.set(className, newCount);
270
- if (currentCount === 0) {
271
- this.add(className);
272
- }
238
+ }
239
+ for (const el of attrRecords){
240
+ if (!visited.has(el)) diffAndSnapshot(el);
241
+ }
242
+ for (const [cls, change] of deltaCounts){
243
+ const current = this.classCounts.get(cls) || 0;
244
+ const next = current + change;
245
+ if (next > 0) {
246
+ this.classCounts.set(cls, next);
247
+ if (current === 0) this.add(cls);
273
248
  } else {
274
- this.classCounts.delete(className);
275
- this.remove(className);
249
+ this.classCounts.delete(cls);
250
+ this.remove(cls);
276
251
  }
277
- });
252
+ }
278
253
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:mutated', {
279
- records: mutationRecords,
280
- classCounts: eachClassCounts,
254
+ records,
255
+ classCounts: deltaCounts,
281
256
  cssRuntime: this
282
257
  });
283
258
  });
284
259
  this.observer.observe(this.root, {
260
+ childList: true,
285
261
  attributes: true,
286
- attributeOldValue: true,
287
262
  attributeFilter: [
288
263
  'class'
289
264
  ],
290
- childList: true,
265
+ attributeOldValue: true,
291
266
  subtree: true
292
267
  });
293
- if (!this.progressive) {
294
- this.host.removeAttribute('hidden');
295
- }
296
- // @ts-ignore
268
+ if (!this.progressive) this.host.removeAttribute('hidden');
297
269
  this.observing = true;
298
270
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:observed', {
299
271
  cssRuntime: this
@@ -436,7 +408,6 @@ class CSSRuntime extends MasterCSS {
436
408
  if (!this.observing) return;
437
409
  if (this.observer) {
438
410
  this.observer.disconnect();
439
- // @ts-expect-error readonly
440
411
  this.observer = undefined;
441
412
  }
442
413
  // @ts-ignore
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@master/css-runtime","type":"module","scripts":{"build":"techor build","dev":"pnpm build --watch","e2e":"playwright test","e2e:watch":"nodemon -e js,mjs,ts,html,css --watch dist --watch e2e --exec playwright test","type-check":"tsc --noEmit","lint":"eslint"},"license":"MIT","description":"Run Master CSS right in the browser","author":"Aoyue Design LLC.","funding":"https://rc.css.master.co/sponsor","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/runtime"},"keywords":["utility","css","lightweight","mastercss","master","runtime","cdn"],"sideEffects":false,"main":"./dist/index.cjs","jsnext:main":"./dist/index.mjs","esnext":"./dist/index.mjs","module":"./dist/index.mjs","browser":"./dist/global.min.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","require":{"development":"./dist/index.development.cjs","default":"./dist/index.cjs"},"import":{"development":"./dist/index.development.mjs","default":"./dist/index.mjs"}}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.63","@master/css-devtools-hook":"^2.0.0-rc.63"},"devDependencies":{"@playwright/test":"^1.49.1","shared":"^2.0.0-rc.63"},"version":"2.0.0-rc.63"}
1
+ {"name":"@master/css-runtime","type":"module","scripts":{"build":"techor build","dev":"pnpm build --watch","e2e":"playwright test","e2e:watch":"nodemon -e js,mjs,ts,html,css --watch dist --watch e2e --exec playwright test","type-check":"tsc --noEmit","lint":"eslint"},"license":"MIT","description":"Run Master CSS right in the browser","author":"Aoyue Design LLC.","funding":"https://rc.css.master.co/sponsor","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/runtime"},"keywords":["utility","css","lightweight","mastercss","master","runtime","cdn"],"sideEffects":false,"main":"./dist/index.cjs","jsnext:main":"./dist/index.mjs","esnext":"./dist/index.mjs","module":"./dist/index.mjs","browser":"./dist/global.min.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","require":{"development":"./dist/index.development.cjs","default":"./dist/index.cjs"},"import":{"development":"./dist/index.development.mjs","default":"./dist/index.mjs"}}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.65","@master/css-devtools-hook":"^2.0.0-rc.65"},"devDependencies":{"@playwright/test":"^1.49.1","shared":"^2.0.0-rc.65"},"version":"2.0.0-rc.65"}