@master/css-runtime 2.0.0-rc.48 → 2.0.0-rc.49

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
@@ -28,10 +28,7 @@ class RuntimeCSS extends MasterCSS {
28
28
  * Observe the DOM for changes and update the running stylesheet. (browser only)
29
29
  * @param options mutation observer options
30
30
  * @returns this
31
- */ observe(options = {
32
- subtree: true,
33
- childList: true
34
- }) {
31
+ */ observe() {
35
32
  if (this.observing) return this;
36
33
  if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
37
34
  const { ownerNode } = sheet;
@@ -51,164 +48,159 @@ class RuntimeCSS extends MasterCSS {
51
48
  this.style.sheet.insertRule(this.layerStatementRule.text);
52
49
  this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
53
50
  }
54
- const handleClassList = (classList)=>{
55
- classList.forEach((className)=>{
56
- if (Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
57
- this.classesUsage[className]++;
51
+ const firstConnectedElementsClasses = new Map();
52
+ const addClasses = (element)=>{
53
+ element.classList.forEach((className)=>{
54
+ if (this.classUsages.has(className)) {
55
+ this.classUsages.set(className, this.classUsages.get(className) + 1);
58
56
  } else {
59
- this.classesUsage[className] = 1;
57
+ this.classUsages.set(className, 1);
60
58
  this.add(className);
61
59
  }
60
+ const classes = firstConnectedElementsClasses.get(element);
61
+ if (classes) {
62
+ classes.push(className);
63
+ } else {
64
+ firstConnectedElementsClasses.set(element, [
65
+ className
66
+ ]);
67
+ }
62
68
  });
63
69
  };
64
- handleClassList(this.host.classList);
65
- if (options.subtree) {
66
- /**
67
- * 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
68
- */ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>handleClassList(element.classList));
69
- }
70
+ addClasses(this.host);
71
+ /**
72
+ * 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
73
+ */ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
74
+ addClasses(element);
75
+ });
76
+ // @ts-expect-error readonly
70
77
  this.observer = new MutationObserver((mutationRecords)=>{
71
- // console.time('css engine');
72
- const correctionOfClassName = {};
73
- const attributeMutationRecords = [];
74
- const updatedElements = [];
75
- const unchangedElements = [];
76
- /**
77
- * 取得所有深層後代的 class names
78
- */ const handleClassNameDeeply = (element, remove)=>{
79
- if (remove) {
80
- element.classList.forEach(removeClassName);
81
- } else {
82
- element.classList.forEach(addClassName);
83
- }
84
- const children = element.children;
85
- for (const eachChildren of children){
86
- if (eachChildren.classList && !updatedElements.includes(eachChildren)) {
87
- updatedElements.push(eachChildren);
88
- handleClassNameDeeply(eachChildren, remove);
89
- }
90
- }
78
+ // console.clear()
79
+ // console.log('-----------')
80
+ // const test = 'swiper-slide-next'
81
+ // console.log(`${test}: ${this.classUsages.get(test)}`)
82
+ const eachClassUsages = new Map();
83
+ const updatedAttrElements = new Set();
84
+ const updatedConnectedElements = new Set();
85
+ const updatedElements = new Set();
86
+ const updateClassUsage = (classes, isAdding = false)=>{
87
+ const usage = isAdding ? 1 : -1;
88
+ classes.forEach((className)=>{
89
+ // if (className === test) console.log(` ${isAdding ? '+' : '-'} ${className} ${(eachClassUsages.get(className) || 0) + usage}`, target)
90
+ eachClassUsages.set(className, (eachClassUsages.get(className) || 0) + usage);
91
+ });
91
92
  };
92
- const addClassName = (className)=>{
93
- if (Object.prototype.hasOwnProperty.call(correctionOfClassName, className)) {
94
- correctionOfClassName[className]++;
95
- } else {
96
- correctionOfClassName[className] = 1;
93
+ const updateElementTree = (element, adding)=>{
94
+ if (element.isConnected) {
95
+ updatedConnectedElements.add(element);
97
96
  }
98
- };
99
- const removeClassName = (className)=>{
100
- if (Object.prototype.hasOwnProperty.call(correctionOfClassName, className)) {
101
- correctionOfClassName[className]--;
102
- } else if (Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
103
- correctionOfClassName[className] = -1;
97
+ updatedElements.add(element);
98
+ updateClassUsage(element.classList, adding);
99
+ for (const child of element.children){
100
+ updateElementTree(child, adding);
104
101
  }
105
102
  };
106
- const handleNodes = (nodes, remove)=>{
107
- for (const eachNode of nodes){
108
- if (eachNode.classList && !updatedElements.includes(eachNode) && !unchangedElements.includes(eachNode)) {
109
- if (eachNode.isConnected !== remove) {
110
- updatedElements.push(eachNode);
111
- handleClassNameDeeply(eachNode, remove);
112
- } else {
113
- unchangedElements.push(eachNode);
114
- }
115
- }
103
+ // console.log('///')
104
+ mutationRecords.forEach((mutation)=>{
105
+ const target = mutation.target;
106
+ switch(mutation.type){
107
+ case 'attributes':
108
+ const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
109
+ const newClassList = target.classList;
110
+ newClassList.forEach((c)=>{
111
+ if (!oldClassList.includes(c)) ;
112
+ });
113
+ break;
116
114
  }
117
- };
118
- for (const mutationRecord of mutationRecords){
119
- const { addedNodes, removedNodes, type, target } = mutationRecord;
120
- if (type === 'attributes') {
121
- /**
122
- * 防止同樣的 MutationRecord 重複執行
123
- * According to this history,
124
- * MutationObserver was designed to work that way.
125
- * Any call to setAttribute triggers a mutation,
126
- * regardless of whether the value is being changed or set to the current value
127
- */ if (attributeMutationRecords.find((eachAttributeMutationRecord)=>eachAttributeMutationRecord.target === target)) {
128
- continue;
129
- } else {
115
+ });
116
+ // console.log('///')
117
+ mutationRecords.forEach((mutation)=>{
118
+ const target = mutation.target;
119
+ switch(mutation.type){
120
+ case 'attributes':
130
121
  /**
131
- * 第一個匹配到的 oldValue 一定是該批變動前的原始狀態值
132
- */ attributeMutationRecords.push(mutationRecord);
133
- }
134
- } else {
135
- // 先判斷節點新增或移除
136
- handleNodes(addedNodes, false);
137
- // 忽略處理新元素的已刪除子節點
138
- if (!target.isConnected || !updatedElements.includes(target)) {
139
- handleNodes(removedNodes, true);
140
- }
122
+ * We only need to determine the first attribute record of the same target,
123
+ * because the first record has the original old class name.
124
+ *
125
+ * The target is skipped if it is contained in the unhandled childList.
126
+ */ if (updatedAttrElements.has(target) || updatedElements.has(target)) return;
127
+ updatedAttrElements.add(target);
128
+ const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
129
+ const newClassList = target.classList;
130
+ const addedClasses = [];
131
+ newClassList.forEach((c)=>{
132
+ if (!oldClassList.includes(c)) addedClasses.push(c);
133
+ });
134
+ const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
135
+ // if (addedClasses.length) console.log('[attribute]', '[add]', addedClasses, target)
136
+ if (addedClasses.length) updateClassUsage(addedClasses, true);
137
+ // if (removedClasses.length) console.log('[attribute]', '[remove]', removedClasses, target)
138
+ if (removedClasses.length) updateClassUsage(removedClasses, false);
139
+ break;
140
+ case 'childList':
141
+ if (updatedConnectedElements.has(target)) return;
142
+ // if (mutation.addedNodes.length) console.log('[childList]', '[add]', mutation.addedNodes)
143
+ mutation.addedNodes.forEach((node)=>'classList' in node && updateElementTree(node, true));
144
+ // if (mutation.removedNodes.length) console.log('[childList]', '[remove]', mutation.removedNodes)
145
+ mutation.removedNodes.forEach((node)=>'classList' in node && updateElementTree(node, false));
146
+ break;
141
147
  }
142
- }
143
- if (!attributeMutationRecords.length && !Object.keys(correctionOfClassName).length) {
144
- // console.timeEnd('css engine');
145
- return;
146
- }
147
- for (const { oldValue, target } of attributeMutationRecords){
148
- /**
149
- * 如果被操作的元素中包含了屬性變更的目標,
150
- * 則將該目標從 existedAttributeMutationTargets 中移除,
151
- * 以防止執行接下來的屬性變更處理
152
- *
153
- * 該批 mutationRecords 中,某個 target 同時有 attribute 及 childList 的變更,
154
- * 則以 childList 節點插入及移除的 target.className 為主
155
- */ const updated = updatedElements.includes(target);
156
- const classNames = target.classList;
157
- const oldClassNames = oldValue ? oldValue.split(' ') : [];
158
- if (updated) {
159
- if (target.isConnected) {
160
- continue;
161
- } else {
162
- for (const oldClassName of oldClassNames){
163
- if (!classNames.contains(oldClassName)) {
164
- removeClassName(oldClassName);
165
- }
166
- }
167
- }
168
- } else if (target.isConnected) {
169
- classNames.forEach((className)=>{
170
- if (!oldClassNames.includes(className)) {
171
- addClassName(className);
172
- }
173
- });
174
- for (const oldClassName of oldClassNames){
175
- if (!classNames.contains(oldClassName)) {
176
- removeClassName(oldClassName);
177
- }
148
+ });
149
+ /**
150
+ * Merge the class usage changes into the current class usage map.
151
+ */ eachClassUsages.forEach((countChange, className)=>{
152
+ let currentCount = this.classUsages.get(className) || 0;
153
+ let newCount = currentCount + countChange;
154
+ if (newCount > 0) {
155
+ this.classUsages.set(className, newCount);
156
+ if (currentCount === 0) {
157
+ this.add(className);
178
158
  }
179
- }
180
- }
181
- for(const className in correctionOfClassName){
182
- const correction = correctionOfClassName[className];
183
- const count = (this.classesUsage[className] || 0) + correction;
184
- if (count === 0) {
185
- // remove
186
- delete this.classesUsage[className];
187
- /**
188
- * class name 從 DOM tree 中被移除,
189
- * 匹配並刪除對應的 rule
190
- */ this.remove(className);
191
159
  } else {
192
- if (!Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
193
- // add
194
- /**
195
- * 新 class name 被 connected 至 DOM tree,
196
- * 匹配並創建對應的 Rule
197
- */ this.add(className);
198
- }
199
- this.classesUsage[className] = count;
160
+ this.classUsages.delete(className);
161
+ this.remove(className);
200
162
  }
201
- }
163
+ });
164
+ // start: debug
165
+ // const safeClassUsages: any = {};
166
+ // ((this.root.constructor.name === 'HTMLDocument') ? this.host : this.container)
167
+ // .querySelectorAll('[class]')
168
+ // .forEach((element) => {
169
+ // element.classList.forEach((className) => {
170
+ // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
171
+ // safeClassUsages[className]++
172
+ // } else {
173
+ // safeClassUsages[className] = 1
174
+ // }
175
+ // })
176
+ // })
177
+ // this.host.classList.forEach((className) => {
178
+ // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
179
+ // safeClassUsages[className]++
180
+ // } else {
181
+ // safeClassUsages[className] = 1
182
+ // }
183
+ // })
184
+ // // 與 this.classUsages 比較並且打印不同的部分
185
+ // for (const className in safeClassUsages) {
186
+ // if (this.classUsages.get(className) !== safeClassUsages[className]) {
187
+ // throw new Error(`[css] ${className} ${this.classUsages.get(className)} (correct: ${safeClassUsages[className]})`)
188
+ // }
189
+ // }
190
+ // end: debug
202
191
  });
203
192
  this.observer.observe(this.root, {
204
- ...options,
205
193
  attributes: true,
206
194
  attributeOldValue: true,
207
195
  attributeFilter: [
208
196
  'class'
209
- ]
197
+ ],
198
+ childList: true,
199
+ subtree: true
210
200
  });
211
- this.host.style.removeProperty('display');
201
+ if (!this.progressive) {
202
+ this.host.style.removeProperty('display');
203
+ }
212
204
  // @ts-ignore
213
205
  this.observing = true;
214
206
  return this;
@@ -303,7 +295,7 @@ class RuntimeCSS extends MasterCSS {
303
295
  handleSyntaxLayer(this.presetLayer);
304
296
  break;
305
297
  case 'components':
306
- this.stylesLayer.native = cssLayerBlockRule;
298
+ this.componentsLayer.native = cssLayerBlockRule;
307
299
  let stylePreText;
308
300
  const createSyntaxRules = (cssRule)=>{
309
301
  if (cssRule.selectorText) {
@@ -328,10 +320,10 @@ class RuntimeCSS extends MasterCSS {
328
320
  for (const node of eachSyntaxRule.nodes){
329
321
  if (!node.native && node.text.includes(stylePreText)) {
330
322
  node.native = cssRule;
331
- if (!this.stylesLayer.rules.includes(eachSyntaxRule)) {
332
- this.stylesLayer.rules.push(eachSyntaxRule);
333
- this.stylesLayer.insertVariables(eachSyntaxRule);
334
- this.stylesLayer.insertAnimations(eachSyntaxRule);
323
+ if (!this.componentsLayer.rules.includes(eachSyntaxRule)) {
324
+ this.componentsLayer.rules.push(eachSyntaxRule);
325
+ this.componentsLayer.insertVariables(eachSyntaxRule);
326
+ this.componentsLayer.insertAnimations(eachSyntaxRule);
335
327
  }
336
328
  matched = true;
337
329
  break;
@@ -344,14 +336,14 @@ class RuntimeCSS extends MasterCSS {
344
336
  console.error(`Cannot recognize the CSS rule in the components layer. \`${cssRule.cssText}\` (https://rc.css.master.co/messages/hydration-errors)`);
345
337
  }
346
338
  }
347
- for (const eachRule of this.stylesLayer.rules){
339
+ for (const eachRule of this.componentsLayer.rules){
348
340
  for(let k = eachRule.nodes.length - 1; k >= 0; k--){
349
341
  if (!eachRule.nodes[k].native) {
350
342
  eachRule.nodes.splice(k, 1);
351
343
  }
352
344
  }
353
345
  }
354
- if (this.stylesLayer.rules.length) this.rules.push(this.stylesLayer);
346
+ if (this.componentsLayer.rules.length) this.rules.push(this.componentsLayer);
355
347
  break;
356
348
  case 'general':
357
349
  handleSyntaxLayer(this.generalLayer);
@@ -363,13 +355,14 @@ class RuntimeCSS extends MasterCSS {
363
355
  if (!this.observing) return;
364
356
  if (this.observer) {
365
357
  this.observer.disconnect();
366
- this.observer = null;
358
+ // @ts-expect-error readonly
359
+ this.observer = undefined;
367
360
  }
368
361
  // @ts-ignore
369
362
  this.observing = false;
370
363
  this.reset();
371
364
  // @ts-expect-error
372
- this.classesUsage = {};
365
+ this.classUsages = {};
373
366
  if (!this.progressive) {
374
367
  this.style?.remove();
375
368
  this.style = null;
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":"PWTEST_WATCH=1 playwright test","test":"vitest","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":"./dist/index.cjs","import":"./dist/index.mjs"}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.48"},"devDependencies":{"@playwright/test":"^1.49.1","@types/cssom":"^0.4.3","css-tree":"^3.0.1","cssom":"^0.5.0","shared":"^2.0.0-rc.48"},"version":"2.0.0-rc.48"}
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","test":"vitest","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":"./dist/index.cjs","import":"./dist/index.mjs"}},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.49"},"devDependencies":{"@playwright/test":"^1.49.1","@types/cssom":"^0.4.3","css-tree":"^3.0.1","cssom":"^0.5.0","shared":"^2.0.0-rc.49"},"version":"2.0.0-rc.49"}