chaincss 1.13.2 → 1.13.3
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/README.md +6 -14
- package/browser/index.js +1 -1
- package/browser/rtt.js +39 -9
- package/browser/vue-composables.js +200 -0
- package/node/atomic-optimizer.js +279 -144
- package/node/btt.js +149 -102
- package/node/chaincss.js +197 -44
- package/node/prefixer.js +2 -2
- package/node/strVal.js +37 -51
- package/node/theme-validator.js +4 -4
- package/package.json +13 -3
- package/types.d.ts +51 -3
- package/node/css-properties.json +0 -633
package/node/atomic-optimizer.js
CHANGED
|
@@ -12,23 +12,41 @@ function kebab(s) {
|
|
|
12
12
|
|
|
13
13
|
class AtomicOptimizer {
|
|
14
14
|
constructor(options = {}) {
|
|
15
|
+
// Ensure arrays are arrays (fix for config merging issues)
|
|
16
|
+
if (options.alwaysAtomic && !Array.isArray(options.alwaysAtomic)) {
|
|
17
|
+
options.alwaysAtomic = Object.values(options.alwaysAtomic);
|
|
18
|
+
}
|
|
19
|
+
if (options.neverAtomic && !Array.isArray(options.neverAtomic)) {
|
|
20
|
+
options.neverAtomic = Object.values(options.neverAtomic);
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
this.options = {
|
|
16
24
|
enabled: true,
|
|
17
|
-
threshold: 3,
|
|
18
|
-
naming: 'hash',
|
|
25
|
+
threshold: 3,
|
|
26
|
+
naming: 'hash',
|
|
19
27
|
cache: true,
|
|
20
28
|
cachePath: './.chaincss-cache',
|
|
21
29
|
minify: true,
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
mode: 'hybrid',
|
|
31
|
+
outputStrategy: 'component-first',
|
|
32
|
+
alwaysAtomic: [],
|
|
33
|
+
neverAtomic: [
|
|
24
34
|
'content', 'animation', 'transition', 'keyframes',
|
|
25
35
|
'counterIncrement', 'counterReset'
|
|
26
36
|
],
|
|
37
|
+
frameworkOutput: {
|
|
38
|
+
react: false,
|
|
39
|
+
vue: false,
|
|
40
|
+
vanilla: true
|
|
41
|
+
},
|
|
42
|
+
preserveSelectors: false,
|
|
43
|
+
verbose: false,
|
|
27
44
|
...options
|
|
28
45
|
};
|
|
29
46
|
|
|
30
|
-
this.usageCount = new Map();
|
|
31
|
-
this.atomicClasses = new Map();
|
|
47
|
+
this.usageCount = new Map();
|
|
48
|
+
this.atomicClasses = new Map();
|
|
49
|
+
this.componentClassMap = new Map();
|
|
32
50
|
this.stats = {
|
|
33
51
|
totalStyles: 0,
|
|
34
52
|
atomicStyles: 0,
|
|
@@ -52,23 +70,21 @@ class AtomicOptimizer {
|
|
|
52
70
|
|
|
53
71
|
const data = JSON.parse(fs.readFileSync(this.options.cachePath, 'utf8'));
|
|
54
72
|
|
|
55
|
-
// Version check
|
|
56
73
|
if (data.version !== '1.0.0') {
|
|
57
|
-
console.log('Cache version mismatch, creating new cache');
|
|
74
|
+
//if (this.options.verbose) console.log('Cache version mismatch, creating new cache');
|
|
58
75
|
return;
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
// Check if config changed
|
|
62
78
|
if (data.config?.threshold !== this.options.threshold) {
|
|
63
|
-
console.log(`Cache threshold (${data.config?.threshold}) differs from current (${this.options.threshold})`);
|
|
79
|
+
//if (this.options.verbose) console.log(`Cache threshold (${data.config?.threshold}) differs from current (${this.options.threshold})`);
|
|
64
80
|
return;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
this.atomicClasses = new Map(data.atomicClasses || []);
|
|
84
|
+
this.componentClassMap = new Map(data.componentClassMap || []);
|
|
68
85
|
this.stats = data.stats || this.stats;
|
|
69
86
|
|
|
70
87
|
const cacheTime = new Date(data.timestamp).toLocaleString();
|
|
71
|
-
console.log(`✅ Loaded ${this.atomicClasses.size} atomic classes from cache (${cacheTime})`);
|
|
72
88
|
|
|
73
89
|
} catch (err) {
|
|
74
90
|
console.log('Could not load cache:', err.message);
|
|
@@ -84,30 +100,17 @@ class AtomicOptimizer {
|
|
|
84
100
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
85
101
|
}
|
|
86
102
|
|
|
87
|
-
// Clean up old cache files (keep last 5)
|
|
88
|
-
if (fs.existsSync(cacheDir)) {
|
|
89
|
-
const files = fs.readdirSync(cacheDir)
|
|
90
|
-
.filter(f => f.startsWith('.chaincss-cache'))
|
|
91
|
-
.map(f => ({
|
|
92
|
-
name: f,
|
|
93
|
-
time: fs.statSync(path.join(cacheDir, f)).mtime.getTime()
|
|
94
|
-
}))
|
|
95
|
-
.sort((a, b) => b.time - a.time);
|
|
96
|
-
|
|
97
|
-
// Keep only the 5 most recent cache files
|
|
98
|
-
files.slice(5).forEach(f => {
|
|
99
|
-
try { fs.unlinkSync(path.join(cacheDir, f.name)); } catch {}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
103
|
const cache = {
|
|
104
104
|
version: '1.0.0',
|
|
105
105
|
timestamp: Date.now(),
|
|
106
106
|
atomicClasses: Array.from(this.atomicClasses.entries()),
|
|
107
|
+
componentClassMap: Array.from(this.componentClassMap.entries()),
|
|
107
108
|
stats: this.stats,
|
|
108
109
|
config: {
|
|
109
110
|
threshold: this.options.threshold,
|
|
110
|
-
naming: this.options.naming
|
|
111
|
+
naming: this.options.naming,
|
|
112
|
+
mode: this.options.mode,
|
|
113
|
+
outputStrategy: this.options.outputStrategy
|
|
111
114
|
}
|
|
112
115
|
};
|
|
113
116
|
|
|
@@ -128,36 +131,46 @@ class AtomicOptimizer {
|
|
|
128
131
|
if (!style || !style.selectors) continue;
|
|
129
132
|
|
|
130
133
|
for (const [prop, value] of Object.entries(style)) {
|
|
131
|
-
if (prop === 'selectors' || prop === 'atRules'
|
|
134
|
+
if (prop === 'selectors' || prop === 'atRules') continue;
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
if (prop === 'hover' && typeof value === 'object') {
|
|
137
|
+
for (const [hoverProp, hoverValue] of Object.entries(value)) {
|
|
138
|
+
this.incrementUsage(hoverProp, hoverValue);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
this.incrementUsage(prop, value);
|
|
142
|
+
}
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
this.stats.uniqueProperties = this.usageCount.size;
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
incrementUsage(prop, value) {
|
|
150
|
+
const key = `${prop}:${value}`;
|
|
151
|
+
const count = (this.usageCount.get(key) || 0) + 1;
|
|
152
|
+
this.usageCount.set(key, count);
|
|
153
|
+
this.stats.totalStyles++;
|
|
154
|
+
|
|
155
|
+
if (this.atomicClasses.has(key)) {
|
|
156
|
+
this.atomicClasses.get(key).usageCount = count;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
142
160
|
shouldBeAtomic(prop, value) {
|
|
143
|
-
|
|
144
|
-
if (this.options.
|
|
161
|
+
if (this.options.mode === 'standard') return false;
|
|
162
|
+
if (this.options.mode === 'atomic') return true;
|
|
145
163
|
|
|
146
|
-
|
|
164
|
+
if (this.options.neverAtomic.includes(prop)) return false;
|
|
147
165
|
if (this.options.alwaysAtomic.includes(prop)) return true;
|
|
148
166
|
|
|
149
|
-
// Critical props that need higher threshold
|
|
150
167
|
const criticalProps = ['position', 'display', 'flex', 'grid', 'zIndex', 'top', 'left', 'right', 'bottom'];
|
|
151
168
|
const isCritical = criticalProps.includes(prop);
|
|
152
169
|
|
|
153
170
|
const key = `${prop}:${value}`;
|
|
154
171
|
const usage = this.usageCount.get(key) || 0;
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
if (isCritical && usage < this.options.threshold * 2) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
173
|
+
if (isCritical && usage < this.options.threshold * 2) return false;
|
|
161
174
|
return usage >= this.options.threshold;
|
|
162
175
|
}
|
|
163
176
|
|
|
@@ -168,7 +181,6 @@ class AtomicOptimizer {
|
|
|
168
181
|
return `c_${hashKey(key)}`;
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
// Readable naming
|
|
172
184
|
const kebabProp = kebab(prop);
|
|
173
185
|
const safeValue = String(value).replace(/[^a-z0-9_-]/gi, '-').slice(0, 30);
|
|
174
186
|
return `${kebabProp}-${safeValue}`;
|
|
@@ -190,6 +202,13 @@ class AtomicOptimizer {
|
|
|
190
202
|
|
|
191
203
|
return this.atomicClasses.get(key).className;
|
|
192
204
|
}
|
|
205
|
+
|
|
206
|
+
getKeyFromClassName(className) {
|
|
207
|
+
for (const [key, atomic] of this.atomicClasses) {
|
|
208
|
+
if (atomic.className === className) return key;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
193
212
|
|
|
194
213
|
// ============================================================================
|
|
195
214
|
// CSS Generation
|
|
@@ -202,117 +221,164 @@ class AtomicOptimizer {
|
|
|
202
221
|
|
|
203
222
|
for (const atomic of sortedClasses) {
|
|
204
223
|
const kebabProp = kebab(atomic.prop);
|
|
205
|
-
|
|
224
|
+
if (this.options.minify) {
|
|
225
|
+
css += `.${atomic.className}{${kebabProp}:${atomic.value}}`;
|
|
226
|
+
} else {
|
|
227
|
+
css += `.${atomic.className} {\n ${kebabProp}: ${atomic.value};\n}\n`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Debug: Log atomic CSS generation
|
|
232
|
+
if (this.options.verbose && css) {
|
|
233
|
+
//console.log(` Generated ${this.atomicClasses.size} atomic classes (${css.length} bytes)`);
|
|
234
|
+
if (css.length > 0) {
|
|
235
|
+
//console.log(`First atomic class: ${css.substring(0, 50)}...`);
|
|
236
|
+
}
|
|
206
237
|
}
|
|
207
238
|
|
|
208
239
|
return css;
|
|
209
240
|
}
|
|
210
241
|
|
|
211
|
-
generateComponentCSS(
|
|
242
|
+
generateComponentCSS(style, selectors) {
|
|
212
243
|
const atomicClasses = [];
|
|
213
|
-
const
|
|
214
|
-
const hoverStyles = {};
|
|
244
|
+
const hoverAtomicClasses = [];
|
|
215
245
|
|
|
216
|
-
//
|
|
246
|
+
// Track which properties become atomic
|
|
217
247
|
for (const [prop, value] of Object.entries(style)) {
|
|
218
248
|
if (prop === 'selectors' || prop === 'atRules') continue;
|
|
219
249
|
|
|
220
250
|
if (prop === 'hover' && typeof value === 'object') {
|
|
221
|
-
// Handle hover styles
|
|
222
251
|
for (const [hoverProp, hoverValue] of Object.entries(value)) {
|
|
223
252
|
if (this.shouldBeAtomic(hoverProp, hoverValue)) {
|
|
224
|
-
|
|
225
|
-
} else {
|
|
226
|
-
hoverStyles[hoverProp] = hoverValue;
|
|
253
|
+
hoverAtomicClasses.push(this.getOrCreateAtomic(hoverProp, hoverValue));
|
|
227
254
|
}
|
|
228
255
|
}
|
|
229
256
|
} else if (this.shouldBeAtomic(prop, value)) {
|
|
230
257
|
atomicClasses.push(this.getOrCreateAtomic(prop, value));
|
|
231
|
-
} else {
|
|
232
|
-
standardStyles[prop] = value;
|
|
233
258
|
}
|
|
234
259
|
}
|
|
235
260
|
|
|
236
|
-
// Generate CSS
|
|
237
261
|
let componentCSS = '';
|
|
238
262
|
const selectorStr = selectors.join(', ');
|
|
239
263
|
|
|
240
|
-
|
|
241
|
-
|
|
264
|
+
// COMPONENT-FIRST STRATEGY (Default)
|
|
265
|
+
if (this.options.outputStrategy === 'component-first') {
|
|
266
|
+
// Include ALL properties in component CSS
|
|
267
|
+
const allStyles = {};
|
|
242
268
|
|
|
243
|
-
//
|
|
244
|
-
for (const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const kebabProp = kebab(atomic.prop);
|
|
248
|
-
componentCSS += ` ${kebabProp}: ${atomic.value};\n`;
|
|
269
|
+
// Add all properties (both atomic and non-atomic)
|
|
270
|
+
for (const [prop, value] of Object.entries(style)) {
|
|
271
|
+
if (prop !== 'selectors' && prop !== 'atRules' && prop !== 'hover') {
|
|
272
|
+
allStyles[prop] = value;
|
|
249
273
|
}
|
|
250
274
|
}
|
|
251
275
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
276
|
+
// Generate CSS with ALL properties
|
|
277
|
+
if (Object.keys(allStyles).length > 0) {
|
|
278
|
+
if (this.options.minify) {
|
|
279
|
+
componentCSS += `${selectorStr}{`;
|
|
280
|
+
for (const [prop, value] of Object.entries(allStyles)) {
|
|
281
|
+
const kebabProp = kebab(prop);
|
|
282
|
+
componentCSS += `${kebabProp}:${value};`;
|
|
283
|
+
}
|
|
284
|
+
componentCSS += `}`;
|
|
285
|
+
} else {
|
|
286
|
+
componentCSS += `${selectorStr} {\n`;
|
|
287
|
+
for (const [prop, value] of Object.entries(allStyles)) {
|
|
288
|
+
const kebabProp = kebab(prop);
|
|
289
|
+
componentCSS += ` ${kebabProp}: ${value};\n`;
|
|
290
|
+
}
|
|
291
|
+
componentCSS += `}\n`;
|
|
292
|
+
}
|
|
256
293
|
}
|
|
257
294
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
295
|
+
// Add hover styles (all hover properties)
|
|
296
|
+
if (style.hover && typeof style.hover === 'object') {
|
|
297
|
+
const allHoverStyles = {};
|
|
298
|
+
for (const [prop, value] of Object.entries(style.hover)) {
|
|
299
|
+
allHoverStyles[prop] = value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (Object.keys(allHoverStyles).length > 0) {
|
|
303
|
+
if (this.options.minify) {
|
|
304
|
+
componentCSS += `${selectorStr}:hover{`;
|
|
305
|
+
for (const [prop, value] of Object.entries(allHoverStyles)) {
|
|
306
|
+
const kebabProp = kebab(prop);
|
|
307
|
+
componentCSS += `${kebabProp}:${value};`;
|
|
308
|
+
}
|
|
309
|
+
componentCSS += `}`;
|
|
310
|
+
} else {
|
|
311
|
+
componentCSS += `${selectorStr}:hover {\n`;
|
|
312
|
+
for (const [prop, value] of Object.entries(allHoverStyles)) {
|
|
313
|
+
const kebabProp = kebab(prop);
|
|
314
|
+
componentCSS += ` ${kebabProp}: ${value};\n`;
|
|
315
|
+
}
|
|
316
|
+
componentCSS += `}\n`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
267
319
|
}
|
|
268
|
-
componentCSS += `}\n`;
|
|
269
320
|
}
|
|
270
321
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
322
|
+
// UTILITY-FIRST STRATEGY (Advanced)
|
|
323
|
+
else {
|
|
324
|
+
const standardStyles = {};
|
|
325
|
+
const hoverStandardStyles = {};
|
|
326
|
+
|
|
327
|
+
for (const [prop, value] of Object.entries(style)) {
|
|
328
|
+
if (prop === 'selectors' || prop === 'atRules') continue;
|
|
329
|
+
|
|
330
|
+
if (prop === 'hover' && typeof value === 'object') {
|
|
331
|
+
for (const [hoverProp, hoverValue] of Object.entries(value)) {
|
|
332
|
+
if (!this.shouldBeAtomic(hoverProp, hoverValue)) {
|
|
333
|
+
hoverStandardStyles[hoverProp] = hoverValue;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else if (!this.shouldBeAtomic(prop, value)) {
|
|
337
|
+
standardStyles[prop] = value;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (Object.keys(standardStyles).length > 0) {
|
|
342
|
+
if (this.options.minify) {
|
|
343
|
+
componentCSS += `${selectorStr}{`;
|
|
344
|
+
for (const [prop, value] of Object.entries(standardStyles)) {
|
|
345
|
+
const kebabProp = kebab(prop);
|
|
346
|
+
componentCSS += `${kebabProp}:${value};`;
|
|
347
|
+
}
|
|
348
|
+
componentCSS += `}`;
|
|
349
|
+
} else {
|
|
350
|
+
componentCSS += `${selectorStr} {\n`;
|
|
351
|
+
for (const [prop, value] of Object.entries(standardStyles)) {
|
|
352
|
+
const kebabProp = kebab(prop);
|
|
353
|
+
componentCSS += ` ${kebabProp}: ${value};\n`;
|
|
354
|
+
}
|
|
355
|
+
componentCSS += `}\n`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (Object.keys(hoverStandardStyles).length > 0) {
|
|
360
|
+
if (this.options.minify) {
|
|
361
|
+
componentCSS += `${selectorStr}:hover{`;
|
|
362
|
+
for (const [prop, value] of Object.entries(hoverStandardStyles)) {
|
|
363
|
+
const kebabProp = kebab(prop);
|
|
364
|
+
componentCSS += `${kebabProp}:${value};`;
|
|
365
|
+
}
|
|
366
|
+
componentCSS += `}`;
|
|
367
|
+
} else {
|
|
368
|
+
componentCSS += `${selectorStr}:hover {\n`;
|
|
369
|
+
for (const [prop, value] of Object.entries(hoverStandardStyles)) {
|
|
370
|
+
const kebabProp = kebab(prop);
|
|
371
|
+
componentCSS += ` ${kebabProp}: ${value};\n`;
|
|
372
|
+
}
|
|
373
|
+
componentCSS += `}\n`;
|
|
291
374
|
}
|
|
292
375
|
}
|
|
293
376
|
}
|
|
294
377
|
|
|
295
|
-
for (const atomic of this.atomicClasses.values()) {
|
|
296
|
-
atomicProps.add(atomic.prop);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const missingProps = [...originalProps].filter(p => !atomicProps.has(p));
|
|
300
|
-
if (missingProps.length > 0) {
|
|
301
|
-
console.warn('⚠️ Missing atomic classes for:', missingProps.slice(0, 10));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
getStats() {
|
|
306
|
-
const savings = this.stats.totalStyles > 0
|
|
307
|
-
? ((this.stats.totalStyles - this.stats.atomicStyles) / this.stats.totalStyles * 100).toFixed(1)
|
|
308
|
-
: 0;
|
|
309
|
-
|
|
310
378
|
return {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
uniqueProperties: this.stats.uniqueProperties,
|
|
315
|
-
savings: `${savings}%`
|
|
379
|
+
css: componentCSS,
|
|
380
|
+
atomicClasses,
|
|
381
|
+
hoverAtomicClasses
|
|
316
382
|
};
|
|
317
383
|
}
|
|
318
384
|
|
|
@@ -330,18 +396,33 @@ class AtomicOptimizer {
|
|
|
330
396
|
};
|
|
331
397
|
}
|
|
332
398
|
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
:
|
|
337
|
-
|
|
338
|
-
|
|
399
|
+
// Reset stats
|
|
400
|
+
this.stats = {
|
|
401
|
+
totalStyles: 0,
|
|
402
|
+
atomicStyles: 0,
|
|
403
|
+
standardStyles: 0,
|
|
404
|
+
uniqueProperties: 0,
|
|
405
|
+
savings: 0
|
|
406
|
+
};
|
|
407
|
+
this.usageCount.clear();
|
|
408
|
+
this.componentClassMap.clear();
|
|
409
|
+
|
|
410
|
+
// Normalize input
|
|
411
|
+
let styleArray = [];
|
|
412
|
+
if (Array.isArray(stylesInput)) {
|
|
413
|
+
styleArray = stylesInput;
|
|
414
|
+
} else if (typeof stylesInput === 'object') {
|
|
415
|
+
styleArray = Object.values(stylesInput).filter(v => v && typeof v === 'object');
|
|
416
|
+
}
|
|
339
417
|
|
|
340
|
-
|
|
418
|
+
if (styleArray.length === 0) {
|
|
419
|
+
return { css: '', map: {}, stats: this.getStats(), atomicCSS: '', componentCSS: '' };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// FIRST: Track usage counts
|
|
341
423
|
this.trackStyles(styleArray);
|
|
342
424
|
|
|
343
|
-
// Generate CSS
|
|
344
|
-
let atomicCSS = this.generateAtomicCSS();
|
|
425
|
+
// SECOND: Generate component CSS (this populates atomicClasses via getOrCreateAtomic)
|
|
345
426
|
let componentCSS = '';
|
|
346
427
|
const classMap = {};
|
|
347
428
|
|
|
@@ -350,42 +431,96 @@ class AtomicOptimizer {
|
|
|
350
431
|
|
|
351
432
|
const selectors = style.selectors;
|
|
352
433
|
const selectorKey = selectors.join(', ');
|
|
434
|
+
const { css, atomicClasses, hoverAtomicClasses } = this.generateComponentCSS(style, selectors);
|
|
435
|
+
componentCSS += css;
|
|
353
436
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (prop === 'selectors' || prop === 'atRules' || prop === 'hover') continue;
|
|
361
|
-
if (this.shouldBeAtomic(prop, value)) {
|
|
362
|
-
atomicClassesForSelector.push(this.getOrCreateAtomic(prop, value));
|
|
437
|
+
if (this.options.outputStrategy === 'utility-first') {
|
|
438
|
+
if (atomicClasses.length > 0) {
|
|
439
|
+
classMap[selectorKey] = atomicClasses.join(' ');
|
|
440
|
+
}
|
|
441
|
+
if (hoverAtomicClasses.length > 0) {
|
|
442
|
+
classMap[`${selectorKey}:hover`] = hoverAtomicClasses.join(' ');
|
|
363
443
|
}
|
|
364
444
|
}
|
|
365
|
-
|
|
445
|
+
|
|
446
|
+
this.componentClassMap.set(selectorKey, {
|
|
447
|
+
atomicClasses,
|
|
448
|
+
hoverAtomicClasses,
|
|
449
|
+
selectors
|
|
450
|
+
});
|
|
366
451
|
}
|
|
367
452
|
|
|
368
|
-
//
|
|
369
|
-
this.
|
|
453
|
+
// THIRD: Generate atomic CSS (now atomicClasses is populated!)
|
|
454
|
+
const atomicCSS = this.generateAtomicCSS();
|
|
455
|
+
|
|
456
|
+
// Combine CSS
|
|
457
|
+
const finalCSS = atomicCSS + componentCSS;
|
|
458
|
+
|
|
459
|
+
// Log stats if verbose
|
|
460
|
+
if (this.options.verbose) {
|
|
461
|
+
const stats = this.getStats();
|
|
462
|
+
//console.log(` Atomic Optimization Stats:`);
|
|
463
|
+
//console.log(` Output strategy: ${this.options.outputStrategy}`);
|
|
464
|
+
//console.log(` Total styles tracked: ${stats.totalStyles}`);
|
|
465
|
+
//console.log(` Atomic classes created: ${this.atomicClasses.size}`);
|
|
466
|
+
//console.log(` Atomic CSS length: ${atomicCSS.length} bytes`);
|
|
467
|
+
//console.log(` Component CSS length: ${componentCSS.length} bytes`);
|
|
468
|
+
//console.log(` Total CSS length: ${finalCSS.length} bytes`);
|
|
469
|
+
//console.log(` Savings: ${stats.savings}`);
|
|
470
|
+
|
|
471
|
+
if (atomicCSS.length === 0 && this.atomicClasses.size > 0) {
|
|
472
|
+
//console.log(` WARNING: ${this.atomicClasses.size} atomic classes exist but generated CSS is empty!`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
370
475
|
|
|
371
476
|
// Save cache
|
|
372
477
|
if (this.options.cache) {
|
|
373
478
|
this.saveCache();
|
|
374
479
|
}
|
|
375
480
|
|
|
376
|
-
|
|
481
|
+
return {
|
|
482
|
+
css: finalCSS,
|
|
483
|
+
map: classMap,
|
|
484
|
+
stats: this.getStats(),
|
|
485
|
+
atomicCSS: atomicCSS,
|
|
486
|
+
componentCSS: componentCSS,
|
|
487
|
+
componentMap: this.componentClassMap
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
getStats() {
|
|
377
492
|
const savings = this.stats.totalStyles > 0
|
|
378
|
-
? ((this.stats.totalStyles - this.
|
|
493
|
+
? ((this.stats.totalStyles - this.stats.atomicStyles) / this.stats.totalStyles * 100).toFixed(1)
|
|
379
494
|
: 0;
|
|
380
495
|
|
|
381
496
|
return {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
497
|
+
totalStyles: this.stats.totalStyles,
|
|
498
|
+
atomicStyles: this.stats.atomicStyles,
|
|
499
|
+
standardStyles: this.stats.standardStyles,
|
|
500
|
+
uniqueProperties: this.stats.uniqueProperties,
|
|
501
|
+
savings: `${savings}%`
|
|
387
502
|
};
|
|
388
503
|
}
|
|
504
|
+
|
|
505
|
+
getAtomicClass(prop, value) {
|
|
506
|
+
const key = `${prop}:${value}`;
|
|
507
|
+
const atomic = this.atomicClasses.get(key);
|
|
508
|
+
return atomic ? atomic.className : null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
getAllAtomicClasses() {
|
|
512
|
+
return Array.from(this.atomicClasses.values());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
clearCache() {
|
|
516
|
+
this.atomicClasses.clear();
|
|
517
|
+
this.componentClassMap.clear();
|
|
518
|
+
this.usageCount.clear();
|
|
519
|
+
if (this.options.cache && fs.existsSync(this.options.cachePath)) {
|
|
520
|
+
fs.unlinkSync(this.options.cachePath);
|
|
521
|
+
}
|
|
522
|
+
//console.log('Atomic optimizer cache cleared');
|
|
523
|
+
}
|
|
389
524
|
}
|
|
390
525
|
|
|
391
526
|
module.exports = { AtomicOptimizer };
|