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,835 @@
1
+ // chaincss/src/core/compiler.ts
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import crypto from 'crypto';
6
+ import chalk from 'chalk';
7
+ import { fileURLToPath, pathToFileURL } from 'url';
8
+ import {
9
+ DEFAULT_CONFIG,
10
+ NEVER_ATOMIC_PROPERTIES,
11
+ ALWAYS_ATOMIC_PROPERTIES,
12
+ VERSION,
13
+ PERFORMANCE,
14
+ MEMORY
15
+ } from './constants.js';
16
+ import { generateClassName, formatCSS, writeFile, getBaseName } from './utils.js';
17
+ import type { ChainCSSConfig, CompileResult, StyleDefinition } from './types.js';
18
+
19
+ // Core Compiler Logic
20
+ import { compile as bttCompile, setAtomicOptimizer, setBreakpoints, setSourceComments, scanFileForStyles } from '../compiler/btt.js';
21
+ import { AtomicOptimizer } from '../compiler/atomic-optimizer.js';
22
+ import ChainCSSPrefixer from '../compiler/prefixer.js';
23
+ import { CacheManager } from '../compiler/cache-manager.js';
24
+ import { PersistentCache } from '../compiler/content-addressable-cache.js';
25
+ import { shorthandMap, macros } from '../compiler/shorthands.js';
26
+ import type { AtomicClass } from '../compiler/atomic-optimizer.js';
27
+
28
+ const __filename = fileURLToPath(import.meta.url);
29
+ const __dirname = path.dirname(__filename);
30
+
31
+ interface CachedStyleEntry {
32
+ result: {
33
+ css: string;
34
+ classMap: Record<string, string>;
35
+ atomicClasses: AtomicClass[];
36
+ stats: any;
37
+ };
38
+ accessCount: number;
39
+ lastAccessed: number;
40
+ hash: string;
41
+ }
42
+
43
+ export class ChainCSSCompiler {
44
+ private config: Required<ChainCSSConfig>;
45
+ private prefixer: ChainCSSPrefixer | null = null;
46
+ public atomicOptimizer: AtomicOptimizer | null = null;
47
+
48
+ private sharedStyles: Map<string, number> = new Map();
49
+ private styleCache = new Map<string, CachedStyleEntry>();
50
+ private classMap = new Map<string, string>();
51
+ private runtimeCache: CacheManager;
52
+ private persistentCache: PersistentCache;
53
+
54
+ private readonly MAX_STYLE_CACHE_SIZE = PERFORMANCE.CACHE_MAX_ENTRIES || 500;
55
+ private importedModules = new Map<string, { timestamp: number; hash: string }>();
56
+ private dependencyGraph = new Map<string, Set<string>>();
57
+ private generatedCSS: string = '';
58
+ private accumulatedCSS: string = '';
59
+ private compileInProgress: boolean = false;
60
+ private compileQueue: Array<{ resolve: () => void; reject: (error: Error) => void }> = [];
61
+
62
+ // LRU tracking for O(1) eviction
63
+ private lruList: string[] = [];
64
+
65
+ constructor(config: ChainCSSConfig) {
66
+ this.config = {
67
+ ...DEFAULT_CONFIG,
68
+ ...config,
69
+ atomic: {
70
+ ...DEFAULT_CONFIG.atomic,
71
+ ...config.atomic,
72
+ neverAtomic: [...NEVER_ATOMIC_PROPERTIES, ...(config.atomic?.neverAtomic || [])],
73
+ alwaysAtomic: [...ALWAYS_ATOMIC_PROPERTIES, ...(config.atomic?.alwaysAtomic || [])]
74
+ }
75
+ } as Required<ChainCSSConfig>;
76
+
77
+ this.setupCompilerGlobals();
78
+
79
+ this.runtimeCache = new CacheManager(this.config.cachePath || './.chaincss-cache');
80
+ this.persistentCache = new PersistentCache({
81
+ cacheDir: (this.config as any).persistentCachePath || './.chaincss/persistent-cache',
82
+ maxAgeDays: (this.config as any).cacheMaxAgeDays || 30,
83
+ maxSizeMB: (this.config as any).cacheMaxSizeMB || 500,
84
+ enabled: (this.config as any).cacheEnabled !== false,
85
+ verbose: this.config.verbose
86
+ });
87
+
88
+ this.atomicOptimizer = new AtomicOptimizer(this.config as any);
89
+
90
+ this.initOptimizer();
91
+ this.initPrefixer();
92
+ }
93
+
94
+ public hasStyles(): boolean {
95
+ const combined = this.getCombinedCSS();
96
+ return !!(combined && combined.trim().length > 0);
97
+ }
98
+
99
+ private async processStyleObject(styleObj: Record<string, any>, componentName: string): Promise<void> {
100
+ if (!this.atomicOptimizer) return;
101
+
102
+ // Transform the style object - expand shorthands
103
+ const finalStyle: Record<string, any> = {};
104
+
105
+ for (let [key, value] of Object.entries(styleObj)) {
106
+ // Handle hover states properly
107
+ if (key === 'hover' && typeof value === 'object') {
108
+ const expandedHover: Record<string, any> = {};
109
+ for (const [hk, hv] of Object.entries(value)) {
110
+ const realKey = shorthandMap[hk] || hk;
111
+ expandedHover[realKey] = hv;
112
+ }
113
+ finalStyle.hover = expandedHover;
114
+ continue;
115
+ }
116
+
117
+ // Handle atRules
118
+ if (key === 'atRules') {
119
+ finalStyle.atRules = value;
120
+ continue;
121
+ }
122
+
123
+ // Handle nested selectors
124
+ if (key.startsWith('.') || key.startsWith('&')) {
125
+ finalStyle[key] = value;
126
+ continue;
127
+ }
128
+
129
+ // Transform standard properties
130
+ const realKey = shorthandMap[key] || key;
131
+ finalStyle[realKey] = value;
132
+ }
133
+
134
+ const result = this.atomicOptimizer.optimize({
135
+ [componentName]: {
136
+ selectors: [componentName],
137
+ ...finalStyle
138
+ }
139
+ });
140
+
141
+ if (result.css && result.css.trim()) {
142
+ this.accumulatedCSS += result.css + '\n';
143
+ }
144
+
145
+ // Cache for HMR - use SHA256 for consistency
146
+ const cacheKey = crypto.createHash('sha256')
147
+ .update(`${componentName}-${JSON.stringify(styleObj)}`)
148
+ .digest('hex')
149
+ .slice(0, 16);
150
+
151
+ this.addToCache(cacheKey, {
152
+ result: {
153
+ css: result.css || '',
154
+ classMap: result.map || {},
155
+ atomicClasses: [],
156
+ stats: this.getStats()
157
+ },
158
+ accessCount: 1,
159
+ lastAccessed: Date.now(),
160
+ hash: cacheKey
161
+ });
162
+ }
163
+
164
+ private addToCache(key: string, entry: CachedStyleEntry): void {
165
+ // If key already exists, just update it
166
+ if (this.styleCache.has(key)) {
167
+ this.styleCache.set(key, entry);
168
+ // Move to end of LRU list (most recently used)
169
+ this.lruList = this.lruList.filter(k => k !== key);
170
+ this.lruList.push(key);
171
+ return;
172
+ }
173
+
174
+ // Evict oldest entries if at capacity
175
+ while (this.styleCache.size >= this.MAX_STYLE_CACHE_SIZE && this.lruList.length > 0) {
176
+ const oldest = this.lruList.shift();
177
+ if (oldest) {
178
+ this.styleCache.delete(oldest);
179
+ if (this.config.verbose) {
180
+ console.log(chalk.gray(` 🧹 Cache evicted: ${oldest.slice(0, 8)}...`));
181
+ }
182
+ }
183
+ }
184
+
185
+ this.styleCache.set(key, entry);
186
+ this.lruList.push(key);
187
+ }
188
+
189
+ /**
190
+ * Scans a raw source string (from Vite) for useChainStyles patterns
191
+ * and registers them with the optimizer.
192
+ * Uses brace-counting parser instead of fragile regex.
193
+ */
194
+ public async compileSource(source: string, id: string): Promise<void> {
195
+ if (!this.atomicOptimizer || id.includes('\0')) return;
196
+
197
+ try {
198
+ let processedCount = 0;
199
+ let searchFrom = 0;
200
+
201
+ while (true) {
202
+ const startIdx = source.indexOf('useChainStyles({', searchFrom);
203
+ if (startIdx === -1) break;
204
+
205
+ // Find the matching closing brace using brace counting
206
+ const braceStart = source.indexOf('{', startIdx);
207
+ if (braceStart === -1) break;
208
+
209
+ let braceCount = 0;
210
+ let endIdx = -1;
211
+ for (let i = braceStart; i < source.length; i++) {
212
+ if (source[i] === '{') braceCount++;
213
+ if (source[i] === '}') braceCount--;
214
+ if (braceCount === 0) {
215
+ endIdx = i;
216
+ break;
217
+ }
218
+ }
219
+
220
+ if (endIdx === -1) break;
221
+
222
+ const stylesBlock = source.substring(braceStart + 1, endIdx);
223
+
224
+ try {
225
+ // Extract component definitions from the block
226
+ const componentRegex = /(\w+):\s*\{/g;
227
+ let componentMatch;
228
+
229
+ while ((componentMatch = componentRegex.exec(stylesBlock)) !== null) {
230
+ const componentKey = componentMatch[1];
231
+ const componentStart = componentMatch.index + componentMatch[0].length;
232
+
233
+ // Find matching closing brace for this component
234
+ let compBraceCount = 0;
235
+ let compEndIdx = -1;
236
+ for (let i = componentStart - 1; i < stylesBlock.length; i++) {
237
+ if (stylesBlock[i] === '{') compBraceCount++;
238
+ if (stylesBlock[i] === '}') compBraceCount--;
239
+ if (compBraceCount === 0) {
240
+ compEndIdx = i;
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (compEndIdx === -1) continue;
246
+
247
+ const componentStyles = stylesBlock.substring(componentStart, compEndIdx);
248
+ const rawObj = this.safeParseStyleObject(`{${componentStyles}}`);
249
+
250
+ if (Object.keys(rawObj).length === 0) continue;
251
+
252
+ // Expand shorthands
253
+ const expandedObj: Record<string, any> = {};
254
+ for (const [k, v] of Object.entries(rawObj)) {
255
+ const realKey = shorthandMap[k] || k;
256
+ expandedObj[realKey] = v;
257
+ }
258
+
259
+ await this.processStyleObject(expandedObj, componentKey);
260
+ processedCount++;
261
+ }
262
+ } catch (parseError) {
263
+ if (this.config.verbose) {
264
+ console.warn(chalk.yellow(` ⚠️ Failed to parse styles in ${id}: ${parseError}`));
265
+ }
266
+ }
267
+
268
+ searchFrom = endIdx + 1;
269
+ }
270
+
271
+ if (this.config.verbose && processedCount > 0) {
272
+ console.log(chalk.gray(` 📝 Processed ${processedCount} styles from ${id}`));
273
+ }
274
+ } catch (error) {
275
+ console.error(chalk.red(` ❌ Error compiling source ${id}: ${error}`));
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Safely parse a style object string without using eval.
281
+ * Supports JSON-like syntax and token references.
282
+ */
283
+ private safeParseStyleObject(input: string): Record<string, any> {
284
+ try {
285
+ // Remove comments
286
+ let cleaned = input
287
+ .replace(/\/\*[\s\S]*?\*\//g, '')
288
+ .replace(/\/\/[^\n]*/g, '');
289
+
290
+ // Handle token references like $colors.primary -> "__TOKEN__colors.primary__"
291
+ const tokenPlaceholders: string[] = [];
292
+ cleaned = cleaned.replace(/\$([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)/g, (match) => {
293
+ tokenPlaceholders.push(match);
294
+ return `"__TOKEN_${tokenPlaceholders.length - 1}__"`;
295
+ });
296
+
297
+ // Try JSON.parse first
298
+ if (cleaned.trim().startsWith('{')) {
299
+ try {
300
+ const result = JSON.parse(cleaned);
301
+ // Restore token references
302
+ return this.restoreTokens(result, tokenPlaceholders);
303
+ } catch {
304
+ // If JSON fails, try a limited object literal parser
305
+ return this.parseObjectLiteral(cleaned, tokenPlaceholders);
306
+ }
307
+ }
308
+ } catch (err) {
309
+ if (this.config.verbose) {
310
+ console.warn(chalk.yellow(` ⚠️ Failed to parse style body: ${input.substring(0, 100)}...`));
311
+ }
312
+ }
313
+
314
+ return {};
315
+ }
316
+
317
+ /**
318
+ * Parse a limited subset of JavaScript object literal syntax.
319
+ * Handles: strings, numbers, booleans, null, nested objects, arrays.
320
+ * Does NOT execute code.
321
+ */
322
+ private parseObjectLiteral(str: string, tokenPlaceholders: string[]): Record<string, any> {
323
+ // This is a simplified safe parser. For production, consider using a library like json5.
324
+ // It handles the common cases without eval.
325
+ try {
326
+ // Replace single-quoted strings with double-quoted
327
+ let normalized = str
328
+ .replace(/'([^'\\]*(\\.[^'\\]*)*)'/g, '"$1"')
329
+ // Handle unquoted property names
330
+ .replace(/(\{|\,)\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
331
+ // Handle trailing commas
332
+ .replace(/,\s*([}\]])/g, '$1');
333
+
334
+ const result = JSON.parse(normalized);
335
+ return this.restoreTokens(result, tokenPlaceholders);
336
+ } catch {
337
+ return {};
338
+ }
339
+ }
340
+
341
+ private restoreTokens(obj: any, tokens: string[]): any {
342
+ if (typeof obj === 'string') {
343
+ const match = obj.match(/^__TOKEN_(\d+)__$/);
344
+ if (match) {
345
+ const idx = parseInt(match[1]);
346
+ return tokens[idx] || obj;
347
+ }
348
+ return obj;
349
+ }
350
+ if (Array.isArray(obj)) {
351
+ return obj.map(item => this.restoreTokens(item, tokens));
352
+ }
353
+ if (obj && typeof obj === 'object') {
354
+ const result: Record<string, any> = {};
355
+ for (const [key, value] of Object.entries(obj)) {
356
+ result[key] = this.restoreTokens(value, tokens);
357
+ }
358
+ return result;
359
+ }
360
+ return obj;
361
+ }
362
+
363
+ /**
364
+ * @deprecated Use safeParseStyleObject instead.
365
+ * Kept for backward compatibility during migration.
366
+ */
367
+ private looseParse(styleBody: string): Record<string, any> {
368
+ return this.safeParseStyleObject(styleBody);
369
+ }
370
+
371
+ private setupCompilerGlobals(): void {
372
+ setSourceComments(this.config.sourceComments !== false);
373
+ if (this.config.breakpoints) {
374
+ setBreakpoints(this.config.breakpoints);
375
+ }
376
+ }
377
+
378
+ // ============================================================================
379
+ // Caching & Imports
380
+ // ============================================================================
381
+
382
+ private hashStyleDef(styleDef: StyleDefinition): string {
383
+ const { _componentName, _generateComponent, _framework, _propsDefinition, ...relevant } = styleDef;
384
+ return crypto.createHash('sha256').update(JSON.stringify(relevant)).digest('hex').slice(0, 16);
385
+ }
386
+
387
+ private async importModule(filePath: string): Promise<Record<string, any>> {
388
+ const absolutePath = path.resolve(filePath);
389
+
390
+ // Check if file exists
391
+ if (!fs.existsSync(absolutePath)) {
392
+ throw new Error(`File not found: ${absolutePath}`);
393
+ }
394
+
395
+ const fileUrl = pathToFileURL(absolutePath).href;
396
+
397
+ try {
398
+ // Check for TSX/JSX files that need preprocessing
399
+ if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) {
400
+ throw new Error(`Component file ${path.basename(filePath)} will be processed by scanner`);
401
+ }
402
+
403
+ // Clear require cache for HMR
404
+ const moduleId = `${fileUrl}?t=${Date.now()}`;
405
+ const imported = await import(/* @vite-ignore */ moduleId);
406
+
407
+ return imported.default && typeof imported.default === 'object'
408
+ ? { ...imported.default, ...imported }
409
+ : imported;
410
+ } catch (error: any) {
411
+ error.message = `Failed to import ${path.basename(filePath)}: ${error.message}`;
412
+ throw error;
413
+ }
414
+ }
415
+
416
+ // ============================================================================
417
+ // Compilation Methods
418
+ // ============================================================================
419
+
420
+ public compileStyle(styleId: string, styleDef: StyleDefinition): CompileResult {
421
+ const hash = this.hashStyleDef(styleDef);
422
+ const cacheKey = `${styleId}:${hash}`;
423
+
424
+ if (this.styleCache.has(cacheKey)) {
425
+ const cached = this.styleCache.get(cacheKey)!;
426
+ cached.lastAccessed = Date.now();
427
+ cached.accessCount++;
428
+ // Update LRU position
429
+ this.lruList = this.lruList.filter(k => k !== cacheKey);
430
+ this.lruList.push(cacheKey);
431
+ return cached.result;
432
+ }
433
+
434
+ // Phase 1: Standard Compile
435
+ let finalCSS = bttCompile({ [styleId]: styleDef });
436
+ let finalClassName = generateClassName(styleId, this.config.atomic.naming);
437
+ let atomicClassNames: string[] = [];
438
+ let atomicClasses: AtomicClass[] = [];
439
+
440
+ if (this.atomicOptimizer && this.config.atomic.enabled) {
441
+ const optimized = this.atomicOptimizer.optimize({ [styleId]: styleDef });
442
+
443
+ const componentMapping = this.atomicOptimizer.getComponentMapEntry(styleId);
444
+ atomicClassNames = componentMapping?.atomicClasses || [];
445
+
446
+ // Get full AtomicClass data from the optimizer instead of creating empty ones
447
+ atomicClasses = atomicClassNames
448
+ .map(className => {
449
+ const atomicEntry = (this.atomicOptimizer as any)?.getAtomicEntry?.(className);
450
+ if (atomicEntry) {
451
+ return atomicEntry;
452
+ }
453
+ // Fallback only if entry not found
454
+ return {
455
+ className,
456
+ prop: '',
457
+ value: '',
458
+ usageCount: 0,
459
+ rules: ''
460
+ };
461
+ })
462
+ .filter(Boolean) as AtomicClass[];
463
+
464
+ if (optimized.map && optimized.map[styleId]) {
465
+ finalClassName = [optimized.map[styleId], ...atomicClassNames].join(' ');
466
+ }
467
+
468
+ // Only use atomic CSS if it produced output, otherwise keep standard CSS
469
+ if (optimized.css && optimized.css.trim()) {
470
+ finalCSS = optimized.css;
471
+ }
472
+ // else keep finalCSS from bttCompile above
473
+ }
474
+
475
+ const result: CompileResult = {
476
+ css: formatCSS(finalCSS, this.config.output.minify),
477
+ classMap: { [styleId]: finalClassName },
478
+ atomicClasses,
479
+ stats: this.getStats()
480
+ };
481
+
482
+ // Cache the result
483
+ this.addToCache(cacheKey, {
484
+ result,
485
+ accessCount: 1,
486
+ lastAccessed: Date.now(),
487
+ hash
488
+ });
489
+
490
+ return result;
491
+ }
492
+
493
+ public compileRecipe(recipeId: string, recipeValue: any): CompileResult {
494
+ try {
495
+ const getAllVariants = recipeValue.getAllVariants;
496
+ if (typeof getAllVariants === 'function') {
497
+ const variants = getAllVariants();
498
+ let css = '';
499
+ const classMap: Record<string, string> = {};
500
+ let allAtomicClassObjects: AtomicClass[] = [];
501
+
502
+ for (const variant of variants) {
503
+ const variantKey = Object.entries(variant)
504
+ .map(([k, v]) => `${k}-${v}`)
505
+ .join('_');
506
+
507
+ const styleDef = recipeValue(variant);
508
+ if (styleDef && styleDef.selectors) {
509
+ const result = this.compileStyle(`${recipeId}_${variantKey}`, styleDef);
510
+ css += result.css;
511
+ Object.assign(classMap, result.classMap);
512
+
513
+ if (result.atomicClasses && result.atomicClasses.length > 0) {
514
+ allAtomicClassObjects.push(...result.atomicClasses);
515
+ }
516
+ }
517
+ }
518
+
519
+ // Deduplicate by className
520
+ const seen = new Set<string>();
521
+ allAtomicClassObjects = allAtomicClassObjects.filter(ac => {
522
+ if (seen.has(ac.className)) return false;
523
+ seen.add(ac.className);
524
+ return true;
525
+ });
526
+
527
+ return {
528
+ css: formatCSS(css, this.config.output.minify),
529
+ classMap,
530
+ atomicClasses: allAtomicClassObjects,
531
+ stats: this.getStats()
532
+ };
533
+ }
534
+ } catch (error) {
535
+ console.error(`Failed to compile recipe ${recipeId}:`, error);
536
+ }
537
+
538
+ return {
539
+ css: '',
540
+ classMap: {},
541
+ atomicClasses: [],
542
+ stats: this.getStats()
543
+ };
544
+ }
545
+
546
+ public async compile(inputFile: string, outputDir: string): Promise<any> {
547
+ const results = await this.compileFile(inputFile);
548
+ const baseName = getBaseName(inputFile);
549
+ this.generateCSSFile(results, path.join(outputDir, `${baseName}.css`));
550
+ return { results };
551
+ }
552
+
553
+ public async compileFile(filePath: string): Promise<Record<string, CompileResult>> {
554
+ const moduleExports = await this.importModule(filePath);
555
+ const results: Record<string, CompileResult> = {};
556
+
557
+ for (const [name, value] of Object.entries(moduleExports)) {
558
+ if (typeof value === 'function' && (value as any).variants) {
559
+ results[name] = this.compileRecipe(name, value);
560
+ } else if (value && typeof value === 'object' && (value as any).selectors) {
561
+ results[name] = this.compileStyle(name, value as StyleDefinition);
562
+ }
563
+ }
564
+ return results;
565
+ }
566
+
567
+ public async compileComponents(components: string[]): Promise<void> {
568
+ // Ensure only one compilation at a time
569
+ if (this.compileInProgress) {
570
+ return new Promise((resolve, reject) => {
571
+ this.compileQueue.push({ resolve, reject });
572
+ });
573
+ }
574
+
575
+ this.compileInProgress = true;
576
+
577
+ try {
578
+ if (this.atomicOptimizer) this.atomicOptimizer.reset();
579
+ this.accumulatedCSS = '';
580
+
581
+ if (!this.config.silent) {
582
+ console.log(chalk.blue('\n🔍 Phase 1: Scanning & Usage Analysis...'));
583
+ }
584
+
585
+ const BATCH_SIZE = PERFORMANCE.BATCH_SIZE || 10;
586
+ const errors: Error[] = [];
587
+
588
+ for (let i = 0; i < components.length; i += BATCH_SIZE) {
589
+ const batch = components.slice(i, i + BATCH_SIZE);
590
+ const batchPromises = batch.map(async (file) => {
591
+ if (typeof file !== 'string' || file.includes('\0') || file.startsWith('virtual:')) {
592
+ return null;
593
+ }
594
+ if (!fs.existsSync(file)) return null;
595
+
596
+ try {
597
+ if (file.endsWith('.tsx') || file.endsWith('.jsx')) {
598
+ const result = scanFileForStyles(file, this.atomicOptimizer);
599
+ if (result.errors.length > 0) {
600
+ errors.push(...result.errors);
601
+ }
602
+ } else if (file.endsWith('.chain.js') || file.endsWith('.chain.ts')) {
603
+ const exports = await this.importModule(file);
604
+ const styles = exports.default || exports;
605
+ const styleArray = Object.values(styles).filter(s => s && typeof s === 'object');
606
+ this.atomicOptimizer?.trackStyles(styleArray as StyleDefinition[]);
607
+ }
608
+ } catch (err) {
609
+ if (this.config.verbose) {
610
+ console.warn(chalk.yellow(` ⚠️ Scanning fallback for ${path.basename(file)}`));
611
+ }
612
+ const result = scanFileForStyles(file, this.atomicOptimizer);
613
+ if (result.errors.length > 0) {
614
+ errors.push(...result.errors);
615
+ }
616
+ }
617
+ return null;
618
+ });
619
+
620
+ await Promise.allSettled(batchPromises);
621
+
622
+ if (this.config.verbose && i % (BATCH_SIZE * 5) === 0) {
623
+ console.log(chalk.gray(` 📊 Processed ${Math.min(i + BATCH_SIZE, components.length)}/${components.length} files`));
624
+ }
625
+ }
626
+
627
+ if (errors.length > 0 && this.config.verbose) {
628
+ console.warn(chalk.yellow(` ⚠️ ${errors.length} scanning errors occurred`));
629
+ }
630
+
631
+ if (!this.config.silent) {
632
+ console.log(chalk.blue('\n🏗️ Phase 2: Generating Component Styles...'));
633
+ }
634
+
635
+ const publicDir = path.resolve(process.cwd(), 'public');
636
+ const manifestDir = path.resolve(process.cwd(), '.chaincss', 'manifest');
637
+
638
+ if (!fs.existsSync(publicDir)) fs.mkdirSync(publicDir, { recursive: true });
639
+ if (!fs.existsSync(manifestDir)) fs.mkdirSync(manifestDir, { recursive: true });
640
+
641
+ let processedComponents = 0;
642
+ const generatedClassFiles: string[] = [];
643
+ let totalAtomicRules = 0;
644
+
645
+ for (const file of components) {
646
+ if (!file.endsWith('.chain.js') && !file.endsWith('.chain.ts')) continue;
647
+
648
+ const baseName = path.basename(file).replace(/\.chain\.(js|ts)$/, '');
649
+ const sourceDir = path.dirname(file);
650
+ let hasContent = false;
651
+ let jsBuffer = `/**
652
+ * ChainCSS Generated Class Map
653
+ * Source: ${path.relative(process.cwd(), file)}
654
+ * Generated: ${new Date().toISOString()}
655
+ * DO NOT EDIT MANUALLY
656
+ */\n\n`;
657
+ let cssBuffer = '';
658
+
659
+ try {
660
+ const rawExports = await this.importModule(file);
661
+ const styles = rawExports.default || rawExports;
662
+
663
+ for (const [name, style] of Object.entries(styles)) {
664
+ if (style && typeof style === 'object' && (style as any).selectors) {
665
+ const result = this.compileStyle(name, style as StyleDefinition);
666
+
667
+ if (this.config.verbose) {
668
+ const className = Object.values(result.classMap)[0];
669
+ console.log(chalk.gray(` 📝 ${name} → ${className || '(empty)'}`));
670
+ }
671
+
672
+ const className = Object.values(result.classMap)[0];
673
+ if (className) {
674
+ jsBuffer += `export const ${name} = '${className}';\n`;
675
+ cssBuffer += result.css + '\n';
676
+ hasContent = true;
677
+ totalAtomicRules += result.atomicClasses?.length || 0;
678
+ }
679
+ }
680
+ }
681
+
682
+ if (hasContent) {
683
+ const targetDir = path.join(sourceDir, 'style');
684
+
685
+ if (!fs.existsSync(targetDir)) {
686
+ fs.mkdirSync(targetDir, { recursive: true });
687
+ }
688
+
689
+ const classFilePath = path.join(targetDir, `${baseName}.class.js`);
690
+ fs.writeFileSync(classFilePath, jsBuffer);
691
+ generatedClassFiles.push(classFilePath);
692
+
693
+ if (cssBuffer.trim()) {
694
+ const cssFilePath = path.join(targetDir, `${baseName}.css`);
695
+ fs.writeFileSync(cssFilePath, formatCSS(cssBuffer, false));
696
+ }
697
+
698
+ processedComponents++;
699
+
700
+ if (this.config.verbose) {
701
+ console.log(chalk.green(` ✨ ${baseName} → ${path.relative(process.cwd(), classFilePath)}`));
702
+ }
703
+ }
704
+ } catch (error) {
705
+ console.error(chalk.red(` ❌ Failed to process ${baseName}: ${(error as Error).message}`));
706
+ }
707
+ }
708
+
709
+ if (!this.config.silent) {
710
+ console.log(chalk.blue('\n🌍 Phase 3: Finalizing Global Assets...'));
711
+ }
712
+
713
+ const finalAtomicCSS = this.atomicOptimizer ? this.atomicOptimizer.generateAtomicCSS() : '';
714
+
715
+ const globalCssPath = path.join(publicDir, 'global.css');
716
+ fs.writeFileSync(globalCssPath, formatCSS(finalAtomicCSS, this.config.output.minify));
717
+
718
+ if (this.config.verbose) {
719
+ console.log(chalk.blue(` 📦 Global CSS → ${path.relative(process.cwd(), globalCssPath)} (${finalAtomicCSS.length} bytes)`));
720
+ }
721
+
722
+ const manifestData = {
723
+ version: VERSION,
724
+ timestamp: new Date().toISOString(),
725
+ atomicMap: this.atomicOptimizer?.atomicMap || {},
726
+ stats: this.getStats(),
727
+ classFiles: generatedClassFiles.map(f => path.relative(process.cwd(), f))
728
+ };
729
+
730
+ const manifestPath = path.join(manifestDir, 'manifest.json');
731
+ fs.writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
732
+
733
+ if (this.config.verbose) {
734
+ console.log(chalk.blue(` 📦 Manifest → ${path.relative(process.cwd(), manifestPath)}`));
735
+ }
736
+
737
+ if (!this.config.silent) {
738
+ console.log(chalk.green(`\n✅ Build Complete!`));
739
+ console.log(chalk.gray(` 📁 Components processed: ${processedComponents}`));
740
+ console.log(chalk.gray(` 📁 Class files generated: ${generatedClassFiles.length}`));
741
+ console.log(chalk.gray(` 📁 Global CSS: ${path.relative(process.cwd(), globalCssPath)}`));
742
+ console.log(chalk.gray(` 📁 Manifest: ${path.relative(process.cwd(), manifestPath)}`));
743
+
744
+ if (this.atomicOptimizer) {
745
+ const atomicCount = Object.keys(this.atomicOptimizer.atomicMap).length;
746
+ const stats = this.atomicOptimizer.getStats();
747
+ console.log(chalk.cyan(`\n📊 Optimization Stats:`));
748
+ console.log(chalk.gray(` Atomic Rules: ${atomicCount}`));
749
+ console.log(chalk.gray(` Total Styles: ${stats.totalStyles}`));
750
+ console.log(chalk.gray(` Unique Properties: ${stats.uniqueProperties}`));
751
+ if (stats.savings) {
752
+ console.log(chalk.green(` CSS Savings: ${stats.savings}`));
753
+ }
754
+ }
755
+ }
756
+
757
+ } finally {
758
+ this.compileInProgress = false;
759
+
760
+ // Process queued compilations safely
761
+ this.drainCompileQueue();
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Drains the compile queue safely, handling items added during draining.
767
+ */
768
+ private drainCompileQueue(): void {
769
+ while (this.compileQueue.length > 0) {
770
+ const queue = [...this.compileQueue];
771
+ this.compileQueue = [];
772
+ for (const item of queue) {
773
+ item.resolve();
774
+ }
775
+ }
776
+ }
777
+
778
+ // ============================================================================
779
+ // Utilities & Plugin Helpers
780
+ // ============================================================================
781
+
782
+ public getCombinedCSS(): string {
783
+ return this.accumulatedCSS;
784
+ }
785
+
786
+ public clearCSS(): void {
787
+ this.accumulatedCSS = '';
788
+ this.styleCache.clear();
789
+ this.lruList = [];
790
+ if (this.atomicOptimizer) {
791
+ this.atomicOptimizer.reset();
792
+ }
793
+ if (this.config.verbose) {
794
+ console.log('[Compiler] CSS cache cleared');
795
+ }
796
+ }
797
+
798
+ public getStats() {
799
+ const stats = this.atomicOptimizer?.getStats();
800
+ return {
801
+ totalStyles: stats?.totalStyles || 0,
802
+ atomicStyles: (stats as any)?.atomicStyles || 0,
803
+ uniqueProperties: (stats as any)?.uniqueProperties || 0,
804
+ savings: stats?.savings || '0%'
805
+ };
806
+ }
807
+
808
+ private generateCSSFile(results: Record<string, CompileResult>, outputPath: string): void {
809
+ let css = '';
810
+ for (const r of Object.values(results)) css += r.css;
811
+ writeFile(outputPath, css);
812
+ }
813
+
814
+ public getAtomicMap(): Record<string, string> {
815
+ return (this.atomicOptimizer as any)?.classMap || {};
816
+ }
817
+
818
+ private initOptimizer(): void {
819
+ if (this.config.atomic.enabled) {
820
+ if (!this.atomicOptimizer) {
821
+ this.atomicOptimizer = new AtomicOptimizer(this.config.atomic);
822
+ setAtomicOptimizer(this.atomicOptimizer);
823
+ }
824
+ }
825
+ }
826
+
827
+ private initPrefixer(): void {
828
+ if (this.config.prefixer.enabled) this.prefixer = new ChainCSSPrefixer(this.config.prefixer);
829
+ }
830
+ }
831
+
832
+ export async function compileChainCSS(inputFile: string, outputDir: string, config?: ChainCSSConfig) {
833
+ const compiler = new ChainCSSCompiler(config || {});
834
+ return await compiler.compile(inputFile, outputDir);
835
+ }