@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/core.d.ts +2 -2
- package/dist/global.min.js +1 -1
- package/dist/index.cjs +141 -148
- package/dist/index.mjs +141 -148
- package/package.json +1 -1
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(
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
} else {
|
|
96
|
-
correctionOfClassName[className] = 1;
|
|
93
|
+
const updateElementTree = (element, adding)=>{
|
|
94
|
+
if (element.isConnected) {
|
|
95
|
+
updatedConnectedElements.add(element);
|
|
97
96
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
332
|
-
this.
|
|
333
|
-
this.
|
|
334
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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":"
|
|
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"}
|