@melcanz85/chaincss 1.11.5 → 1.12.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/node/chaincss.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- const {NodeVM} = require('vm2');
3
2
  const path = require('path');
4
3
  const fs = require('fs');
4
+ const Module = require('module');
5
5
  const chokidar = require('chokidar');
6
6
  const CleanCSS = require('clean-css');
7
7
  const { $, run, compile: originalCompile, chain } = require('./btt');
@@ -9,8 +9,11 @@ const ChainCSSPrefixer = require('./prefixer.js');
9
9
  const strVal = require('./strVal.js');
10
10
  const { AtomicOptimizer } = require('./atomic-optimizer');
11
11
  const { CacheManager } = require('./cache-manager');
12
+
12
13
  const fileCache = new Map();
14
+ const compiledCache = new Map();
13
15
  let atomicOptimizer = null;
16
+
14
17
  let config = {
15
18
  atomic: {
16
19
  enabled: false,
@@ -28,7 +31,9 @@ let config = {
28
31
  sourceMapInline: false
29
32
  }
30
33
  };
34
+
31
35
  let prefixer = new ChainCSSPrefixer(config.prefixer);
36
+
32
37
  function deft_to_userConf(target, source) {
33
38
  const result = { ...target };
34
39
  for (const key in source) {
@@ -38,9 +43,9 @@ function deft_to_userConf(target, source) {
38
43
  result[key] = source[key];
39
44
  }
40
45
  }
41
-
42
46
  return result;
43
47
  }
48
+
44
49
  const ensureConfigExists = () => {
45
50
  const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
46
51
  const configExists = fs.existsSync(configPath);
@@ -50,6 +55,7 @@ const ensureConfigExists = () => {
50
55
  console.log('-- Successfully created config file: ./chaincss.config.cjs\n');
51
56
  }
52
57
  };
58
+
53
59
  const loadUserConfig = () => {
54
60
  const configPath = path.join(process.cwd(), 'chaincss.config.cjs');
55
61
  if (fs.existsSync(configPath)) {
@@ -69,6 +75,7 @@ const loadUserConfig = () => {
69
75
  }
70
76
  }
71
77
  };
78
+
72
79
  const initAtomicOptimizer = () => {
73
80
  if (config.atomic.enabled) {
74
81
  atomicOptimizer = new AtomicOptimizer(config.atomic);
@@ -76,9 +83,11 @@ const initAtomicOptimizer = () => {
76
83
  console.log('-- Atomic optimizer disabled\n');
77
84
  }
78
85
  };
86
+
79
87
  const initPrefixer = () => {
80
88
  prefixer = new ChainCSSPrefixer(config.prefixer);
81
89
  };
90
+
82
91
  function parseArgs(args) {
83
92
  const result = {
84
93
  inputFile: null,
@@ -88,7 +97,8 @@ function parseArgs(args) {
88
97
  browsers: null,
89
98
  prefixerMode: null,
90
99
  sourceMap: null,
91
- sourceMapInline: false
100
+ sourceMapInline: false,
101
+ atomic: false
92
102
  };
93
103
 
94
104
  for (let i = 0; i < args.length; i++) {
@@ -108,6 +118,8 @@ function parseArgs(args) {
108
118
  result.sourceMap = false;
109
119
  } else if (arg === '--source-map-inline') {
110
120
  result.sourceMapInline = true;
121
+ } else if (arg === '--atomic') {
122
+ result.atomic = true;
111
123
  } else if (!result.inputFile) {
112
124
  result.inputFile = arg;
113
125
  } else if (!result.outputFile) {
@@ -116,6 +128,7 @@ function parseArgs(args) {
116
128
  }
117
129
  return result;
118
130
  }
131
+
119
132
  const applyCliOptions = (cliOptions) => {
120
133
  if (cliOptions.sourceMap !== null) {
121
134
  config.prefixer.sourceMap = cliOptions.sourceMap;
@@ -132,7 +145,11 @@ const applyCliOptions = (cliOptions) => {
132
145
  if (cliOptions.browsers) {
133
146
  config.prefixer.browsers = cliOptions.browsers;
134
147
  }
148
+ if (cliOptions.atomic) {
149
+ config.atomic.enabled = true;
150
+ }
135
151
  };
152
+
136
153
  function watch(inputFile, outputFile) {
137
154
  chokidar.watch(inputFile).on('change', async () => {
138
155
  try {
@@ -142,96 +159,117 @@ function watch(inputFile, outputFile) {
142
159
  }
143
160
  });
144
161
  }
162
+
145
163
  const compile = (obj) => {
146
164
  originalCompile(obj);
147
165
  let css = chain.cssOutput || '';
148
166
  if (atomicOptimizer && config.atomic.enabled) {
149
- css = atomicOptimizer.optimize(obj);
167
+ const result = atomicOptimizer.optimize(obj);
168
+ css = result.css;
150
169
  chain.cssOutput = css;
170
+ chain.classMap = result.map;
171
+ chain.atomicStats = result.stats;
151
172
  }
152
173
  return css;
153
174
  };
175
+
154
176
  const transpilerModule = {
155
177
  $,
156
178
  run,
157
179
  compile: originalCompile,
158
180
  chain
159
181
  };
182
+
183
+ // Native module-based JCSS file processor (replaces vm2)
160
184
  const processJCSSFile = (filePath) => {
161
- if (fileCache.has(filePath)) {
162
- return fileCache.get(filePath);
185
+ const abs = path.resolve(filePath);
186
+
187
+ if (fileCache.has(abs)) return fileCache.get(abs);
188
+
189
+ if (!fs.existsSync(abs)) {
190
+ throw new Error(`File not found: ${abs}`);
163
191
  }
164
- if (!fs.existsSync(filePath)) {
165
- throw new Error(`File not found: ${filePath}`);
192
+
193
+ const content = fs.readFileSync(abs, 'utf8');
194
+ const dirname = path.dirname(abs);
195
+
196
+ const m = new Module(abs, module.parent);
197
+ m.filename = abs;
198
+ m.paths = Module._nodeModulePaths(dirname);
199
+
200
+ const get = (relativePath) => processJCSSFile(path.resolve(dirname, relativePath));
201
+
202
+ let compiledFn = compiledCache.get(abs);
203
+ if (!compiledFn) {
204
+ compiledFn = new Function(
205
+ 'exports',
206
+ 'require',
207
+ 'module',
208
+ '__filename',
209
+ '__dirname',
210
+ '$',
211
+ 'run',
212
+ 'compile',
213
+ 'chain',
214
+ 'get',
215
+ content
216
+ );
217
+ compiledCache.set(abs, compiledFn);
166
218
  }
167
- const content = fs.readFileSync(filePath, 'utf8');
168
- const vm = new NodeVM({
169
- console: 'inherit',
170
- timeout: 5000,
171
- sandbox: {
172
- ...transpilerModule,
173
- get: (relativePath) => {
174
- const baseDir = path.dirname(filePath);
175
- const targetPath = path.resolve(baseDir, relativePath);
176
- return processJCSSFile(targetPath);
177
- },
178
- __filename: filePath,
179
- __dirname: path.dirname(filePath),
180
- module: { exports: {} },
181
- exports: {}
182
- },
183
- require: {
184
- external: true,
185
- builtin: ['path', 'fs'],
186
- root: './'
187
- }
188
- });
189
- const wrappedContent = `
190
- module.exports = {};
191
- ${content}
192
- module.exports;
193
- `;
219
+
194
220
  try {
195
- const exports = vm.run(wrappedContent, filePath);
196
- fileCache.set(filePath, exports);
197
- return exports;
221
+ compiledFn(
222
+ m.exports,
223
+ require,
224
+ m,
225
+ abs,
226
+ dirname,
227
+ $,
228
+ run,
229
+ originalCompile,
230
+ chain,
231
+ get
232
+ );
198
233
  } catch (err) {
199
- console.error(`Error processing ${filePath}:`, err.message);
234
+ console.error(`Error processing ${abs}:`, err.message);
200
235
  throw err;
201
236
  }
237
+
238
+ fileCache.set(abs, m.exports);
239
+ return m.exports;
202
240
  };
241
+
203
242
  const processScript = (scriptBlock, filename) => {
204
- const vm = new NodeVM({
205
- console: 'inherit',
206
- timeout: 5000,
207
- sandbox: {
208
- ...transpilerModule,
209
- get: (relativePath) => {
210
- const baseDir = path.dirname(filename);
211
- const targetPath = path.resolve(baseDir, relativePath);
212
- return processJCSSFile(targetPath);
213
- },
214
- __filename: filename,
215
- __dirname: path.dirname(filename),
216
- module: { exports: {} },
217
- require: (path) => require(path)
218
- },
219
- require: {
220
- external: true,
221
- builtin: ['path', 'fs'],
222
- root: './'
223
- }
224
- });
225
- const jsCode = scriptBlock.trim();
243
+ const dirname = path.dirname(filename);
244
+ const get = (relativePath) => processJCSSFile(path.resolve(dirname, relativePath));
245
+
246
+ chain.cssOutput = '';
247
+
248
+ let compiledFn = compiledCache.get(`script:${filename}`);
249
+ if (!compiledFn) {
250
+ compiledFn = new Function(
251
+ '$',
252
+ 'run',
253
+ 'compile',
254
+ 'chain',
255
+ 'get',
256
+ '__filename',
257
+ '__dirname',
258
+ scriptBlock
259
+ );
260
+ compiledCache.set(`script:${filename}`, compiledFn);
261
+ }
262
+
226
263
  try {
227
- const result = vm.run(jsCode, filename);
228
- const css = vm.sandbox.chain?.cssOutput || '';
229
- return css;
264
+ compiledFn($, run, originalCompile, chain, get, filename, dirname);
230
265
  } catch (err) {
231
266
  console.error(`Error processing script in ${filename}:`, err.message);
232
267
  throw err;
233
268
  }
269
+
270
+ return chain.cssOutput || '';
234
271
  };
272
+
235
273
  const processJavascriptBlocks = (content, inputpath) => {
236
274
  const blocks = content.split(/<@([\s\S]*?)@>/gm);
237
275
  let outputCSS = '';
@@ -254,6 +292,7 @@ const processJavascriptBlocks = (content, inputpath) => {
254
292
  }
255
293
  return outputCSS.trim();
256
294
  };
295
+
257
296
  const validateCSS = (css) => {
258
297
  const openBraces = (css.match(/{/g) || []).length;
259
298
  const closeBraces = (css.match(/}/g) || []).length;
@@ -263,6 +302,7 @@ const validateCSS = (css) => {
263
302
  }
264
303
  return true;
265
304
  };
305
+
266
306
  const processAndMinifyCss = async (css, inputFile, outputFile) => {
267
307
  if (!validateCSS(css)) {
268
308
  throw new Error('Invalid CSS syntax - check for missing braces');
@@ -300,22 +340,104 @@ const processAndMinifyCss = async (css, inputFile, outputFile) => {
300
340
  }
301
341
  return { css: finalCss, map: finalSourceMap };
302
342
  };
343
+
303
344
  const processor = async (inputFile, outputFile) => {
304
345
  try {
305
346
  const input = path.resolve(inputFile);
306
347
  const output = path.resolve(outputFile);
348
+
349
+ const outputDir = path.dirname(output);
350
+ if (!fs.existsSync(outputDir)) {
351
+ fs.mkdirSync(outputDir, { recursive: true });
352
+ }
353
+
307
354
  const content = fs.readFileSync(input, 'utf8');
308
355
  const processedCSS = processJavascriptBlocks(content, input);
309
356
  if (!validateCSS(processedCSS)) {
310
357
  throw new Error('Invalid CSS syntax');
311
358
  }
312
- const stylePath = path.join(output, 'global.css');
359
+ const stylePath = path.join(outputDir, 'global.css');
313
360
  const result = await processAndMinifyCss(processedCSS, input, stylePath);
314
361
  if (result.css) {
315
362
  fs.writeFileSync(stylePath, result.css, 'utf8');
363
+ console.log(`✅ CSS compiled successfully: ${stylePath}`);
316
364
  if (result.map) {
317
365
  const mapFile = `${stylePath}.map`;
318
366
  fs.writeFileSync(mapFile, result.map, 'utf8');
367
+ console.log(`✅ Source map: ${mapFile}`);
368
+ }
369
+
370
+ // ========== ATOMIC CLASS MAP GENERATION ==========
371
+ if (atomicOptimizer && config.atomic.enabled && chain.classMap && Object.keys(chain.classMap).length > 0) {
372
+ // Write map.json (selector → atomic class string)
373
+ const mapJsonPath = path.join(outputDir, 'global.map.json');
374
+ const mapData = {
375
+ version: '1.0.0',
376
+ generated: new Date().toISOString(),
377
+ input: inputFile,
378
+ output: stylePath,
379
+ atomicEnabled: true,
380
+ threshold: config.atomic.threshold,
381
+ classMap: chain.classMap,
382
+ stats: chain.atomicStats
383
+ };
384
+ fs.writeFileSync(mapJsonPath, JSON.stringify(mapData, null, 2), 'utf8');
385
+ console.log(`✅ Class map: ${mapJsonPath}`);
386
+
387
+ // Write JS module for easy import
388
+ const jsPath = path.join(outputDir, 'global.classes.js');
389
+ const jsContent = `/**
390
+ * ChainCSS Atomic Class Map
391
+ * Generated: ${new Date().toISOString()}
392
+ * Threshold: ${config.atomic.threshold}
393
+ */
394
+
395
+ export const classMap = ${JSON.stringify(chain.classMap, null, 2)};
396
+
397
+ export const getClass = (selector) => classMap[selector] || '';
398
+
399
+ export default classMap;
400
+ `;
401
+ fs.writeFileSync(jsPath, jsContent, 'utf8');
402
+ console.log(`✅ JS module: ${jsPath}`);
403
+
404
+ // Write TypeScript definitions
405
+ const dtsPath = path.join(outputDir, 'global.classes.d.ts');
406
+ const dtsContent = `/**
407
+ * ChainCSS Atomic Class Map Type Definitions
408
+ * Generated: ${new Date().toISOString()}
409
+ */
410
+
411
+ export const classMap: Record<string, string>;
412
+ export const getClass: (selector: string) => string;
413
+ declare const _default: Record<string, string>;
414
+ export default _default;
415
+ `;
416
+ fs.writeFileSync(dtsPath, dtsContent, 'utf8');
417
+ console.log(`✅ TypeScript definitions: ${dtsPath}`);
418
+
419
+ // Update manifest
420
+ const manifestPath = path.join(outputDir, 'chaincss-manifest.json');
421
+ let manifest = {};
422
+ if (fs.existsSync(manifestPath)) {
423
+ try {
424
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
425
+ } catch (e) {}
426
+ }
427
+
428
+ manifest[path.basename(stylePath)] = {
429
+ css: path.basename(stylePath),
430
+ map: path.basename(mapJsonPath),
431
+ js: path.basename(jsPath),
432
+ dts: path.basename(dtsPath),
433
+ generated: new Date().toISOString(),
434
+ input: inputFile,
435
+ threshold: config.atomic.threshold,
436
+ stats: chain.atomicStats
437
+ };
438
+
439
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
440
+ console.log(`✅ Manifest: ${manifestPath}`);
319
441
  }
320
442
  }
321
443
  } catch (err) {
@@ -323,6 +445,7 @@ const processor = async (inputFile, outputFile) => {
323
445
  throw err;
324
446
  }
325
447
  };
448
+
326
449
  if (require.main === module) {
327
450
  ensureConfigExists();
328
451
  loadUserConfig();
@@ -348,6 +471,7 @@ if (require.main === module) {
348
471
  }
349
472
  })();
350
473
  }
474
+
351
475
  module.exports = {
352
476
  processor,
353
477
  watch,
package/node/strVal.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const strVal = {
2
- userConf: `// Project Configuration
2
+ userConf: `// Project Configuration
3
3
  module.exports = {
4
4
  atomic: {
5
5
  enabled: true,
@@ -18,7 +18,7 @@ module.exports = {
18
18
  }
19
19
  };
20
20
  `,
21
- cli_opt_guide: `
21
+ cli_opt_guide: `
22
22
  ChainCSS - JavaScript-powered CSS preprocessor
23
23
 
24
24
  Usage:
package/package.json CHANGED
@@ -1,38 +1,79 @@
1
1
  {
2
2
  "name": "@melcanz85/chaincss",
3
- "version": "1.11.5",
4
- "description": "A simple package transpiler for js to css",
3
+ "version": "1.12.0",
4
+ "description": "Chainable CSS-in-JS with build-time compilation, atomic CSS, and zero-runtime options",
5
+ "keywords": [
6
+ "css",
7
+ "css-in-js",
8
+ "chaincss",
9
+ "atomic-css",
10
+ "zero-runtime",
11
+ "design-tokens",
12
+ "react",
13
+ "nextjs",
14
+ "vite",
15
+ "webpack"
16
+ ],
17
+ "author": "Rommel Caneos",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/melcanz08/chaincss.git"
22
+ },
23
+ "homepage": "https://chaincss.dev",
24
+ "bugs": {
25
+ "url": "https://github.com/melcanz08/chaincss/issues"
26
+ },
5
27
  "exports": {
6
28
  ".": {
7
29
  "types": "./types.d.ts",
8
30
  "node": {
9
- "require": "./node/index.js"
31
+ "require": "./node/btt.js",
32
+ "import": "./node/btt.js"
10
33
  },
11
34
  "browser": {
12
- "import": "./browser/index.js"
35
+ "import": "./browser/rtt.js",
36
+ "require": "./browser/rtt.js"
13
37
  },
14
- "default": "./browser/index.js"
38
+ "default": "./browser/rtt.js"
15
39
  },
16
- "./node": {
40
+ "./react": {
17
41
  "types": "./types.d.ts",
18
- "require": "./node/index.js"
42
+ "browser": {
43
+ "import": "./browser/react-hooks.js",
44
+ "require": "./browser/react-hooks.js"
45
+ },
46
+ "default": "./browser/react-hooks.js"
19
47
  },
20
- "./browser": {
48
+ "./vite-plugin": {
21
49
  "types": "./types.d.ts",
22
- "import": "./browser/index.js"
50
+ "import": "./node/plugins/vite-plugin.js",
51
+ "require": "./node/plugins/vite-plugin.js",
52
+ "default": "./node/plugins/vite-plugin.js"
23
53
  },
24
- "./react": {
54
+ "./webpack-plugin": {
25
55
  "types": "./types.d.ts",
26
- "browser": {
27
- "import": "./browser/index.js"
28
- },
29
- "default": "./browser/index.js"
56
+ "import": "./node/plugins/webpack-plugin.js",
57
+ "require": "./node/plugins/webpack-plugin.js",
58
+ "default": "./node/plugins/webpack-plugin.js"
59
+ },
60
+ "./next-plugin": {
61
+ "types": "./types.d.ts",
62
+ "import": "./node/plugins/next-plugin.js",
63
+ "require": "./node/plugins/next-plugin.js",
64
+ "default": "./node/plugins/next-plugin.js"
65
+ },
66
+ "./loader": {
67
+ "types": "./types.d.ts",
68
+ "import": "./node/loaders/chaincss-loader.js",
69
+ "require": "./node/loaders/chaincss-loader.js",
70
+ "default": "./node/loaders/chaincss-loader.js"
30
71
  }
31
72
  },
32
73
  "types": "types.d.ts",
33
74
  "files": [
34
- "node/",
35
75
  "browser/",
76
+ "node/",
36
77
  "shared/",
37
78
  "types.d.ts",
38
79
  "README.md",
@@ -40,28 +81,27 @@
40
81
  ],
41
82
  "publishConfig": {
42
83
  "access": "public",
43
- "registry": "https://registry.npmjs.org/"
44
- },
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/melcanz08/chaincss.git"
84
+ "registry": "https://registry.npmjs.org/",
85
+ "provenance": true
48
86
  },
49
- "homepage": "https://melcanz08.github.io/chaincss-website",
50
87
  "bin": {
51
88
  "chaincss": "./node/chaincss.js"
52
89
  },
53
90
  "dependencies": {
54
91
  "chokidar": "^3.5.3",
55
- "clean-css": "^5.3.3",
56
- "vm2": "^3.9.19"
92
+ "clean-css": "^5.3.3"
57
93
  },
58
94
  "peerDependencies": {
95
+ "react": ">=16.8.0",
59
96
  "autoprefixer": "^10.0.0",
97
+ "postcss": "^8.5.6",
60
98
  "browserslist": "^4.28.1",
61
- "caniuse-db": "^1.0.30001770",
62
- "postcss": "^8.5.6"
99
+ "caniuse-db": "^1.0.30001770"
63
100
  },
64
101
  "peerDependenciesMeta": {
102
+ "react": {
103
+ "optional": true
104
+ },
65
105
  "autoprefixer": {
66
106
  "optional": true
67
107
  },
@@ -75,14 +115,8 @@
75
115
  "optional": true
76
116
  }
77
117
  },
78
- "keywords": [
79
- "css",
80
- "js",
81
- "transpiler",
82
- "compiler",
83
- "css-in-js",
84
- "react"
85
- ],
86
- "author": "Rommel Caneos",
87
- "license": "MIT"
118
+ "engines": {
119
+ "node": ">=14.0.0"
120
+ },
121
+ "sideEffects": false
88
122
  }