chaincss 2.0.6 → 2.1.0

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +28 -0
  4. package/README.md +454 -231
  5. package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
  6. package/demo/index.html +16 -0
  7. package/demo/package.json +20 -0
  8. package/demo/src/App.tsx +117 -0
  9. package/demo/src/chaincss-barrel.ts +9 -0
  10. package/demo/src/main.tsx +8 -0
  11. package/demo/src/styles.chain.ts +300 -0
  12. package/demo/vite.config.ts +46 -0
  13. package/dist/cli/commands/build.d.ts +0 -1
  14. package/dist/cli/commands/cache.d.ts +1 -0
  15. package/dist/cli/commands/init.d.ts +6 -3
  16. package/dist/cli/commands/timeline.d.ts +0 -1
  17. package/dist/cli/commands/watch.d.ts +0 -1
  18. package/dist/cli/index.d.ts +0 -1
  19. package/dist/cli/index.js +3213 -5296
  20. package/dist/cli/types.d.ts +51 -20
  21. package/dist/cli/utils/config-loader.d.ts +0 -1
  22. package/dist/cli/utils/file-utils.d.ts +27 -3
  23. package/dist/cli/utils/logger.d.ts +0 -1
  24. package/dist/compiler/Chain.d.ts +215 -0
  25. package/dist/compiler/animations.d.ts +76 -0
  26. package/dist/compiler/atomic-optimizer.d.ts +47 -12
  27. package/dist/compiler/breakpoints.d.ts +46 -0
  28. package/dist/compiler/btt.d.ts +36 -60
  29. package/dist/compiler/cache-manager.d.ts +58 -4
  30. package/dist/compiler/commonProps.d.ts +0 -1
  31. package/dist/compiler/content-addressable-cache.d.ts +78 -0
  32. package/dist/compiler/helpers.d.ts +54 -0
  33. package/dist/compiler/index.d.ts +16 -9
  34. package/dist/compiler/index.js +4450 -4316
  35. package/dist/compiler/prefixer.d.ts +17 -1
  36. package/dist/compiler/shorthands.d.ts +28 -0
  37. package/dist/compiler/suggestions.d.ts +43 -0
  38. package/dist/compiler/theme-contract.d.ts +16 -27
  39. package/dist/compiler/token-resolver.d.ts +69 -0
  40. package/dist/compiler/tokens.d.ts +33 -8
  41. package/dist/core/auto-detector.d.ts +34 -0
  42. package/dist/core/common-utils.d.ts +97 -0
  43. package/dist/core/compiler.d.ts +63 -23
  44. package/dist/core/constants.d.ts +137 -36
  45. package/dist/core/smart-chain.d.ts +3 -0
  46. package/dist/core/types.d.ts +122 -15
  47. package/dist/core/utils.d.ts +134 -17
  48. package/dist/index.d.ts +52 -8
  49. package/dist/index.js +7090 -5578
  50. package/dist/plugins/vite.d.ts +7 -5
  51. package/dist/plugins/vite.js +2964 -25641
  52. package/dist/plugins/webpack.d.ts +24 -1
  53. package/dist/plugins/webpack.js +209 -72
  54. package/dist/runtime/Chain.d.ts +32 -0
  55. package/dist/runtime/auto-hooks.d.ts +11 -0
  56. package/dist/runtime/hmr.d.ts +22 -2
  57. package/dist/runtime/index.d.ts +3 -2
  58. package/dist/runtime/index.js +3649 -301
  59. package/dist/runtime/injector.d.ts +39 -71
  60. package/dist/runtime/react.d.ts +17 -12
  61. package/dist/runtime/svelte.d.ts +15 -0
  62. package/dist/runtime/types.d.ts +126 -4
  63. package/dist/runtime/utils.d.ts +0 -1
  64. package/dist/runtime/vue.d.ts +34 -14
  65. package/package.json +59 -66
  66. package/src/cli/commands/build.ts +133 -0
  67. package/src/cli/commands/cache.ts +371 -0
  68. package/src/cli/commands/init.ts +230 -0
  69. package/src/cli/commands/timeline.ts +435 -0
  70. package/src/cli/commands/watch.ts +211 -0
  71. package/src/cli/index.ts +226 -0
  72. package/src/cli/types.ts +100 -0
  73. package/src/cli/utils/config-loader.ts +174 -0
  74. package/src/cli/utils/file-utils.ts +139 -0
  75. package/src/cli/utils/logger.ts +74 -0
  76. package/src/compiler/Chain.ts +831 -0
  77. package/src/compiler/animations.ts +517 -0
  78. package/src/compiler/atomic-optimizer.ts +786 -0
  79. package/src/compiler/breakpoints.ts +347 -0
  80. package/src/compiler/btt.ts +1147 -0
  81. package/src/compiler/cache-manager.ts +446 -0
  82. package/src/compiler/commonProps.ts +18 -0
  83. package/src/compiler/content-addressable-cache.ts +478 -0
  84. package/src/compiler/helpers.ts +407 -0
  85. package/src/compiler/index.ts +72 -0
  86. package/src/compiler/prefixer.ts +724 -0
  87. package/src/compiler/shorthands.ts +558 -0
  88. package/src/compiler/suggestions.ts +436 -0
  89. package/src/compiler/theme-contract.ts +197 -0
  90. package/src/compiler/token-resolver.ts +241 -0
  91. package/src/compiler/tokens.ts +612 -0
  92. package/src/core/auto-detector.ts +187 -0
  93. package/src/core/common-utils.ts +423 -0
  94. package/src/core/compiler.ts +835 -0
  95. package/src/core/constants.ts +424 -0
  96. package/src/core/index.ts +107 -0
  97. package/src/core/smart-chain.ts +163 -0
  98. package/src/core/types.ts +257 -0
  99. package/src/core/utils.ts +598 -0
  100. package/src/index.ts +208 -0
  101. package/src/plugins/vite.d.ts +316 -0
  102. package/src/plugins/vite.ts +424 -0
  103. package/src/plugins/webpack.d.ts +289 -0
  104. package/src/plugins/webpack.ts +416 -0
  105. package/src/runtime/Chain.ts +242 -0
  106. package/src/runtime/auto-hooks.tsx +127 -0
  107. package/src/runtime/auto-vue.ts +72 -0
  108. package/src/runtime/hmr.ts +212 -0
  109. package/src/runtime/index.ts +82 -0
  110. package/src/runtime/injector.ts +273 -0
  111. package/src/runtime/react.tsx +269 -0
  112. package/src/runtime/svelte.ts +15 -0
  113. package/src/runtime/types.ts +256 -0
  114. package/src/runtime/utils.ts +128 -0
  115. package/src/runtime/vite-env.d.ts +120 -0
  116. package/src/runtime/vue.ts +231 -0
  117. package/tsconfig.build.json +41 -0
  118. package/tsconfig.json +25 -0
  119. package/tsconfig.runtimes.json +18 -0
  120. package/dist/cli/cli.cjs +0 -7
  121. package/dist/cli/commands/build.d.ts.map +0 -1
  122. package/dist/cli/commands/compile.d.ts +0 -3
  123. package/dist/cli/commands/compile.d.ts.map +0 -1
  124. package/dist/cli/commands/init.d.ts.map +0 -1
  125. package/dist/cli/commands/timeline.d.ts.map +0 -1
  126. package/dist/cli/commands/watch.d.ts.map +0 -1
  127. package/dist/cli/index.d.ts.map +0 -1
  128. package/dist/cli/types.d.ts.map +0 -1
  129. package/dist/cli/utils/config-loader.d.ts.map +0 -1
  130. package/dist/cli/utils/file-utils.d.ts.map +0 -1
  131. package/dist/cli/utils/logger.d.ts.map +0 -1
  132. package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
  133. package/dist/compiler/btt.d.ts.map +0 -1
  134. package/dist/compiler/cache-manager.d.ts.map +0 -1
  135. package/dist/compiler/commonProps.d.ts.map +0 -1
  136. package/dist/compiler/index.d.ts.map +0 -1
  137. package/dist/compiler/prefixer.d.ts.map +0 -1
  138. package/dist/compiler/theme-contract.d.ts.map +0 -1
  139. package/dist/compiler/tokens.d.ts.map +0 -1
  140. package/dist/compiler/types.d.ts +0 -57
  141. package/dist/compiler/types.d.ts.map +0 -1
  142. package/dist/core/compiler.d.ts.map +0 -1
  143. package/dist/core/constants.d.ts.map +0 -1
  144. package/dist/core/index.d.ts +0 -4
  145. package/dist/core/index.d.ts.map +0 -1
  146. package/dist/core/types.d.ts.map +0 -1
  147. package/dist/core/utils.d.ts.map +0 -1
  148. package/dist/index.d.ts.map +0 -1
  149. package/dist/plugins/vite.d.ts.map +0 -1
  150. package/dist/plugins/webpack.d.ts.map +0 -1
  151. package/dist/runtime/hmr.d.ts.map +0 -1
  152. package/dist/runtime/index.d.ts.map +0 -1
  153. package/dist/runtime/injector.d.ts.map +0 -1
  154. package/dist/runtime/react.d.ts.map +0 -1
  155. package/dist/runtime/react.js +0 -270
  156. package/dist/runtime/types.d.ts.map +0 -1
  157. package/dist/runtime/utils.d.ts.map +0 -1
  158. package/dist/runtime/vue.d.ts.map +0 -1
  159. package/dist/runtime/vue.js +0 -232
