@master/css-runtime 2.0.0-rc.5 → 2.0.0-rc.50

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 ADDED
@@ -0,0 +1,409 @@
1
+ import { MasterCSS, config, Rule } from '@master/css';
2
+
3
+ class RuntimeCSS extends MasterCSS {
4
+ root;
5
+ customConfig;
6
+ // @ts-expect-error
7
+ host;
8
+ observing;
9
+ progressive;
10
+ // @ts-expect-error
11
+ container;
12
+ observer;
13
+ constructor(root = document, customConfig = config){
14
+ super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false;
15
+ this.init();
16
+ }
17
+ init() {
18
+ const existingRuntimeCSS = globalThis.runtimeCSSs.find((eachCSS)=>eachCSS.root === this.root);
19
+ if (existingRuntimeCSS) throw new Error('Cannot create multiple RuntimeCSS instances for the same root element.');
20
+ const rootConstructorName = this.root?.constructor.name;
21
+ if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
22
+ // @ts-ignore
23
+ this.root.defaultView.globalThis.runtimeCSS = this;
24
+ // @ts-ignore readonly
25
+ this.container = this.root.head;
26
+ // @ts-ignore readonly
27
+ this.host = this.root.documentElement;
28
+ } else {
29
+ // @ts-ignore readonly
30
+ this.container = this.root;
31
+ // @ts-ignore readonly
32
+ this.host = this.root.host;
33
+ }
34
+ runtimeCSSs.push(this);
35
+ }
36
+ /**
37
+ * Observe the DOM for changes and update the running stylesheet. (browser only)
38
+ * @param options mutation observer options
39
+ * @returns this
40
+ */ observe() {
41
+ if (this.observing) return this;
42
+ if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
43
+ const { ownerNode } = sheet;
44
+ if (ownerNode && ownerNode.id === 'master') {
45
+ this.style = ownerNode;
46
+ // @ts-ignore
47
+ this.progressive = true;
48
+ break;
49
+ }
50
+ }
51
+ if (this.progressive) {
52
+ this.hydrate(this.style.sheet.cssRules);
53
+ } else {
54
+ this.style = document.createElement('style');
55
+ this.style.id = 'master';
56
+ this.container.append(this.style);
57
+ this.style.sheet.insertRule(this.layerStatementRule.text);
58
+ this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
59
+ }
60
+ const firstConnectedElementsClasses = new Map();
61
+ const addClasses = (element)=>{
62
+ element.classList.forEach((className)=>{
63
+ if (this.classUsages.has(className)) {
64
+ this.classUsages.set(className, this.classUsages.get(className) + 1);
65
+ } else {
66
+ this.classUsages.set(className, 1);
67
+ this.add(className);
68
+ }
69
+ const classes = firstConnectedElementsClasses.get(element);
70
+ if (classes) {
71
+ classes.push(className);
72
+ } else {
73
+ firstConnectedElementsClasses.set(element, [
74
+ className
75
+ ]);
76
+ }
77
+ });
78
+ };
79
+ addClasses(this.host);
80
+ /**
81
+ * 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
82
+ */ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
83
+ addClasses(element);
84
+ });
85
+ // @ts-expect-error readonly
86
+ this.observer = new MutationObserver((mutationRecords)=>{
87
+ // console.clear()
88
+ // console.log('-----------')
89
+ // const test = 'swiper-slide-next'
90
+ // console.log(`${test}: ${this.classUsages.get(test)}`)
91
+ const eachClassUsages = new Map();
92
+ const updatedAttrElements = new Set();
93
+ const updatedConnectedElements = new Set();
94
+ const updatedElements = new Set();
95
+ const updateClassUsage = (classes, isAdding = false)=>{
96
+ const usage = isAdding ? 1 : -1;
97
+ classes.forEach((className)=>{
98
+ // if (className === test) console.log(` ${isAdding ? '+' : '-'} ${className} ${(eachClassUsages.get(className) || 0) + usage}`, target)
99
+ eachClassUsages.set(className, (eachClassUsages.get(className) || 0) + usage);
100
+ });
101
+ };
102
+ const updateElementTree = (element, adding)=>{
103
+ if (element.isConnected) {
104
+ updatedConnectedElements.add(element);
105
+ }
106
+ updatedElements.add(element);
107
+ updateClassUsage(element.classList, adding);
108
+ for (const child of element.children){
109
+ updateElementTree(child, adding);
110
+ }
111
+ };
112
+ // console.log('///')
113
+ mutationRecords.forEach((mutation)=>{
114
+ const target = mutation.target;
115
+ switch(mutation.type){
116
+ case 'attributes':
117
+ const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
118
+ const newClassList = target.classList;
119
+ newClassList.forEach((c)=>{
120
+ if (!oldClassList.includes(c)) ;
121
+ });
122
+ break;
123
+ }
124
+ });
125
+ // console.log('///')
126
+ mutationRecords.forEach((mutation)=>{
127
+ const target = mutation.target;
128
+ switch(mutation.type){
129
+ case 'attributes':
130
+ /**
131
+ * We only need to determine the first attribute record of the same target,
132
+ * because the first record has the original old class name.
133
+ *
134
+ * The target is skipped if it is contained in the unhandled childList.
135
+ */ if (updatedAttrElements.has(target) || updatedElements.has(target)) return;
136
+ updatedAttrElements.add(target);
137
+ const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
138
+ const newClassList = target.classList;
139
+ const addedClasses = [];
140
+ newClassList.forEach((c)=>{
141
+ if (!oldClassList.includes(c)) addedClasses.push(c);
142
+ });
143
+ const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
144
+ // if (addedClasses.length) console.log('[attribute]', '[add]', addedClasses, target)
145
+ if (addedClasses.length) updateClassUsage(addedClasses, true);
146
+ // if (removedClasses.length) console.log('[attribute]', '[remove]', removedClasses, target)
147
+ if (removedClasses.length) updateClassUsage(removedClasses, false);
148
+ break;
149
+ case 'childList':
150
+ if (updatedConnectedElements.has(target)) return;
151
+ // if (mutation.addedNodes.length) console.log('[childList]', '[add]', mutation.addedNodes)
152
+ mutation.addedNodes.forEach((node)=>'classList' in node && updateElementTree(node, true));
153
+ // if (mutation.removedNodes.length) console.log('[childList]', '[remove]', mutation.removedNodes)
154
+ mutation.removedNodes.forEach((node)=>'classList' in node && updateElementTree(node, false));
155
+ break;
156
+ }
157
+ });
158
+ /**
159
+ * Merge the class usage changes into the current class usage map.
160
+ */ eachClassUsages.forEach((countChange, className)=>{
161
+ let currentCount = this.classUsages.get(className) || 0;
162
+ let newCount = currentCount + countChange;
163
+ if (newCount > 0) {
164
+ this.classUsages.set(className, newCount);
165
+ if (currentCount === 0) {
166
+ this.add(className);
167
+ }
168
+ } else {
169
+ this.classUsages.delete(className);
170
+ this.remove(className);
171
+ }
172
+ });
173
+ // start: debug
174
+ // const safeClassUsages: any = {};
175
+ // ((this.root.constructor.name === 'HTMLDocument') ? this.host : this.container)
176
+ // .querySelectorAll('[class]')
177
+ // .forEach((element) => {
178
+ // element.classList.forEach((className) => {
179
+ // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
180
+ // safeClassUsages[className]++
181
+ // } else {
182
+ // safeClassUsages[className] = 1
183
+ // }
184
+ // })
185
+ // })
186
+ // this.host.classList.forEach((className) => {
187
+ // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
188
+ // safeClassUsages[className]++
189
+ // } else {
190
+ // safeClassUsages[className] = 1
191
+ // }
192
+ // })
193
+ // // 與 this.classUsages 比較並且打印不同的部分
194
+ // for (const className in safeClassUsages) {
195
+ // if (this.classUsages.get(className) !== safeClassUsages[className]) {
196
+ // throw new Error(`[css] ${className} ${this.classUsages.get(className)} (correct: ${safeClassUsages[className]})`)
197
+ // }
198
+ // }
199
+ // end: debug
200
+ });
201
+ this.observer.observe(this.root, {
202
+ attributes: true,
203
+ attributeOldValue: true,
204
+ attributeFilter: [
205
+ 'class'
206
+ ],
207
+ childList: true,
208
+ subtree: true
209
+ });
210
+ if (!this.progressive) {
211
+ this.host.style.removeProperty('display');
212
+ }
213
+ // @ts-ignore
214
+ this.observing = true;
215
+ return this;
216
+ }
217
+ hydrate(nativeCSSRules) {
218
+ const cssSyntaxLayerBlockRules = [];
219
+ for(let i = 0; i < nativeCSSRules.length; i++){
220
+ const eachNativeCSSRule = nativeCSSRules[i];
221
+ if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
222
+ const cssLayerBlockRule = eachNativeCSSRule;
223
+ if (eachNativeCSSRule.name === 'theme') {
224
+ this.themeLayer.native = cssLayerBlockRule;
225
+ let variableRule;
226
+ let lastVariableName;
227
+ for(let j = 0; j < cssLayerBlockRule.cssRules.length; j++){
228
+ const cssRule = cssLayerBlockRule.cssRules[j];
229
+ const variableCSSRule = cssRule.constructor.name === 'CSSMediaRule' ? cssRule.cssRules[0] : cssRule;
230
+ const variableName = variableCSSRule.style[0].slice(2);
231
+ if (variableName !== lastVariableName) {
232
+ lastVariableName = variableName;
233
+ variableRule = new Rule(variableName);
234
+ this.themeLayer.rules.push(variableRule);
235
+ this.themeLayer.usages[variableRule.name] = 0;
236
+ }
237
+ variableRule?.nodes.push({
238
+ native: cssRule,
239
+ text: cssRule.cssText
240
+ });
241
+ }
242
+ if (this.themeLayer.rules.length) this.rules.push(this.themeLayer);
243
+ } else {
244
+ cssSyntaxLayerBlockRules.push(cssLayerBlockRule);
245
+ }
246
+ } else if (eachNativeCSSRule.constructor.name === 'CSSLayerStatementRule') {
247
+ this.layerStatementRule.nodes[0].native = eachNativeCSSRule;
248
+ } else if (eachNativeCSSRule.constructor.name === 'CSSKeyframesRule') {
249
+ const keyframsRule = eachNativeCSSRule;
250
+ const animationRule = new Rule(keyframsRule.name, [
251
+ {
252
+ native: keyframsRule,
253
+ text: keyframsRule.cssText
254
+ }
255
+ ]);
256
+ this.animationsNonLayer.rules.push(animationRule);
257
+ this.rules.push(animationRule);
258
+ this.animationsNonLayer.usages[animationRule.name] = 0;
259
+ }
260
+ }
261
+ for (const cssLayerBlockRule of cssSyntaxLayerBlockRules){
262
+ const handleSyntaxLayer = (layer)=>{
263
+ layer.native = cssLayerBlockRule;
264
+ for(let j = 0; j < cssLayerBlockRule.cssRules.length; j++){
265
+ const cssRule = cssLayerBlockRule.cssRules[j];
266
+ const createSyntaxRule = (cssRule)=>{
267
+ if (cssRule.selectorText) {
268
+ const syntaxRule = this.createFromSelectorText(cssRule.selectorText)[0];
269
+ if (syntaxRule) return syntaxRule;
270
+ } else if (cssRule.cssRules) {
271
+ for (const eachCSSRule of cssRule.cssRules){
272
+ const syntaxRule = createSyntaxRule(eachCSSRule);
273
+ if (syntaxRule) return syntaxRule;
274
+ }
275
+ }
276
+ };
277
+ const syntaxRule = createSyntaxRule(cssRule);
278
+ if (syntaxRule) {
279
+ layer.rules.push(syntaxRule);
280
+ layer.insertVariables(syntaxRule);
281
+ layer.insertAnimations(syntaxRule);
282
+ for (const eachNode of syntaxRule.nodes){
283
+ if (!eachNode.native) eachNode.native = cssRule;
284
+ }
285
+ } else {
286
+ cssLayerBlockRule.deleteRule?.(j--);
287
+ console.error(`Cannot recognize the CSS rule in the ${layer.name} layer. \`${cssRule.cssText}\` (https://rc.css.master.co/messages/hydration-errors)`);
288
+ }
289
+ }
290
+ for (const eachRule of layer.rules){
291
+ for(let k = eachRule.nodes.length - 1; k >= 0; k--){
292
+ if (!eachRule.nodes[k].native) {
293
+ eachRule.nodes.splice(k, 1);
294
+ }
295
+ }
296
+ }
297
+ if (layer.rules.length) this.rules.push(layer);
298
+ };
299
+ switch(cssLayerBlockRule.name){
300
+ case 'base':
301
+ handleSyntaxLayer(this.baseLayer);
302
+ break;
303
+ case 'preset':
304
+ handleSyntaxLayer(this.presetLayer);
305
+ break;
306
+ case 'components':
307
+ this.componentsLayer.native = cssLayerBlockRule;
308
+ let stylePreText;
309
+ const createSyntaxRules = (cssRule)=>{
310
+ if (cssRule.selectorText) {
311
+ const syntaxRules = this.createFromSelectorText(cssRule.selectorText);
312
+ if (syntaxRules.length) {
313
+ stylePreText = cssRule.selectorText + '{';
314
+ return syntaxRules;
315
+ }
316
+ } else if (cssRule.cssRules) {
317
+ for (const eachCSSRule of cssRule.cssRules){
318
+ const syntaxRules = this.createFromSelectorText(eachCSSRule.selectorText);
319
+ if (syntaxRules) return syntaxRules;
320
+ }
321
+ }
322
+ };
323
+ for(let j = 0; j < cssLayerBlockRule.cssRules.length; j++){
324
+ const cssRule = cssLayerBlockRule.cssRules[j];
325
+ const syntaxRules = createSyntaxRules(cssRule);
326
+ if (syntaxRules) {
327
+ let matched = false;
328
+ for (const eachSyntaxRule of syntaxRules){
329
+ for (const node of eachSyntaxRule.nodes){
330
+ if (!node.native && node.text.includes(stylePreText)) {
331
+ node.native = cssRule;
332
+ if (!this.componentsLayer.rules.includes(eachSyntaxRule)) {
333
+ this.componentsLayer.rules.push(eachSyntaxRule);
334
+ this.componentsLayer.insertVariables(eachSyntaxRule);
335
+ this.componentsLayer.insertAnimations(eachSyntaxRule);
336
+ }
337
+ matched = true;
338
+ break;
339
+ }
340
+ }
341
+ if (matched) break;
342
+ }
343
+ } else {
344
+ cssLayerBlockRule?.deleteRule?.(j--);
345
+ console.error(`Cannot recognize the CSS rule in the components layer. \`${cssRule.cssText}\` (https://rc.css.master.co/messages/hydration-errors)`);
346
+ }
347
+ }
348
+ for (const eachRule of this.componentsLayer.rules){
349
+ for(let k = eachRule.nodes.length - 1; k >= 0; k--){
350
+ if (!eachRule.nodes[k].native) {
351
+ eachRule.nodes.splice(k, 1);
352
+ }
353
+ }
354
+ }
355
+ if (this.componentsLayer.rules.length) this.rules.push(this.componentsLayer);
356
+ break;
357
+ case 'general':
358
+ handleSyntaxLayer(this.generalLayer);
359
+ break;
360
+ }
361
+ }
362
+ }
363
+ disconnect() {
364
+ if (!this.observing) return;
365
+ if (this.observer) {
366
+ this.observer.disconnect();
367
+ // @ts-expect-error readonly
368
+ this.observer = undefined;
369
+ }
370
+ // @ts-ignore
371
+ this.observing = false;
372
+ this.reset();
373
+ this.classUsages.clear();
374
+ if (!this.progressive) {
375
+ this.style?.remove();
376
+ this.style = null;
377
+ }
378
+ return this;
379
+ }
380
+ refresh(customConfig = this.customConfig) {
381
+ if (!this.observing || !this.style.sheet) return this;
382
+ for(let i = 1; i <= this.style.sheet.cssRules.length - 1; i++){
383
+ this.style.sheet.deleteRule(i);
384
+ }
385
+ super.refresh(customConfig);
386
+ return this;
387
+ }
388
+ destroy() {
389
+ this.disconnect();
390
+ runtimeCSSs.splice(runtimeCSSs.indexOf(this), 1);
391
+ return this;
392
+ }
393
+ }
394
+ const runtimeCSSs = [];
395
+ (()=>{
396
+ globalThis.RuntimeCSS = RuntimeCSS;
397
+ globalThis.runtimeCSSs = runtimeCSSs;
398
+ })();
399
+
400
+ /**
401
+ * Initialize a new RuntimeCSS instance and observe the target root
402
+ * @param config master css config
403
+ * @param targetRoot target root to observe
404
+ * @returns master css instance
405
+ */ function initCSSRuntime(config) {
406
+ return new RuntimeCSS(document, config).observe();
407
+ }
408
+
409
+ export { RuntimeCSS, RuntimeCSS as default, initCSSRuntime };
@@ -1,9 +1,9 @@
1
- import { CSSRuntime } from './core';
1
+ import { RuntimeCSS } from './core';
2
2
  import type { Config } from '@master/css';
