chaincss 1.13.1 ā 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -14
- package/browser/index.js +1 -1
- package/browser/rtt.js +39 -9
- package/browser/vue-composables.js +200 -0
- package/node/atomic-optimizer.js +279 -144
- package/node/btt.js +149 -102
- package/node/chaincss.js +197 -44
- package/node/prefixer.js +1 -1
- package/node/strVal.js +62 -13
- package/node/theme-validator.js +4 -4
- package/package.json +13 -3
- package/types.d.ts +51 -3
- package/node/css-properties.json +0 -633
package/node/chaincss.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const Module = require('module');
|
|
5
5
|
const chokidar = require('chokidar');
|
|
6
6
|
const CleanCSS = require('clean-css');
|
|
7
|
-
const { $, run, compile: originalCompile, chain } = require('./btt');
|
|
7
|
+
const { $, run, compile: originalCompile, chain, setAtomicOptimizer, createTokens, responsive, tokens } = require('./btt');
|
|
8
8
|
const ChainCSSPrefixer = require('./prefixer.js');
|
|
9
9
|
const strVal = require('./strVal.js');
|
|
10
10
|
const { AtomicOptimizer } = require('./atomic-optimizer');
|
|
@@ -17,11 +17,21 @@ let atomicOptimizer = null;
|
|
|
17
17
|
let config = {
|
|
18
18
|
atomic: {
|
|
19
19
|
enabled: false,
|
|
20
|
-
threshold:
|
|
21
|
-
naming: 'hash',
|
|
20
|
+
threshold: 2,
|
|
21
|
+
naming: 'hash',
|
|
22
22
|
cache: true,
|
|
23
23
|
cachePath: './.chaincss-cache',
|
|
24
|
-
minify: true
|
|
24
|
+
minify: true,
|
|
25
|
+
mode: 'hybrid',
|
|
26
|
+
alwaysAtomic: [],
|
|
27
|
+
neverAtomic: ['content', 'animation', 'transition', 'keyframes', 'counterIncrement', 'counterReset'],
|
|
28
|
+
frameworkOutput: {
|
|
29
|
+
react: false,
|
|
30
|
+
vue: false,
|
|
31
|
+
vanilla: true
|
|
32
|
+
},
|
|
33
|
+
preserveSelectors: false,
|
|
34
|
+
verbose: false
|
|
25
35
|
},
|
|
26
36
|
prefixer: {
|
|
27
37
|
enabled: true,
|
|
@@ -35,9 +45,14 @@ let config = {
|
|
|
35
45
|
let prefixer = new ChainCSSPrefixer(config.prefixer);
|
|
36
46
|
|
|
37
47
|
function deft_to_userConf(target, source) {
|
|
48
|
+
// Handle arrays specially
|
|
49
|
+
if (Array.isArray(source)) {
|
|
50
|
+
return source; // Return array as-is, don't merge
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
const result = { ...target };
|
|
39
54
|
for (const key in source) {
|
|
40
|
-
if (source[key] instanceof Object && key in target) {
|
|
55
|
+
if (source[key] instanceof Object && !Array.isArray(source[key]) && key in target) {
|
|
41
56
|
result[key] = deft_to_userConf(target[key], source[key]);
|
|
42
57
|
} else {
|
|
43
58
|
result[key] = source[key];
|
|
@@ -50,8 +65,7 @@ const ensureConfigExists = () => {
|
|
|
50
65
|
const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
|
|
51
66
|
const configExists = fs.existsSync(configPath);
|
|
52
67
|
if (!configExists && !process.env.CHAINCSS_SKIP_CONFIG) {
|
|
53
|
-
|
|
54
|
-
fs.writeFileSync(configPath, defaultConfig);
|
|
68
|
+
fs.writeFileSync(configPath, strVal.userConf);
|
|
55
69
|
console.log('-- Successfully created config file: ./chaincss.config.cjs\n');
|
|
56
70
|
}
|
|
57
71
|
};
|
|
@@ -61,7 +75,35 @@ const loadUserConfig = () => {
|
|
|
61
75
|
if (fs.existsSync(configPath)) {
|
|
62
76
|
try {
|
|
63
77
|
const userConfig = require(configPath);
|
|
64
|
-
|
|
78
|
+
|
|
79
|
+
// Deep merge that preserves arrays
|
|
80
|
+
function deepMerge(target, source) {
|
|
81
|
+
const result = { ...target };
|
|
82
|
+
for (const key in source) {
|
|
83
|
+
if (Array.isArray(source[key])) {
|
|
84
|
+
result[key] = [...source[key]]; // Copy array
|
|
85
|
+
} else if (source[key] instanceof Object && key in target) {
|
|
86
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
87
|
+
} else {
|
|
88
|
+
result[key] = source[key];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
config = deepMerge(config, userConfig);
|
|
95
|
+
|
|
96
|
+
// Ensure atomic arrays are arrays
|
|
97
|
+
if (config.atomic) {
|
|
98
|
+
if (!Array.isArray(config.atomic.alwaysAtomic)) {
|
|
99
|
+
config.atomic.alwaysAtomic = [];
|
|
100
|
+
}
|
|
101
|
+
if (!Array.isArray(config.atomic.neverAtomic)) {
|
|
102
|
+
config.atomic.neverAtomic = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate prefixer browsers
|
|
65
107
|
if (config.prefixer) {
|
|
66
108
|
if (typeof config.prefixer.browsers === 'string') {
|
|
67
109
|
config.prefixer.browsers = config.prefixer.browsers.split(',').map(b => b.trim());
|
|
@@ -78,9 +120,14 @@ const loadUserConfig = () => {
|
|
|
78
120
|
|
|
79
121
|
const initAtomicOptimizer = () => {
|
|
80
122
|
if (config.atomic.enabled) {
|
|
123
|
+
//if (config.atomic.verbose) {
|
|
124
|
+
//console.log('--Initializing Atomic Optimizer with config:', JSON.stringify(config.atomic, null, 2));
|
|
125
|
+
//}
|
|
126
|
+
|
|
81
127
|
atomicOptimizer = new AtomicOptimizer(config.atomic);
|
|
82
|
-
|
|
83
|
-
|
|
128
|
+
|
|
129
|
+
// Inject the configured atomic optimizer into btt
|
|
130
|
+
setAtomicOptimizer(atomicOptimizer);
|
|
84
131
|
}
|
|
85
132
|
};
|
|
86
133
|
|
|
@@ -98,7 +145,11 @@ function parseArgs(args) {
|
|
|
98
145
|
prefixerMode: null,
|
|
99
146
|
sourceMap: null,
|
|
100
147
|
sourceMapInline: false,
|
|
101
|
-
atomic: false
|
|
148
|
+
atomic: false,
|
|
149
|
+
atomicMode: null,
|
|
150
|
+
atomicNaming: null,
|
|
151
|
+
atomicVerbose: false,
|
|
152
|
+
preserveSelectors: false
|
|
102
153
|
};
|
|
103
154
|
|
|
104
155
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -120,12 +171,20 @@ function parseArgs(args) {
|
|
|
120
171
|
result.sourceMapInline = true;
|
|
121
172
|
} else if (arg === '--atomic') {
|
|
122
173
|
result.atomic = true;
|
|
174
|
+
} else if (arg === '--atomic-mode' && args[i + 1]) {
|
|
175
|
+
result.atomicMode = args[i + 1];
|
|
176
|
+
i++;
|
|
177
|
+
} else if (arg === '--atomic-naming' && args[i + 1]) {
|
|
178
|
+
result.atomicNaming = args[i + 1];
|
|
179
|
+
i++;
|
|
180
|
+
} else if (arg === '--atomic-verbose') {
|
|
181
|
+
result.atomicVerbose = true;
|
|
182
|
+
} else if (arg === '--preserve-selectors') {
|
|
183
|
+
result.preserveSelectors = true;
|
|
123
184
|
} else if (!result.inputFile) {
|
|
124
185
|
result.inputFile = arg;
|
|
125
186
|
} else if (!result.outputFile) {
|
|
126
187
|
result.outputFile = arg;
|
|
127
|
-
}else if (arg === '--validate-themes') {
|
|
128
|
-
result.validateThemes = true;
|
|
129
188
|
}
|
|
130
189
|
}
|
|
131
190
|
return result;
|
|
@@ -150,14 +209,18 @@ const applyCliOptions = (cliOptions) => {
|
|
|
150
209
|
if (cliOptions.atomic) {
|
|
151
210
|
config.atomic.enabled = true;
|
|
152
211
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
if (cliOptions.atomicMode && ['atomic', 'standard', 'hybrid'].includes(cliOptions.atomicMode)) {
|
|
213
|
+
config.atomic.mode = cliOptions.atomicMode;
|
|
214
|
+
}
|
|
215
|
+
if (cliOptions.atomicNaming && ['hash', 'readable'].includes(cliOptions.atomicNaming)) {
|
|
216
|
+
config.atomic.naming = cliOptions.atomicNaming;
|
|
217
|
+
}
|
|
218
|
+
if (cliOptions.atomicVerbose) {
|
|
219
|
+
config.atomic.verbose = true;
|
|
220
|
+
}
|
|
221
|
+
if (cliOptions.preserveSelectors) {
|
|
222
|
+
config.atomic.preserveSelectors = true;
|
|
223
|
+
}
|
|
161
224
|
};
|
|
162
225
|
|
|
163
226
|
function watch(inputFile, outputFile) {
|
|
@@ -190,7 +253,7 @@ const transpilerModule = {
|
|
|
190
253
|
chain
|
|
191
254
|
};
|
|
192
255
|
|
|
193
|
-
// Native module-based JCSS file processor
|
|
256
|
+
// Native module-based JCSS file processor
|
|
194
257
|
const processJCSSFile = (filePath) => {
|
|
195
258
|
const abs = path.resolve(filePath);
|
|
196
259
|
|
|
@@ -222,6 +285,9 @@ const processJCSSFile = (filePath) => {
|
|
|
222
285
|
'compile',
|
|
223
286
|
'chain',
|
|
224
287
|
'get',
|
|
288
|
+
'createTokens',
|
|
289
|
+
'responsive',
|
|
290
|
+
'tokens',
|
|
225
291
|
content
|
|
226
292
|
);
|
|
227
293
|
compiledCache.set(abs, compiledFn);
|
|
@@ -238,7 +304,10 @@ const processJCSSFile = (filePath) => {
|
|
|
238
304
|
run,
|
|
239
305
|
originalCompile,
|
|
240
306
|
chain,
|
|
241
|
-
get
|
|
307
|
+
get,
|
|
308
|
+
createTokens,
|
|
309
|
+
responsive,
|
|
310
|
+
tokens
|
|
242
311
|
);
|
|
243
312
|
} catch (err) {
|
|
244
313
|
console.error(`Error processing ${abs}:`, err.message);
|
|
@@ -263,6 +332,9 @@ const processScript = (scriptBlock, filename) => {
|
|
|
263
332
|
'compile',
|
|
264
333
|
'chain',
|
|
265
334
|
'get',
|
|
335
|
+
'createTokens',
|
|
336
|
+
'responsive',
|
|
337
|
+
'tokens',
|
|
266
338
|
'__filename',
|
|
267
339
|
'__dirname',
|
|
268
340
|
scriptBlock
|
|
@@ -271,7 +343,7 @@ const processScript = (scriptBlock, filename) => {
|
|
|
271
343
|
}
|
|
272
344
|
|
|
273
345
|
try {
|
|
274
|
-
compiledFn($, run, originalCompile, chain, get, filename, dirname);
|
|
346
|
+
compiledFn($, run, originalCompile, chain, get, createTokens, responsive, tokens, filename, dirname);
|
|
275
347
|
} catch (err) {
|
|
276
348
|
console.error(`Error processing script in ${filename}:`, err.message);
|
|
277
349
|
throw err;
|
|
@@ -289,14 +361,16 @@ const processJavascriptBlocks = (content, inputpath) => {
|
|
|
289
361
|
outputCSS += blocks[i];
|
|
290
362
|
} else {
|
|
291
363
|
const scriptBlock = blocks[i];
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
364
|
+
if (scriptBlock && scriptBlock.trim()) {
|
|
365
|
+
try {
|
|
366
|
+
const blockCSS = processScript(scriptBlock, inputpath);
|
|
367
|
+
if (typeof blockCSS !== 'object' && typeof blockCSS !== 'undefined') {
|
|
368
|
+
outputCSS += blockCSS;
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error(`Error processing script block:`, err.stack);
|
|
372
|
+
throw err;
|
|
296
373
|
}
|
|
297
|
-
} catch (err) {
|
|
298
|
-
console.error(`Error processing script block:`, err.stack);
|
|
299
|
-
throw err;
|
|
300
374
|
}
|
|
301
375
|
}
|
|
302
376
|
}
|
|
@@ -351,6 +425,26 @@ const processAndMinifyCss = async (css, inputFile, outputFile) => {
|
|
|
351
425
|
return { css: finalCss, map: finalSourceMap };
|
|
352
426
|
};
|
|
353
427
|
|
|
428
|
+
const writeFrameworkOutput = (outputDir, result) => {
|
|
429
|
+
const frameworkOutputs = [];
|
|
430
|
+
|
|
431
|
+
if (config.atomic.frameworkOutput.react && result.frameworkOutput) {
|
|
432
|
+
const reactPath = path.join(outputDir, 'atomic.react.js');
|
|
433
|
+
fs.writeFileSync(reactPath, result.frameworkOutput, 'utf8');
|
|
434
|
+
frameworkOutputs.push(`React: ${reactPath}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (config.atomic.frameworkOutput.vue && result.frameworkOutput) {
|
|
438
|
+
const vuePath = path.join(outputDir, 'atomic.vue.js');
|
|
439
|
+
fs.writeFileSync(vuePath, result.frameworkOutput, 'utf8');
|
|
440
|
+
frameworkOutputs.push(`Vue: ${vuePath}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (frameworkOutputs.length > 0) {
|
|
444
|
+
console.log(`Framework outputs: ${frameworkOutputs.join(', ')}`);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
354
448
|
const processor = async (inputFile, outputFile) => {
|
|
355
449
|
try {
|
|
356
450
|
const input = path.resolve(inputFile);
|
|
@@ -365,6 +459,7 @@ const processor = async (inputFile, outputFile) => {
|
|
|
365
459
|
}
|
|
366
460
|
const stylePath = path.join(outputDir, 'global.css');
|
|
367
461
|
const result = await processAndMinifyCss(processedCSS, input, stylePath);
|
|
462
|
+
|
|
368
463
|
if (result.css) {
|
|
369
464
|
fs.writeFileSync(stylePath, result.css, 'utf8');
|
|
370
465
|
if (result.map) {
|
|
@@ -373,38 +468,81 @@ const processor = async (inputFile, outputFile) => {
|
|
|
373
468
|
}
|
|
374
469
|
|
|
375
470
|
// ========== ATOMIC CLASS MAP GENERATION ==========
|
|
376
|
-
|
|
377
|
-
|
|
471
|
+
// ALWAYS generate atomic files when atomic optimizer is enabled
|
|
472
|
+
if (atomicOptimizer && config.atomic.enabled) {
|
|
473
|
+
|
|
474
|
+
// Get atomic classes from the optimizer
|
|
475
|
+
const atomicClasses = atomicOptimizer.getAllAtomicClasses();
|
|
476
|
+
const atomicClassMap = {};
|
|
477
|
+
|
|
478
|
+
// Build class map from component map
|
|
479
|
+
if (atomicOptimizer.componentMap) {
|
|
480
|
+
for (const [selector, data] of atomicOptimizer.componentMap) {
|
|
481
|
+
if (data.atomicClasses && data.atomicClasses.length > 0) {
|
|
482
|
+
atomicClassMap[selector] = data.atomicClasses.join(' ');
|
|
483
|
+
}
|
|
484
|
+
if (data.hoverAtomicClasses && data.hoverAtomicClasses.length > 0) {
|
|
485
|
+
atomicClassMap[`${selector}:hover`] = data.hoverAtomicClasses.join(' ');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Write map.json
|
|
378
491
|
const mapJsonPath = path.join(outputDir, 'global.map.json');
|
|
379
492
|
const mapData = {
|
|
380
|
-
version: '
|
|
493
|
+
version: '2.0.0',
|
|
381
494
|
generated: new Date().toISOString(),
|
|
382
495
|
input: inputFile,
|
|
383
496
|
output: stylePath,
|
|
384
497
|
atomicEnabled: true,
|
|
498
|
+
mode: config.atomic.mode,
|
|
499
|
+
naming: config.atomic.naming,
|
|
500
|
+
outputStrategy: config.atomic.outputStrategy,
|
|
385
501
|
threshold: config.atomic.threshold,
|
|
386
|
-
classMap:
|
|
387
|
-
|
|
502
|
+
classMap: atomicClassMap,
|
|
503
|
+
atomicClasses: atomicClasses.map(a => ({ className: a.className, prop: a.prop, value: a.value })),
|
|
504
|
+
stats: chain.atomicStats || atomicOptimizer.getStats()
|
|
388
505
|
};
|
|
389
506
|
fs.writeFileSync(mapJsonPath, JSON.stringify(mapData, null, 2), 'utf8');
|
|
390
|
-
console.log(`ā
Class map: ${mapJsonPath}`);
|
|
391
507
|
|
|
392
|
-
|
|
508
|
+
|
|
509
|
+
// Write JS module
|
|
393
510
|
const jsPath = path.join(outputDir, 'global.classes.js');
|
|
394
|
-
|
|
511
|
+
let jsContent = `/**
|
|
395
512
|
* ChainCSS Atomic Class Map
|
|
396
513
|
* Generated: ${new Date().toISOString()}
|
|
514
|
+
* Mode: ${config.atomic.mode}
|
|
515
|
+
* Output Strategy: ${config.atomic.outputStrategy}
|
|
516
|
+
* Naming: ${config.atomic.naming}
|
|
397
517
|
* Threshold: ${config.atomic.threshold}
|
|
398
518
|
*/
|
|
399
519
|
|
|
400
|
-
export const classMap = ${JSON.stringify(
|
|
520
|
+
export const classMap = ${JSON.stringify(atomicClassMap, null, 2)};
|
|
521
|
+
|
|
522
|
+
export const atomicClasses = ${JSON.stringify(atomicClasses.map(a => a.className), null, 2)};
|
|
401
523
|
|
|
402
524
|
export const getClass = (selector) => classMap[selector] || '';
|
|
403
525
|
|
|
526
|
+
export const getAtomicClass = (prop, value) => {
|
|
527
|
+
const atomic = atomicClasses.find(a => a.prop === prop && a.value === value);
|
|
528
|
+
return atomic ? atomic.className : null;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
export const getAllClasses = () => Object.values(classMap).join(' ');
|
|
532
|
+
|
|
533
|
+
export const applyClasses = (element, selector) => {
|
|
534
|
+
if (!element) return;
|
|
535
|
+
const classes = getClass(selector);
|
|
536
|
+
if (classes) {
|
|
537
|
+
element.className = classes;
|
|
538
|
+
}
|
|
539
|
+
return classes;
|
|
540
|
+
};
|
|
541
|
+
|
|
404
542
|
export default classMap;
|
|
405
543
|
`;
|
|
544
|
+
|
|
406
545
|
fs.writeFileSync(jsPath, jsContent, 'utf8');
|
|
407
|
-
console.log(`ā
JS module: ${jsPath}`);
|
|
408
546
|
|
|
409
547
|
// Write TypeScript definitions
|
|
410
548
|
const dtsPath = path.join(outputDir, 'global.classes.d.ts');
|
|
@@ -413,13 +551,24 @@ export default classMap;
|
|
|
413
551
|
* Generated: ${new Date().toISOString()}
|
|
414
552
|
*/
|
|
415
553
|
|
|
554
|
+
export interface AtomicClass {
|
|
555
|
+
className: string;
|
|
556
|
+
prop: string;
|
|
557
|
+
value: string;
|
|
558
|
+
}
|
|
559
|
+
|
|
416
560
|
export const classMap: Record<string, string>;
|
|
561
|
+
export const atomicClasses: string[];
|
|
417
562
|
export const getClass: (selector: string) => string;
|
|
563
|
+
export const getAtomicClass: (prop: string, value: string) => string | null;
|
|
564
|
+
export const getAllClasses: () => string;
|
|
565
|
+
export const applyClasses: (element: HTMLElement | null, selector: string) => string | undefined;
|
|
566
|
+
|
|
418
567
|
declare const _default: Record<string, string>;
|
|
419
568
|
export default _default;
|
|
420
569
|
`;
|
|
421
570
|
fs.writeFileSync(dtsPath, dtsContent, 'utf8');
|
|
422
|
-
|
|
571
|
+
|
|
423
572
|
|
|
424
573
|
// Update manifest
|
|
425
574
|
const manifestPath = path.join(outputDir, 'chaincss-manifest.json');
|
|
@@ -431,18 +580,22 @@ export default _default;
|
|
|
431
580
|
}
|
|
432
581
|
|
|
433
582
|
manifest[path.basename(stylePath)] = {
|
|
583
|
+
version: '2.0.0',
|
|
434
584
|
css: path.basename(stylePath),
|
|
435
585
|
map: path.basename(mapJsonPath),
|
|
436
586
|
js: path.basename(jsPath),
|
|
437
587
|
dts: path.basename(dtsPath),
|
|
438
588
|
generated: new Date().toISOString(),
|
|
439
589
|
input: inputFile,
|
|
590
|
+
mode: config.atomic.mode,
|
|
591
|
+
outputStrategy: config.atomic.outputStrategy,
|
|
592
|
+
naming: config.atomic.naming,
|
|
440
593
|
threshold: config.atomic.threshold,
|
|
441
|
-
|
|
594
|
+
atomicClassesCount: atomicClasses.length,
|
|
595
|
+
stats: chain.atomicStats || atomicOptimizer.getStats()
|
|
442
596
|
};
|
|
443
597
|
|
|
444
598
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
445
|
-
console.log(`ā
Manifest: ${manifestPath}`);
|
|
446
599
|
}
|
|
447
600
|
}
|
|
448
601
|
} catch (err) {
|
package/node/prefixer.js
CHANGED
|
@@ -39,7 +39,7 @@ class ChainCSSPrefixer {
|
|
|
39
39
|
}
|
|
40
40
|
determineMode() {
|
|
41
41
|
if (this.config.mode === 'full' && !this.hasAutoprefixer) {
|
|
42
|
-
console.warn('
|
|
42
|
+
console.warn('Full mode requested but autoprefixer not installed. Falling back to lightweight mode.');
|
|
43
43
|
console.warn(' To use full mode: npm install autoprefixer postcss caniuse-db browserslist\n');
|
|
44
44
|
return 'lightweight';
|
|
45
45
|
}
|
package/node/strVal.js
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
const strVal = {
|
|
2
|
-
userConf: `//
|
|
2
|
+
userConf: `// ChainCSS Configuration
|
|
3
|
+
// Generated: ${new Date().toISOString()}
|
|
4
|
+
|
|
3
5
|
module.exports = {
|
|
4
6
|
atomic: {
|
|
5
|
-
enabled: true,
|
|
6
|
-
threshold: 3,
|
|
7
|
-
naming: 'hash',
|
|
8
|
-
cache: true,
|
|
7
|
+
enabled: true, // Enable atomic CSS optimization
|
|
8
|
+
threshold: 3, // Minimum usage count for atomic conversion
|
|
9
|
+
naming: 'hash', // 'hash' (c_3b82f6) or 'readable' (bg-blue-500)
|
|
10
|
+
cache: true, // Cache atomic classes between builds
|
|
9
11
|
cachePath: './.chaincss-cache',
|
|
10
|
-
minify: true
|
|
12
|
+
minify: true, // Minify CSS output
|
|
13
|
+
mode: 'hybrid', // 'atomic' | 'standard' | 'hybrid'
|
|
14
|
+
alwaysAtomic: [], // Force these properties to be atomic
|
|
15
|
+
neverAtomic: [ // Never make these properties atomic
|
|
16
|
+
'content', 'animation', 'transition', 'keyframes',
|
|
17
|
+
'counterIncrement', 'counterReset'
|
|
18
|
+
],
|
|
19
|
+
outputStrategy: 'component-first',
|
|
20
|
+
frameworkOutput: {
|
|
21
|
+
react: false, // Generate React hooks
|
|
22
|
+
vue: false, // Generate Vue composables
|
|
23
|
+
vanilla: true // Generate vanilla JS class map
|
|
24
|
+
},
|
|
25
|
+
preserveSelectors: false, // Keep original selector names in comments
|
|
26
|
+
verbose: true // Show detailed atomic optimization stats
|
|
11
27
|
},
|
|
12
28
|
prefixer: {
|
|
13
|
-
mode: 'auto',
|
|
14
|
-
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
15
29
|
enabled: true,
|
|
30
|
+
mode: 'auto', // 'auto' | 'always' | 'never'
|
|
31
|
+
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
16
32
|
sourceMap: true,
|
|
17
33
|
sourceMapInline: false
|
|
18
34
|
}
|
|
@@ -25,18 +41,51 @@ Usage:
|
|
|
25
41
|
chaincss <inputFile> <outputFile> [options]
|
|
26
42
|
|
|
27
43
|
Options:
|
|
28
|
-
--watch Watch for changes
|
|
44
|
+
--watch Watch for changes and auto-recompile
|
|
29
45
|
--no-prefix Disable automatic prefixing
|
|
30
46
|
--prefixer-mode <mode> Set prefixer mode (auto|lightweight|full)
|
|
31
47
|
--browsers <list> Browser support list (comma-separated)
|
|
32
48
|
--no-source-map Disable source maps
|
|
33
49
|
--source-map-inline Use inline source maps
|
|
50
|
+
|
|
51
|
+
# Atomic CSS Optimization
|
|
52
|
+
--atomic Enable atomic CSS optimization
|
|
53
|
+
--atomic-mode <mode> Atomic mode: atomic, standard, hybrid (default: hybrid)
|
|
54
|
+
--atomic-naming <scheme> Naming scheme: hash, readable (default: hash)
|
|
55
|
+
--atomic-verbose Show detailed atomic optimization stats
|
|
56
|
+
--preserve-selectors Keep original selector names in comments
|
|
57
|
+
--no-atomic-cache Disable atomic CSS cache
|
|
58
|
+
|
|
59
|
+
# Output Control
|
|
60
|
+
--no-minify Disable CSS minification
|
|
61
|
+
|
|
62
|
+
# Help
|
|
63
|
+
--help, -h Show this help message
|
|
64
|
+
--version, -v Show version number
|
|
34
65
|
|
|
35
66
|
Examples:
|
|
36
|
-
|
|
37
|
-
chaincss style.jcss
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
# Basic compilation
|
|
68
|
+
chaincss style.jcss dist/
|
|
69
|
+
|
|
70
|
+
# Watch mode for development
|
|
71
|
+
chaincss style.jcss dist/ --watch
|
|
72
|
+
|
|
73
|
+
# Atomic CSS optimization
|
|
74
|
+
chaincss style.jcss dist/ --atomic
|
|
75
|
+
|
|
76
|
+
# With custom naming scheme
|
|
77
|
+
chaincss style.jcss dist/ --atomic --atomic-naming readable
|
|
78
|
+
|
|
79
|
+
# Verbose output for debugging
|
|
80
|
+
chaincss style.jcss dist/ --atomic --atomic-verbose
|
|
81
|
+
|
|
82
|
+
# Custom browser support
|
|
83
|
+
chaincss style.jcss dist/ --browsers "> 1%, last 2 versions, not dead"
|
|
84
|
+
|
|
85
|
+
Notes:
|
|
86
|
+
- Atomic CSS optimization reduces CSS size by reusing common style patterns
|
|
87
|
+
- Use --watch during development for instant updates
|
|
88
|
+
- Use --atomic for production builds to optimize CSS bundle size
|
|
40
89
|
`
|
|
41
90
|
}
|
|
42
91
|
|
package/node/theme-validator.js
CHANGED
|
@@ -8,7 +8,7 @@ export function validateThemeFiles(configPath) {
|
|
|
8
8
|
|
|
9
9
|
const { contract, themes } = config;
|
|
10
10
|
|
|
11
|
-
console.log('\
|
|
11
|
+
console.log('\nValidating Theme Contract...\n');
|
|
12
12
|
|
|
13
13
|
const errors = [];
|
|
14
14
|
|
|
@@ -16,9 +16,9 @@ export function validateThemeFiles(configPath) {
|
|
|
16
16
|
const themeName = theme.name || `theme-${index}`;
|
|
17
17
|
try {
|
|
18
18
|
validateTheme(contract, theme.values);
|
|
19
|
-
console.log(
|
|
19
|
+
console.log(`${themeName}: Valid`);
|
|
20
20
|
} catch (err) {
|
|
21
|
-
errors.push(
|
|
21
|
+
errors.push(`${themeName}: ${err.message}`);
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -28,5 +28,5 @@ export function validateThemeFiles(configPath) {
|
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
console.log('\
|
|
31
|
+
console.log('\nAll themes valid!\n');
|
|
32
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaincss",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.3",
|
|
4
4
|
"description": "Chainable CSS-in-JS with build-time compilation, atomic CSS, and zero-runtime options",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -61,11 +61,20 @@
|
|
|
61
61
|
},
|
|
62
62
|
"default": "./browser/react-hooks.js"
|
|
63
63
|
},
|
|
64
|
-
"./
|
|
64
|
+
"./vue": {
|
|
65
|
+
"types": "./types.d.ts",
|
|
66
|
+
"browser": {
|
|
67
|
+
"import": "./browser/vue-composables.js",
|
|
68
|
+
"require": "./browser/vue-composables.js"
|
|
69
|
+
},
|
|
70
|
+
"default": "./browser/vue-composables.js"
|
|
71
|
+
},
|
|
72
|
+
"./browser": {
|
|
65
73
|
"browser": {
|
|
66
74
|
"import": "./browser/*",
|
|
67
75
|
"require": "./browser/*"
|
|
68
|
-
}
|
|
76
|
+
},
|
|
77
|
+
"default": "./browser/index.js"
|
|
69
78
|
},
|
|
70
79
|
"./node/*": {
|
|
71
80
|
"node": {
|
|
@@ -120,6 +129,7 @@
|
|
|
120
129
|
"clean-css": "^5.3.3"
|
|
121
130
|
},
|
|
122
131
|
"peerDependencies": {
|
|
132
|
+
"vue": "^3.0.0",
|
|
123
133
|
"react": ">=16.8.0",
|
|
124
134
|
"autoprefixer": "^10.0.0",
|
|
125
135
|
"postcss": "^8.5.6",
|
package/types.d.ts
CHANGED
|
@@ -250,9 +250,10 @@ export function recipe<TVariants extends Record<string, Record<string, any>>>(
|
|
|
250
250
|
options: RecipeOptions<TVariants>
|
|
251
251
|
): Recipe<TVariants>;
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
// ============================================================================
|
|
254
254
|
// THEME CONTRACT
|
|
255
|
-
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
256
257
|
export interface ThemeContract {
|
|
257
258
|
[key: string]: string | Record<string, any>;
|
|
258
259
|
}
|
|
@@ -274,4 +275,51 @@ export function createTokens(
|
|
|
274
275
|
export function validateTheme(
|
|
275
276
|
contract: ThemeContract,
|
|
276
277
|
theme: any
|
|
277
|
-
): boolean;
|
|
278
|
+
): boolean;
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Vue JS composables
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
declare module 'chaincss/vue' {
|
|
285
|
+
import { Ref, ComputedRef, Component } from 'vue';
|
|
286
|
+
|
|
287
|
+
export interface UseAtomicClassesOptions {
|
|
288
|
+
atomic?: boolean;
|
|
289
|
+
global?: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface UseAtomicClassesReturn {
|
|
293
|
+
classes: Ref<Record<string, any>>;
|
|
294
|
+
cx: (name: string) => any;
|
|
295
|
+
cn: (...names: string[]) => string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function useAtomicClasses(
|
|
299
|
+
styles: Record<string, any> | Ref<Record<string, any>> | ComputedRef<Record<string, any>>,
|
|
300
|
+
options?: UseAtomicClassesOptions
|
|
301
|
+
): UseAtomicClassesReturn;
|
|
302
|
+
|
|
303
|
+
export const ChainCSSGlobal: Component<{
|
|
304
|
+
styles: Record<string, any>;
|
|
305
|
+
atomic?: boolean;
|
|
306
|
+
}>;
|
|
307
|
+
|
|
308
|
+
export function createStyledComponent(
|
|
309
|
+
styles: Record<string, any> | ((props: any) => Record<string, any>),
|
|
310
|
+
options?: {
|
|
311
|
+
name?: string;
|
|
312
|
+
atomic?: boolean;
|
|
313
|
+
props?: Record<string, any>;
|
|
314
|
+
}
|
|
315
|
+
): Component;
|
|
316
|
+
|
|
317
|
+
export function createTheme<T extends Record<string, any>>(
|
|
318
|
+
themes: T
|
|
319
|
+
): {
|
|
320
|
+
currentTheme: Ref<keyof T>;
|
|
321
|
+
themeStyles: ComputedRef<T[keyof T]>;
|
|
322
|
+
setTheme: (themeName: keyof T) => void;
|
|
323
|
+
toggleTheme: () => void;
|
|
324
|
+
};
|
|
325
|
+
}
|