@melcanz85/chaincss 1.6.3 β†’ 1.7.0

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
@@ -3,13 +3,16 @@
3
3
  [![npm version](https://badge.fury.io/js/@melcanz85%2Fchaincss.svg?v=2)](https://badge.fury.io/js/@melcanz85%2Fchaincss)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A simple JavaScript-to-CSS transpiler that converts JS objects into optimized CSS.
6
+ ⚑ **Compile-time CSS-in-JS** - Runs during build, not in the browser!
7
+
8
+ A simple JavaScript-to-CSS transpiler that converts JS objects into optimized CSS **without runtime overhead**.
7
9
 
8
10
  ## πŸš€ Installation
9
11
 
10
12
  ```bash
11
13
  npm install @melcanz85/chaincss
12
14
  ```
15
+
13
16
  πŸ“¦ Usage (Node.js)
14
17
 
15
18
  Quick Setup
@@ -22,11 +25,9 @@ Quick Setup
22
25
 
23
26
  ### Update your package.json scripts:
24
27
 
25
- json
26
-
27
- "scripts": {
28
- "start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.js --exec 'node processor.js'\""
29
- }
28
+ "scripts": {
29
+ "start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.js --exec 'node processor.js'\""
30
+ }
30
31
 
31
32
 
32
33
  ## πŸ”§ CSS Prefixing
@@ -49,7 +50,7 @@ No additional installation needed!
49
50
  For complete prefixing coverage of all CSS properties:
50
51
 
51
52
  ```bash
52
- npm install autoprefixer postcss browserslist caniuse-db
53
+ npm install autoprefixer postcss browserslist caniuse-db
53
54
  ```
54
55
  Project Structure
55
56
 
@@ -185,12 +186,188 @@ In chaincss/processor.js:
185
186
  @>
186
187
  }
187
188
 
189
+
190
+ ## βš›οΈ Using ChainCSS with React
191
+
192
+ ChainCSS works great with React and Vite! Here's how to set it up:
193
+
194
+ ### Quick Start with React + Vite
195
+
196
+ 1. **Create a new React project**
197
+ ```bash
198
+ npm create vite@latest my-app -- --template react
199
+ cd my-app
200
+ ```
201
+
202
+ ### Project Structure (Component-First Approach)
203
+
204
+ my-react-app/
205
+ β”œβ”€β”€ src/
206
+ β”‚ β”œβ”€β”€ components/
207
+ β”‚ β”‚ β”œβ”€β”€ Navbar/
208
+ β”‚ β”‚ β”‚ β”œβ”€β”€ Navbar.jsx
209
+ β”‚ β”‚ β”‚ └── navbar.jcss # Chain definitions with fluent API
210
+ β”‚ β”‚ β”œβ”€β”€ Button/
211
+ β”‚ β”‚ β”‚ β”œβ”€β”€ Button.jsx
212
+ β”‚ β”‚ β”‚ └── button.jcss # Chain definitions with fluent API
213
+ β”‚ β”‚ └── ...
214
+ β”‚ β”œβ”€β”€ style/
215
+ β”‚ β”‚ └── global.css # Generated CSS
216
+ β”‚ β”œβ”€β”€ App.jsx
217
+ β”‚ └── main.jsx
218
+ β”œβ”€β”€ chaincss/
219
+ β”‚ β”œβ”€β”€ main.jcss # Entry point - imports and compiles
220
+ β”‚ └── processor.js # Processing script
221
+ └── package.json
222
+
223
+ ### How It Works
224
+
225
+ 1. **Each component has its own `.jcss` file** with style definitions as JavaScript objects
226
+ 2. **`main.jcss` imports all component styles** using `get()` function
227
+ 3. **Styles are merged and compiled** into a single `global.css` file
228
+ 4. **React components import the generated CSS** and use the class names
229
+
230
+ ### Example: Button Component
231
+
232
+ **src/components/Nav/navbar.jcss**
233
+
234
+ const navbar = chain
235
+ .bg('rgba(255, 255, 255, 0.95)')
236
+ .backdropFilter('blur(10px)')
237
+ .padding('1rem 5%')
238
+ .position('fixed')
239
+ .width('100%')
240
+ .top('0')
241
+ .zIndex('1000')
242
+ .boxShadow('0 2px 20px rgba(0,0,0,0.1)')
243
+ .block('.navbar');
244
+
245
+ module.exports = {navbar}
246
+
247
+ **src/components/Hero/hero.jcss**
248
+
249
+ const hero = chain
250
+ .padding('120px 5% 80px')
251
+ .bg('linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
252
+ .color('white')
253
+ .textAlign('center')
254
+ .block('.hero');
255
+
256
+ module.exports = {hero}
257
+
258
+ **chaincss/main.jcss**
259
+
260
+ // You can mix a default style using run() method or even vanilla css (without delimeters)
261
+ <@
262
+ run(
263
+ chain.margin('0').padding('0').boxSizing('border-box').block('*'),
264
+ chain
265
+ .fontFamily("-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif")
266
+ .lineHeight('1.6')
267
+ .color('#1e293b')
268
+ .bg('linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
269
+ .block('body')
270
+ );
271
+ @>
272
+
273
+ .button {
274
+ background-color: #667eea;
275
+ color: white;
276
+ padding: 0.5rem 1rem;
277
+ border-radius: 4px;
278
+ border: none;
279
+ cursor: pointer;
280
+ }
281
+
282
+ <@
283
+ // Import all component styles
284
+ const nav = get('./src/components/Navbar/navbar.jcss');
285
+ const hero = get('./src/components/Hero/hero.jcss');
286
+
287
+ // Merge for one compilation
288
+ const allStyles = Object.assign({},nav,hero);
289
+
290
+ // Overwrite padding in navbar chain!
291
+ nav.navbar.padding = '1rem 5%';
292
+
293
+ compile(allStyles);
294
+ @>
295
+
296
+ // you can add keyframes and media queries in this setup
297
+ @keyframes fadeInUp {
298
+ <@
299
+ run(
300
+ chain.opacity('0').transform('translateY(20px)').block('from'),
301
+ chain.opacity('1').transform('translateY(0)').block('to'),
302
+ );
303
+ @>
304
+ }
305
+
306
+ /* Responsive */
307
+ @media (max-width: 768px) {
308
+ <@
309
+ run(
310
+ chain.fontSize('2.5rem').block('.hero h1'),
311
+ chain.flexDirection('column').gap('1rem').block('.stats'),
312
+ chain.flexDirection('column').alignItems('center').block('.cta-buttons'),
313
+ chain.gridTemplateColumns('1fr').block('.example-container'),
314
+ chain.display('none').block('.nav-links')
315
+ );
316
+ @>
317
+ }
318
+
319
+ Benefits of This Approach
320
+
321
+ βœ… Component Co-location: Styles live next to their components
322
+
323
+ βœ… Single Source of Truth: Each component manages its own styles
324
+
325
+ βœ… Easy to Maintain: Add/remove components without touching a central style file
326
+
327
+ βœ… Scalable: Perfect for large React applications
328
+
329
+ βœ… No Redundancy: Styles are automatically merged and optimized
330
+
331
+ **This structure is much cleaner and follows React best practices! Each component owns its styles, and `main.jcss` simply orchestrates the compilation.**
332
+
333
+ 🎯 Best Practices with React
334
+
335
+ 1. Component-Specific Styles
336
+
337
+ * Keep styles close to components: Button/button.jcss
338
+
339
+ * Use descriptive class names that match your components
340
+
341
+ 2. Global Styles
342
+
343
+ * Use main.jcss for global resets and animations
344
+
345
+ * Import generated CSS once in your root component
346
+
347
+ 3. Dynamic Styles
348
+
349
+ // In your .jcss file
350
+ const theme = {
351
+ primary: '#667eea',
352
+ secondary: '#764ba2'
353
+ };
354
+
355
+ const button = chain
356
+ .backgroundColor(theme.primary)
357
+ .block('.btn');
358
+
359
+ const buttonHover = chain
360
+ .backgroundColor(theme.secondary)
361
+ .block('.btn:hover');
362
+
188
363
  πŸ“ Notes
189
364
 
190
- You can directly put css syntax code on your main file.
365
+ You can directly put css syntax code on your main.jcss file.
191
366
 
192
367
  But chainCSS syntax must be wrapped in <@ @> delimiters.
193
368
 
369
+ run() and compile() method should be separate block <@ run() @> <@ compile @>
370
+
194
371
  The get() function imports chaining definitions from your chain.jcss file
195
372
 
196
373
  You can modify your style in between get() and compile() in the
@@ -205,11 +382,11 @@ VS Code
205
382
 
206
383
  Add this to your project's .vscode/settings.json:
207
384
 
208
- {
209
- "files.associations": {
210
- "*.jcss": "javascript"
385
+ {
386
+ "files.associations": {
387
+ "*.jcss": "javascript"
388
+ }
211
389
  }
212
- }
213
390
 
214
391
  WebStorm / IntelliJ IDEA
215
392
 
@@ -268,10 +445,35 @@ Status Feature Description
268
445
 
269
446
  βœ… Watch mode Auto-recompile on file changes
270
447
 
448
+
449
+ ## πŸš€ Key Differentiators
450
+
451
+ - **βš™οΈ Compile-time, not runtime** - chaincss processes your styles during build, generating pure CSS files. Zero JavaScript execution in the browser means faster page loads!
452
+
453
+ - **🏎️ Performance by design** - Unlike runtime CSS-in-JS libraries, chaincss adds no bundle weight and causes no layout shifts
454
+
455
+ - **πŸ”§ Build-time processing** - Your `.jcss` files are transformed before deployment, not in the user's browser
456
+
457
+
458
+ ## πŸ”„ chaincss vs Other Approaches
459
+
460
+ | Feature | chaincss | Runtime CSS-in-JS | Tailwind | Vanilla CSS |
461
+ |----------------|-------------------|---------------------|---------------|-------------|
462
+ | **When CSS is | βš™οΈ **Build time** | πŸ•’ Runtime (browser)| βš™οΈ Build time | πŸ“ Already written |
463
+ generated**
464
+
465
+ | **Browser work**| None - just | Executes JS to | None - just | None |
466
+ serves CSS generate CSS serves CSS
467
+
468
+ | **Dynamic values**| βœ… Via JS at | βœ… Via props at | ⚠️ Limited | ❌ Manual |
469
+ build time runtime
470
+
471
+ | **Bundle size** | Just the CSS | CSS + JS runtime | Just the CSS | Just the CSS |
472
+
271
473
  πŸ‘¨β€πŸ’» Contributing
272
474
 
273
475
  Contributions are welcome! Feel free to open issues or submit pull requests.
274
476
 
275
477
  πŸ“„ License
276
478
 
277
- MIT Β© Rommel E. Caneos
479
+ MIT Β© Rommel Caneos
package/chaincss.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // INCLUDE SEVERAL NODEJS MODULES
4
3
  const vm = require('vm');
5
4
  const path = require('path');
6
5
  const fs = require('fs');
@@ -18,21 +17,16 @@ let prefixerConfig = {
18
17
  const prefixer = new ChainCSSPrefixer(prefixerConfig);
19
18
 
20
19
  const processScript = (scriptBlock) => {
21
- const context = vm.createContext({
22
- ...transpilerModule
23
- });
24
-
20
+ const context = vm.createContext({ ...transpilerModule});
25
21
  const jsCode = scriptBlock.trim();
26
22
  const chainScript = new vm.Script(jsCode);
27
23
  chainScript.runInContext(context);
28
24
  return context.chain.cssOutput;
29
25
  };
30
26
 
31
- // FUNCTION TO CONVERT JS CODES TO CSS CODE (FIRST STEP)
32
27
  const processJavascriptBlocks = (content) => {
33
28
  const blocks = content.split(/<@([\s\S]*?)@>/gm);
34
29
  let outputCSS = '';
35
-
36
30
  for (let i = 0; i < blocks.length; i++) {
37
31
  if (i % 2 === 0) {
38
32
  outputCSS += blocks[i]; // Write the existing CSS as is
@@ -49,15 +43,13 @@ const processJavascriptBlocks = (content) => {
49
43
  }
50
44
  }
51
45
  }
52
-
53
46
  return outputCSS.trim();
54
47
  };
55
48
 
56
- // NEW: Validate CSS (check for unclosed blocks)
49
+ // Validate CSS (check for unclosed blocks)
57
50
  const validateCSS = (css) => {
58
51
  const openBraces = (css.match(/{/g) || []).length;
59
52
  const closeBraces = (css.match(/}/g) || []).length;
60
-
61
53
  if (openBraces !== closeBraces) {
62
54
  console.error(`CSS syntax error: Unclosed blocks (${openBraces} opening vs ${closeBraces} closing braces)`);
63
55
  return false;
@@ -66,17 +58,14 @@ const validateCSS = (css) => {
66
58
  };
67
59
 
68
60
  // Modified minification function with source map support
69
-
70
61
  const processAndMinifyCss = async (css, inputFile, outputFile) => {
71
- // First validate the CSS
72
62
  if (!validateCSS(css)) {
73
63
  throw new Error('Invalid CSS syntax - check for missing braces');
74
64
  }
75
-
65
+
76
66
  // Step 1: Apply prefixer (if enabled)
77
67
  let processedCss = css;
78
68
  let sourceMap = null;
79
-
80
69
  if (prefixerConfig.enabled) {
81
70
  try {
82
71
  const result = await prefixer.process(css, {
@@ -84,10 +73,8 @@ const processAndMinifyCss = async (css, inputFile, outputFile) => {
84
73
  to: outputFile,
85
74
  map: prefixerConfig.sourceMap !== false
86
75
  });
87
-
88
76
  processedCss = result.css;
89
77
  sourceMap = result.map;
90
-
91
78
  } catch (err) {
92
79
  console.error('⚠Prefixer error:', err.message);
93
80
  processedCss = css;
@@ -100,19 +87,15 @@ const processAndMinifyCss = async (css, inputFile, outputFile) => {
100
87
  console.error('CSS Minification Errors:', output.errors);
101
88
  return { css: null, map: null };
102
89
  }
103
-
104
90
  let finalCss = output.styles;
105
-
106
91
  if (sourceMap && !prefixerConfig.sourceMapInline) {
107
92
  const mapFileName = path.basename(`${outputFile}.map`);
108
93
  finalCss += `\n/*# sourceMappingURL=${mapFileName} */`;
109
94
  }
110
-
111
95
  return { css: finalCss, map: sourceMap };
112
96
  };
113
97
 
114
98
  // Main processor function - FIXED ORDER
115
-
116
99
  const processor = async (inputFile, outputFile) => {
117
100
  try {
118
101
  const input = path.resolve(inputFile);
@@ -126,19 +109,18 @@ const processor = async (inputFile, outputFile) => {
126
109
  if (!validateCSS(processedCSS)) {
127
110
  throw new Error('Invalid CSS syntax');
128
111
  }
129
-
112
+
130
113
  // STEP 3: Apply prefixer and minify with source maps
131
-
132
- const result = await processAndMinifyCss(processedCSS, input, output);
114
+ const stylePath = output + '/global.css';
115
+ const result = await processAndMinifyCss(processedCSS, input, stylePath);
133
116
  if (result.css) {
134
- fs.writeFileSync(output, result.css, 'utf8');
135
-
117
+ fs.writeFileSync(stylePath, result.css, 'utf8');
118
+
136
119
  //Write source map if generated
137
120
  if (result.map) {
138
- const mapFile = `${output}.map`;
121
+ const mapFile = `${stylePath}.map`;
139
122
  fs.writeFileSync(mapFile, result.map, 'utf8');
140
123
  }
141
-
142
124
  if (prefixerConfig.enabled) {
143
125
  console.log(` Prefixer: ${prefixerConfig.mode} mode (${prefixerConfig.browsers.join(', ')})`);
144
126
  }
@@ -155,9 +137,7 @@ const processor = async (inputFile, outputFile) => {
155
137
 
156
138
  // Watch function
157
139
  function watch(inputFile, outputFile) {
158
- console.log(`Watching for changes in ${inputFile}...`);
159
140
  chokidar.watch(inputFile).on('change', async () => {
160
- console.log(`File changed: ${inputFile}`);
161
141
  try {
162
142
  await processor(inputFile, outputFile);
163
143
  } catch (err) {
@@ -167,7 +147,6 @@ function watch(inputFile, outputFile) {
167
147
  }
168
148
 
169
149
  // Parse CLI arguments
170
-
171
150
  function parseArgs(args) {
172
151
  const result = {
173
152
  inputFile: null,
@@ -213,7 +192,6 @@ function parseArgs(args) {
213
192
  if (require.main === module) {
214
193
  const args = process.argv.slice(2);
215
194
  const cliOptions = parseArgs(args);
216
-
217
195
  if (!cliOptions.inputFile || !cliOptions.outputFile) {
218
196
  console.log(`
219
197
  ChainCSS - JavaScript-powered CSS preprocessor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melcanz85/chaincss",
3
- "version": "1.6.3",
3
+ "version": "1.7.0",
4
4
  "description": "A simple package transpiler for js to css",
5
5
  "main": "chaincss.js",
6
6
  "publishConfig": {
package/prefixer.js CHANGED
@@ -105,7 +105,7 @@ class ChainCSSPrefixer {
105
105
  }
106
106
  }
107
107
 
108
- // πŸš€ Full mode with Autoprefixer
108
+ // Full mode with Autoprefixer
109
109
  async processWithAutoprefixer(cssString, options, mapOptions) {
110
110
  const from = options.from || 'input.css';
111
111
  const to = options.to || 'output.css';
@@ -124,7 +124,7 @@ class ChainCSSPrefixer {
124
124
  };
125
125
  }
126
126
 
127
- // πŸ”§ Lightweight mode with built-in prefixer
127
+ // Lightweight mode with built-in prefixer
128
128
  async processWithBuiltIn(cssString, options, mapOptions) {
129
129
  if (!this.hasBuiltInDeps) {
130
130
  return { css: cssString, map: null };
package/transpiler.js CHANGED
@@ -209,7 +209,6 @@ const compile = (obj) => {
209
209
  let selectors = element.selectors || []; // Provide default empty array if selectors is undefined
210
210
  let elementCSS = '';
211
211
  for (let prop in element) {
212
-
213
212
  if (element.hasOwnProperty(prop) && prop !== 'selectors') {
214
213
  // Convert camelCase to kebab-case
215
214
  const kebabKey = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
@@ -218,7 +217,6 @@ const compile = (obj) => {
218
217
  }
219
218
  selectors = selectors.join();
220
219
  cssString += `${selectors} {\n${elementCSS}}\n`;
221
-
222
220
  }
223
221
  }
224
222
 
@@ -230,7 +228,6 @@ const get = (filename) => {
230
228
  if (fileExt !== '.jcss') {
231
229
  throw new Error(`Import error: ${filename} must have .jcss extension`);
232
230
  }
233
-
234
231
  // Try to resolve the path
235
232
  const resolvedPath = path.resolve(process.cwd(), filename);
236
233
 
@@ -238,7 +235,7 @@ const get = (filename) => {
238
235
  const exists = fs.existsSync(resolvedPath);
239
236
 
240
237
  if (!exists) {
241
- throw new Error(`File not found: ${filename} (resolved to: ${resolvedPath})`);
238
+ throw new Error('File not found: '+filename+'(resolved to: '+resolvedPath+')');
242
239
  }
243
240
 
244
241
  return require(resolvedPath);