@melcanz85/chaincss 1.7.3 → 1.8.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 +117 -144
- package/chaincss.js +4 -2
- package/package.json +1 -1
- package/transpiler.js +148 -186
package/README.md
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@melcanz85/chaincss)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
**Compile-time CSS-in-JS** - Runs during build, not in the browser!
|
|
7
7
|
|
|
8
8
|
A simple JavaScript-to-CSS transpiler that converts JS objects into optimized CSS **without runtime overhead**.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Installation
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
npm install @melcanz85/chaincss
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
**Usage (Node.js)**
|
|
17
17
|
|
|
18
18
|
Quick Setup
|
|
19
19
|
|
|
@@ -26,50 +26,68 @@ Quick Setup
|
|
|
26
26
|
### Update your package.json scripts:
|
|
27
27
|
|
|
28
28
|
"scripts": {
|
|
29
|
-
"start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.
|
|
29
|
+
"start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.cjs --exec 'node processor.cjs'\""
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## CSS Prefixing
|
|
34
34
|
|
|
35
|
-
ChainCSS offers two prefixing
|
|
35
|
+
ChainCSS offers two prefixing approaches:
|
|
36
36
|
|
|
37
|
-
### 1.
|
|
37
|
+
### 1. Built-in Prefixer (Auto Mode, Default)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Our lightweight built-in prefixer handles the most common CSS properties:
|
|
40
40
|
- Flexbox & Grid
|
|
41
41
|
- Transforms & Animations
|
|
42
42
|
- Filters & Effects
|
|
43
43
|
- Text effects
|
|
44
44
|
- Box properties
|
|
45
45
|
|
|
46
|
-
No additional installation needed
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
**No additional installation needed!** Just run:
|
|
47
|
+
```bash
|
|
48
|
+
npx chaincss input.jcss output.css --watch
|
|
49
|
+
```
|
|
50
|
+
### 2. Full Autoprefixer Mode (Complete Coverage)
|
|
49
51
|
|
|
50
52
|
For complete prefixing coverage of all CSS properties:
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
55
|
npm install autoprefixer postcss browserslist caniuse-db
|
|
56
|
+
|
|
57
|
+
# then run this command
|
|
58
|
+
npx chaincss input.jcss output.css --watch --prefixer-mode full
|
|
59
|
+
|
|
54
60
|
```
|
|
55
|
-
Project Structure
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
### 3. Make Full Mode Permanent
|
|
63
|
+
|
|
64
|
+
Edit your package.json script:
|
|
65
|
+
|
|
66
|
+
"scripts": {
|
|
67
|
+
"css:watch": "chaincss src/styles.jcss dist/styles.css --watch --prefixer-mode full",
|
|
68
|
+
"start": "concurrently \"npm run dev\" \"npm run css:watch\""
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
**Project Structure (vanillajs, vanilla nodejs)**
|
|
73
|
+
|
|
74
|
+
Create this folder structure in your vanillaJS project:
|
|
58
75
|
|
|
59
76
|
your-project/
|
|
60
77
|
├── chaincss/ # ChainCSS source files
|
|
61
78
|
│ ├── main.jcss # Main entry file
|
|
62
79
|
│ ├── chain.jcss # Chaining definitions
|
|
63
|
-
│ └── processor.js
|
|
80
|
+
│ └── processor.js # Processing script
|
|
64
81
|
├── public/ # Output files
|
|
65
82
|
│ ├── index.html
|
|
66
83
|
│ └── style.css # Generated CSS
|
|
67
84
|
├── node_modules/
|
|
68
85
|
├── package.json
|
|
69
|
-
|
|
86
|
+
├── package.lock.json
|
|
87
|
+
└── server.js
|
|
70
88
|
|
|
71
89
|
|
|
72
|
-
The Initialization processor Setup
|
|
90
|
+
**The Initialization processor Setup**
|
|
73
91
|
|
|
74
92
|
In chaincss/processor.js:
|
|
75
93
|
|
|
@@ -83,107 +101,60 @@ In chaincss/processor.js:
|
|
|
83
101
|
process.exit(1);
|
|
84
102
|
}
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
## Code Examples
|
|
87
105
|
|
|
88
106
|
//--Chaining File (chaincss/chain.jcss):
|
|
89
107
|
|
|
90
|
-
|
|
91
|
-
The chain methods are the same as the css properties but in camelCase mode
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const headerBg = 'rgba(255, 255, 255, 0.95)';
|
|
99
|
-
const bodyFontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
|
100
|
-
Ubuntu, sans-serif";
|
|
101
|
-
const headerBoxShadow = '0 2px 20px rgba(0,0,0,0.1)';
|
|
102
|
-
const logoFontSize = '1.8rem';
|
|
103
|
-
|
|
104
|
-
const reset = chain
|
|
105
|
-
.margin('0')
|
|
106
|
-
.padding('0')
|
|
107
|
-
.boxSizing('border-box')
|
|
108
|
-
.block('*');
|
|
109
|
-
|
|
110
|
-
const body = chain
|
|
111
|
-
.fontFamily(bodyFontFamily)
|
|
112
|
-
.lineHeight('1.6')
|
|
113
|
-
.color('#1e293b')
|
|
114
|
-
.bg(bodyBg)
|
|
115
|
-
.block('body');
|
|
116
|
-
|
|
117
|
-
/* Header/Navigation */
|
|
118
|
-
const navbar = chain
|
|
119
|
-
.bg(headerBg)
|
|
120
|
-
.backdropFilter('blur(10px)')
|
|
121
|
-
.padding('1rem 5%')
|
|
122
|
-
.position('fixed')
|
|
123
|
-
.width('100%')
|
|
124
|
-
.top('0')
|
|
125
|
-
.zIndex('1000')
|
|
126
|
-
.boxShadow(headerBoxShadow)
|
|
108
|
+
**This is where the chaining happens all codes must be in javascript syntax.
|
|
109
|
+
The chain methods are the same as the css properties but in camelCase mode.
|
|
110
|
+
The value of the block() method is the css selector which is always at the
|
|
111
|
+
end of the chain or block.**
|
|
112
|
+
|
|
113
|
+
/* Header/Navigation */
|
|
114
|
+
const navbar = $().backdropFilter('blur(10px)').padding('1rem 5%')
|
|
115
|
+
.position('fixed').width('100%').top('0').zIndex('1000').boxShadow(headerBoxShadow)
|
|
127
116
|
.block('.navbar');
|
|
128
117
|
|
|
129
|
-
const nav_container =
|
|
130
|
-
.
|
|
131
|
-
.margin('0 auto')
|
|
132
|
-
.display('flex')
|
|
133
|
-
.justifyContent('space-between')
|
|
134
|
-
.alignItems('center')
|
|
135
|
-
.block('.nav-container');
|
|
136
|
-
|
|
137
|
-
const logo = chain
|
|
138
|
-
.fontSize(logoFontSize)
|
|
139
|
-
.fontWeight('700')
|
|
140
|
-
.bg('linear-gradient(135deg, #667eea, #764ba2)')
|
|
141
|
-
.backgroundClip('text')
|
|
142
|
-
.textFillColor('transparent')
|
|
143
|
-
.letterSpacing('-0.5px')
|
|
144
|
-
.block('.logo');
|
|
118
|
+
const nav_container = $().maxWidth('1200px').margin('0 auto').display('flex')
|
|
119
|
+
.justifyContent('space-between').alignItems('center').block('.nav-container');
|
|
145
120
|
|
|
146
121
|
module.exports = {
|
|
147
|
-
reset,
|
|
148
122
|
navbar,
|
|
149
|
-
nav_container
|
|
150
|
-
logo
|
|
123
|
+
nav_container
|
|
151
124
|
};
|
|
152
125
|
|
|
153
126
|
|
|
154
|
-
|
|
127
|
+
**Main File (chaincss/main.jcss):**
|
|
155
128
|
|
|
156
129
|
<@
|
|
157
130
|
// Import chaining definitions
|
|
158
131
|
const style = get('./chain.jcss');
|
|
159
132
|
|
|
160
133
|
// Override specific styles
|
|
161
|
-
style.
|
|
134
|
+
style.navbar.padding = '2rem 5%';
|
|
162
135
|
|
|
163
136
|
// Compile to CSS
|
|
164
137
|
compile(style);
|
|
165
138
|
@>
|
|
166
139
|
|
|
167
140
|
@keyframes fadeInUp {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@>
|
|
141
|
+
<@
|
|
142
|
+
const from = $().opacity('0').transform('translateY(20px)').block('from');
|
|
143
|
+
const to = $().chain.opacity('1').transform('translateY(0)').block('to');
|
|
144
|
+
run(from,to);
|
|
145
|
+
@>
|
|
174
146
|
}
|
|
175
147
|
|
|
176
148
|
/* Responsive */
|
|
177
149
|
@media (max-width: 768px) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@>
|
|
150
|
+
<@
|
|
151
|
+
const hero_h1 = $().fontSize('2.5rem').block('.hero h1');
|
|
152
|
+
const stats = $().flexDirection('column').gap('1rem').block('.stats');
|
|
153
|
+
const cta_buttons = $().flexDirection('column').alignItems('center').block('.cta-buttons');
|
|
154
|
+
const ex_container = $().gridTemplateColumns('1fr').block('.example-container');
|
|
155
|
+
const nav_links = $().display('none').block('.nav-links');
|
|
156
|
+
run(hero_h1,stats,cta_buttons,ex_container,nav_links);
|
|
157
|
+
@>
|
|
187
158
|
}
|
|
188
159
|
|
|
189
160
|
|
|
@@ -231,7 +202,7 @@ ChainCSS works great with React and Vite! Here's how to set it up:
|
|
|
231
202
|
|
|
232
203
|
**src/components/Nav/navbar.jcss**
|
|
233
204
|
|
|
234
|
-
const navbar =
|
|
205
|
+
const navbar = $()
|
|
235
206
|
.bg('rgba(255, 255, 255, 0.95)')
|
|
236
207
|
.backdropFilter('blur(10px)')
|
|
237
208
|
.padding('1rem 5%')
|
|
@@ -259,15 +230,11 @@ ChainCSS works great with React and Vite! Here's how to set it up:
|
|
|
259
230
|
|
|
260
231
|
// You can mix a default style using run() method or even vanilla css (without delimeters)
|
|
261
232
|
<@
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
.color('#1e293b')
|
|
268
|
-
.bg('linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
|
|
269
|
-
.block('body')
|
|
270
|
-
);
|
|
233
|
+
const reset = $().margin('0').padding('0').boxSizing('border-box').block('*');
|
|
234
|
+
const body = $().fontFamily("-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
235
|
+
Oxygen, Ubuntu, sans-serif").lineHeight('1.6').color('#1e293b')
|
|
236
|
+
.background('linear-gradient(135deg, #667eea 0%, #764ba2 100%)').block('body');
|
|
237
|
+
run(reset, body);
|
|
271
238
|
@>
|
|
272
239
|
|
|
273
240
|
.button {
|
|
@@ -281,42 +248,44 @@ ChainCSS works great with React and Vite! Here's how to set it up:
|
|
|
281
248
|
|
|
282
249
|
<@
|
|
283
250
|
// Import all component styles
|
|
284
|
-
const nav = get('./src/components/
|
|
285
|
-
const hero = get('./src/components/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
251
|
+
const nav = get('./src/components/nav/nav.jcss');
|
|
252
|
+
const hero = get('./src/components/sect_hero/hero.jcss');
|
|
253
|
+
const feature = get('./src/components/sect_feature/feature.jcss');
|
|
254
|
+
const example = get('./src/components/sect_example/example.jcss');
|
|
255
|
+
const gstart = get('./src/components/sect_gStart/gStart.jcss');
|
|
256
|
+
const footer = get('./src/components/footer/footer.jcss');
|
|
257
|
+
|
|
258
|
+
const merged = Object.assign({},nav,hero,feature,example,gstart,footer);
|
|
259
|
+
|
|
260
|
+
// Overwrite your chaining file
|
|
261
|
+
nav.logo.textDecoration = 'none';
|
|
262
|
+
//example.css_output.overflowWrap = 'break-word';
|
|
263
|
+
|
|
264
|
+
compile(merged);
|
|
294
265
|
@>
|
|
295
266
|
|
|
296
267
|
// you can add keyframes and media queries in this setup
|
|
297
268
|
@keyframes fadeInUp {
|
|
298
269
|
<@
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
270
|
+
const from = $().opacity('0').transform('translateY(20px)').block('from');
|
|
271
|
+
const to = $().opacity('1').transform('translateY(0)').block('to');
|
|
272
|
+
run(from,to);
|
|
303
273
|
@>
|
|
304
|
-
|
|
274
|
+
}
|
|
305
275
|
|
|
306
276
|
/* Responsive */
|
|
307
277
|
@media (max-width: 768px) {
|
|
308
278
|
<@
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
);
|
|
279
|
+
const hero = $().fontSize('2.5rem').block('.hero h1');
|
|
280
|
+
const stats = $().flexDirection('column').gap('1rem').block('.stats');
|
|
281
|
+
const ctaButtons = $().flexDirection('column').alignItems('center').block('.cta-buttons');
|
|
282
|
+
const exampleContainer = $().gridTemplateColumns('1fr').block('.example-container');
|
|
283
|
+
const navLinks = $().display('none').block('.nav-links');
|
|
284
|
+
run(hero,stats,ctaButtons,exampleContainer,navLinks);
|
|
316
285
|
@>
|
|
317
|
-
|
|
286
|
+
}
|
|
318
287
|
|
|
319
|
-
Benefits of This Approach
|
|
288
|
+
**Benefits of This Approach**
|
|
320
289
|
|
|
321
290
|
✅ Component Co-location: Styles live next to their components
|
|
322
291
|
|
|
@@ -328,9 +297,10 @@ Benefits of This Approach
|
|
|
328
297
|
|
|
329
298
|
✅ No Redundancy: Styles are automatically merged and optimized
|
|
330
299
|
|
|
331
|
-
|
|
300
|
+
This structure is much cleaner and follows React best practices! Each component owns its styles, and
|
|
301
|
+
`main.jcss` simply orchestrates the compilation.
|
|
332
302
|
|
|
333
|
-
🎯 Best Practices with React
|
|
303
|
+
## 🎯 Best Practices with React
|
|
334
304
|
|
|
335
305
|
1. Component-Specific Styles
|
|
336
306
|
|
|
@@ -360,7 +330,7 @@ Benefits of This Approach
|
|
|
360
330
|
.backgroundColor(theme.secondary)
|
|
361
331
|
.block('.btn:hover');
|
|
362
332
|
|
|
363
|
-
📝 Notes
|
|
333
|
+
## 📝 Notes
|
|
364
334
|
|
|
365
335
|
You can directly put css syntax code on your main.jcss file.
|
|
366
336
|
|
|
@@ -373,12 +343,12 @@ Benefits of This Approach
|
|
|
373
343
|
You can modify your style in between get() and compile() in the
|
|
374
344
|
main file it will overwrite the styles in the chain file.
|
|
375
345
|
|
|
376
|
-
🎨 Editor Support
|
|
346
|
+
## 🎨 Editor Support
|
|
377
347
|
|
|
378
348
|
Since .jcss files are just JavaScript files with ChainCSS syntax, you can
|
|
379
349
|
easily enable proper syntax highlighting in your editor:
|
|
380
350
|
|
|
381
|
-
VS Code
|
|
351
|
+
**VS Code**
|
|
382
352
|
|
|
383
353
|
Add this to your project's .vscode/settings.json:
|
|
384
354
|
|
|
@@ -388,7 +358,7 @@ Add this to your project's .vscode/settings.json:
|
|
|
388
358
|
}
|
|
389
359
|
}
|
|
390
360
|
|
|
391
|
-
WebStorm / IntelliJ IDEA
|
|
361
|
+
**WebStorm / IntelliJ IDEA**
|
|
392
362
|
|
|
393
363
|
Go to Settings/Preferences → Editor → File Types
|
|
394
364
|
|
|
@@ -396,13 +366,13 @@ WebStorm / IntelliJ IDEA
|
|
|
396
366
|
|
|
397
367
|
Click + and add *.jcss to the registered patterns
|
|
398
368
|
|
|
399
|
-
Vim / Neovim
|
|
369
|
+
**Vim / Neovim**
|
|
400
370
|
|
|
401
371
|
Add to your .vimrc or init.vim:
|
|
402
372
|
|
|
403
373
|
au BufRead,BufNewFile *.jcss setfiletype javascript
|
|
404
374
|
|
|
405
|
-
Sublime Text
|
|
375
|
+
**Sublime Text**
|
|
406
376
|
|
|
407
377
|
Create or edit ~/Library/Application Support/Sublime Text/Packages/User/JCSS.sublime-settings:
|
|
408
378
|
|
|
@@ -413,7 +383,7 @@ json
|
|
|
413
383
|
"syntax": "Packages/JavaScript/JavaScript.sublime-syntax"
|
|
414
384
|
}
|
|
415
385
|
|
|
416
|
-
Atom
|
|
386
|
+
**Atom**
|
|
417
387
|
|
|
418
388
|
Add to your config.cson:
|
|
419
389
|
coffeescript
|
|
@@ -428,10 +398,11 @@ coffeescript
|
|
|
428
398
|
|
|
429
399
|
Other Editors
|
|
430
400
|
|
|
431
|
-
Most modern editors allow you to associate file extensions with language modes.
|
|
401
|
+
Most modern editors allow you to associate file extensions with language modes.
|
|
402
|
+
Simply configure your editor to treat .jcss files as JavaScript.
|
|
432
403
|
|
|
433
404
|
|
|
434
|
-
|
|
405
|
+
## Features
|
|
435
406
|
|
|
436
407
|
Status Feature Description
|
|
437
408
|
|
|
@@ -440,34 +411,36 @@ Status Feature Description
|
|
|
440
411
|
✅ Vendor prefixing Auto-add -webkit-, -moz-, etc.
|
|
441
412
|
|
|
442
413
|
✅ Keyframe animations @keyframes support
|
|
414
|
+
|
|
415
|
+
✅ Media queries responsive @media support
|
|
443
416
|
|
|
444
417
|
✅ Source maps Debug generated CSS
|
|
445
418
|
|
|
446
419
|
✅ Watch mode Auto-recompile on file changes
|
|
447
420
|
|
|
448
421
|
|
|
449
|
-
|
|
422
|
+
### Key Differentiators
|
|
450
423
|
|
|
451
|
-
-
|
|
424
|
+
- ** 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
425
|
|
|
453
|
-
-
|
|
426
|
+
- ** Performance by design** - Unlike runtime CSS-in-JS libraries, chaincss adds no bundle weight and causes no layout shifts
|
|
454
427
|
|
|
455
|
-
-
|
|
428
|
+
- ** Build-time processing** - Your `.jcss` files are transformed before deployment, not in the user's browser
|
|
456
429
|
|
|
457
430
|
|
|
458
|
-
|
|
431
|
+
### chaincss vs Other Approaches
|
|
459
432
|
|
|
460
433
|
| Feature | chaincss | Runtime CSS-in-JS | Tailwind | Vanilla CSS |
|
|
461
434
|
|----------------|-------------------|---------------------|---------------|-------------|
|
|
462
|
-
| **When CSS is generated** |
|
|
435
|
+
| **When CSS is generated** | **Build time** | Runtime (browser) | Build time | Already written |
|
|
463
436
|
| **Browser work**| None - just serves CSS | Executes JS to generate CSS | None - just serves CSS | None |
|
|
464
|
-
| **Dynamic values**|
|
|
437
|
+
| **Dynamic values**| Via JS at build time | Via props at runtime | ⚠ Limited | Manual |
|
|
465
438
|
| **Bundle size** | Just the CSS | CSS + JS runtime | Just the CSS | Just the CSS |
|
|
466
439
|
|
|
467
|
-
👨💻 Contributing
|
|
440
|
+
### 👨💻 Contributing
|
|
468
441
|
|
|
469
442
|
Contributions are welcome! Feel free to open issues or submit pull requests.
|
|
470
443
|
|
|
471
|
-
|
|
444
|
+
### License
|
|
472
445
|
|
|
473
446
|
MIT © Rommel Caneos
|
package/chaincss.js
CHANGED
|
@@ -17,7 +17,10 @@ let prefixerConfig = {
|
|
|
17
17
|
const prefixer = new ChainCSSPrefixer(prefixerConfig);
|
|
18
18
|
|
|
19
19
|
const processScript = (scriptBlock) => {
|
|
20
|
-
const context = vm.createContext({
|
|
20
|
+
const context = vm.createContext({
|
|
21
|
+
...transpilerModule,
|
|
22
|
+
$: transpilerModule.$
|
|
23
|
+
});
|
|
21
24
|
const jsCode = scriptBlock.trim();
|
|
22
25
|
const chainScript = new vm.Script(jsCode);
|
|
23
26
|
chainScript.runInContext(context);
|
|
@@ -173,7 +176,6 @@ function parseArgs(args) {
|
|
|
173
176
|
} else if (arg === '--browsers' && args[i + 1]) {
|
|
174
177
|
result.browsers = args[i + 1].split(',');
|
|
175
178
|
i++;
|
|
176
|
-
// NEW: Add these two
|
|
177
179
|
} else if (arg === '--no-source-map') {
|
|
178
180
|
result.sourceMap = false;
|
|
179
181
|
} else if (arg === '--source-map-inline') {
|
package/package.json
CHANGED
package/transpiler.js
CHANGED
|
@@ -1,203 +1,159 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
|
|
3
4
|
const chain = {
|
|
4
|
-
cssOutput
|
|
5
|
+
cssOutput: undefined,
|
|
5
6
|
catcher: {},
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
borderSideStyle(side, value){
|
|
22
|
-
if (side === 'top') {
|
|
23
|
-
this.catcher.borderTopStyle = value;
|
|
24
|
-
} else if (side === 'right') {
|
|
25
|
-
this.catcher.borderRightStyle = value;
|
|
26
|
-
} else if (side === 'bottom') {
|
|
27
|
-
this.catcher.borderBottomStyle = value;
|
|
28
|
-
} else if (side === 'left') {
|
|
29
|
-
this.catcher.borderLeftStyle = value;
|
|
7
|
+
cachedValidProperties: [],
|
|
8
|
+
|
|
9
|
+
// Initialize properties synchronously
|
|
10
|
+
initializeProperties() {
|
|
11
|
+
try {
|
|
12
|
+
const jsonPath = path.join(__dirname, 'css-properties.json');
|
|
13
|
+
if (fs.existsSync(jsonPath)) {
|
|
14
|
+
const data = fs.readFileSync(jsonPath, 'utf8');
|
|
15
|
+
this.cachedValidProperties = JSON.parse(data);
|
|
16
|
+
|
|
17
|
+
} else {
|
|
18
|
+
console.log('⚠️ CSS properties not cached, will load on first use');
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('Error loading CSS properties:', error.message);
|
|
30
22
|
}
|
|
31
|
-
return this;
|
|
32
23
|
},
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
textDecoration(value, style){
|
|
73
|
-
if (style === undefined) {
|
|
74
|
-
this.catcher.textDecoration = value;
|
|
75
|
-
} else {
|
|
76
|
-
this.catcher.textDecorationStyle = value;
|
|
25
|
+
async getCSSProperties() {
|
|
26
|
+
try {
|
|
27
|
+
const jsonPath = path.join(__dirname, 'css-properties.json');
|
|
28
|
+
|
|
29
|
+
// Check if file already exists
|
|
30
|
+
try {
|
|
31
|
+
await fs.promises.access(jsonPath);
|
|
32
|
+
const existingData = await fs.promises.readFile(jsonPath, 'utf8');
|
|
33
|
+
const objProp = JSON.parse(existingData);
|
|
34
|
+
this.cachedValidProperties = objProp;
|
|
35
|
+
return objProp;
|
|
36
|
+
} catch {
|
|
37
|
+
const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
|
|
38
|
+
const response = await fetch(url);
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
const allProperties = Object.keys(data);
|
|
41
|
+
|
|
42
|
+
// Strip vendor prefixes and remove duplicates
|
|
43
|
+
const baseProperties = new Set();
|
|
44
|
+
|
|
45
|
+
allProperties.forEach(prop => {
|
|
46
|
+
// Remove vendor prefixes (-webkit-, -moz-, -ms-, -o-)
|
|
47
|
+
const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
|
|
48
|
+
baseProperties.add(baseProp);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Convert Set back to array and sort
|
|
52
|
+
const cleanProperties = Array.from(baseProperties).sort();
|
|
53
|
+
|
|
54
|
+
// Save cleaned properties
|
|
55
|
+
await fs.promises.writeFile(jsonPath, JSON.stringify(cleanProperties, null, 2));
|
|
56
|
+
|
|
57
|
+
this.cachedValidProperties = cleanProperties;
|
|
58
|
+
return cleanProperties;
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Error loading CSS properties:', error.message);
|
|
62
|
+
return [];
|
|
77
63
|
}
|
|
78
|
-
return this;
|
|
79
64
|
},
|
|
80
65
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
whiteSpace(sws){ this.catcher.whiteSpace = sws; return this; },
|
|
87
|
-
|
|
88
|
-
// Font
|
|
89
|
-
font(f){ this.catcher.font = f; return this; },
|
|
90
|
-
fontFamily(ff){ this.catcher.fontFamily = ff; return this; },
|
|
91
|
-
fontStyle(fs){ this.catcher.fontStyle = fs; return this; },
|
|
92
|
-
fontWeight(fw){ this.catcher.fontWeight = fw; return this; },
|
|
93
|
-
fontVariant(fv){ this.catcher.fontVariant = fv; return this; },
|
|
94
|
-
fontSize(fsz){ this.catcher.fontSize = fsz; return this; },
|
|
95
|
-
|
|
96
|
-
// List Style
|
|
97
|
-
listStyle(ls){ this.catcher.listStyle = ls; return this; },
|
|
98
|
-
listStyleType(lst){ this.catcher.listStyleType = lst; return this; },
|
|
99
|
-
listStyleImage(lsi){ this.catcher.listStyleImage = lsi; return this; },
|
|
100
|
-
listStylePosition(lsp){ this.catcher.listStylePosition = lsp; return this; },
|
|
101
|
-
|
|
102
|
-
// Display
|
|
103
|
-
display(d){ this.catcher.display = d; return this; },
|
|
104
|
-
flex(f){ this.catcher.flex = f; return this; },
|
|
105
|
-
alignContent(ac){ this.catcher.alignContent = ac; return this; },
|
|
106
|
-
alignSelf(as){ this.catcher.alignSelf = as; return this; },
|
|
107
|
-
alignItems(ai){ this.catcher.alignItems = ai; return this; },
|
|
108
|
-
justifyContent(jc){ this.catcher.justifyContent = jc; return this; },
|
|
109
|
-
flexWrap(fw){ this.catcher.flexWrap = fw; return this; },
|
|
110
|
-
flexGrow(fg){ this.catcher.flexGrow = fg; return this; },
|
|
111
|
-
flexDirection(fd){ this.catcher.flexDirection = fd; return this; },
|
|
112
|
-
order(o){ this.catcher.order = o; return this; },
|
|
113
|
-
visibility(v){ this.catcher.visibility = v; return this; },
|
|
114
|
-
|
|
115
|
-
// Position
|
|
116
|
-
position(p){ this.catcher.position = p; return this; },
|
|
117
|
-
top(t){ this.catcher.top = t; return this; },
|
|
118
|
-
left(l){ this.catcher.left = l; return this; },
|
|
119
|
-
bottom(b){ this.catcher.bottom = b; return this; },
|
|
120
|
-
|
|
121
|
-
// Overflow
|
|
122
|
-
overflow(o){ this.catcher.overflow = o; return this; },
|
|
123
|
-
overflowX(ox){ this.catcher.overflowX = ox; return this; },
|
|
124
|
-
overflowY(oy){ this.catcher.overflowY = oy; return this; },
|
|
125
|
-
overflowWrap(ow){ this.catcher.overflowWrap = ow; return this; },
|
|
66
|
+
// Synchronous version for internal use
|
|
67
|
+
getCachedProperties() {
|
|
68
|
+
return this.cachedValidProperties;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
126
71
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
backgroundClip(bc){ this.catcher.backgroundClip = bc; return this; },
|
|
130
|
-
gridTemplateColumns(gtc){ this.catcher.gridTemplateColumns = gtc; return this; },
|
|
131
|
-
right(r){ this.catcher.right = r; return this; },
|
|
132
|
-
transform(tf){ this.catcher.transform = tf; return this; },
|
|
133
|
-
boxShadow(bs){ this.catcher.boxShadow = bs; return this; },
|
|
134
|
-
backdropFilter(bf){ this.catcher.backdropFilter = bf; return this; },
|
|
135
|
-
gap(g){ this.catcher.gap = g; return this; },
|
|
72
|
+
// Initialize properties synchronously when module loads
|
|
73
|
+
chain.initializeProperties();
|
|
136
74
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
clear(c){ this.catcher.clear = c; return this; },
|
|
75
|
+
function $(){
|
|
76
|
+
const catcher = {};
|
|
140
77
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Box-sizing
|
|
145
|
-
boxSizing(bs){ this.catcher.boxSizing = bs; return this; },
|
|
146
|
-
|
|
147
|
-
// Opacity
|
|
148
|
-
opacity(o){ this.catcher.opacity = o; return this; },
|
|
78
|
+
// Use cached properties if available
|
|
79
|
+
const validProperties = chain.cachedValidProperties;
|
|
149
80
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
81
|
+
const handler = {
|
|
82
|
+
get: (target, prop) => {
|
|
83
|
+
if (prop === 'block') {
|
|
84
|
+
return function(...args) {chain
|
|
85
|
+
// If no args, just return current catcher
|
|
86
|
+
if (args.length === 0) {
|
|
87
|
+
const result = { ...catcher };
|
|
88
|
+
// Clear catcher
|
|
89
|
+
Object.keys(catcher).forEach(key => delete catcher[key]);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Create result with selectors
|
|
94
|
+
const result = {
|
|
95
|
+
selectors: args,
|
|
96
|
+
...catcher
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Clear catcher
|
|
100
|
+
Object.keys(catcher).forEach(key => delete catcher[key]);
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Convert camelCase to kebab-case for CSS property
|
|
107
|
+
const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
108
|
+
|
|
109
|
+
// Validate property exists (optional) - use cached properties
|
|
110
|
+
if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
|
|
111
|
+
console.warn(`⚠️ Warning: '${cssProperty}' may not be a valid CSS property`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Return a function that sets the value
|
|
115
|
+
return function(value) {
|
|
116
|
+
catcher[prop] = value;
|
|
117
|
+
return proxy; // Return proxy for chaining
|
|
118
|
+
};
|
|
174
119
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Create the proxy
|
|
123
|
+
const proxy = new Proxy({}, handler);
|
|
124
|
+
|
|
125
|
+
// Trigger async load if needed (don't await)
|
|
126
|
+
if (chain.cachedValidProperties.length === 0) {
|
|
127
|
+
chain.getCSSProperties().catch(err => {
|
|
128
|
+
console.error('Failed to load CSS properties:', err.message);
|
|
129
|
+
});
|
|
181
130
|
}
|
|
131
|
+
|
|
132
|
+
return proxy;
|
|
182
133
|
};
|
|
183
134
|
|
|
184
|
-
|
|
185
135
|
const run = (...args) => {
|
|
186
|
-
let
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
136
|
+
let cssOutput = '';
|
|
137
|
+
|
|
138
|
+
args.forEach((value) => {
|
|
139
|
+
if (value && value.selectors) {
|
|
140
|
+
let rule = `${value.selectors.join(', ')} {\n`;
|
|
141
|
+
|
|
142
|
+
// Add all properties (excluding 'selectors')
|
|
190
143
|
for (let key in value) {
|
|
191
|
-
if(key !== 'selectors'){
|
|
192
|
-
const kebabKey = key.replace(/([
|
|
193
|
-
|
|
144
|
+
if (key !== 'selectors' && value.hasOwnProperty(key)) {
|
|
145
|
+
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
146
|
+
rule += ` ${kebabKey}: ${value[key]};\n`;
|
|
194
147
|
}
|
|
195
148
|
}
|
|
196
|
-
|
|
197
|
-
|
|
149
|
+
|
|
150
|
+
rule += `}\n\n`;
|
|
151
|
+
cssOutput += rule;
|
|
198
152
|
}
|
|
199
|
-
);
|
|
200
|
-
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
chain.cssOutput = cssOutput.trim();
|
|
156
|
+
return cssOutput.trim();
|
|
201
157
|
};
|
|
202
158
|
|
|
203
159
|
const compile = (obj) => {
|
|
@@ -206,21 +162,23 @@ const compile = (obj) => {
|
|
|
206
162
|
for (const key in obj) {
|
|
207
163
|
if (obj.hasOwnProperty(key)) {
|
|
208
164
|
const element = obj[key];
|
|
209
|
-
let selectors = element.selectors || [];
|
|
165
|
+
let selectors = element.selectors || [];
|
|
210
166
|
let elementCSS = '';
|
|
167
|
+
|
|
211
168
|
for (let prop in element) {
|
|
212
169
|
if (element.hasOwnProperty(prop) && prop !== 'selectors') {
|
|
213
170
|
// Convert camelCase to kebab-case
|
|
214
|
-
const kebabKey = prop.replace(/([
|
|
171
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
215
172
|
elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
|
|
216
173
|
}
|
|
217
174
|
}
|
|
218
|
-
|
|
219
|
-
cssString += `${selectors} {\n${elementCSS}}\n`;
|
|
175
|
+
|
|
176
|
+
cssString += `${selectors.join(', ')} {\n${elementCSS}}\n`;
|
|
220
177
|
}
|
|
221
178
|
}
|
|
222
179
|
|
|
223
|
-
chain.cssOutput = cssString;
|
|
180
|
+
chain.cssOutput = cssString.trim();
|
|
181
|
+
return cssString.trim();
|
|
224
182
|
};
|
|
225
183
|
|
|
226
184
|
const get = (filename) => {
|
|
@@ -228,25 +186,29 @@ const get = (filename) => {
|
|
|
228
186
|
if (fileExt !== '.jcss') {
|
|
229
187
|
throw new Error(`Import error: ${filename} must have .jcss extension`);
|
|
230
188
|
}
|
|
189
|
+
|
|
231
190
|
// Try to resolve the path
|
|
232
191
|
const resolvedPath = path.resolve(process.cwd(), filename);
|
|
233
192
|
|
|
234
193
|
// Check if file exists
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (!exists) {
|
|
238
|
-
throw new Error('File not found: '+filename+'(resolved to: '+resolvedPath+')');
|
|
194
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
195
|
+
throw new Error(`File not found: ${filename} (resolved to: ${resolvedPath})`);
|
|
239
196
|
}
|
|
240
197
|
|
|
241
198
|
return require(resolvedPath);
|
|
242
199
|
};
|
|
243
200
|
|
|
201
|
+
// Make chaincss available globally
|
|
244
202
|
if (typeof global !== 'undefined') {
|
|
245
203
|
global.chain = chain;
|
|
204
|
+
global.run = run;
|
|
205
|
+
global.compile = compile;
|
|
206
|
+
global.$ = $;
|
|
246
207
|
}
|
|
247
208
|
|
|
248
209
|
module.exports = {
|
|
249
210
|
chain,
|
|
211
|
+
$,
|
|
250
212
|
run,
|
|
251
213
|
compile,
|
|
252
214
|
get
|