@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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # ChainCSS
2
2
 
3
+ ![npm downloads](https://img.shields.io/npm/dm/@melcanz85/chaincss)
3
4
  [![npm version](https://img.shields.io/npm/v/@melcanz85/chaincss.svg)](https://www.npmjs.com/package/@melcanz85/chaincss)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- // chaincss/button.jcss
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
- // chaincss/processor.js
52
- const chaincss = require('@melcanz85/chaincss');
56
+ <@
57
+ const button = get('./button.js');
53
58
 
54
- chaincss.processor('./chaincss/main.jcss', './dist/style.css');
55
- // Outputs pure CSS: .btn { background: #667eea; color: white; ... }
56
- // Zero JavaScript sent to browser
57
- // Max performance, smallest bundle
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
- // Styles injected at runtime
83
- // Automatic cleanup on unmount
84
- // Fully dynamic based on props
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
- # 1. Install
253
- npm install @melcanz85/chaincss
252
+ # 1. Install
253
+ npm install @melcanz85/chaincss
254
254
 
255
- # 2. Create processor.cjs
256
- echo "const chaincss = require('@melcanz85/chaincss');
257
- chaincss.processor('./chaincss/main.jcss', './dist');" > processor.js
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
- # 3. Create your first .jcss file
260
- mkdir chaincss
261
- echo "const hello = $().color('red').block('.hello');
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 Bundle Size Dynamic Styles Learning Curve
305
+ Approach Runtime Cost Bundle Size Dynamic Styles Learning Curve
308
306
 
309
- **ChainCSS (Build)** **Zero** **Just CSS** Build-time Low
307
+ **ChainCSS (Build)** **Zero** **Just CSS** Build-time Low
310
308
 
311
- **ChainCSS (Runtime)** Minimal Small runtime Full Low
309
+ **ChainCSS (Runtime)** Minimal Small runtime Full Low
312
310
 
313
- Styled Components 5-10KB runtime CSS + runtime Full Medium
311
+ Styled Components 5-10KB runtime CSS + runtime Full Medium
314
312
 
315
- Emotion 8-12KB runtime CSS + runtime Full Medium
313
+ Emotion 8-12KB runtime CSS + runtime Full Medium
316
314
 
317
- Tailwind Zero Just CSS ⚠️ Limited High
315
+ Tailwind Zero Just CSS Limited High
318
316
 
319
- CSS Modules Zero Just CSS None Low
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
@@ -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(`Loaded ${this.atomicClasses.size} atomic classes from cache (${cacheTime})`);
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
- // Configuration
17
+ // Default configuration
29
18
  let config = {
30
19
  atomic: {
31
- enabled: false, // Default off for backward compatibility
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
- try {
40
- // Try .cjs first (for ES Module projects)
41
- let configPath = path.join(process.cwd(), 'chaincss.config.cjs');
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
- const userConfig = require(configPath);
45
- config = { ...config, ...userConfig };
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
- // Fall back to .js
48
- configPath = path.join(process.cwd(), 'chaincss.config.js');
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 (fs.existsSync(configPath)) {
51
- const userConfig = require(configPath);
52
- config = { ...config, ...userConfig };
53
- } else {
54
- console.log('No config file found');
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
- } catch (err) {
59
- console.log('Error loading config:', err.message);
145
+ return result;
60
146
  }
61
147
 
62
- // Initialize atomic optimizer if enabled
63
- if (config.atomic.enabled) {
64
- atomicOptimizer = new AtomicOptimizer(config.atomic);
65
- } else {
66
- console.log('Atomic optimizer disabled (config.atomic.enabled =', config.atomic.enabled, ')');
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
- ...transpilerModule,
159
- get: (relativePath) => {
160
- const baseDir = path.dirname(filename);
161
- const targetPath = path.resolve(baseDir, relativePath);
162
- return processJCSSFile(targetPath);
163
- },
164
- __filename: filename,
165
- __dirname: path.dirname(filename),
166
- module: { exports: {} },
167
- require: (path) => require(path)
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
- external: true, // Allow some external modules
171
- builtin: ['path', 'fs'], // Allow specific Node built-ins
172
- root: './' // Restrict to project root
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
- const result = vm.run(jsCode, filename);
180
- return transpilerModule.chain.cssOutput;
274
+ const result = vm.run(jsCode, filename);
275
+ return transpilerModule.chain.cssOutput;
181
276
  } catch (err) {
182
- console.error(`Error processing script in ${filename}:`, err.message);
183
- throw err;
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]; // Write the existing CSS as is
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 (check for unclosed blocks)
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 sourceMap = null;
230
- if (prefixerConfig.enabled) {
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: prefixerConfig.sourceMap !== false
329
+ map: config.prefixer.sourceMap !== false
236
330
  });
237
331
  processedCss = result.css;
238
- sourceMap = result.map;
332
+ sourceMapFromPrefixer = result.map;
239
333
  } catch (err) {
240
- console.error('Prefixer error:', err.message);
334
+ console.error('Prefixer error:', err.message);
241
335
  processedCss = css;
242
336
  }
243
337
  }
244
338
 
245
- // Step 2: Minify
246
- const output = new CleanCSS().minify(processedCss);
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
- if (sourceMap && !prefixerConfig.sourceMapInline) {
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
- return { css: finalCss, map: sourceMap };
358
+
359
+ return { css: finalCss, map: finalSourceMap };
257
360
  };
258
361
 
259
- // Main processor function - FIXED ORDER
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
- // STEP 2: Validate the CSS
370
+
269
371
  if (!validateCSS(processedCSS)) {
270
372
  throw new Error('Invalid CSS syntax');
271
373
  }
272
374
 
273
- // STEP 3: Apply prefixer and minify with source maps
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
- if (prefixerConfig.enabled) {
285
- console.log(` Prefixer: ${prefixerConfig.mode} mode (${prefixerConfig.browsers.join(', ')})`);
286
- }
287
- //Show source map status
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
- // sourceMap
376
- if (cliOptions.sourceMap !== undefined) {
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
- if (cliOptions.browsers) {
394
- prefixerConfig.browsers = cliOptions.browsers;
395
- // Re-initialize prefixer with new config
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.0",
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, {