@melcanz85/chaincss 1.11.0 → 1.11.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/node/chaincss.js CHANGED
@@ -4,17 +4,13 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const chokidar = require('chokidar');
6
6
  const CleanCSS = require('clean-css');
7
+ const { $, run, compile: originalCompile, chain } = require('./btt');
7
8
  const ChainCSSPrefixer = require('./prefixer.js');
8
- const fileCache = new Map();
9
9
  const strVal = require('./strVal.js');
10
10
  const { AtomicOptimizer } = require('./atomic-optimizer');
11
11
  const { CacheManager } = require('./cache-manager');
12
- const { $, run, compile: originalCompile, chain } = require('./btt');
13
-
14
- // Atomic optimizer instance (will be initialized after config)
12
+ const fileCache = new Map();
15
13
  let atomicOptimizer = null;
16
-
17
- // Default configuration
18
14
  let config = {
19
15
  atomic: {
20
16
  enabled: false,
@@ -31,15 +27,10 @@ let config = {
31
27
  sourceMap: true,
32
28
  sourceMapInline: false
33
29
  }
34
- };
35
-
36
- // Prefixer instance
30
+ };
37
31
  let prefixer = new ChainCSSPrefixer(config.prefixer);
38
-
39
- // From default to user configuration
40
32
  function deft_to_userConf(target, source) {
41
33
  const result = { ...target };
42
-
43
34
  for (const key in source) {
44
35
  if (source[key] instanceof Object && key in target) {
45
36
  result[key] = deft_to_userConf(target[key], source[key]);
@@ -50,36 +41,25 @@ function deft_to_userConf(target, source) {
50
41
 
51
42
  return result;
52
43
  }
53
-
54
- // Ensure config file exists
55
44
  const ensureConfigExists = () => {
56
45
  const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
57
46
  const configExists = fs.existsSync(configPath);
58
-
59
47
  if (!configExists && !process.env.CHAINCSS_SKIP_CONFIG) {
60
48
  const defaultConfig = strVal.userConf;
61
-
62
49
  fs.writeFileSync(configPath, defaultConfig);
63
50
  console.log('-- Successfully created config file: ./chaincss.config.cjs\n');
64
51
  }
65
52
  };
66
-
67
- // Load user config
68
53
  const loadUserConfig = () => {
69
54
  const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
70
-
71
55
  if (fs.existsSync(configPath)) {
72
56
  try {
73
57
  const userConfig = require(configPath);
74
58
  config = deft_to_userConf(config, userConfig);
75
-
76
- // CRITICAL: Ensure browsers is ALWAYS an array
77
59
  if (config.prefixer) {
78
- // If browsers is a string, convert to array
79
60
  if (typeof config.prefixer.browsers === 'string') {
80
61
  config.prefixer.browsers = config.prefixer.browsers.split(',').map(b => b.trim());
81
62
  }
82
- // If browsers is not an array at this point, set default
83
63
  if (!Array.isArray(config.prefixer.browsers)) {
84
64
  config.prefixer.browsers = ['> 0.5%', 'last 2 versions', 'not dead'];
85
65
  }
@@ -89,23 +69,16 @@ const loadUserConfig = () => {
89
69
  }
90
70
  }
91
71
  };
92
-
93
- // Initialize atomic optimizer based on config
94
72
  const initAtomicOptimizer = () => {
95
73
  if (config.atomic.enabled) {
96
74
  atomicOptimizer = new AtomicOptimizer(config.atomic);
97
- console.log('-- Atomic optimizer enabled\n');
98
75
  } else {
99
76
  console.log('-- Atomic optimizer disabled\n');
100
77
  }
101
78
  };
102
-
103
- // Initialization of prefixer object
104
79
  const initPrefixer = () => {
105
80
  prefixer = new ChainCSSPrefixer(config.prefixer);
106
81
  };
107
-
108
- // Parse CLI arguments
109
82
  function parseArgs(args) {
110
83
  const result = {
111
84
  inputFile: null,
@@ -141,11 +114,8 @@ function parseArgs(args) {
141
114
  result.outputFile = arg;
142
115
  }
143
116
  }
144
-
145
117
  return result;
146
118
  }
147
-
148
- // Apply CLI options to config
149
119
  const applyCliOptions = (cliOptions) => {
150
120
  if (cliOptions.sourceMap !== null) {
151
121
  config.prefixer.sourceMap = cliOptions.sourceMap;
@@ -163,8 +133,6 @@ const applyCliOptions = (cliOptions) => {
163
133
  config.prefixer.browsers = cliOptions.browsers;
164
134
  }
165
135
  };
166
-
167
- // Watch function
168
136
  function watch(inputFile, outputFile) {
169
137
  chokidar.watch(inputFile).on('change', async () => {
170
138
  try {
@@ -174,39 +142,29 @@ function watch(inputFile, outputFile) {
174
142
  }
175
143
  });
176
144
  }
177
-
178
- // Create the wrapped compile function
179
145
  const compile = (obj) => {
180
146
  originalCompile(obj);
181
-
147
+ let css = chain.cssOutput || '';
182
148
  if (atomicOptimizer && config.atomic.enabled) {
183
- const optimized = atomicOptimizer.optimize(obj);
184
- chain.cssOutput = optimized;
185
- return optimized;
149
+ css = atomicOptimizer.optimize(obj);
150
+ chain.cssOutput = css;
186
151
  }
187
- return chain.cssOutput;
152
+ return css;
188
153
  };
189
-
190
- // Create a combined module for VM sandbox
191
154
  const transpilerModule = {
192
155
  $,
193
156
  run,
194
- compile,
157
+ compile: originalCompile,
195
158
  chain
196
159
  };
197
-
198
- // Recursive file processing function
199
160
  const processJCSSFile = (filePath) => {
200
161
  if (fileCache.has(filePath)) {
201
162
  return fileCache.get(filePath);
202
163
  }
203
-
204
164
  if (!fs.existsSync(filePath)) {
205
165
  throw new Error(`File not found: ${filePath}`);
206
166
  }
207
-
208
167
  const content = fs.readFileSync(filePath, 'utf8');
209
-
210
168
  const vm = new NodeVM({
211
169
  console: 'inherit',
212
170
  timeout: 5000,
@@ -228,13 +186,11 @@ const processJCSSFile = (filePath) => {
228
186
  root: './'
229
187
  }
230
188
  });
231
-
232
189
  const wrappedContent = `
233
190
  module.exports = {};
234
191
  ${content}
235
192
  module.exports;
236
193
  `;
237
-
238
194
  try {
239
195
  const exports = vm.run(wrappedContent, filePath);
240
196
  fileCache.set(filePath, exports);
@@ -244,7 +200,6 @@ const processJCSSFile = (filePath) => {
244
200
  throw err;
245
201
  }
246
202
  };
247
-
248
203
  const processScript = (scriptBlock, filename) => {
249
204
  const vm = new NodeVM({
250
205
  console: 'inherit',
@@ -267,18 +222,16 @@ const processScript = (scriptBlock, filename) => {
267
222
  root: './'
268
223
  }
269
224
  });
270
-
271
225
  const jsCode = scriptBlock.trim();
272
-
273
226
  try {
274
227
  const result = vm.run(jsCode, filename);
275
- return transpilerModule.chain.cssOutput;
228
+ const css = vm.sandbox.chain?.cssOutput || '';
229
+ return css;
276
230
  } catch (err) {
277
231
  console.error(`Error processing script in ${filename}:`, err.message);
278
232
  throw err;
279
233
  }
280
234
  };
281
-
282
235
  const processJavascriptBlocks = (content, inputpath) => {
283
236
  const blocks = content.split(/<@([\s\S]*?)@>/gm);
284
237
  let outputCSS = '';
@@ -289,9 +242,9 @@ const processJavascriptBlocks = (content, inputpath) => {
289
242
  } else {
290
243
  const scriptBlock = blocks[i];
291
244
  try {
292
- const outputProcessScript = processScript(scriptBlock, inputpath);
293
- if (typeof outputProcessScript !== 'object' && typeof outputProcessScript !== 'undefined') {
294
- outputCSS += outputProcessScript;
245
+ const blockCSS = processScript(scriptBlock, inputpath);
246
+ if (typeof blockCSS !== 'object' && typeof blockCSS !== 'undefined') {
247
+ outputCSS += blockCSS;
295
248
  }
296
249
  } catch (err) {
297
250
  console.error(`Error processing script block:`, err.stack);
@@ -301,8 +254,6 @@ const processJavascriptBlocks = (content, inputpath) => {
301
254
  }
302
255
  return outputCSS.trim();
303
256
  };
304
-
305
- // Validate CSS
306
257
  const validateCSS = (css) => {
307
258
  const openBraces = (css.match(/{/g) || []).length;
308
259
  const closeBraces = (css.match(/}/g) || []).length;
@@ -312,15 +263,12 @@ const validateCSS = (css) => {
312
263
  }
313
264
  return true;
314
265
  };
315
-
316
266
  const processAndMinifyCss = async (css, inputFile, outputFile) => {
317
267
  if (!validateCSS(css)) {
318
268
  throw new Error('Invalid CSS syntax - check for missing braces');
319
269
  }
320
-
321
270
  let processedCss = css;
322
271
  let sourceMapFromPrefixer = null;
323
-
324
272
  if (config.prefixer.enabled) {
325
273
  try {
326
274
  const result = await prefixer.process(css, {
@@ -335,93 +283,61 @@ const processAndMinifyCss = async (css, inputFile, outputFile) => {
335
283
  processedCss = css;
336
284
  }
337
285
  }
338
-
339
286
  const minifyOptions = {
340
287
  sourceMap: config.prefixer.sourceMap === true,
341
288
  sourceMapInlineSources: true
342
289
  };
343
-
344
290
  const output = new CleanCSS(minifyOptions).minify(processedCss);
345
-
346
291
  if (output.errors.length > 0) {
347
292
  console.error('CSS Minification Errors:', output.errors);
348
293
  return { css: null, map: null };
349
294
  }
350
-
351
295
  let finalCss = output.styles;
352
296
  let finalSourceMap = output.sourceMap ? JSON.stringify(output.sourceMap) : sourceMapFromPrefixer;
353
-
354
297
  if (finalSourceMap && !config.prefixer.sourceMapInline) {
355
298
  const mapFileName = path.basename(`${outputFile}.map`);
356
299
  finalCss += `\n/*# sourceMappingURL=${mapFileName} */`;
357
300
  }
358
-
359
301
  return { css: finalCss, map: finalSourceMap };
360
302
  };
361
-
362
- // Main processor function
363
303
  const processor = async (inputFile, outputFile) => {
364
304
  try {
365
305
  const input = path.resolve(inputFile);
366
306
  const output = path.resolve(outputFile);
367
307
  const content = fs.readFileSync(input, 'utf8');
368
-
369
308
  const processedCSS = processJavascriptBlocks(content, input);
370
-
371
309
  if (!validateCSS(processedCSS)) {
372
310
  throw new Error('Invalid CSS syntax');
373
311
  }
374
-
375
312
  const stylePath = path.join(output, 'global.css');
376
313
  const result = await processAndMinifyCss(processedCSS, input, stylePath);
377
-
378
314
  if (result.css) {
379
315
  fs.writeFileSync(stylePath, result.css, 'utf8');
380
-
381
316
  if (result.map) {
382
317
  const mapFile = `${stylePath}.map`;
383
318
  fs.writeFileSync(mapFile, result.map, 'utf8');
384
319
  }
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`);
389
320
  }
390
321
  } catch (err) {
391
322
  console.error(`Failed to process ${inputFile}:`, err.message);
392
323
  throw err;
393
324
  }
394
325
  };
395
-
396
- // Main CLI logic
397
326
  if (require.main === module) {
398
- // Step 1: Ensure config exists
399
327
  ensureConfigExists();
400
-
401
- // Step 2: Load user config
402
328
  loadUserConfig();
403
-
404
- // Step 3: Parse CLI arguments
405
329
  const args = process.argv.slice(2);
406
330
  const cliOptions = parseArgs(args);
407
-
408
331
  if (!cliOptions.inputFile || !cliOptions.outputFile) {
409
332
  console.log(strVal.cli_opt_guide);
410
333
  process.exit(1);
411
334
  }
412
-
413
- // Step 4: Apply CLI options (overrides config)
414
335
  applyCliOptions(cliOptions);
415
-
416
- // Step 5: Initialize components with final config (ONCE)
417
336
  initAtomicOptimizer();
418
- initPrefixer(); // Only called once now!
419
-
420
- // Step 6: Run processor
337
+ initPrefixer();
421
338
  (async () => {
422
339
  try {
423
340
  await processor(cliOptions.inputFile, cliOptions.outputFile);
424
-
425
341
  if (cliOptions.watchMode) {
426
342
  console.log('-- Watching for changes...\n');
427
343
  watch(cliOptions.inputFile, cliOptions.outputFile);
@@ -432,7 +348,6 @@ if (require.main === module) {
432
348
  }
433
349
  })();
434
350
  }
435
-
436
351
  module.exports = {
437
352
  processor,
438
353
  watch,
package/node/index.js CHANGED
@@ -1,13 +1,2 @@
1
- const { $, run, compile, chain, tokens, createTokens } = require('./btt');
2
- const { processor, watch } = require('./chaincss');
3
-
4
- module.exports = {
5
- $,
6
- run,
7
- compile,
8
- processor,
9
- watch,
10
- chain,
11
- tokens,
12
- createTokens
13
- };
1
+ const {$,run,compile,chain,tokens,createTokens} = require('./btt');
2
+ module.exports = {$,run,compile,chain,tokens,createTokens};
package/node/prefixer.js CHANGED
@@ -1,21 +1,14 @@
1
1
  let postcss, browserslist, caniuse, autoprefixer;
2
-
3
- // Try to load optional dependencies
4
2
  try {
5
3
  postcss = require('postcss');
6
4
  browserslist = require('browserslist');
7
5
  caniuse = require('caniuse-db/fulldata-json/data-2.0.json');
8
6
  } catch (err) {
9
- // Optional deps not installed - will use lightweight mode
10
7
  }
11
-
12
- // Try to load Autoprefixer (optional)
13
8
  try {
14
9
  autoprefixer = require('autoprefixer');
15
10
  } catch (err) {
16
- // Autoprefixer not installed - will use built-in
17
11
  }
18
-
19
12
  class ChainCSSPrefixer {
20
13
  constructor(config = {}) {
21
14
  this.config = {
@@ -26,15 +19,9 @@ class ChainCSSPrefixer {
26
19
  sourceMapInline: config.sourceMapInline || false,
27
20
  ...config
28
21
  };
29
-
30
- // Check what's available
31
22
  this.hasBuiltInDeps = !!(postcss && browserslist && caniuse);
32
23
  this.hasAutoprefixer = !!autoprefixer;
33
-
34
- // Determine which mode to use
35
24
  this.prefixerMode = this.determineMode();
36
-
37
- // Built-in prefixer data
38
25
  this.caniuseData = caniuse ? caniuse.data : null;
39
26
  this.commonProperties = this.getCommonProperties();
40
27
  this.specialValues = {
@@ -42,75 +29,53 @@ class ChainCSSPrefixer {
42
29
  'background-clip': ['text'],
43
30
  'position': ['sticky']
44
31
  };
45
-
46
32
  this.browserPrefixMap = {
47
33
  'chrome': 'webkit', 'safari': 'webkit', 'firefox': 'moz',
48
34
  'ie': 'ms', 'edge': 'webkit', 'ios_saf': 'webkit',
49
35
  'and_chr': 'webkit', 'android': 'webkit', 'opera': 'webkit',
50
36
  'op_mob': 'webkit', 'samsung': 'webkit', 'and_ff': 'moz'
51
37
  };
52
-
53
38
  this.targetBrowsers = null;
54
39
  }
55
-
56
-
57
40
  determineMode() {
58
- // User explicitly wants full mode but Autoprefixer not installed
59
41
  if (this.config.mode === 'full' && !this.hasAutoprefixer) {
60
42
  console.warn('⚠️ Full mode requested but autoprefixer not installed. Falling back to lightweight mode.');
61
43
  console.warn(' To use full mode: npm install autoprefixer postcss caniuse-db browserslist\n');
62
44
  return 'lightweight';
63
45
  }
64
-
65
- // User explicitly wants lightweight mode
66
46
  if (this.config.mode === 'lightweight') {
67
47
  return 'lightweight';
68
48
  }
69
-
70
- // User wants full mode and it's available
71
49
  if (this.config.mode === 'full' && this.hasAutoprefixer) {
72
50
  return 'full';
73
51
  }
74
-
75
- // Auto mode: use full if available, otherwise lightweight
76
52
  if (this.config.mode === 'auto') {
77
53
  return this.hasAutoprefixer ? 'full' : 'lightweight';
78
54
  }
79
-
80
55
  return 'lightweight';
81
56
  }
82
-
83
57
  async process(cssString, options = {}) {
84
58
  if (!this.config.enabled) {
85
59
  return { css: cssString, map: null };
86
60
  }
87
-
88
61
  try {
89
- // Set up source map options
90
62
  const mapOptions = {
91
63
  inline: this.config.sourceMapInline,
92
64
  annotation: false,
93
65
  sourcesContent: true
94
66
  };
95
-
96
67
  if (this.prefixerMode === 'full') {
97
68
  return await this.processWithAutoprefixer(cssString, options, mapOptions);
98
69
  }
99
-
100
70
  return await this.processWithBuiltIn(cssString, options, mapOptions);
101
-
102
71
  } catch (err) {
103
72
  console.error('Prefixer error:', err.message);
104
73
  return { css: cssString, map: null };
105
74
  }
106
75
  }
107
-
108
- // Full mode with Autoprefixer
109
- async processWithAutoprefixer(cssString, options, mapOptions) {
110
-
76
+ async processWithAutoprefixer(cssString, options, mapOptions) {
111
77
  const from = options.from || 'input.css';
112
78
  const to = options.to || 'output.css';
113
-
114
79
  const result = await postcss([
115
80
  autoprefixer({ overrideBrowserslist: this.config.browsers })
116
81
  ]).process(cssString, {
@@ -118,19 +83,15 @@ class ChainCSSPrefixer {
118
83
  to,
119
84
  map: this.config.sourceMap ? mapOptions : false
120
85
  });
121
-
122
86
  return {
123
87
  css: result.css,
124
88
  map: result.map ? result.map.toString() : null
125
89
  };
126
90
  }
127
-
128
- // Lightweight mode with built-in prefixer
129
91
  async processWithBuiltIn(cssString, options, mapOptions) {
130
92
  if (!this.hasBuiltInDeps) {
131
93
  return { css: cssString, map: null };
132
94
  }
133
-
134
95
  this.targetBrowsers = browserslist(this.config.browsers);
135
96
  const from = options.from || 'input.css';
136
97
  const to = options.to || 'output.css';
@@ -141,13 +102,11 @@ class ChainCSSPrefixer {
141
102
  to,
142
103
  map: this.config.sourceMap ? mapOptions : false
143
104
  });
144
-
145
105
  return {
146
106
  css: result.css,
147
107
  map: result.map ? result.map.toString() : null
148
108
  };
149
109
  }
150
-
151
110
  createBuiltInPlugin() {
152
111
  return (root) => {
153
112
  root.walkDecls(decl => {
@@ -155,40 +114,30 @@ class ChainCSSPrefixer {
155
114
  });
156
115
  };
157
116
  }
158
-
159
117
  processBuiltInDeclaration(decl) {
160
118
  const { prop, value } = decl;
161
-
162
119
  if (this.commonProperties.includes(prop)) {
163
120
  this.addPrefixesFromCaniuse(decl);
164
121
  }
165
-
166
122
  if (this.specialValues[prop]?.includes(value)) {
167
123
  this.addSpecialValuePrefixes(decl);
168
124
  }
169
125
  }
170
-
171
126
  addPrefixesFromCaniuse(decl) {
172
127
  if (!this.caniuseData) return;
173
-
174
128
  const feature = this.findFeature(decl.prop);
175
129
  if (!feature) return;
176
-
177
130
  const prefixes = new Set();
178
-
179
131
  this.targetBrowsers.forEach(browser => {
180
132
  const [id, versionStr] = browser.split(' ');
181
133
  const version = parseFloat(versionStr.split('-')[0]);
182
134
  const stats = feature.stats[id];
183
-
184
135
  if (stats) {
185
136
  const versions = Object.keys(stats)
186
137
  .map(v => parseFloat(v.split('-')[0]))
187
138
  .filter(v => !isNaN(v))
188
139
  .sort((a, b) => a - b);
189
-
190
140
  const closestVersion = versions.find(v => v <= version) || versions[0];
191
-
192
141
  if (closestVersion) {
193
142
  const support = stats[closestVersion.toString()];
194
143
  if (support && support.includes('x')) {
@@ -206,10 +155,8 @@ class ChainCSSPrefixer {
206
155
  });
207
156
  });
208
157
  }
209
-
210
158
  addSpecialValuePrefixes(decl) {
211
159
  const { prop, value } = decl;
212
-
213
160
  if (prop === 'display') {
214
161
  if (value === 'flex' || value === 'inline-flex') {
215
162
  decl.cloneBefore({ prop: 'display', value: `-webkit-${value}` });
@@ -225,19 +172,16 @@ class ChainCSSPrefixer {
225
172
  });
226
173
  }
227
174
  }
228
-
229
175
  if (prop === 'background-clip' && value === 'text') {
230
176
  decl.cloneBefore({ prop: '-webkit-background-clip', value: 'text' });
231
177
  }
232
-
233
178
  if (prop === 'position' && value === 'sticky') {
234
179
  decl.cloneBefore({ prop: 'position', value: '-webkit-sticky' });
235
180
  }
236
181
  }
237
182
 
238
183
  findFeature(property) {
239
- if (!this.caniuseData) return null;
240
-
184
+ if (!this.caniuseData) return null;
241
185
  const featureMap = {
242
186
  'transform': 'transforms2d',
243
187
  'transform-origin': 'transforms2d',
@@ -267,11 +211,9 @@ class ChainCSSPrefixer {
267
211
  'grid-column': 'css-grid',
268
212
  'grid-row': 'css-grid'
269
213
  };
270
-
271
214
  const featureId = featureMap[property];
272
215
  return featureId ? this.caniuseData[featureId] : null;
273
216
  }
274
-
275
217
  getCommonProperties() {
276
218
  return [
277
219
  'transform', 'transform-origin', 'transform-style',
@@ -292,5 +234,4 @@ class ChainCSSPrefixer {
292
234
  ];
293
235
  }
294
236
  }
295
-
296
237
  module.exports = ChainCSSPrefixer;
package/node/strVal.js CHANGED
@@ -10,7 +10,7 @@ module.exports = {
10
10
  minify: true
11
11
  },
12
12
  prefixer: {
13
- mode: 'full',
13
+ mode: 'auto',
14
14
  browsers: ['> 0.5%', 'last 2 versions', 'not dead'],
15
15
  enabled: true,
16
16
  sourceMap: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melcanz85/chaincss",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "A simple package transpiler for js to css",
5
5
  "exports": {
6
6
  ".": {