@@ -0,0 +1,786 @@
1
+ // chaincss/src/compiler/atomic-optimizer.ts
2
+ import { ChainCSSConfig } from '../cli/types.js';
3
+ import crypto from 'crypto';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+
7
+ // Types
8
+ export interface AtomicClass {
9
+ className: string;
10
+ prop: string;
11
+ value: any;
12
+ usageCount: number;
13
+ rules?: string;
14
+ createdAt?: number;
15
+ hash?: string;
16
+ }
17
+
18
+ export interface AtomicOptimizerStats {
19
+ totalStyles: number;
20
+ atomicStyles: number;
21
+ standardStyles: number;
22
+ uniqueProperties: number;
23
+ savings: string;
24
+ cacheHitRate?: number;
25
+ }
26
+
27
+ export interface AtomicOptimizerOptions {
28
+ enabled?: boolean;
29
+ threshold?: number;
30
+ naming?: 'hash' | 'readable';
31
+ cache?: boolean;
32
+ cachePath?: string;
33
+ minify?: boolean;
34
+ mode?: 'standard' | 'atomic' | 'hybrid';
35
+ outputStrategy?: 'component-first' | 'utility-first';
36
+ alwaysAtomic?: string[];
37
+ neverAtomic?: string[];
38
+ frameworkOutput?: {
39
+ react?: boolean;
40
+ vue?: boolean;
41
+ vanilla?: boolean;
42
+ };
43
+ preserveSelectors?: boolean;
44
+ verbose?: boolean;
45
+ }
46
+
47
+ export interface ComponentClassMapEntry {
48
+ atomicClasses: string[];
49
+ hoverAtomicClasses: string[];
50
+ selectors: string[];
51
+ componentClassName?: string;
52
+ }
53
+
54
+ export interface OptimizeResult {
55
+ css: string;
56
+ map: Record<string, string>;
57
+ stats: AtomicOptimizerStats;
58
+ atomicCSS: string;
59
+ componentCSS: string;
60
+ componentMap?: Map<string, ComponentClassMapEntry>;
61
+ }
62
+
63
+ // Utility functions
64
+ function hashKey(key: string): string {
65
+ return crypto.createHash('sha1').update(key).digest('hex').slice(0, 6);
66
+ }
67
+
68
+ function kebab(s: string): string {
69
+ return s.replace(/([A-Z])/g, '-$1').toLowerCase();
70
+ }
71
+
72
+ export class AtomicOptimizer {
73
+ private config: ChainCSSConfig;
74
+ options: Required<AtomicOptimizerOptions>;
75
+ private usageCount: Map<string, number>;
76
+ private atomicClasses: Map<string, AtomicClass>;
77
+ public atomicMap: Record<string, string> = {};
78
+ componentClassMap: Map<string, ComponentClassMapEntry>;
79
+ stats: {
80
+ totalStyles: number;
81
+ atomicStyles: number;
82
+ standardStyles: number;
83
+ uniqueProperties: number;
84
+ savings: number;
85
+ cacheHits: number;
86
+ cacheMisses: number;
87
+ };
88
+
89
+ constructor(config: any) {
90
+ this.config = config;
91
+ const atomicOptions = config.atomic || {};
92
+
93
+ this.options = {
94
+ enabled: atomicOptions.enabled !== false,
95
+ threshold: atomicOptions.threshold ?? 2,
96
+ naming: atomicOptions.naming ?? (process.env.NODE_ENV === 'production' ? 'hash' : 'readable'),
97
+ cache: atomicOptions.cache !== false,
98
+ cachePath: atomicOptions.cachePath || './.chaincss-cache/atomic-cache.json',
99
+ minify: config.output?.minify ?? true,
100
+ mode: atomicOptions.mode || 'hybrid',
101
+ outputStrategy: atomicOptions.outputStrategy || 'component-first',
102
+ alwaysAtomic: atomicOptions.alwaysAtomic || [],
103
+ neverAtomic: atomicOptions.neverAtomic || [
104
+ 'content', 'animation', 'transition', 'keyframes',
105
+ 'animation-name', 'animation-duration', 'animation-timing-function',
106
+ 'transition-property', 'transition-duration'
107
+ ],
108
+ frameworkOutput: { react: false, vue: false, vanilla: true },
109
+ preserveSelectors: atomicOptions.preserveSelectors || false,
110
+ verbose: config.verbose || false,
111
+ };
112
+
113
+ this.usageCount = new Map();
114
+ this.atomicClasses = new Map();
115
+ this.componentClassMap = new Map();
116
+ this.stats = {
117
+ totalStyles: 0,
118
+ atomicStyles: 0,
119
+ standardStyles: 0,
120
+ uniqueProperties: 0,
121
+ savings: 0,
122
+ cacheHits: 0,
123
+ cacheMisses: 0
124
+ };
125
+
126
+ if (this.options.cache) {
127
+ this.loadCache();
128
+ }
129
+ }
130
+
131
+ public componentMap = new Map<string, ComponentClassMapEntry>();
132
+
133
+ /**
134
+ * Get usage count for a specific property-value pair
135
+ */
136
+ public getUsageCount(prop: string, value: string, context: string = ''): number {
137
+ const key = context ? `${context}|${prop}:${value}` : `${prop}:${value}`;
138
+ return this.usageCount.get(key) || 0;
139
+ }
140
+
141
+ /**
142
+ * Increment usage count for a specific property-value pair
143
+ */
144
+ public incrementUsageCount(prop: string, value: string, context: string = ''): void {
145
+ const key = context ? `${context}|${prop}:${value}` : `${prop}:${value}`;
146
+ const current = this.usageCount.get(key) || 0;
147
+ this.usageCount.set(key, current + 1);
148
+ this.stats.totalStyles++;
149
+ }
150
+
151
+ /**
152
+ * Get the usage count map for debugging
153
+ */
154
+ public getUsageCountMap(): Map<string, number> {
155
+ return new Map(this.usageCount);
156
+ }
157
+
158
+ // ============================================================================
159
+ // Cache Management
160
+ // ============================================================================
161
+
162
+ private loadCache(): void {
163
+ try {
164
+ const cacheDir = path.dirname(this.options.cachePath);
165
+ if (!fs.existsSync(cacheDir)) {
166
+ fs.mkdirSync(cacheDir, { recursive: true });
167
+ }
168
+
169
+ if (!fs.existsSync(this.options.cachePath)) return;
170
+
171
+ const data = JSON.parse(fs.readFileSync(this.options.cachePath, 'utf8'));
172
+
173
+ if (data.version !== '2.0.0') {
174
+ if (this.options.verbose) {
175
+ console.log('Cache version mismatch, rebuilding...');
176
+ }
177
+ return;
178
+ }
179
+
180
+ if (data.config?.threshold !== this.options.threshold) {
181
+ if (this.options.verbose) {
182
+ console.log('Threshold changed, rebuilding cache...');
183
+ }
184
+ return;
185
+ }
186
+
187
+ // Restore atomic classes
188
+ if (data.atomicClasses) {
189
+ for (const [key, value] of data.atomicClasses) {
190
+ this.atomicClasses.set(key, value);
191
+ // Also rebuild atomicMap
192
+ const atomic = value;
193
+ this.atomicMap[`${atomic.prop}:${atomic.value}`] = atomic.className;
194
+ }
195
+ }
196
+
197
+ if (data.componentClassMap) {
198
+ for (const [key, value] of data.componentClassMap) {
199
+ this.componentClassMap.set(key, value);
200
+ }
201
+ }
202
+
203
+ if (data.stats) {
204
+ this.stats = { ...this.stats, ...data.stats };
205
+ }
206
+
207
+ if (this.options.verbose) {
208
+ console.log(`✅ Cache loaded: ${this.atomicClasses.size} atomic classes`);
209
+ }
210
+
211
+ } catch (err) {
212
+ if (this.options.verbose) {
213
+ console.log('Could not load cache:', (err as Error).message);
214
+ }
215
+ }
216
+ }
217
+
218
+ private saveCache(): void {
219
+ if (!this.options.cache) return;
220
+
221
+ try {
222
+ const cacheDir = path.dirname(this.options.cachePath);
223
+ if (!fs.existsSync(cacheDir)) {
224
+ fs.mkdirSync(cacheDir, { recursive: true });
225
+ }
226
+
227
+ const cache = {
228
+ version: '2.0.0',
229
+ timestamp: Date.now(),
230
+ atomicClasses: Array.from(this.atomicClasses.entries()),
231
+ componentClassMap: Array.from(this.componentClassMap.entries()),
232
+ stats: {
233
+ totalStyles: this.stats.totalStyles,
234
+ atomicStyles: this.stats.atomicStyles,
235
+ standardStyles: this.stats.standardStyles,
236
+ uniqueProperties: this.stats.uniqueProperties
237
+ },
238
+ config: {
239
+ threshold: this.options.threshold,
240
+ naming: this.options.naming,
241
+ mode: this.options.mode,
242
+ outputStrategy: this.options.outputStrategy
243
+ }
244
+ };
245
+
246
+ fs.writeFileSync(this.options.cachePath, JSON.stringify(cache, null, 2), 'utf8');
247
+
248
+ if (this.options.verbose) {
249
+ console.log(`💾 Cache saved: ${this.atomicClasses.size} atomic classes`);
250
+ }
251
+ } catch (err) {
252
+ if (this.options.verbose) {
253
+ console.log('Could not save cache:', (err as Error).message);
254
+ }
255
+ }
256
+ }
257
+
258
+ // ============================================================================
259
+ // Style Tracking
260
+ // ============================================================================
261
+
262
+ public trackStyles(styles: any[]): void {
263
+ if (!Array.isArray(styles)) return;
264
+
265
+ for (const styleDef of styles) {
266
+ if (!styleDef || typeof styleDef !== 'object') continue;
267
+
268
+ for (const [prop, value] of Object.entries(styleDef)) {
269
+ if (prop === 'selectors' || prop === 'path' || prop === 'atRules' || prop === 'nestedRules') continue;
270
+ if (prop.startsWith('_')) continue;
271
+
272
+ if (typeof value === 'string' || typeof value === 'number') {
273
+ this.incrementUsageCount(prop, String(value));
274
+ }
275
+ }
276
+
277
+ // Handle hover states
278
+ if (styleDef.hover && typeof styleDef.hover === 'object') {
279
+ for (const [hProp, hValue] of Object.entries(styleDef.hover)) {
280
+ this.incrementUsageCount(hProp, String(hValue), 'hover');
281
+ }
282
+ }
283
+
284
+ // Handle nested rules
285
+ if (styleDef.nestedRules && Array.isArray(styleDef.nestedRules)) {
286
+ for (const nested of styleDef.nestedRules) {
287
+ if (nested.styles) {
288
+ this.trackStyles([nested.styles]);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ // ============================================================================
296
+ // String-Based Scanning
297
+ // ============================================================================
298
+
299
+ public process(styleChain: string): void {
300
+ try {
301
+ const styleObj: Record<string, any> = {};
302
+ // Better regex for parsing chain methods
303
+ const methodRegex = /\.([a-zA-Z0-9]+)\s*\(\s*(['"]?)([^'")]+)\2\s*\)/g;
304
+ let match;
305
+
306
+ while ((match = methodRegex.exec(styleChain)) !== null) {
307
+ const [, prop, , value] = match;
308
+ if (prop && value) {
309
+ styleObj[prop] = value;
310
+ }
311
+ }
312
+
313
+ if (Object.keys(styleObj).length > 0) {
314
+ this.trackStyles([styleObj]);
315
+
316
+ for (const [prop, value] of Object.entries(styleObj)) {
317
+ const className = this.getOrCreateAtomic(prop, value as string);
318
+ this.atomicMap[`${prop}:${value}`] = className;
319
+ }
320
+ }
321
+ } catch (e) {
322
+ // Silent fail for malformed chains
323
+ if (this.options.verbose) {
324
+ console.log('Failed to process style chain:', e);
325
+ }
326
+ }
327
+ }
328
+
329
+ private processStyleObject(style: any, context: string = 'base'): void {
330
+ if (!style) return;
331
+
332
+ for (const [prop, value] of Object.entries(style)) {
333
+ if (['selectors', 'atRules', 'nestedRules', 'hover'].includes(prop)) continue;
334
+ if (prop.startsWith('_')) continue;
335
+
336
+ this.incrementUsage(prop, value as string, context);
337
+ }
338
+
339
+ if (style.hover) {
340
+ for (const [hProp, hVal] of Object.entries(style.hover)) {
341
+ this.incrementUsage(hProp, hVal as string, `${context}:hover`);
342
+ }
343
+ }
344
+
345
+ if (style.nestedRules) {
346
+ style.nestedRules.forEach((nested: any) =>
347
+ this.processStyleObject(nested.styles, context)
348
+ );
349
+ }
350
+
351
+ if (style.atRules) {
352
+ style.atRules.forEach((rule: any) => {
353
+ if (rule.styles) this.processStyleObject(rule.styles, rule.query || context);
354
+ });
355
+ }
356
+ }
357
+
358
+ // Fixed: Class name generation with proper prefixes
359
+ private generateClassName(prop: string, value: string, type: string): string {
360
+ const hash = crypto.createHash('md5')
361
+ .update(`${prop}${value}`)
362
+ .digest('hex')
363
+ .slice(0, 6);
364
+
365
+ const propKebab = kebab(prop);
366
+
367
+ // For atomic utilities, use 'a-' prefix
368
+ if (type === 'atomic') {
369
+ return `a-${propKebab}-${hash}`;
370
+ }
371
+
372
+ // For components, use single 'c-' prefix
373
+ return `c-${propKebab}-${hash}`;
374
+ }
375
+
376
+ private incrementUsage(prop: string, value: string, context: string = 'base'): void {
377
+ const key = `${context}|${prop}:${value}`;
378
+ const count = (this.usageCount.get(key) || 0) + 1;
379
+ this.usageCount.set(key, count);
380
+ this.stats.totalStyles++;
381
+
382
+ if (this.atomicClasses.has(key)) {
383
+ const atomic = this.atomicClasses.get(key)!;
384
+ atomic.usageCount = count;
385
+ }
386
+ }
387
+
388
+ private shouldBeAtomic(prop: string, value: string, context: string = 'base'): boolean {
389
+ if (this.options.mode === 'standard') return false;
390
+ if (this.options.mode === 'atomic') return true;
391
+
392
+ const kebabProp = kebab(prop);
393
+
394
+ if (this.options.neverAtomic.includes(kebabProp)) return false;
395
+ if (this.options.alwaysAtomic.includes(kebabProp)) return true;
396
+
397
+ const key = `${context}|${prop}:${value}`;
398
+ const usage = this.usageCount.get(key) || 0;
399
+
400
+ return usage >= this.options.threshold;
401
+ }
402
+
403
+ private getOrCreateAtomic(prop: string, value: string, context: string = 'base'): string {
404
+ const key = `${context}|${prop}:${value}`;
405
+
406
+ // Check cache hit
407
+ if (this.atomicClasses.has(key)) {
408
+ this.stats.cacheHits++;
409
+ return this.atomicClasses.get(key)!.className;
410
+ }
411
+
412
+ this.stats.cacheMisses++;
413
+
414
+ const className = this.generateClassName(prop, value, 'atomic');
415
+ const propKebab = kebab(prop);
416
+
417
+ this.atomicClasses.set(key, {
418
+ className,
419
+ prop,
420
+ value,
421
+ rules: `${propKebab}: ${value};`,
422
+ usageCount: this.usageCount.get(key) || 0,
423
+ createdAt: Date.now(),
424
+ hash: crypto.createHash('md5').update(key).digest('hex').slice(0, 8)
425
+ });
426
+
427
+ this.stats.atomicStyles++;
428
+ this.stats.uniqueProperties++;
429
+
430
+ return className;
431
+ }
432
+
433
+ public getKeyFromClassName(className: string): string | null {
434
+ for (const [key, atomic] of this.atomicClasses) {
435
+ if (atomic.className === className) return key;
436
+ }
437
+ return null;
438
+ }
439
+
440
+ // ============================================================================
441
+ // CSS Generation
442
+ // ============================================================================
443
+
444
+ public generateAtomicCSS(): string {
445
+ let css = "/* ChainCSS Atomic Bundle */\n";
446
+
447
+ // Sort atomic classes for consistent output
448
+ const sorted = Array.from(this.atomicClasses.values()).sort((a, b) =>
449
+ a.className.localeCompare(b.className)
450
+ );
451
+
452
+ for (const data of sorted) {
453
+ css += `.${data.className} { ${data.rules} }\n`;
454
+ }
455
+
456
+ return css;
457
+ }
458
+
459
+ public generateComponentCSS(style: any, selectors: string[], context: string = 'base'): {
460
+ css: string;
461
+ atomicClasses: string[];
462
+ } {
463
+ const atomicClasses: string[] = [];
464
+ let standardRules = '';
465
+ const selectorStr = selectors.join(', ');
466
+
467
+ for (const [prop, value] of Object.entries(style)) {
468
+ if (['selectors', 'atRules', 'nestedRules', 'hover'].includes(prop) || prop.startsWith('_')) continue;
469
+ if (typeof value === 'object') continue;
470
+
471
+ if (this.shouldBeAtomic(prop, value as string, context)) {
472
+ const atomicClass = this.getOrCreateAtomic(prop, value as string, context);
473
+ atomicClasses.push(atomicClass);
474
+ } else {
475
+ standardRules += ` ${kebab(prop)}: ${value};\n`;
476
+ }
477
+ }
478
+
479
+ let css = standardRules ? `${selectorStr} {\n${standardRules}}\n` : '';
480
+
481
+ if (style.nestedRules) {
482
+ style.nestedRules.forEach((nested: any) => {
483
+ const nestedSelector = nested.selector.replace('&', selectorStr);
484
+ const nestedResult = this.generateComponentCSS(nested.styles, [nestedSelector], context);
485
+ css += nestedResult.css;
486
+ atomicClasses.push(...nestedResult.atomicClasses);
487
+ });
488
+ }
489
+
490
+ if (style.atRules) {
491
+ style.atRules.forEach((rule: any) => {
492
+ if (rule.styles) {
493
+ const ruleResult = this.generateComponentCSS(rule.styles, selectors, rule.query || context);
494
+ css += `@${rule.type} ${rule.query} {\n${ruleResult.css}}\n`;
495
+ atomicClasses.push(...ruleResult.atomicClasses);
496
+ }
497
+ });
498
+ }
499
+
500
+ return { css, atomicClasses };
501
+ }
502
+
503
+ /**
504
+ * Generate a clean component name without any prefixes
505
+ */
506
+ private getCleanComponentName(rawName: string, componentId: string): string {
507
+ // Remove leading dot
508
+ let clean = rawName.replace(/^\./, '');
509
+
510
+ // Remove any existing 'c-' or 'c-c-' prefixes
511
+ clean = clean.replace(/^c-+/, '');
512
+
513
+ // Replace special characters with hyphens
514
+ clean = clean.replace(/[^a-zA-Z0-9_-]/g, '-');
515
+
516
+ // Ensure it's not empty
517
+ if (!clean || clean === '&') {
518
+ clean = componentId.replace(/^c-+/, '');
519
+ }
520
+
521
+ // Remove componentId prefix if it's duplicated
522
+ const componentIdClean = componentId.replace(/^c-+/, '');
523
+ if (clean === componentIdClean || clean === `c-${componentIdClean}`) {
524
+ clean = componentIdClean;
525
+ }
526
+
527
+ return clean;
528
+ }
529
+
530
+ // Helper method to generate pseudo-class CSS
531
+ private generatePseudoCSS(
532
+ pseudoClass: string,
533
+ styles: Record<string, any>,
534
+ selector: string
535
+ ): string {
536
+ let css = '';
537
+ const pseudoSelector = `${selector}:${pseudoClass}`;
538
+ let rules = '';
539
+
540
+ for (const [prop, value] of Object.entries(styles)) {
541
+ if (prop === 'selectors' || prop.startsWith('_')) continue;
542
+ const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
543
+ rules += ` ${kebabProp}: ${value};\n`;
544
+ }
545
+
546
+ if (rules) {
547
+ css = `${pseudoSelector} {\n${rules}}\n`;
548
+ }
549
+
550
+ return css;
551
+ }
552
+
553
+ // ============================================================================
554
+ // ✅ MAIN OPTIMIZE METHOD - FULLY FIXED
555
+ // ============================================================================
556
+
557
+ public optimize(styles: Record<string, any>): OptimizeResult {
558
+ const componentId = Object.keys(styles)[0];
559
+ let styleDef = styles[componentId];
560
+ let atomicClasses: string[] = [];
561
+
562
+ if (!styleDef || typeof styleDef !== 'object') {
563
+ return {
564
+ css: '',
565
+ map: {},
566
+ stats: this.getStats(),
567
+ atomicCSS: '',
568
+ componentCSS: '',
569
+ componentMap: this.componentMap
570
+ };
571
+ }
572
+
573
+ // 1. Selector & Class Name Generation
574
+ let rawName = Array.isArray(styleDef.selectors) ? styleDef.selectors[0] : styleDef.selectors;
575
+ if (!rawName || rawName === '&') {
576
+ rawName = componentId;
577
+ }
578
+
579
+ let cleanName = rawName.replace(/^\./, '').replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
580
+ if (!cleanName || cleanName.length > 50) {
581
+ cleanName = 'component';
582
+ }
583
+
584
+ const componentHash = crypto.createHash('md5')
585
+ .update(cleanName + componentId)
586
+ .digest('hex')
587
+ .slice(0, 6);
588
+
589
+ const componentClassName = `c-${cleanName}-${componentHash}`;
590
+ const selector = `.${componentClassName}`;
591
+
592
+ if (this.options.verbose) {
593
+ console.log(`[AtomicOptimizer] Optimizing component: ${componentId} -> ${componentClassName}`);
594
+ }
595
+
596
+ let classList: string[] = [componentClassName];
597
+ let localRules = "";
598
+ let pseudoRules = "";
599
+
600
+ // 2. Process each style property
601
+ for (const [prop, value] of Object.entries(styleDef)) {
602
+ // Skip metadata
603
+ if (prop === 'selectors' || prop === 'path' || prop.startsWith('_')) continue;
604
+
605
+ // Handle Pseudo-classes (hover, focus, active, etc.)
606
+ if (typeof value === 'object' && value !== null) {
607
+ if (['hover', 'focus', 'active', 'visited', 'disabled', 'checked'].includes(prop)) {
608
+ pseudoRules += this.generatePseudoCSS(prop, value as Record<string, any>, selector);
609
+ }
610
+ continue;
611
+ }
612
+
613
+ if (value === null || value === undefined) continue;
614
+
615
+ const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
616
+ const stringValue = String(value);
617
+
618
+ // 3. ATOMIC CHECK
619
+ if (this.shouldBeAtomic(prop, stringValue)) {
620
+ // Get or Create the global rule
621
+ const atomicClass = this.getOrCreateAtomic(prop, stringValue);
622
+ classList.push(atomicClass);
623
+ atomicClasses.push(atomicClass);
624
+
625
+ // POPULATE THE MAP: Fixes the empty atomicMap issue
626
+ this.atomicMap[`${prop}:${stringValue}`] = atomicClass;
627
+
628
+ // Update stats
629
+ this.stats.atomicStyles++;
630
+
631
+ if (this.options.verbose) {
632
+ console.log(` [Atomic] ${kebabProp}: ${stringValue} -> .${atomicClass}`);
633
+ }
634
+ } else {
635
+ // It stays local to the component
636
+ localRules += ` ${kebabProp}: ${stringValue};\n`;
637
+ this.stats.standardStyles++;
638
+
639
+ if (this.options.verbose) {
640
+ console.log(` [Standard] ${kebabProp}: ${stringValue}`);
641
+ }
642
+ }
643
+ }
644
+
645
+ // Store component mapping
646
+ this.componentMap.set(componentId, {
647
+ atomicClasses: atomicClasses,
648
+ hoverAtomicClasses: [], // Will be populated if hover styles exist
649
+ selectors: [selector],
650
+ componentClassName: componentClassName
651
+ });
652
+
653
+ this.componentClassMap.set(componentId, {
654
+ atomicClasses: atomicClasses,
655
+ hoverAtomicClasses: [],
656
+ selectors: [selector]
657
+ });
658
+
659
+ // 4. Build final CSS Output
660
+ let componentCSS = '';
661
+ if (localRules) {
662
+ componentCSS = `${selector} {\n${localRules}}\n`;
663
+ }
664
+
665
+ if (pseudoRules) {
666
+ componentCSS += pseudoRules;
667
+ }
668
+
669
+ const finalClassName = classList.join(' ');
670
+
671
+ // Save cache for future builds
672
+ this.saveCache();
673
+
674
+ // 5. Return the full result for the compiler to write to disk
675
+ return {
676
+ css: componentCSS,
677
+ map: {
678
+ [componentId]: finalClassName
679
+ },
680
+ stats: this.getStats(),
681
+ atomicCSS: this.generateAtomicCSS(),
682
+ componentCSS: componentCSS,
683
+ componentMap: this.componentMap
684
+ };
685
+ }
686
+
687
+ // Helper to process pseudo-states
688
+ private processPseudoState(
689
+ state: string,
690
+ styles: Record<string, any>,
691
+ selector: string
692
+ ): string {
693
+ let css = '';
694
+ const pseudoSelector = `${selector}:${state}`;
695
+ let rules = '';
696
+
697
+ for (const [prop, value] of Object.entries(styles)) {
698
+ if (prop === 'selectors' || prop.startsWith('_')) continue;
699
+ const kebabProp = kebab(prop);
700
+ rules += ` ${kebabProp}: ${value};\n`;
701
+ }
702
+
703
+ if (rules) {
704
+ css = `${pseudoSelector} {\n${rules}}\n`;
705
+ }
706
+
707
+ return css;
708
+ }
709
+
710
+ public reset(): void {
711
+ this.usageCount.clear();
712
+ this.atomicClasses.clear();
713
+ this.componentClassMap.clear();
714
+ this.componentMap.clear();
715
+ this.atomicMap = {};
716
+ this.stats = {
717
+ totalStyles: 0,
718
+ atomicStyles: 0,
719
+ standardStyles: 0,
720
+ uniqueProperties: 0,
721
+ savings: 0,
722
+ cacheHits: 0,
723
+ cacheMisses: 0
724
+ };
725
+
726
+ if (this.options.verbose) {
727
+ console.log('AtomicOptimizer reset');
728
+ }
729
+ }
730
+
731
+ getStats(): AtomicOptimizerStats {
732
+ const total = this.stats.totalStyles;
733
+ const generatedRules = this.stats.uniqueProperties + this.stats.standardStyles;
734
+
735
+ // Calculate real savings
736
+ let savingsValue = 0;
737
+ if (total > 0) {
738
+ savingsValue = ((total - generatedRules) / total) * 100;
739
+ }
740
+
741
+ const totalCacheRequests = this.stats.cacheHits + this.stats.cacheMisses;
742
+ const cacheHitRate = totalCacheRequests > 0 ? (this.stats.cacheHits / totalCacheRequests) : 0;
743
+
744
+ return {
745
+ totalStyles: total,
746
+ atomicStyles: this.atomicClasses.size,
747
+ standardStyles: this.stats.standardStyles,
748
+ uniqueProperties: this.stats.uniqueProperties,
749
+ savings: `${savingsValue.toFixed(1)}%`,
750
+ cacheHitRate: cacheHitRate
751
+ };
752
+ }
753
+
754
+ getAtomicClass(prop: string, value: string, context: string = ''): string | null {
755
+ const key = context ? `${context}|${prop}:${value}` : `${prop}:${value}`;
756
+ const atomic = this.atomicClasses.get(key);
757
+ return atomic ? atomic.className : null;
758
+ }
759
+
760
+ getAllAtomicClasses(): AtomicClass[] {
761
+ return Array.from(this.atomicClasses.values());
762
+ }
763
+
764
+ clearCache(): void {
765
+ this.atomicClasses.clear();
766
+ this.componentClassMap.clear();
767
+ this.usageCount.clear();
768
+ this.atomicMap = {};
769
+ if (this.options.cache && fs.existsSync(this.options.cachePath)) {
770
+ fs.unlinkSync(this.options.cachePath);
771
+ if (this.options.verbose) {
772
+ console.log('Cache cleared');
773
+ }
774
+ }
775
+ }
776
+
777
+ public getComponentMapEntry(name: string): ComponentClassMapEntry | undefined {
778
+ return this.componentClassMap.get(name);
779
+ }
780
+
781
+ public getAtomicMap(): Record<string, string> {
782
+ return this.atomicMap;
783
+ }
784
+ }
785
+
786
+ export { AtomicOptimizer as default };