@melcanz85/chaincss 1.9.0 → 1.9.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 +82 -86
- package/atomic-optimizer.js +1 -7
- package/chaincss.js +210 -188
- package/package.json +3 -2
- package/prefixer.js +2 -3
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# ChainCSS
|
|
2
2
|
|
|
3
|
+

|
|
3
4
|
[](https://www.npmjs.com/package/@melcanz85/chaincss)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
|
|
@@ -11,17 +12,7 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
11
12
|
|
|
12
13
|
**Runtime hooks** → Dynamic, prop-based styles when you need them
|
|
13
14
|
|
|
14
|
-
"The performance of vanilla CSS with the power of JavaScript — now with **CHOICE.**"
|
|
15
15
|
|
|
16
|
-
```javascript
|
|
17
|
-
// Same beautiful API, two powerful modes
|
|
18
|
-
const button = $()
|
|
19
|
-
.color('white')
|
|
20
|
-
.backgroundColor('#667eea')
|
|
21
|
-
.padding('1rem')
|
|
22
|
-
.borderRadius('4px')
|
|
23
|
-
.block('.btn');
|
|
24
|
-
````
|
|
25
16
|
## Installation
|
|
26
17
|
|
|
27
18
|
```bash
|
|
@@ -29,6 +20,19 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
29
20
|
npm install @melcanz85/chaincss
|
|
30
21
|
```
|
|
31
22
|
|
|
23
|
+
### File Structure
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
your-project/
|
|
27
|
+
├── node_module
|
|
28
|
+
├── src/
|
|
29
|
+
│ ├── main.jcss # Entry point - imports & compiles
|
|
30
|
+
│ └── *.jcss # Your style definitions
|
|
31
|
+
├── style/ # Generated CSS will be stored stored here
|
|
32
|
+
├── index.html # Your web page
|
|
33
|
+
└── package.json
|
|
34
|
+
```
|
|
35
|
+
|
|
32
36
|
## Two Powerful Modes - One API
|
|
33
37
|
|
|
34
38
|
### Mode 1: Build-time (Zero Runtime)
|
|
@@ -36,7 +40,7 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
36
40
|
**Perfect for:** Static styles, layouts, design systems — anything that doesn't change.
|
|
37
41
|
|
|
38
42
|
```javascript
|
|
39
|
-
//
|
|
43
|
+
// src/button.jcss
|
|
40
44
|
const button = $()
|
|
41
45
|
.backgroundColor('#667eea')
|
|
42
46
|
.color('white')
|
|
@@ -46,16 +50,31 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
46
50
|
|
|
47
51
|
module.exports = { button };
|
|
48
52
|
````
|
|
53
|
+
**in your main.jcss**
|
|
49
54
|
|
|
50
55
|
```javascript
|
|
51
|
-
|
|
52
|
-
const
|
|
56
|
+
<@
|
|
57
|
+
const button = get('./button.js');
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
compile(button);
|
|
60
|
+
@>
|
|
61
|
+
```
|
|
62
|
+
..then run this in terminal/command prompt
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx chaincss ./src/main.jcss ./style --watch
|
|
66
|
+
# ./style/global.css generated!
|
|
67
|
+
````
|
|
68
|
+
OR with vanilla nodejs project
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx chaincss ./src/main.jcss ./style --watch & node server.js
|
|
72
|
+
# ./style/global.css generated!
|
|
58
73
|
````
|
|
74
|
+
* Note: running `npx chaincss ./src/main.jcss ./style --watch ` for the first time will
|
|
75
|
+
generate chaincss.config.js with default values. You can edit this to
|
|
76
|
+
customize your build!.
|
|
77
|
+
|
|
59
78
|
### Mode 2: Runtime (React Hooks)
|
|
60
79
|
|
|
61
80
|
**Perfect for:** Dynamic styles that respond to props, state, or themes.
|
|
@@ -79,9 +98,11 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
79
98
|
|
|
80
99
|
return <button className={styles.button}>{children}</button>;
|
|
81
100
|
}
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
101
|
+
// ✅ Styles injected at runtime
|
|
102
|
+
// ✅ Automatic cleanup on unmount
|
|
103
|
+
// ✅ Fully dynamic based on props
|
|
104
|
+
|
|
105
|
+
|
|
85
106
|
```
|
|
86
107
|
|
|
87
108
|
## Use BOTH in the Same Project!
|
|
@@ -107,25 +128,25 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
107
128
|
|
|
108
129
|
## Features at a Glance
|
|
109
130
|
|
|
110
|
-
Feature Status Description
|
|
131
|
+
Feature Status Description
|
|
111
132
|
|
|
112
|
-
Zero Runtime ✅ Pure CSS output, no JS in browser
|
|
133
|
+
Zero Runtime ✅ Pure CSS output, no JS in browser
|
|
113
134
|
|
|
114
|
-
React Hooks ✅ Dynamic runtime styles when needed
|
|
135
|
+
React Hooks ✅ Dynamic runtime styles when needed
|
|
115
136
|
|
|
116
|
-
Atomic CSS ✅ 90% smaller CSS files
|
|
137
|
+
Atomic CSS ✅ 90% smaller CSS files
|
|
117
138
|
|
|
118
|
-
TypeScript ✅ First-class type support
|
|
139
|
+
TypeScript ✅ First-class type support
|
|
119
140
|
|
|
120
|
-
Design Tokens ✅ Centralized design system
|
|
141
|
+
Design Tokens ✅ Centralized design system
|
|
121
142
|
|
|
122
|
-
Auto-prefixing ✅ Built-in + full Autoprefixer
|
|
143
|
+
Auto-prefixing ✅ Built-in + full Autoprefixer
|
|
123
144
|
|
|
124
|
-
Source Maps ✅ Debug your .jcss files
|
|
145
|
+
Source Maps ✅ Debug your .jcss files
|
|
125
146
|
|
|
126
|
-
Watch Mode ✅ Instant recompilation
|
|
147
|
+
Watch Mode ✅ Instant recompilation
|
|
127
148
|
|
|
128
|
-
VM Security ✅ Safe code execution
|
|
149
|
+
VM Security ✅ Safe code execution
|
|
129
150
|
|
|
130
151
|
|
|
131
152
|
## The ChainCSS API
|
|
@@ -150,19 +171,7 @@ VM Security ✅ Safe code execution
|
|
|
150
171
|
.boxShadow('0 0 0 3px rgba(102,126,234,0.5)')
|
|
151
172
|
.block('.btn');
|
|
152
173
|
````
|
|
153
|
-
### File Structure
|
|
154
174
|
|
|
155
|
-
```text
|
|
156
|
-
your-project/
|
|
157
|
-
├── chaincss/
|
|
158
|
-
│ ├── main.jcss # Entry point - imports & compiles
|
|
159
|
-
│ ├── processor.cjs # Build script
|
|
160
|
-
│ └── *.jcss # Your style definitions
|
|
161
|
-
├── src/
|
|
162
|
-
│ └── style/
|
|
163
|
-
│ └── global.css # Generated CSS
|
|
164
|
-
└── package.json
|
|
165
|
-
```
|
|
166
175
|
### Basic Example
|
|
167
176
|
|
|
168
177
|
**chaincss/button.jcss**
|
|
@@ -187,15 +196,6 @@ VM Security ✅ Safe code execution
|
|
|
187
196
|
@>
|
|
188
197
|
````
|
|
189
198
|
|
|
190
|
-
**chaincss/processor.js**
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
|
|
194
|
-
const chaincss = require('@melcanz85/chaincss');
|
|
195
|
-
|
|
196
|
-
chaincss.processor('./chaincss/main.jcss', './src/style');
|
|
197
|
-
// Generates ./src/style/global.css
|
|
198
|
-
```
|
|
199
199
|
## Advanced Features
|
|
200
200
|
|
|
201
201
|
### Design Tokens
|
|
@@ -249,22 +249,19 @@ ChainCSS uses **secure VM sandboxing** to safely execute your .jcss files. No ev
|
|
|
249
249
|
### With Node.js (Vanilla)
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
# 1. Install
|
|
253
|
+
npm install @melcanz85/chaincss
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
# 2. Create your first .jcss file
|
|
256
|
+
mkdir chaincss
|
|
257
|
+
echo "const hello = $().color('red').block('.hello');
|
|
258
|
+
compile({ hello });" > chaincss/main.jcss
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
compile({ hello });" > chaincss/main.jcss
|
|
263
|
-
|
|
264
|
-
# 4. Build
|
|
265
|
-
node processor.js
|
|
266
|
-
# ✅ ./dist/global.css generated!
|
|
260
|
+
# 3. Build
|
|
261
|
+
npx chaincss ./src/main.jcss ./style --watch & node server.js
|
|
262
|
+
# ./style/global.css generated!
|
|
267
263
|
```
|
|
264
|
+
|
|
268
265
|
### With React + Vite
|
|
269
266
|
|
|
270
267
|
```bash
|
|
@@ -278,6 +275,7 @@ ChainCSS uses **secure VM sandboxing** to safely execute your .jcss files. No ev
|
|
|
278
275
|
# 3. Create component with styles
|
|
279
276
|
mkdir -p src/components/Button
|
|
280
277
|
```
|
|
278
|
+
|
|
281
279
|
**src/components/Button/Button.jsx**
|
|
282
280
|
|
|
283
281
|
```jsx
|
|
@@ -304,19 +302,19 @@ ChainCSS uses **secure VM sandboxing** to safely execute your .jcss files. No ev
|
|
|
304
302
|
|
|
305
303
|
## Performance Comparison
|
|
306
304
|
|
|
307
|
-
Approach Runtime Cost
|
|
305
|
+
Approach Runtime Cost Bundle Size Dynamic Styles Learning Curve
|
|
308
306
|
|
|
309
|
-
**ChainCSS (Build)**
|
|
307
|
+
**ChainCSS (Build)** **Zero** **Just CSS** Build-time Low
|
|
310
308
|
|
|
311
|
-
**ChainCSS (Runtime)** Minimal
|
|
309
|
+
**ChainCSS (Runtime)** Minimal Small runtime Full Low
|
|
312
310
|
|
|
313
|
-
Styled Components 5-10KB runtime
|
|
311
|
+
Styled Components 5-10KB runtime CSS + runtime Full Medium
|
|
314
312
|
|
|
315
|
-
Emotion 8-12KB runtime
|
|
313
|
+
Emotion 8-12KB runtime CSS + runtime Full Medium
|
|
316
314
|
|
|
317
|
-
Tailwind Zero
|
|
315
|
+
Tailwind Zero Just CSS Limited High
|
|
318
316
|
|
|
319
|
-
CSS Modules Zero
|
|
317
|
+
CSS Modules Zero Just CSS None Low
|
|
320
318
|
|
|
321
319
|
**ChainCSS is the ONLY library that gives you BOTH worlds!**
|
|
322
320
|
|
|
@@ -346,39 +344,38 @@ Create chaincss.config.js in your project root:
|
|
|
346
344
|
};
|
|
347
345
|
```
|
|
348
346
|
|
|
349
|
-
|
|
350
347
|
## API Reference
|
|
351
348
|
|
|
352
349
|
### Core Functions
|
|
353
350
|
|
|
354
|
-
Function Description
|
|
351
|
+
Function Description
|
|
355
352
|
|
|
356
|
-
`$()` Create a new chain builder
|
|
353
|
+
`$()` Create a new chain builder
|
|
357
354
|
|
|
358
|
-
`.block(selector)` End chain and assign selector(s)
|
|
355
|
+
`.block(selector)` End chain and assign selector(s)
|
|
359
356
|
|
|
360
|
-
`compile(styles)` Compile style objects to CSS
|
|
357
|
+
`compile(styles)` Compile style objects to CSS
|
|
361
358
|
|
|
362
|
-
`run(...styles)` Process inline styles
|
|
359
|
+
`run(...styles)` Process inline styles
|
|
363
360
|
|
|
364
|
-
`get(filename)` Import .jcss files
|
|
361
|
+
`get(filename)` Import .jcss files
|
|
365
362
|
|
|
366
|
-
`processor(input, output)` Build-time processor
|
|
363
|
+
`processor(input, output)` Build-time processor
|
|
367
364
|
|
|
368
365
|
|
|
369
366
|
### React Hooks
|
|
370
367
|
|
|
371
|
-
Hook Description
|
|
368
|
+
Hook Description
|
|
372
369
|
|
|
373
|
-
`useChainStyles(styles, options)` Basic styles hook
|
|
370
|
+
`useChainStyles(styles, options)` Basic styles hook
|
|
374
371
|
|
|
375
|
-
`useDynamicChainStyles(factory, deps)` Styles that depend on props/state
|
|
372
|
+
`useDynamicChainStyles(factory, deps)` Styles that depend on props/state
|
|
376
373
|
|
|
377
|
-
`useThemeChainStyles(styles, theme)` Theme-aware styles
|
|
374
|
+
`useThemeChainStyles(styles, theme)` Theme-aware styles
|
|
378
375
|
|
|
379
|
-
`ChainCSSGlobal` Global styles component
|
|
376
|
+
`ChainCSSGlobal` Global styles component
|
|
380
377
|
|
|
381
|
-
`cx(...classes)` Conditional class merging
|
|
378
|
+
`cx(...classes)` Conditional class merging
|
|
382
379
|
|
|
383
380
|
|
|
384
381
|
## Editor Support
|
|
@@ -405,7 +402,6 @@ Hook Description
|
|
|
405
402
|
au BufRead,BufNewFile `*.jcss` setfiletype javascript
|
|
406
403
|
```
|
|
407
404
|
|
|
408
|
-
|
|
409
405
|
## Roadmap
|
|
410
406
|
|
|
411
407
|
* Zero-runtime compilation
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melcanz85/chaincss",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.3",
|
|
4
4
|
"description": "A simple package transpiler for js to css",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.react.js",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"chokidar": "^3.5.3",
|
|
43
|
-
"clean-css": "^5.3.3"
|
|
43
|
+
"clean-css": "^5.3.3",
|
|
44
|
+
"vm2": "^3.9.19"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
47
|
"autoprefixer": "^10.0.0",
|
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, {
|