@melcanz85/chaincss 1.9.2 → 1.9.4
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 +44 -65
- package/atomic-optimizer.js +1 -7
- package/chaincss.js +210 -188
- package/package.json +1 -1
- package/prefixer.js +2 -3
package/README.md
CHANGED
|
@@ -12,17 +12,6 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
12
12
|
|
|
13
13
|
**Runtime hooks** → Dynamic, prop-based styles when you need them
|
|
14
14
|
|
|
15
|
-
"The performance of vanilla CSS with the power of JavaScript — now with **CHOICE.**"
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
// Same beautiful API, two powerful modes
|
|
19
|
-
const button = $()
|
|
20
|
-
.color('white')
|
|
21
|
-
.backgroundColor('#667eea')
|
|
22
|
-
.padding('1rem')
|
|
23
|
-
.borderRadius('4px')
|
|
24
|
-
.block('.btn');
|
|
25
|
-
````
|
|
26
15
|
## Installation
|
|
27
16
|
|
|
28
17
|
```bash
|
|
@@ -30,6 +19,19 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
30
19
|
npm install @melcanz85/chaincss
|
|
31
20
|
```
|
|
32
21
|
|
|
22
|
+
### File Structure
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
your-project/
|
|
26
|
+
├── node_module
|
|
27
|
+
├── src/
|
|
28
|
+
│ ├── main.jcss # Entry point - imports & compiles
|
|
29
|
+
│ └── *.jcss # Your style definitions
|
|
30
|
+
├── style/ # Generated CSS will be stored here
|
|
31
|
+
├── index.html # Your web page
|
|
32
|
+
└── package.json
|
|
33
|
+
```
|
|
34
|
+
|
|
33
35
|
## Two Powerful Modes - One API
|
|
34
36
|
|
|
35
37
|
### Mode 1: Build-time (Zero Runtime)
|
|
@@ -47,11 +49,31 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
47
49
|
|
|
48
50
|
module.exports = { button };
|
|
49
51
|
````
|
|
52
|
+
**in your main.jcss**
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
<@
|
|
56
|
+
const button = get('./button.js');
|
|
57
|
+
|
|
58
|
+
compile(button);
|
|
59
|
+
@>
|
|
60
|
+
```
|
|
61
|
+
..then run this in terminal/command prompt
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx chaincss ./src/main.jcss ./style --watch
|
|
65
|
+
# ./style/global.css generated!
|
|
66
|
+
````
|
|
67
|
+
OR with vanilla nodejs project
|
|
50
68
|
|
|
51
69
|
```bash
|
|
52
70
|
npx chaincss ./src/main.jcss ./style --watch & node server.js
|
|
53
71
|
# ./style/global.css generated!
|
|
54
72
|
````
|
|
73
|
+
* Note: running `npx chaincss ./src/main.jcss ./style --watch ` for the first time will
|
|
74
|
+
generate chaincss.config.js with default values. You can edit this to
|
|
75
|
+
customize your build!.
|
|
76
|
+
|
|
55
77
|
### Mode 2: Runtime (React Hooks)
|
|
56
78
|
|
|
57
79
|
**Perfect for:** Dynamic styles that respond to props, state, or themes.
|
|
@@ -148,19 +170,7 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
148
170
|
.boxShadow('0 0 0 3px rgba(102,126,234,0.5)')
|
|
149
171
|
.block('.btn');
|
|
150
172
|
````
|
|
151
|
-
### File Structure
|
|
152
173
|
|
|
153
|
-
```text
|
|
154
|
-
your-project/
|
|
155
|
-
├── node_module
|
|
156
|
-
├── src/
|
|
157
|
-
│ ├── main.jcss # Entry point - imports & compiles
|
|
158
|
-
│ └── chain.jcss # Your style definitions
|
|
159
|
-
│
|
|
160
|
-
├── style/
|
|
161
|
-
│ └── global.css # Generated CSS
|
|
162
|
-
└── package.json
|
|
163
|
-
```
|
|
164
174
|
### Basic Example
|
|
165
175
|
|
|
166
176
|
**chaincss/button.jcss**
|
|
@@ -228,11 +238,6 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
228
238
|
**Before (standard CSS):** 4,823 chars
|
|
229
239
|
**After (atomic CSS):** 499 chars → **90% smaller!**
|
|
230
240
|
|
|
231
|
-
### Built-in Security
|
|
232
|
-
|
|
233
|
-
ChainCSS uses **secure VM sandboxing** to safely execute your .jcss files. No eval, no global leaks, no security risks.
|
|
234
|
-
|
|
235
|
-
|
|
236
241
|
## Quick Start Guides
|
|
237
242
|
|
|
238
243
|
### With Node.js (Vanilla)
|
|
@@ -307,64 +312,38 @@ compile({ hello });" > chaincss/main.jcss
|
|
|
307
312
|
|
|
308
313
|
**ChainCSS is the ONLY library that gives you BOTH worlds!**
|
|
309
314
|
|
|
310
|
-
|
|
311
|
-
## Configuration
|
|
312
|
-
|
|
313
|
-
Create chaincss.config.js in your project root:
|
|
314
|
-
|
|
315
|
-
```javascript
|
|
316
|
-
|
|
317
|
-
module.exports = {
|
|
318
|
-
// Atomic CSS optimization
|
|
319
|
-
atomic: {
|
|
320
|
-
enabled: true,
|
|
321
|
-
threshold: 3, // Min usage for atomic conversion
|
|
322
|
-
naming: 'hash' // 'hash' | 'readable' | 'short'
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
// Prefixer options
|
|
326
|
-
prefixer: {
|
|
327
|
-
mode: 'auto', // 'auto' or 'full'
|
|
328
|
-
browsers: ['> 0.5%', 'last 2 versions']
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
// Source maps
|
|
332
|
-
sourceMaps: true
|
|
333
|
-
};
|
|
334
|
-
```
|
|
335
|
-
|
|
336
315
|
## API Reference
|
|
337
316
|
|
|
338
317
|
### Core Functions
|
|
339
318
|
|
|
340
319
|
Function Description
|
|
341
320
|
|
|
342
|
-
|
|
321
|
+
$() Create a new chain builder
|
|
343
322
|
|
|
344
|
-
|
|
323
|
+
.block(selector) End chain and assign selector(s)
|
|
345
324
|
|
|
346
|
-
|
|
325
|
+
compile(styles) Compile style objects to CSS
|
|
347
326
|
|
|
348
|
-
|
|
327
|
+
run(...styles) Process inline styles
|
|
349
328
|
|
|
350
|
-
|
|
329
|
+
get(filename) Import .jcss files
|
|
351
330
|
|
|
352
|
-
|
|
331
|
+
processor(input, output) Build-time processor
|
|
353
332
|
|
|
354
333
|
|
|
355
334
|
### React Hooks
|
|
356
335
|
|
|
357
336
|
Hook Description
|
|
358
337
|
|
|
359
|
-
|
|
338
|
+
useChainStyles(styles, options) Basic styles hook
|
|
360
339
|
|
|
361
|
-
|
|
340
|
+
useDynamicChainStyles(factory, deps) Styles that depend on props/state
|
|
362
341
|
|
|
363
|
-
|
|
342
|
+
useThemeChainStyles(styles, theme) Theme-aware styles
|
|
364
343
|
|
|
365
|
-
|
|
344
|
+
ChainCSSGlobal Global styles component
|
|
366
345
|
|
|
367
|
-
|
|
346
|
+
cx(...classes) Conditional class merging
|
|
368
347
|
|
|
369
348
|
|
|
370
349
|
## Editor Support
|
package/atomic-optimizer.js
CHANGED
|
@@ -40,7 +40,7 @@ class AtomicOptimizer {
|
|
|
40
40
|
this.stats = cache.stats || this.stats;
|
|
41
41
|
|
|
42
42
|
const cacheTime = new Date(cache.timestamp).toLocaleString();
|
|
43
|
-
console.log(
|
|
43
|
+
console.log(`--Loaded ${this.atomicClasses.size} atomic classes from cache (${cacheTime})\n`);
|
|
44
44
|
|
|
45
45
|
// Verify config matches
|
|
46
46
|
if (cache.config) {
|
|
@@ -237,8 +237,6 @@ class AtomicOptimizer {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
optimize(styles) {
|
|
240
|
-
console.log('ChainCSS Atomic Optimizer running...');
|
|
241
|
-
|
|
242
240
|
// Track usage first
|
|
243
241
|
this.trackStyles(styles);
|
|
244
242
|
|
|
@@ -257,10 +255,6 @@ class AtomicOptimizer {
|
|
|
257
255
|
// Calculate savings
|
|
258
256
|
const savings = ((this.stats.totalStyles - this.atomicClasses.size) / this.stats.totalStyles * 100).toFixed(1);
|
|
259
257
|
|
|
260
|
-
//console.log(`Optimization complete:`);
|
|
261
|
-
//console.log(`Atomic classes created: ${this.atomicClasses.size}`);
|
|
262
|
-
//console.log(`CSS size reduction: ~${savings}%`);
|
|
263
|
-
|
|
264
258
|
// Save cache if enabled
|
|
265
259
|
if (this.options.cache) {
|
|
266
260
|
this.saveCache();
|
package/chaincss.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
const {NodeVM} = require('vm2');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const fs = require('fs');
|
|
@@ -7,72 +6,179 @@ const chokidar = require('chokidar');
|
|
|
7
6
|
const CleanCSS = require('clean-css');
|
|
8
7
|
const ChainCSSPrefixer = require('./prefixer.js');
|
|
9
8
|
const fileCache = new Map();
|
|
10
|
-
|
|
11
|
-
let prefixerConfig = {
|
|
12
|
-
enabled: true,
|
|
13
|
-
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
14
|
-
mode: 'auto' // 'auto', 'lightweight', or 'full'
|
|
15
|
-
};
|
|
16
|
-
const prefixer = new ChainCSSPrefixer(prefixerConfig);
|
|
17
|
-
|
|
18
|
-
// IMPORT THE CORE FROM TRANSPILER - use aliasing
|
|
19
|
-
const { $, run, compile: originalCompile, chain } = require('./transpiler');
|
|
20
|
-
|
|
21
|
-
// Import atomic optimizer
|
|
9
|
+
const strVal = require('./strVal.js');
|
|
22
10
|
const { AtomicOptimizer } = require('./atomic-optimizer');
|
|
23
11
|
const { CacheManager } = require('./cache-manager');
|
|
12
|
+
const { $, run, compile: originalCompile, chain } = require('./transpiler');
|
|
24
13
|
|
|
25
|
-
// Atomic optimizer instance
|
|
14
|
+
// Atomic optimizer instance (will be initialized after config)
|
|
26
15
|
let atomicOptimizer = null;
|
|
27
16
|
|
|
28
|
-
//
|
|
17
|
+
// Default configuration
|
|
29
18
|
let config = {
|
|
30
19
|
atomic: {
|
|
31
|
-
enabled: false,
|
|
20
|
+
enabled: false,
|
|
32
21
|
threshold: 3,
|
|
33
22
|
naming: 'hash',
|
|
34
23
|
cache: true,
|
|
24
|
+
cachePath: './.chaincss-cache',
|
|
35
25
|
minify: true
|
|
26
|
+
},
|
|
27
|
+
prefixer: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
mode: 'auto',
|
|
30
|
+
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
31
|
+
sourceMap: true,
|
|
32
|
+
sourceMapInline: false
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Prefixer instance
|
|
37
|
+
let prefixer = new ChainCSSPrefixer(config.prefixer);
|
|
38
|
+
|
|
39
|
+
// From default to user configuration
|
|
40
|
+
function deft_to_userConf(target, source) {
|
|
41
|
+
const result = { ...target };
|
|
42
|
+
|
|
43
|
+
for (const key in source) {
|
|
44
|
+
if (source[key] instanceof Object && key in target) {
|
|
45
|
+
result[key] = deft_to_userConf(target[key], source[key]);
|
|
46
|
+
} else {
|
|
47
|
+
result[key] = source[key];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ensure config file exists
|
|
55
|
+
const ensureConfigExists = () => {
|
|
56
|
+
const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
|
|
57
|
+
const configExists = fs.existsSync(configPath);
|
|
58
|
+
|
|
59
|
+
if (!configExists && !process.env.CHAINCSS_SKIP_CONFIG) {
|
|
60
|
+
const defaultConfig = strVal.userConf;
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(configPath, defaultConfig);
|
|
63
|
+
console.log('-- Successfully created config file: ./chaincss.config.cjs\n');
|
|
36
64
|
}
|
|
37
65
|
};
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
// Load user config
|
|
68
|
+
const loadUserConfig = () => {
|
|
69
|
+
const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
|
|
42
70
|
|
|
43
71
|
if (fs.existsSync(configPath)) {
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
try {
|
|
73
|
+
const userConfig = require(configPath);
|
|
74
|
+
config = deft_to_userConf(config, userConfig);
|
|
75
|
+
|
|
76
|
+
// CRITICAL: Ensure browsers is ALWAYS an array
|
|
77
|
+
if (config.prefixer) {
|
|
78
|
+
// If browsers is a string, convert to array
|
|
79
|
+
if (typeof config.prefixer.browsers === 'string') {
|
|
80
|
+
config.prefixer.browsers = config.prefixer.browsers.split(',').map(b => b.trim());
|
|
81
|
+
}
|
|
82
|
+
// If browsers is not an array at this point, set default
|
|
83
|
+
if (!Array.isArray(config.prefixer.browsers)) {
|
|
84
|
+
config.prefixer.browsers = ['> 0.5%', 'last 2 versions', 'not dead'];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.log('-- Error loading config:', err.message, '\n');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Initialize atomic optimizer based on config
|
|
94
|
+
const initAtomicOptimizer = () => {
|
|
95
|
+
if (config.atomic.enabled) {
|
|
96
|
+
atomicOptimizer = new AtomicOptimizer(config.atomic);
|
|
97
|
+
console.log('-- Atomic optimizer enabled\n');
|
|
46
98
|
} else {
|
|
47
|
-
|
|
48
|
-
|
|
99
|
+
console.log('-- Atomic optimizer disabled\n');
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Initialization of prefixer object
|
|
104
|
+
const initPrefixer = () => {
|
|
105
|
+
prefixer = new ChainCSSPrefixer(config.prefixer);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Parse CLI arguments
|
|
109
|
+
function parseArgs(args) {
|
|
110
|
+
const result = {
|
|
111
|
+
inputFile: null,
|
|
112
|
+
outputFile: null,
|
|
113
|
+
watchMode: false,
|
|
114
|
+
noPrefix: false,
|
|
115
|
+
browsers: null,
|
|
116
|
+
prefixerMode: null,
|
|
117
|
+
sourceMap: null,
|
|
118
|
+
sourceMapInline: false
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < args.length; i++) {
|
|
122
|
+
const arg = args[i];
|
|
49
123
|
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
if (arg === '--watch') {
|
|
125
|
+
result.watchMode = true;
|
|
126
|
+
} else if (arg === '--no-prefix') {
|
|
127
|
+
result.noPrefix = true;
|
|
128
|
+
} else if (arg === '--prefixer-mode' && args[i + 1]) {
|
|
129
|
+
result.prefixerMode = args[i + 1];
|
|
130
|
+
i++;
|
|
131
|
+
} else if (arg === '--browsers' && args[i + 1]) {
|
|
132
|
+
result.browsers = args[i + 1].split(',').map(b => b.trim());
|
|
133
|
+
i++;
|
|
134
|
+
} else if (arg === '--no-source-map') {
|
|
135
|
+
result.sourceMap = false;
|
|
136
|
+
} else if (arg === '--source-map-inline') {
|
|
137
|
+
result.sourceMapInline = true;
|
|
138
|
+
} else if (!result.inputFile) {
|
|
139
|
+
result.inputFile = arg;
|
|
140
|
+
} else if (!result.outputFile) {
|
|
141
|
+
result.outputFile = arg;
|
|
55
142
|
}
|
|
56
143
|
}
|
|
57
144
|
|
|
58
|
-
|
|
59
|
-
console.log('Error loading config:', err.message);
|
|
145
|
+
return result;
|
|
60
146
|
}
|
|
61
147
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
148
|
+
// Apply CLI options to config
|
|
149
|
+
const applyCliOptions = (cliOptions) => {
|
|
150
|
+
if (cliOptions.sourceMap !== null) {
|
|
151
|
+
config.prefixer.sourceMap = cliOptions.sourceMap;
|
|
152
|
+
}
|
|
153
|
+
if (cliOptions.sourceMapInline) {
|
|
154
|
+
config.prefixer.sourceMapInline = true;
|
|
155
|
+
}
|
|
156
|
+
if (cliOptions.prefixerMode) {
|
|
157
|
+
config.prefixer.mode = cliOptions.prefixerMode;
|
|
158
|
+
}
|
|
159
|
+
if (cliOptions.noPrefix) {
|
|
160
|
+
config.prefixer.enabled = false;
|
|
161
|
+
}
|
|
162
|
+
if (cliOptions.browsers) {
|
|
163
|
+
config.prefixer.browsers = cliOptions.browsers;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
68
166
|
|
|
167
|
+
// Watch function
|
|
168
|
+
function watch(inputFile, outputFile) {
|
|
169
|
+
chokidar.watch(inputFile).on('change', async () => {
|
|
170
|
+
try {
|
|
171
|
+
await processor(inputFile, outputFile);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('Error during watch processing:', err);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
69
177
|
|
|
70
178
|
// Create the wrapped compile function
|
|
71
179
|
const compile = (obj) => {
|
|
72
|
-
// First, do standard compilation to get styles
|
|
73
180
|
originalCompile(obj);
|
|
74
181
|
|
|
75
|
-
// If atomic is enabled, optimize
|
|
76
182
|
if (atomicOptimizer && config.atomic.enabled) {
|
|
77
183
|
const optimized = atomicOptimizer.optimize(obj);
|
|
78
184
|
chain.cssOutput = optimized;
|
|
@@ -89,22 +195,18 @@ const transpilerModule = {
|
|
|
89
195
|
chain
|
|
90
196
|
};
|
|
91
197
|
|
|
92
|
-
|
|
93
198
|
// Recursive file processing function
|
|
94
199
|
const processJCSSFile = (filePath) => {
|
|
95
|
-
// Check cache first
|
|
96
200
|
if (fileCache.has(filePath)) {
|
|
97
201
|
return fileCache.get(filePath);
|
|
98
202
|
}
|
|
99
203
|
|
|
100
|
-
// Check if file exists
|
|
101
204
|
if (!fs.existsSync(filePath)) {
|
|
102
205
|
throw new Error(`File not found: ${filePath}`);
|
|
103
206
|
}
|
|
104
207
|
|
|
105
208
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
106
209
|
|
|
107
|
-
// Create a new VM instance for this file
|
|
108
210
|
const vm = new NodeVM({
|
|
109
211
|
console: 'inherit',
|
|
110
212
|
timeout: 5000,
|
|
@@ -127,15 +229,9 @@ const processJCSSFile = (filePath) => {
|
|
|
127
229
|
}
|
|
128
230
|
});
|
|
129
231
|
|
|
130
|
-
// Wrap the content - DON'T redeclare module!
|
|
131
232
|
const wrappedContent = `
|
|
132
|
-
// Clear any existing exports
|
|
133
233
|
module.exports = {};
|
|
134
|
-
|
|
135
|
-
// Run the actual file content
|
|
136
234
|
${content}
|
|
137
|
-
|
|
138
|
-
// Return the exports
|
|
139
235
|
module.exports;
|
|
140
236
|
`;
|
|
141
237
|
|
|
@@ -149,52 +245,51 @@ const processJCSSFile = (filePath) => {
|
|
|
149
245
|
}
|
|
150
246
|
};
|
|
151
247
|
|
|
152
|
-
const processScript = (scriptBlock,filename) => {
|
|
153
|
-
|
|
248
|
+
const processScript = (scriptBlock, filename) => {
|
|
154
249
|
const vm = new NodeVM({
|
|
155
250
|
console: 'inherit',
|
|
156
251
|
timeout: 5000,
|
|
157
252
|
sandbox: {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
253
|
+
...transpilerModule,
|
|
254
|
+
get: (relativePath) => {
|
|
255
|
+
const baseDir = path.dirname(filename);
|
|
256
|
+
const targetPath = path.resolve(baseDir, relativePath);
|
|
257
|
+
return processJCSSFile(targetPath);
|
|
258
|
+
},
|
|
259
|
+
__filename: filename,
|
|
260
|
+
__dirname: path.dirname(filename),
|
|
261
|
+
module: { exports: {} },
|
|
262
|
+
require: (path) => require(path)
|
|
168
263
|
},
|
|
169
264
|
require: {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
265
|
+
external: true,
|
|
266
|
+
builtin: ['path', 'fs'],
|
|
267
|
+
root: './'
|
|
173
268
|
}
|
|
174
269
|
});
|
|
175
270
|
|
|
176
271
|
const jsCode = scriptBlock.trim();
|
|
177
272
|
|
|
178
273
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
274
|
+
const result = vm.run(jsCode, filename);
|
|
275
|
+
return transpilerModule.chain.cssOutput;
|
|
181
276
|
} catch (err) {
|
|
182
|
-
|
|
183
|
-
|
|
277
|
+
console.error(`Error processing script in ${filename}:`, err.message);
|
|
278
|
+
throw err;
|
|
184
279
|
}
|
|
185
280
|
};
|
|
186
281
|
|
|
187
282
|
const processJavascriptBlocks = (content, inputpath) => {
|
|
188
283
|
const blocks = content.split(/<@([\s\S]*?)@>/gm);
|
|
189
284
|
let outputCSS = '';
|
|
285
|
+
|
|
190
286
|
for (let i = 0; i < blocks.length; i++) {
|
|
191
287
|
if (i % 2 === 0) {
|
|
192
|
-
outputCSS += blocks[i];
|
|
288
|
+
outputCSS += blocks[i];
|
|
193
289
|
} else {
|
|
194
290
|
const scriptBlock = blocks[i];
|
|
195
291
|
try {
|
|
196
|
-
const outputProcessScript = processScript(scriptBlock,inputpath);
|
|
197
|
-
|
|
292
|
+
const outputProcessScript = processScript(scriptBlock, inputpath);
|
|
198
293
|
if (typeof outputProcessScript !== 'object' && typeof outputProcessScript !== 'undefined') {
|
|
199
294
|
outputCSS += outputProcessScript;
|
|
200
295
|
}
|
|
@@ -207,7 +302,7 @@ const processJavascriptBlocks = (content, inputpath) => {
|
|
|
207
302
|
return outputCSS.trim();
|
|
208
303
|
};
|
|
209
304
|
|
|
210
|
-
// Validate CSS
|
|
305
|
+
// Validate CSS
|
|
211
306
|
const validateCSS = (css) => {
|
|
212
307
|
const openBraces = (css.match(/{/g) || []).length;
|
|
213
308
|
const closeBraces = (css.match(/}/g) || []).length;
|
|
@@ -218,76 +313,79 @@ const validateCSS = (css) => {
|
|
|
218
313
|
return true;
|
|
219
314
|
};
|
|
220
315
|
|
|
221
|
-
// Modified minification function with source map support
|
|
222
316
|
const processAndMinifyCss = async (css, inputFile, outputFile) => {
|
|
223
317
|
if (!validateCSS(css)) {
|
|
224
318
|
throw new Error('Invalid CSS syntax - check for missing braces');
|
|
225
319
|
}
|
|
226
320
|
|
|
227
|
-
// Step 1: Apply prefixer (if enabled)
|
|
228
321
|
let processedCss = css;
|
|
229
|
-
let
|
|
230
|
-
|
|
322
|
+
let sourceMapFromPrefixer = null;
|
|
323
|
+
|
|
324
|
+
if (config.prefixer.enabled) {
|
|
231
325
|
try {
|
|
232
326
|
const result = await prefixer.process(css, {
|
|
233
327
|
from: inputFile,
|
|
234
328
|
to: outputFile,
|
|
235
|
-
map:
|
|
329
|
+
map: config.prefixer.sourceMap !== false
|
|
236
330
|
});
|
|
237
331
|
processedCss = result.css;
|
|
238
|
-
|
|
332
|
+
sourceMapFromPrefixer = result.map;
|
|
239
333
|
} catch (err) {
|
|
240
|
-
console.error('
|
|
334
|
+
console.error('Prefixer error:', err.message);
|
|
241
335
|
processedCss = css;
|
|
242
336
|
}
|
|
243
337
|
}
|
|
244
338
|
|
|
245
|
-
|
|
246
|
-
|
|
339
|
+
const minifyOptions = {
|
|
340
|
+
sourceMap: config.prefixer.sourceMap === true,
|
|
341
|
+
sourceMapInlineSources: true
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const output = new CleanCSS(minifyOptions).minify(processedCss);
|
|
345
|
+
|
|
247
346
|
if (output.errors.length > 0) {
|
|
248
347
|
console.error('CSS Minification Errors:', output.errors);
|
|
249
348
|
return { css: null, map: null };
|
|
250
349
|
}
|
|
350
|
+
|
|
251
351
|
let finalCss = output.styles;
|
|
252
|
-
|
|
352
|
+
let finalSourceMap = output.sourceMap ? JSON.stringify(output.sourceMap) : sourceMapFromPrefixer;
|
|
353
|
+
|
|
354
|
+
if (finalSourceMap && !config.prefixer.sourceMapInline) {
|
|
253
355
|
const mapFileName = path.basename(`${outputFile}.map`);
|
|
254
356
|
finalCss += `\n/*# sourceMappingURL=${mapFileName} */`;
|
|
255
357
|
}
|
|
256
|
-
|
|
358
|
+
|
|
359
|
+
return { css: finalCss, map: finalSourceMap };
|
|
257
360
|
};
|
|
258
361
|
|
|
259
|
-
// Main processor function
|
|
362
|
+
// Main processor function
|
|
260
363
|
const processor = async (inputFile, outputFile) => {
|
|
261
364
|
try {
|
|
262
365
|
const input = path.resolve(inputFile);
|
|
263
366
|
const output = path.resolve(outputFile);
|
|
264
367
|
const content = fs.readFileSync(input, 'utf8');
|
|
265
368
|
|
|
266
|
-
// STEP 1: Process JavaScript blocks first
|
|
267
369
|
const processedCSS = processJavascriptBlocks(content, input);
|
|
268
|
-
|
|
370
|
+
|
|
269
371
|
if (!validateCSS(processedCSS)) {
|
|
270
372
|
throw new Error('Invalid CSS syntax');
|
|
271
373
|
}
|
|
272
374
|
|
|
273
|
-
|
|
274
|
-
const stylePath = output + '/global.css';
|
|
375
|
+
const stylePath = path.join(output, 'global.css');
|
|
275
376
|
const result = await processAndMinifyCss(processedCSS, input, stylePath);
|
|
377
|
+
|
|
276
378
|
if (result.css) {
|
|
277
379
|
fs.writeFileSync(stylePath, result.css, 'utf8');
|
|
278
|
-
|
|
279
|
-
//Write source map if generated
|
|
380
|
+
|
|
280
381
|
if (result.map) {
|
|
281
382
|
const mapFile = `${stylePath}.map`;
|
|
282
383
|
fs.writeFileSync(mapFile, result.map, 'utf8');
|
|
283
384
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (result.map) {
|
|
289
|
-
console.log(` Source maps: enabled`);
|
|
290
|
-
}
|
|
385
|
+
|
|
386
|
+
console.log(`-- Prefixer: ${config.prefixer.mode} mode (${config.prefixer.browsers.join(', ')})\n`);
|
|
387
|
+
console.log(`-- Source maps: ${result.map ? 'enabled' : 'disabled'}\n`);
|
|
388
|
+
console.log(`-- Successfully generated css: ./${path.relative(process.cwd(), stylePath)}\n`);
|
|
291
389
|
}
|
|
292
390
|
} catch (err) {
|
|
293
391
|
console.error(`Failed to process ${inputFile}:`, err.message);
|
|
@@ -295,113 +393,37 @@ const processor = async (inputFile, outputFile) => {
|
|
|
295
393
|
}
|
|
296
394
|
};
|
|
297
395
|
|
|
298
|
-
// Watch function
|
|
299
|
-
function watch(inputFile, outputFile) {
|
|
300
|
-
chokidar.watch(inputFile).on('change', async () => {
|
|
301
|
-
try {
|
|
302
|
-
await processor(inputFile, outputFile);
|
|
303
|
-
} catch (err) {
|
|
304
|
-
console.error('Error during watch processing:', err);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Parse CLI arguments
|
|
310
|
-
function parseArgs(args) {
|
|
311
|
-
const result = {
|
|
312
|
-
inputFile: null,
|
|
313
|
-
outputFile: null,
|
|
314
|
-
watchMode: false,
|
|
315
|
-
noPrefix: false,
|
|
316
|
-
browsers: null,
|
|
317
|
-
prefixerMode: 'auto',
|
|
318
|
-
sourceMap: true,
|
|
319
|
-
sourceMapInline: false
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
for (let i = 0; i < args.length; i++) {
|
|
323
|
-
const arg = args[i];
|
|
324
|
-
|
|
325
|
-
if (arg === '--watch') {
|
|
326
|
-
result.watchMode = true;
|
|
327
|
-
} else if (arg === '--no-prefix') {
|
|
328
|
-
result.noPrefix = true;
|
|
329
|
-
} else if (arg === '--prefixer-mode' && args[i + 1]) {
|
|
330
|
-
result.prefixerMode = args[i + 1];
|
|
331
|
-
i++;
|
|
332
|
-
} else if (arg === '--browsers' && args[i + 1]) {
|
|
333
|
-
result.browsers = args[i + 1].split(',');
|
|
334
|
-
i++;
|
|
335
|
-
} else if (arg === '--no-source-map') {
|
|
336
|
-
result.sourceMap = false;
|
|
337
|
-
} else if (arg === '--source-map-inline') {
|
|
338
|
-
result.sourceMapInline = true;
|
|
339
|
-
} else if (!result.inputFile) {
|
|
340
|
-
result.inputFile = arg;
|
|
341
|
-
} else if (!result.outputFile) {
|
|
342
|
-
result.outputFile = arg;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return result;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
396
|
// Main CLI logic
|
|
350
397
|
if (require.main === module) {
|
|
398
|
+
// Step 1: Ensure config exists
|
|
399
|
+
ensureConfigExists();
|
|
400
|
+
|
|
401
|
+
// Step 2: Load user config
|
|
402
|
+
loadUserConfig();
|
|
403
|
+
|
|
404
|
+
// Step 3: Parse CLI arguments
|
|
351
405
|
const args = process.argv.slice(2);
|
|
352
406
|
const cliOptions = parseArgs(args);
|
|
407
|
+
|
|
353
408
|
if (!cliOptions.inputFile || !cliOptions.outputFile) {
|
|
354
|
-
console.log(
|
|
355
|
-
ChainCSS - JavaScript-powered CSS preprocessor
|
|
356
|
-
|
|
357
|
-
Usage:
|
|
358
|
-
chaincss <inputFile> <outputFile> [options]
|
|
359
|
-
|
|
360
|
-
Options:
|
|
361
|
-
--watch Watch for changes
|
|
362
|
-
--no-prefix Disable automatic prefixing
|
|
363
|
-
--browsers <list> Browser support list (comma-separated)
|
|
364
|
-
Example: --browsers ">1%,last 2 versions,not IE 11"
|
|
365
|
-
|
|
366
|
-
Examples:
|
|
367
|
-
chaincss style.jcss style.css
|
|
368
|
-
chaincss style.jcss style.css --watch
|
|
369
|
-
chaincss style.jcss style.css --browsers ">5%,last 2 safari versions"
|
|
370
|
-
chaincss style.jcss style.css --no-prefix
|
|
371
|
-
`);
|
|
409
|
+
console.log(strVal.cli_opt_guide);
|
|
372
410
|
process.exit(1);
|
|
373
411
|
}
|
|
374
412
|
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
prefixerConfig.sourceMap = cliOptions.sourceMap;
|
|
378
|
-
}
|
|
379
|
-
if (cliOptions.sourceMapInline) {
|
|
380
|
-
prefixerConfig.sourceMapInline = true;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Then apply to prefixer:
|
|
384
|
-
if (cliOptions.prefixerMode) {
|
|
385
|
-
prefixerConfig.mode = cliOptions.prefixerMode;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Apply CLI options
|
|
389
|
-
if (cliOptions.noPrefix) {
|
|
390
|
-
prefixerConfig.enabled = false;
|
|
391
|
-
}
|
|
413
|
+
// Step 4: Apply CLI options (overrides config)
|
|
414
|
+
applyCliOptions(cliOptions);
|
|
392
415
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
Object.assign(prefixer, new ChainCSSPrefixer(prefixerConfig));
|
|
397
|
-
}
|
|
416
|
+
// Step 5: Initialize components with final config (ONCE)
|
|
417
|
+
initAtomicOptimizer();
|
|
418
|
+
initPrefixer(); // Only called once now!
|
|
398
419
|
|
|
399
|
-
// Run processor
|
|
420
|
+
// Step 6: Run processor
|
|
400
421
|
(async () => {
|
|
401
422
|
try {
|
|
402
423
|
await processor(cliOptions.inputFile, cliOptions.outputFile);
|
|
403
424
|
|
|
404
425
|
if (cliOptions.watchMode) {
|
|
426
|
+
console.log('-- Watching for changes...\n');
|
|
405
427
|
watch(cliOptions.inputFile, cliOptions.outputFile);
|
|
406
428
|
}
|
|
407
429
|
} catch (err) {
|
package/package.json
CHANGED
package/prefixer.js
CHANGED
|
@@ -58,7 +58,7 @@ class ChainCSSPrefixer {
|
|
|
58
58
|
// User explicitly wants full mode but Autoprefixer not installed
|
|
59
59
|
if (this.config.mode === 'full' && !this.hasAutoprefixer) {
|
|
60
60
|
console.warn('⚠️ Full mode requested but autoprefixer not installed. Falling back to lightweight mode.');
|
|
61
|
-
console.warn(' To use full mode: npm install autoprefixer postcss');
|
|
61
|
+
console.warn(' To use full mode: npm install autoprefixer postcss caniuse-db browserslist\n');
|
|
62
62
|
return 'lightweight';
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -107,6 +107,7 @@ class ChainCSSPrefixer {
|
|
|
107
107
|
|
|
108
108
|
// Full mode with Autoprefixer
|
|
109
109
|
async processWithAutoprefixer(cssString, options, mapOptions) {
|
|
110
|
+
|
|
110
111
|
const from = options.from || 'input.css';
|
|
111
112
|
const to = options.to || 'output.css';
|
|
112
113
|
|
|
@@ -131,10 +132,8 @@ class ChainCSSPrefixer {
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
this.targetBrowsers = browserslist(this.config.browsers);
|
|
134
|
-
|
|
135
135
|
const from = options.from || 'input.css';
|
|
136
136
|
const to = options.to || 'output.css';
|
|
137
|
-
|
|
138
137
|
const result = await postcss([
|
|
139
138
|
this.createBuiltInPlugin()
|
|
140
139
|
]).process(cssString, {
|