@melcanz85/chaincss 1.5.27 → 1.6.1
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 +166 -118
- package/chaincss.js +224 -42
- package/package.json +22 -2
- package/prefixer.js +297 -0
- package/transpiler.js +1 -0
package/README.md
CHANGED
|
@@ -8,18 +8,19 @@ A simple JavaScript-to-CSS transpiler that converts JS objects into optimized CS
|
|
|
8
8
|
## 🚀 Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npm install @melcanz85/chaincss
|
|
12
|
-
|
|
11
|
+
npm install @melcanz85/chaincss
|
|
12
|
+
```
|
|
13
13
|
📦 Usage (Node.js)
|
|
14
|
-
Quick Setup
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
Quick Setup
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
### Install development dependencies:
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev nodemon concurrently
|
|
21
|
+
```
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
### Update your package.json scripts:
|
|
23
24
|
|
|
24
25
|
json
|
|
25
26
|
|
|
@@ -27,115 +28,160 @@ json
|
|
|
27
28
|
"start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.js --exec 'node processor.js'\""
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
|
|
32
|
+
## 🔧 CSS Prefixing
|
|
33
|
+
|
|
34
|
+
ChainCSS offers two prefixing modes:
|
|
35
|
+
|
|
36
|
+
### 1. Lightweight Mode (Default, ~50KB)
|
|
37
|
+
Built-in prefixer that handles the most common CSS properties:
|
|
38
|
+
- Flexbox & Grid
|
|
39
|
+
- Transforms & Animations
|
|
40
|
+
- Filters & Effects
|
|
41
|
+
- Text effects
|
|
42
|
+
- Box properties
|
|
43
|
+
|
|
44
|
+
No additional installation needed!
|
|
45
|
+
|
|
46
|
+
### 2. Full Mode (Uses Autoprefixer)
|
|
47
|
+
For complete prefixing coverage of all CSS properties:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install autoprefixer postcss browserslist caniuse-db
|
|
51
|
+
```
|
|
30
52
|
Project Structure
|
|
31
53
|
|
|
32
54
|
Create this folder structure in your project:
|
|
33
55
|
|
|
34
|
-
your-project/
|
|
35
|
-
├── chaincss/ # ChainCSS source files
|
|
36
|
-
│ ├── main.jcss # Main entry file
|
|
37
|
-
│ ├── chain.jcss # Chaining definitions
|
|
38
|
-
│ └── processor.js # Processing script
|
|
39
|
-
├── public/ # Output files
|
|
40
|
-
│ ├── index.html
|
|
41
|
-
│ └── style.css # Generated CSS
|
|
42
|
-
├── node_modules/
|
|
43
|
-
├── package.json
|
|
44
|
-
└── package-lock.json
|
|
56
|
+
your-project/
|
|
57
|
+
├── chaincss/ # ChainCSS source files
|
|
58
|
+
│ ├── main.jcss # Main entry file
|
|
59
|
+
│ ├── chain.jcss # Chaining definitions
|
|
60
|
+
│ └── processor.js # Processing script
|
|
61
|
+
├── public/ # Output files
|
|
62
|
+
│ ├── index.html
|
|
63
|
+
│ └── style.css # Generated CSS
|
|
64
|
+
├── node_modules/
|
|
65
|
+
├── package.json
|
|
66
|
+
└── package-lock.json
|
|
45
67
|
|
|
46
68
|
|
|
47
69
|
The Initialization processor Setup
|
|
48
70
|
|
|
49
71
|
In chaincss/processor.js:
|
|
50
72
|
|
|
51
|
-
const chain = require("@melcanz85/chaincss");
|
|
73
|
+
const chain = require("@melcanz85/chaincss");
|
|
52
74
|
|
|
53
|
-
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} catch (err) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
75
|
+
try {
|
|
76
|
+
// Process main file and output CSS
|
|
77
|
+
chain.processor('./chaincss/main.jcss', './public/style.css');
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error('Error processing chainCSS file:', err.stack);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
60
82
|
|
|
61
83
|
💻 Code Examples
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
//--Chaining File (chaincss/chain.jcss):
|
|
86
|
+
|
|
87
|
+
### This is where the chaining happens all codes must be in javascript syntax.
|
|
88
|
+
The chain methods are the same as the css properties but in camelCase mode
|
|
89
|
+
and the exception of the background property which is shorten to 'bg' only for
|
|
90
|
+
example background-color is bgColor() in chaincss. The value of the block()
|
|
91
|
+
method is the css selector which is always at the end of the chain or block.
|
|
92
|
+
|
|
93
|
+
// Variables for consistent styling
|
|
94
|
+
const bodyBg = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
95
|
+
const headerBg = 'rgba(255, 255, 255, 0.95)';
|
|
96
|
+
const bodyFontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
|
97
|
+
Ubuntu, sans-serif";
|
|
98
|
+
const headerBoxShadow = '0 2px 20px rgba(0,0,0,0.1)';
|
|
99
|
+
const logoFontSize = '1.8rem';
|
|
100
|
+
|
|
101
|
+
const reset = chain
|
|
102
|
+
.margin('0')
|
|
103
|
+
.padding('0')
|
|
104
|
+
.boxSizing('border-box')
|
|
105
|
+
.block('*');
|
|
106
|
+
|
|
107
|
+
const body = chain
|
|
108
|
+
.fontFamily(bodyFontFamily)
|
|
109
|
+
.lineHeight('1.6')
|
|
110
|
+
.color('#1e293b')
|
|
111
|
+
.bg(bodyBg)
|
|
112
|
+
.block('body');
|
|
113
|
+
|
|
114
|
+
/* Header/Navigation */
|
|
115
|
+
const navbar = chain
|
|
116
|
+
.bg(headerBg)
|
|
117
|
+
.backdropFilter('blur(10px)')
|
|
118
|
+
.padding('1rem 5%')
|
|
119
|
+
.position('fixed')
|
|
120
|
+
.width('100%')
|
|
121
|
+
.top('0')
|
|
122
|
+
.zIndex('1000')
|
|
123
|
+
.boxShadow(headerBoxShadow)
|
|
124
|
+
.block('.navbar');
|
|
125
|
+
|
|
126
|
+
const nav_container = chain
|
|
127
|
+
.maxWidth('1200px')
|
|
128
|
+
.margin('0 auto')
|
|
129
|
+
.display('flex')
|
|
130
|
+
.justifyContent('space-between')
|
|
131
|
+
.alignItems('center')
|
|
132
|
+
.block('.nav-container');
|
|
133
|
+
|
|
134
|
+
const logo = chain
|
|
135
|
+
.fontSize(logoFontSize)
|
|
136
|
+
.fontWeight('700')
|
|
137
|
+
.bg('linear-gradient(135deg, #667eea, #764ba2)')
|
|
138
|
+
.backgroundClip('text')
|
|
139
|
+
.textFillColor('transparent')
|
|
140
|
+
.letterSpacing('-0.5px')
|
|
141
|
+
.block('.logo');
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
reset,
|
|
145
|
+
navbar,
|
|
146
|
+
nav_container,
|
|
147
|
+
logo
|
|
148
|
+
};
|
|
111
149
|
|
|
112
150
|
|
|
113
151
|
//--Main File (chaincss/main.jcss):
|
|
114
152
|
|
|
115
|
-
<@
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@>
|
|
125
|
-
|
|
126
|
-
@
|
|
127
|
-
<@
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
chain.order(4).display('flex').width('100%').justifyContent('flex-end').block('.burgerWrapper')
|
|
135
|
-
);
|
|
136
|
-
@>
|
|
137
|
-
}
|
|
153
|
+
<@
|
|
154
|
+
// Import chaining definitions
|
|
155
|
+
const style = get('./chain.jcss');
|
|
156
|
+
|
|
157
|
+
// Override specific styles
|
|
158
|
+
style.logo.fontSize = '2rem';
|
|
159
|
+
|
|
160
|
+
// Compile to CSS
|
|
161
|
+
compile(style);
|
|
162
|
+
@>
|
|
163
|
+
|
|
164
|
+
@keyframes fadeInUp {
|
|
165
|
+
<@
|
|
166
|
+
run(
|
|
167
|
+
chain.opacity('0').transform('translateY(20px)').block('from'),
|
|
168
|
+
chain.opacity('1').transform('translateY(0)').block('to'),
|
|
169
|
+
);
|
|
170
|
+
@>
|
|
171
|
+
}
|
|
138
172
|
|
|
173
|
+
/* Responsive */
|
|
174
|
+
@media (max-width: 768px) {
|
|
175
|
+
<@
|
|
176
|
+
run(
|
|
177
|
+
chain.fontSize('2.5rem').block('.hero h1'),
|
|
178
|
+
chain.flexDirection('column').gap('1rem').block('.stats'),
|
|
179
|
+
chain.flexDirection('column').alignItems('center').block('.cta-buttons'),
|
|
180
|
+
chain.gridTemplateColumns('1fr').block('.example-container'),
|
|
181
|
+
chain.display('none').block('.nav-links')
|
|
182
|
+
);
|
|
183
|
+
@>
|
|
184
|
+
}
|
|
139
185
|
|
|
140
186
|
📝 Notes
|
|
141
187
|
|
|
@@ -143,13 +189,16 @@ module.exports = {
|
|
|
143
189
|
|
|
144
190
|
But chainCSS syntax must be wrapped in <@ @> delimiters.
|
|
145
191
|
|
|
146
|
-
The get() function imports chaining definitions from
|
|
192
|
+
The get() function imports chaining definitions from your chain.jcss file
|
|
147
193
|
|
|
148
|
-
|
|
194
|
+
You can modify your style in between get() and compile() in the
|
|
195
|
+
main file it will overwrite the styles in the chain file.
|
|
149
196
|
|
|
150
197
|
🎨 Editor Support
|
|
151
198
|
|
|
152
|
-
Since .jcss files are just JavaScript files with ChainCSS syntax, you can
|
|
199
|
+
Since .jcss files are just JavaScript files with ChainCSS syntax, you can
|
|
200
|
+
easily enable proper syntax highlighting in your editor:
|
|
201
|
+
|
|
153
202
|
VS Code
|
|
154
203
|
|
|
155
204
|
Add this to your project's .vscode/settings.json:
|
|
@@ -171,13 +220,13 @@ WebStorm / IntelliJ IDEA
|
|
|
171
220
|
Vim / Neovim
|
|
172
221
|
|
|
173
222
|
Add to your .vimrc or init.vim:
|
|
174
|
-
vim
|
|
175
223
|
|
|
176
|
-
au BufRead,BufNewFile *.jcss setfiletype javascript
|
|
224
|
+
au BufRead,BufNewFile *.jcss setfiletype javascript
|
|
177
225
|
|
|
178
226
|
Sublime Text
|
|
179
227
|
|
|
180
|
-
Create or edit ~/Library/Application Support/Sublime Text/Packages/User/JCSS.sublime-settings:
|
|
228
|
+
Create or edit ~/Library/Application Support/Sublime Text/Packages/User/JCSS.sublime-settings:
|
|
229
|
+
|
|
181
230
|
json
|
|
182
231
|
|
|
183
232
|
{
|
|
@@ -190,12 +239,13 @@ Atom
|
|
|
190
239
|
Add to your config.cson:
|
|
191
240
|
coffeescript
|
|
192
241
|
|
|
193
|
-
"*":
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
242
|
+
"*":
|
|
243
|
+
core:
|
|
244
|
+
customFileTypes:
|
|
245
|
+
"source.js": [
|
|
246
|
+
"jcss"
|
|
247
|
+
]
|
|
248
|
+
|
|
199
249
|
|
|
200
250
|
Other Editors
|
|
201
251
|
|
|
@@ -208,19 +258,17 @@ Status Feature Description
|
|
|
208
258
|
|
|
209
259
|
✅ Basic JS → CSS Convert plain JS objects to CSS
|
|
210
260
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
🚧 Vendor prefixing Auto-add -webkit-, -moz-, etc.
|
|
214
|
-
|
|
215
|
-
🚧 Source maps Debug generated CSS
|
|
261
|
+
✅ Vendor prefixing Auto-add -webkit-, -moz-, etc.
|
|
216
262
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
✅
|
|
263
|
+
✅ Keyframe animations @keyframes support
|
|
264
|
+
|
|
265
|
+
✅ Source maps Debug generated CSS
|
|
220
266
|
|
|
267
|
+
✅ Watch mode Auto-recompile on file changes
|
|
221
268
|
|
|
222
269
|
👨💻 Contributing
|
|
223
|
-
|
|
270
|
+
|
|
271
|
+
Contributions are welcome! Feel free to open issues or submit pull requests.
|
|
224
272
|
|
|
225
273
|
📄 License
|
|
226
274
|
|
package/chaincss.js
CHANGED
|
@@ -7,39 +7,29 @@ const fs = require('fs');
|
|
|
7
7
|
const chokidar = require('chokidar');
|
|
8
8
|
const CleanCSS = require('clean-css');
|
|
9
9
|
const transpilerModule = require('./transpiler.js');
|
|
10
|
+
const ChainCSSPrefixer = require('./prefixer.js');
|
|
11
|
+
|
|
12
|
+
let prefixerConfig = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
15
|
+
mode: 'auto' // 'auto', 'lightweight', or 'full'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const prefixer = new ChainCSSPrefixer(prefixerConfig);
|
|
10
19
|
|
|
11
20
|
const processScript = (scriptBlock) => {
|
|
12
|
-
//const output = 'cssOutput = undefined;';
|
|
13
21
|
const context = vm.createContext({
|
|
14
|
-
...transpilerModule
|
|
22
|
+
...transpilerModule
|
|
15
23
|
});
|
|
16
24
|
|
|
17
|
-
const jsCode = scriptBlock.trim();
|
|
25
|
+
const jsCode = scriptBlock.trim();
|
|
18
26
|
const chainScript = new vm.Script(jsCode);
|
|
19
|
-
chainScript.runInContext(context);
|
|
20
|
-
return context.chain.cssOutput;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// CSS Minification Function
|
|
24
|
-
const minifyCss = (css) => {
|
|
25
|
-
const output = new CleanCSS().minify(css);
|
|
26
|
-
if (output.errors.length > 0) {
|
|
27
|
-
console.error('CSS Minification Errors:', output.errors);
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return output.styles;
|
|
27
|
+
chainScript.runInContext(context);
|
|
28
|
+
return context.chain.cssOutput;
|
|
31
29
|
};
|
|
32
30
|
|
|
33
|
-
// FUNCTION TO CONVERT JS CODES TO CSS CODE
|
|
34
|
-
const
|
|
35
|
-
/*const allowedExtensions = ['.jcss'];
|
|
36
|
-
const fileExt = path.extname(inputFile).toLowerCase();
|
|
37
|
-
|
|
38
|
-
if (!allowedExtensions.includes(fileExt)) {
|
|
39
|
-
throw new Error(`Invalid file extension: ${fileExt}. Only .jcss files are allowed.`);
|
|
40
|
-
}*/
|
|
41
|
-
const input = path.resolve(inputFile);
|
|
42
|
-
const content = fs.readFileSync(input, 'utf8');
|
|
31
|
+
// FUNCTION TO CONVERT JS CODES TO CSS CODE (FIRST STEP)
|
|
32
|
+
const processJavascriptBlocks = (content) => {
|
|
43
33
|
const blocks = content.split(/<@([\s\S]*?)@>/gm);
|
|
44
34
|
let outputCSS = '';
|
|
45
35
|
|
|
@@ -54,43 +44,235 @@ const processor = (inputFile, outputFile) => {
|
|
|
54
44
|
outputCSS += outputProcessScript;
|
|
55
45
|
}
|
|
56
46
|
} catch (err) {
|
|
57
|
-
console.error(`Error processing script block
|
|
58
|
-
throw err;
|
|
47
|
+
console.error(`Error processing script block:`, err.stack);
|
|
48
|
+
throw err;
|
|
59
49
|
}
|
|
60
50
|
}
|
|
61
51
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
|
|
53
|
+
return outputCSS.trim();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// NEW: Validate CSS (check for unclosed blocks)
|
|
57
|
+
const validateCSS = (css) => {
|
|
58
|
+
const openBraces = (css.match(/{/g) || []).length;
|
|
59
|
+
const closeBraces = (css.match(/}/g) || []).length;
|
|
60
|
+
|
|
61
|
+
if (openBraces !== closeBraces) {
|
|
62
|
+
console.error(`CSS syntax error: Unclosed blocks (${openBraces} opening vs ${closeBraces} closing braces)`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Modified minification function with source map support
|
|
69
|
+
|
|
70
|
+
const processAndMinifyCss = async (css, inputFile, outputFile) => {
|
|
71
|
+
// First validate the CSS
|
|
72
|
+
if (!validateCSS(css)) {
|
|
73
|
+
throw new Error('Invalid CSS syntax - check for missing braces');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Step 1: Apply prefixer (if enabled)
|
|
77
|
+
let processedCss = css;
|
|
78
|
+
let sourceMap = null;
|
|
79
|
+
|
|
80
|
+
if (prefixerConfig.enabled) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await prefixer.process(css, {
|
|
83
|
+
from: inputFile,
|
|
84
|
+
to: outputFile,
|
|
85
|
+
map: prefixerConfig.sourceMap !== false
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
processedCss = result.css;
|
|
89
|
+
sourceMap = result.map;
|
|
90
|
+
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error('⚠Prefixer error:', err.message);
|
|
93
|
+
processedCss = css;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Step 2: Minify
|
|
98
|
+
const output = new CleanCSS().minify(processedCss);
|
|
99
|
+
if (output.errors.length > 0) {
|
|
100
|
+
console.error('CSS Minification Errors:', output.errors);
|
|
101
|
+
return { css: null, map: null };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let finalCss = output.styles;
|
|
105
|
+
|
|
106
|
+
if (sourceMap && !prefixerConfig.sourceMapInline) {
|
|
107
|
+
const mapFileName = path.basename(`${outputFile}.map`);
|
|
108
|
+
finalCss += `\n/*# sourceMappingURL=${mapFileName} */`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { css: finalCss, map: sourceMap };
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Main processor function - FIXED ORDER
|
|
115
|
+
|
|
116
|
+
const processor = async (inputFile, outputFile) => {
|
|
117
|
+
try {
|
|
118
|
+
const input = path.resolve(inputFile);
|
|
119
|
+
const output = path.resolve(outputFile);
|
|
120
|
+
const content = fs.readFileSync(input, 'utf8');
|
|
121
|
+
|
|
122
|
+
// STEP 1: Process JavaScript blocks first
|
|
123
|
+
const processedCSS = processJavascriptBlocks(content);
|
|
124
|
+
|
|
125
|
+
// STEP 2: Validate the CSS
|
|
126
|
+
if (!validateCSS(processedCSS)) {
|
|
127
|
+
throw new Error('Invalid CSS syntax');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// STEP 3: Apply prefixer and minify with source maps
|
|
131
|
+
|
|
132
|
+
const result = await processAndMinifyCss(processedCSS, input, output);
|
|
133
|
+
if (result.css) {
|
|
134
|
+
fs.writeFileSync(output, result.css, 'utf8');
|
|
135
|
+
|
|
136
|
+
//Write source map if generated
|
|
137
|
+
if (result.map) {
|
|
138
|
+
const mapFile = `${output}.map`;
|
|
139
|
+
fs.writeFileSync(mapFile, result.map, 'utf8');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (prefixerConfig.enabled) {
|
|
143
|
+
console.log(` Prefixer: ${prefixerConfig.mode} mode (${prefixerConfig.browsers.join(', ')})`);
|
|
144
|
+
}
|
|
145
|
+
//Show source map status
|
|
146
|
+
if (result.map) {
|
|
147
|
+
console.log(` Source maps: enabled`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error(`Failed to process ${inputFile}:`, err.message);
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
66
154
|
};
|
|
67
155
|
|
|
68
156
|
// Watch function
|
|
69
157
|
function watch(inputFile, outputFile) {
|
|
70
158
|
console.log(`Watching for changes in ${inputFile}...`);
|
|
71
|
-
chokidar.watch(inputFile).on('change', () => {
|
|
159
|
+
chokidar.watch(inputFile).on('change', async () => {
|
|
72
160
|
console.log(`File changed: ${inputFile}`);
|
|
73
|
-
|
|
161
|
+
try {
|
|
162
|
+
await processor(inputFile, outputFile);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error('Error during watch processing:', err);
|
|
165
|
+
}
|
|
74
166
|
});
|
|
75
167
|
}
|
|
76
168
|
|
|
169
|
+
// Parse CLI arguments
|
|
170
|
+
|
|
171
|
+
function parseArgs(args) {
|
|
172
|
+
const result = {
|
|
173
|
+
inputFile: null,
|
|
174
|
+
outputFile: null,
|
|
175
|
+
watchMode: false,
|
|
176
|
+
noPrefix: false,
|
|
177
|
+
browsers: null,
|
|
178
|
+
prefixerMode: 'auto',
|
|
179
|
+
// NEW: Add these
|
|
180
|
+
sourceMap: true, // Enable source maps by default
|
|
181
|
+
sourceMapInline: false
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < args.length; i++) {
|
|
185
|
+
const arg = args[i];
|
|
186
|
+
|
|
187
|
+
if (arg === '--watch') {
|
|
188
|
+
result.watchMode = true;
|
|
189
|
+
} else if (arg === '--no-prefix') {
|
|
190
|
+
result.noPrefix = true;
|
|
191
|
+
} else if (arg === '--prefixer-mode' && args[i + 1]) {
|
|
192
|
+
result.prefixerMode = args[i + 1];
|
|
193
|
+
i++;
|
|
194
|
+
} else if (arg === '--browsers' && args[i + 1]) {
|
|
195
|
+
result.browsers = args[i + 1].split(',');
|
|
196
|
+
i++;
|
|
197
|
+
// NEW: Add these two
|
|
198
|
+
} else if (arg === '--no-source-map') {
|
|
199
|
+
result.sourceMap = false;
|
|
200
|
+
} else if (arg === '--source-map-inline') {
|
|
201
|
+
result.sourceMapInline = true;
|
|
202
|
+
} else if (!result.inputFile) {
|
|
203
|
+
result.inputFile = arg;
|
|
204
|
+
} else if (!result.outputFile) {
|
|
205
|
+
result.outputFile = arg;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
77
212
|
// Main CLI logic
|
|
78
213
|
if (require.main === module) {
|
|
79
214
|
const args = process.argv.slice(2);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
215
|
+
const cliOptions = parseArgs(args);
|
|
216
|
+
|
|
217
|
+
if (!cliOptions.inputFile || !cliOptions.outputFile) {
|
|
218
|
+
console.log(`
|
|
219
|
+
ChainCSS - JavaScript-powered CSS preprocessor
|
|
220
|
+
|
|
221
|
+
Usage:
|
|
222
|
+
chaincss <inputFile> <outputFile> [options]
|
|
83
223
|
|
|
84
|
-
|
|
85
|
-
|
|
224
|
+
Options:
|
|
225
|
+
--watch Watch for changes
|
|
226
|
+
--no-prefix Disable automatic prefixing
|
|
227
|
+
--browsers <list> Browser support list (comma-separated)
|
|
228
|
+
Example: --browsers ">1%,last 2 versions,not IE 11"
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
chaincss style.jcss style.css
|
|
232
|
+
chaincss style.jcss style.css --watch
|
|
233
|
+
chaincss style.jcss style.css --browsers ">5%,last 2 safari versions"
|
|
234
|
+
chaincss style.jcss style.css --no-prefix
|
|
235
|
+
`);
|
|
86
236
|
process.exit(1);
|
|
87
237
|
}
|
|
88
238
|
|
|
89
|
-
|
|
239
|
+
// sourceMap
|
|
240
|
+
if (cliOptions.sourceMap !== undefined) {
|
|
241
|
+
prefixerConfig.sourceMap = cliOptions.sourceMap;
|
|
242
|
+
}
|
|
243
|
+
if (cliOptions.sourceMapInline) {
|
|
244
|
+
prefixerConfig.sourceMapInline = true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Then apply to prefixer:
|
|
248
|
+
if (cliOptions.prefixerMode) {
|
|
249
|
+
prefixerConfig.mode = cliOptions.prefixerMode;
|
|
250
|
+
}
|
|
90
251
|
|
|
91
|
-
|
|
92
|
-
|
|
252
|
+
// Apply CLI options
|
|
253
|
+
if (cliOptions.noPrefix) {
|
|
254
|
+
prefixerConfig.enabled = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (cliOptions.browsers) {
|
|
258
|
+
prefixerConfig.browsers = cliOptions.browsers;
|
|
259
|
+
// Re-initialize prefixer with new config
|
|
260
|
+
Object.assign(prefixer, new ChainCSSPrefixer(prefixerConfig));
|
|
93
261
|
}
|
|
262
|
+
|
|
263
|
+
// Run processor
|
|
264
|
+
(async () => {
|
|
265
|
+
try {
|
|
266
|
+
await processor(cliOptions.inputFile, cliOptions.outputFile);
|
|
267
|
+
|
|
268
|
+
if (cliOptions.watchMode) {
|
|
269
|
+
watch(cliOptions.inputFile, cliOptions.outputFile);
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error('Fatal error:', err);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
})();
|
|
94
276
|
}
|
|
95
277
|
|
|
96
|
-
module.exports = { processor, watch };
|
|
278
|
+
module.exports = { processor, watch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melcanz85/chaincss",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "A simple package transpiler for js to css",
|
|
5
5
|
"main": "chaincss.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/melcanz08/chaincss.git"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.
|
|
14
|
+
"homepage": "https://melcanz08.github.io/chaincss-website",
|
|
15
15
|
"bin": {
|
|
16
16
|
"chaincss": "chaincss.js"
|
|
17
17
|
},
|
|
@@ -19,6 +19,26 @@
|
|
|
19
19
|
"chokidar": "^3.5.3",
|
|
20
20
|
"clean-css": "^5.3.3"
|
|
21
21
|
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"autoprefixer": "^10.0.0",
|
|
24
|
+
"browserslist": "^4.28.1",
|
|
25
|
+
"caniuse-db": "^1.0.30001770",
|
|
26
|
+
"postcss": "^8.5.6"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"autoprefixer": {
|
|
30
|
+
"optional": true
|
|
31
|
+
},
|
|
32
|
+
"postcss": {
|
|
33
|
+
"optional": true
|
|
34
|
+
},
|
|
35
|
+
"browserslist": {
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"caniuse-db": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
22
42
|
"keywords": [
|
|
23
43
|
"css",
|
|
24
44
|
"js",
|
package/prefixer.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
let postcss, browserslist, caniuse, autoprefixer;
|
|
2
|
+
|
|
3
|
+
// Try to load optional dependencies
|
|
4
|
+
try {
|
|
5
|
+
postcss = require('postcss');
|
|
6
|
+
browserslist = require('browserslist');
|
|
7
|
+
caniuse = require('caniuse-db/fulldata-json/data-2.0.json');
|
|
8
|
+
} catch (err) {
|
|
9
|
+
// Optional deps not installed - will use lightweight mode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Try to load Autoprefixer (optional)
|
|
13
|
+
try {
|
|
14
|
+
autoprefixer = require('autoprefixer');
|
|
15
|
+
} catch (err) {
|
|
16
|
+
// Autoprefixer not installed - will use built-in
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class ChainCSSPrefixer {
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
browsers: config.browsers || ['> 0.5%', 'last 2 versions', 'not dead'],
|
|
23
|
+
enabled: config.enabled !== false,
|
|
24
|
+
mode: config.mode || 'auto',
|
|
25
|
+
sourceMap: config.sourceMap !== false, // Enable source maps by default
|
|
26
|
+
sourceMapInline: config.sourceMapInline || false,
|
|
27
|
+
...config
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Check what's available
|
|
31
|
+
this.hasBuiltInDeps = !!(postcss && browserslist && caniuse);
|
|
32
|
+
this.hasAutoprefixer = !!autoprefixer;
|
|
33
|
+
|
|
34
|
+
// Determine which mode to use
|
|
35
|
+
this.prefixerMode = this.determineMode();
|
|
36
|
+
|
|
37
|
+
// Built-in prefixer data
|
|
38
|
+
this.caniuseData = caniuse ? caniuse.data : null;
|
|
39
|
+
this.commonProperties = this.getCommonProperties();
|
|
40
|
+
this.specialValues = {
|
|
41
|
+
'display': ['flex', 'inline-flex', 'grid', 'inline-grid'],
|
|
42
|
+
'background-clip': ['text'],
|
|
43
|
+
'position': ['sticky']
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.browserPrefixMap = {
|
|
47
|
+
'chrome': 'webkit', 'safari': 'webkit', 'firefox': 'moz',
|
|
48
|
+
'ie': 'ms', 'edge': 'webkit', 'ios_saf': 'webkit',
|
|
49
|
+
'and_chr': 'webkit', 'android': 'webkit', 'opera': 'webkit',
|
|
50
|
+
'op_mob': 'webkit', 'samsung': 'webkit', 'and_ff': 'moz'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.targetBrowsers = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
determineMode() {
|
|
58
|
+
// User explicitly wants full mode but Autoprefixer not installed
|
|
59
|
+
if (this.config.mode === 'full' && !this.hasAutoprefixer) {
|
|
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');
|
|
62
|
+
return 'lightweight';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// User explicitly wants lightweight mode
|
|
66
|
+
if (this.config.mode === 'lightweight') {
|
|
67
|
+
return 'lightweight';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// User wants full mode and it's available
|
|
71
|
+
if (this.config.mode === 'full' && this.hasAutoprefixer) {
|
|
72
|
+
return 'full';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Auto mode: use full if available, otherwise lightweight
|
|
76
|
+
if (this.config.mode === 'auto') {
|
|
77
|
+
return this.hasAutoprefixer ? 'full' : 'lightweight';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return 'lightweight';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async process(cssString, options = {}) {
|
|
84
|
+
if (!this.config.enabled) {
|
|
85
|
+
return { css: cssString, map: null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Set up source map options
|
|
90
|
+
const mapOptions = {
|
|
91
|
+
inline: this.config.sourceMapInline,
|
|
92
|
+
annotation: false, // We'll add the comment ourselves
|
|
93
|
+
sourcesContent: true
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (this.prefixerMode === 'full') {
|
|
97
|
+
return await this.processWithAutoprefixer(cssString, options, mapOptions);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return await this.processWithBuiltIn(cssString, options, mapOptions);
|
|
101
|
+
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('⚠️ Prefixer error:', err.message);
|
|
104
|
+
return { css: cssString, map: null };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 🚀 Full mode with Autoprefixer
|
|
109
|
+
async processWithAutoprefixer(cssString, options, mapOptions) {
|
|
110
|
+
const from = options.from || 'input.css';
|
|
111
|
+
const to = options.to || 'output.css';
|
|
112
|
+
|
|
113
|
+
const result = await postcss([
|
|
114
|
+
autoprefixer({ overrideBrowserslist: this.config.browsers })
|
|
115
|
+
]).process(cssString, {
|
|
116
|
+
from,
|
|
117
|
+
to,
|
|
118
|
+
map: this.config.sourceMap ? mapOptions : false
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
css: result.css,
|
|
123
|
+
map: result.map ? result.map.toString() : null
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 🔧 Lightweight mode with built-in prefixer
|
|
128
|
+
async processWithBuiltIn(cssString, options, mapOptions) {
|
|
129
|
+
if (!this.hasBuiltInDeps) {
|
|
130
|
+
return { css: cssString, map: null };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.targetBrowsers = browserslist(this.config.browsers);
|
|
134
|
+
|
|
135
|
+
const from = options.from || 'input.css';
|
|
136
|
+
const to = options.to || 'output.css';
|
|
137
|
+
|
|
138
|
+
const result = await postcss([
|
|
139
|
+
this.createBuiltInPlugin()
|
|
140
|
+
]).process(cssString, {
|
|
141
|
+
from,
|
|
142
|
+
to,
|
|
143
|
+
map: this.config.sourceMap ? mapOptions : false
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
css: result.css,
|
|
148
|
+
map: result.map ? result.map.toString() : null
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
createBuiltInPlugin() {
|
|
153
|
+
return (root) => {
|
|
154
|
+
root.walkDecls(decl => {
|
|
155
|
+
this.processBuiltInDeclaration(decl);
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
processBuiltInDeclaration(decl) {
|
|
161
|
+
const { prop, value } = decl;
|
|
162
|
+
|
|
163
|
+
if (this.commonProperties.includes(prop)) {
|
|
164
|
+
this.addPrefixesFromCaniuse(decl);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.specialValues[prop]?.includes(value)) {
|
|
168
|
+
this.addSpecialValuePrefixes(decl);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
addPrefixesFromCaniuse(decl) {
|
|
173
|
+
if (!this.caniuseData) return;
|
|
174
|
+
|
|
175
|
+
const feature = this.findFeature(decl.prop);
|
|
176
|
+
if (!feature) return;
|
|
177
|
+
|
|
178
|
+
const prefixes = new Set();
|
|
179
|
+
|
|
180
|
+
this.targetBrowsers.forEach(browser => {
|
|
181
|
+
const [id, versionStr] = browser.split(' ');
|
|
182
|
+
const version = parseFloat(versionStr.split('-')[0]);
|
|
183
|
+
const stats = feature.stats[id];
|
|
184
|
+
|
|
185
|
+
if (stats) {
|
|
186
|
+
const versions = Object.keys(stats)
|
|
187
|
+
.map(v => parseFloat(v.split('-')[0]))
|
|
188
|
+
.filter(v => !isNaN(v))
|
|
189
|
+
.sort((a, b) => a - b);
|
|
190
|
+
|
|
191
|
+
const closestVersion = versions.find(v => v <= version) || versions[0];
|
|
192
|
+
|
|
193
|
+
if (closestVersion) {
|
|
194
|
+
const support = stats[closestVersion.toString()];
|
|
195
|
+
if (support && support.includes('x')) {
|
|
196
|
+
const prefix = this.browserPrefixMap[id.split('-')[0]];
|
|
197
|
+
if (prefix) prefixes.add(prefix);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
prefixes.forEach(prefix => {
|
|
204
|
+
decl.cloneBefore({
|
|
205
|
+
prop: `-${prefix}-${decl.prop}`,
|
|
206
|
+
value: decl.value
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
addSpecialValuePrefixes(decl) {
|
|
212
|
+
const { prop, value } = decl;
|
|
213
|
+
|
|
214
|
+
if (prop === 'display') {
|
|
215
|
+
if (value === 'flex' || value === 'inline-flex') {
|
|
216
|
+
decl.cloneBefore({ prop: 'display', value: `-webkit-${value}` });
|
|
217
|
+
decl.cloneBefore({
|
|
218
|
+
prop: 'display',
|
|
219
|
+
value: value === 'flex' ? '-ms-flexbox' : '-ms-inline-flexbox'
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (value === 'grid' || value === 'inline-grid') {
|
|
223
|
+
decl.cloneBefore({
|
|
224
|
+
prop: 'display',
|
|
225
|
+
value: value === 'grid' ? '-ms-grid' : '-ms-inline-grid'
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (prop === 'background-clip' && value === 'text') {
|
|
231
|
+
decl.cloneBefore({ prop: '-webkit-background-clip', value: 'text' });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (prop === 'position' && value === 'sticky') {
|
|
235
|
+
decl.cloneBefore({ prop: 'position', value: '-webkit-sticky' });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
findFeature(property) {
|
|
240
|
+
if (!this.caniuseData) return null;
|
|
241
|
+
|
|
242
|
+
const featureMap = {
|
|
243
|
+
'transform': 'transforms2d',
|
|
244
|
+
'transform-origin': 'transforms2d',
|
|
245
|
+
'transform-style': 'transforms3d',
|
|
246
|
+
'perspective': 'transforms3d',
|
|
247
|
+
'backface-visibility': 'transforms3d',
|
|
248
|
+
'transition': 'css-transitions',
|
|
249
|
+
'animation': 'css-animation',
|
|
250
|
+
'backdrop-filter': 'backdrop-filter',
|
|
251
|
+
'filter': 'css-filters',
|
|
252
|
+
'user-select': 'user-select-none',
|
|
253
|
+
'appearance': 'css-appearance',
|
|
254
|
+
'mask-image': 'css-masks',
|
|
255
|
+
'box-shadow': 'css-boxshadow',
|
|
256
|
+
'border-radius': 'border-radius',
|
|
257
|
+
'text-fill-color': 'text-stroke',
|
|
258
|
+
'text-stroke': 'text-stroke',
|
|
259
|
+
'background-clip': 'background-img-opts',
|
|
260
|
+
'flex': 'flexbox',
|
|
261
|
+
'flex-grow': 'flexbox',
|
|
262
|
+
'flex-shrink': 'flexbox',
|
|
263
|
+
'flex-basis': 'flexbox',
|
|
264
|
+
'justify-content': 'flexbox',
|
|
265
|
+
'align-items': 'flexbox',
|
|
266
|
+
'grid': 'css-grid',
|
|
267
|
+
'grid-template': 'css-grid',
|
|
268
|
+
'grid-column': 'css-grid',
|
|
269
|
+
'grid-row': 'css-grid'
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const featureId = featureMap[property];
|
|
273
|
+
return featureId ? this.caniuseData[featureId] : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getCommonProperties() {
|
|
277
|
+
return [
|
|
278
|
+
'transform', 'transform-origin', 'transform-style',
|
|
279
|
+
'transition', 'transition-property', 'transition-duration', 'transition-timing-function',
|
|
280
|
+
'animation', 'animation-name', 'animation-duration', 'animation-timing-function',
|
|
281
|
+
'animation-delay', 'animation-iteration-count', 'animation-direction',
|
|
282
|
+
'animation-fill-mode', 'animation-play-state',
|
|
283
|
+
'backdrop-filter', 'filter',
|
|
284
|
+
'user-select', 'appearance',
|
|
285
|
+
'text-fill-color', 'text-stroke', 'text-stroke-color', 'text-stroke-width',
|
|
286
|
+
'background-clip',
|
|
287
|
+
'mask-image', 'mask-clip', 'mask-composite', 'mask-origin',
|
|
288
|
+
'mask-position', 'mask-repeat', 'mask-size',
|
|
289
|
+
'box-shadow', 'border-radius', 'box-sizing',
|
|
290
|
+
'display', 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
|
|
291
|
+
'justify-content', 'align-items', 'align-self', 'align-content',
|
|
292
|
+
'grid', 'grid-template', 'grid-column', 'grid-row'
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = ChainCSSPrefixer;
|
package/transpiler.js
CHANGED
|
@@ -124,6 +124,7 @@ const chain = {
|
|
|
124
124
|
overflowY(oy){ this.catcher.overflowY = oy; return this; },
|
|
125
125
|
overflowWrap(ow){ this.catcher.overflowWrap = ow; return this; },
|
|
126
126
|
|
|
127
|
+
animation(a){ this.catcher.animation = a; return this; },
|
|
127
128
|
textFillColor(tfc){ this.catcher.textFillColor = tfc; return this; },
|
|
128
129
|
backgroundClip(bc){ this.catcher.backgroundClip = bc; return this; },
|
|
129
130
|
gridTemplateColumns(gtc){ this.catcher.gridTemplateColumns = gtc; return this; },
|