chaincss 2.0.7 → 2.1.1
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/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +28 -0
- package/README.md +455 -226
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
- package/demo/index.html +16 -0
- package/demo/package.json +20 -0
- package/demo/src/App.tsx +117 -0
- package/demo/src/chaincss-barrel.ts +9 -0
- package/demo/src/main.tsx +8 -0
- package/demo/src/styles.chain.ts +300 -0
- package/demo/vite.config.ts +46 -0
- package/dist/cli/commands/build.d.ts +0 -1
- package/dist/cli/commands/cache.d.ts +1 -0
- package/dist/cli/commands/init.d.ts +6 -3
- package/dist/cli/commands/timeline.d.ts +0 -1
- package/dist/cli/commands/watch.d.ts +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +3213 -5296
- package/dist/cli/types.d.ts +51 -20
- package/dist/cli/utils/config-loader.d.ts +0 -1
- package/dist/cli/utils/file-utils.d.ts +27 -3
- package/dist/cli/utils/logger.d.ts +0 -1
- package/dist/compiler/Chain.d.ts +215 -0
- package/dist/compiler/animations.d.ts +76 -0
- package/dist/compiler/atomic-optimizer.d.ts +47 -12
- package/dist/compiler/breakpoints.d.ts +46 -0
- package/dist/compiler/btt.d.ts +36 -60
- package/dist/compiler/cache-manager.d.ts +58 -4
- package/dist/compiler/commonProps.d.ts +0 -1
- package/dist/compiler/content-addressable-cache.d.ts +78 -0
- package/dist/compiler/helpers.d.ts +54 -0
- package/dist/compiler/index.d.ts +16 -9
- package/dist/compiler/index.js +4450 -4316
- package/dist/compiler/prefixer.d.ts +17 -1
- package/dist/compiler/shorthands.d.ts +28 -0
- package/dist/compiler/suggestions.d.ts +43 -0
- package/dist/compiler/theme-contract.d.ts +16 -27
- package/dist/compiler/token-resolver.d.ts +69 -0
- package/dist/compiler/tokens.d.ts +33 -8
- package/dist/core/auto-detector.d.ts +34 -0
- package/dist/core/common-utils.d.ts +97 -0
- package/dist/core/compiler.d.ts +63 -23
- package/dist/core/constants.d.ts +137 -36
- package/dist/core/smart-chain.d.ts +3 -0
- package/dist/core/types.d.ts +122 -15
- package/dist/core/utils.d.ts +134 -17
- package/dist/index.d.ts +52 -8
- package/dist/index.js +7090 -5578
- package/dist/plugins/vite.d.ts +7 -5
- package/dist/plugins/vite.js +2964 -25641
- package/dist/plugins/webpack.d.ts +24 -1
- package/dist/plugins/webpack.js +209 -72
- package/dist/runtime/Chain.d.ts +32 -0
- package/dist/runtime/auto-hooks.d.ts +11 -0
- package/dist/runtime/hmr.d.ts +22 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +3648 -301
- package/dist/runtime/injector.d.ts +39 -72
- package/dist/runtime/react.d.ts +17 -12
- package/dist/runtime/svelte.d.ts +15 -0
- package/dist/runtime/types.d.ts +126 -4
- package/dist/runtime/utils.d.ts +0 -1
- package/dist/runtime/vue.d.ts +34 -14
- package/package.json +59 -66
- package/src/cli/commands/build.ts +133 -0
- package/src/cli/commands/cache.ts +371 -0
- package/src/cli/commands/init.ts +230 -0
- package/src/cli/commands/timeline.ts +435 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +226 -0
- package/src/cli/types.ts +100 -0
- package/src/cli/utils/config-loader.ts +174 -0
- package/src/cli/utils/file-utils.ts +139 -0
- package/src/cli/utils/logger.ts +74 -0
- package/src/compiler/Chain.ts +831 -0
- package/src/compiler/animations.ts +517 -0
- package/src/compiler/atomic-optimizer.ts +786 -0
- package/src/compiler/breakpoints.ts +347 -0
- package/src/compiler/btt.ts +1147 -0
- package/src/compiler/cache-manager.ts +446 -0
- package/src/compiler/commonProps.ts +18 -0
- package/src/compiler/content-addressable-cache.ts +478 -0
- package/src/compiler/helpers.ts +407 -0
- package/src/compiler/index.ts +72 -0
- package/src/compiler/prefixer.ts +720 -0
- package/src/compiler/shorthands.ts +558 -0
- package/src/compiler/suggestions.ts +436 -0
- package/src/compiler/theme-contract.ts +197 -0
- package/src/compiler/token-resolver.ts +241 -0
- package/src/compiler/tokens.ts +612 -0
- package/src/core/auto-detector.ts +187 -0
- package/src/core/common-utils.ts +423 -0
- package/src/core/compiler.ts +835 -0
- package/src/core/constants.ts +424 -0
- package/src/core/index.ts +107 -0
- package/src/core/smart-chain.ts +163 -0
- package/src/core/types.ts +257 -0
- package/src/core/utils.ts +598 -0
- package/src/index.ts +208 -0
- package/src/plugins/vite.d.ts +316 -0
- package/src/plugins/vite.ts +424 -0
- package/src/plugins/webpack.d.ts +289 -0
- package/src/plugins/webpack.ts +416 -0
- package/src/runtime/Chain.ts +242 -0
- package/src/runtime/auto-hooks.tsx +127 -0
- package/src/runtime/auto-vue.ts +72 -0
- package/src/runtime/hmr.ts +212 -0
- package/src/runtime/index.ts +82 -0
- package/src/runtime/injector.ts +273 -0
- package/src/runtime/react.tsx +269 -0
- package/src/runtime/svelte.ts +15 -0
- package/src/runtime/types.ts +256 -0
- package/src/runtime/utils.ts +128 -0
- package/src/runtime/vite-env.d.ts +120 -0
- package/src/runtime/vue.ts +231 -0
- package/tsconfig.build.json +41 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtimes.json +18 -0
- package/dist/cli/cli.cjs +0 -7
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/compile.d.ts +0 -3
- package/dist/cli/commands/compile.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/timeline.d.ts.map +0 -1
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/utils/config-loader.d.ts.map +0 -1
- package/dist/cli/utils/file-utils.d.ts.map +0 -1
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
- package/dist/compiler/btt.d.ts.map +0 -1
- package/dist/compiler/cache-manager.d.ts.map +0 -1
- package/dist/compiler/commonProps.d.ts.map +0 -1
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/prefixer.d.ts.map +0 -1
- package/dist/compiler/theme-contract.d.ts.map +0 -1
- package/dist/compiler/tokens.d.ts.map +0 -1
- package/dist/compiler/types.d.ts +0 -57
- package/dist/compiler/types.d.ts.map +0 -1
- package/dist/core/compiler.d.ts.map +0 -1
- package/dist/core/constants.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/utils.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/plugins/vite.d.ts.map +0 -1
- package/dist/plugins/webpack.d.ts.map +0 -1
- package/dist/runtime/hmr.d.ts.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/injector.d.ts.map +0 -1
- package/dist/runtime/react.d.ts.map +0 -1
- package/dist/runtime/react.js +0 -324
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/utils.d.ts.map +0 -1
- package/dist/runtime/vue.d.ts.map +0 -1
- package/dist/runtime/vue.js +0 -286
|
@@ -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
|
+
}
|