3
3
  /**
4
- * Initialize a new CSSRuntime instance and observe the target root
4
+ * Initialize a new RuntimeCSS instance and observe the target root
5
5
  * @param config master css config
6
6
  * @param targetRoot target root to observe
7
7
  * @returns master css instance
8
8
  */
9
- export default function initCSSRuntime(config?: Config): CSSRuntime;
9
+ export default function initCSSRuntime(config?: Config): RuntimeCSS;
@@ -1,8 +1,9 @@
1
1
  import type { Config } from '@master/css';
2
- import type { CSSRuntime as _CSSRuntime } from '../core';
2
+ import type { RuntimeCSS as _CSSRuntime } from '../core';
3
3
  declare global {
4
- var CSSRuntime: typeof _CSSRuntime;
5
- var cssRuntimes: _CSSRuntime[];
6
- var cssRuntime: _CSSRuntime;
4
+ var RuntimeCSS: typeof _CSSRuntime;
5
+ var runtimeCSSs: _CSSRuntime[];
6
+ var runtimeCSS: _CSSRuntime;
7
7
  var masterCSSConfig: Config;
8
+ var masterCSSConfigs: Config[];
8
9
  }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@master/css-runtime","scripts":{"build":"pnpm run \"/^build:/\"","build:iife":"techor pack \"src/iife.ts\" --outfile \"dist/iife.bundle.js\" --bundle --format iife --no-external --no-clean","build:esm":"techor pack \"src/index.ts\" --outfile \"dist/index.bundle.mjs\" --bundle --format esm --no-clean","build:cjs":"techor pack \"src/index.ts\" --outfile \"dist/index.bundle.js\" --bundle --format cjs --no-clean","build:type":"tsc --emitDeclarationOnly --declaration --outDir dist","dev":"pnpm run \"/^build:(?!type)/\" --watch","test":"jest","type-check":"tsc --noEmit","lint":"eslint src"},"license":"MIT","description":"Run the Master CSS right in the browser","author":"Aoyue Design LLC.","funding":"https://css.master.co/docs/donate","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.bundle.js","jsnext:main":"./dist/index.bundle.mjs","esnext":"./dist/index.bundle.mjs","module":"./dist/index.bundle.mjs","browser":"./dist/iife.bundle.js","types":"./dist/index.d.ts","exports":{".":{"require":"./dist/index.bundle.js","import":"./dist/index.bundle.mjs","types":"./dist/index.d.ts"}},"files":["dist"],"publishConfig":{"access":"public"},"dependencies":{"@master/css":"^2.0.0-rc.5"},"version":"2.0.0-rc.5"}
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.50"},"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.50"},"version":"2.0.0-rc.50"}