@finggujadhav/compiler 0.9.3 ā 0.9.4
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/cli.js +104 -25
- package/engine.js +41 -11
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* FingguFlux CLI
|
|
4
|
-
*
|
|
4
|
+
* v0.9.4 Intelligence Upgrade
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { scanFiles, getProjectFiles } from './scanner.js';
|
|
9
9
|
import { CompilerEngine } from './engine.js';
|
|
10
10
|
|
|
11
|
-
// Basic named argument parser
|
|
12
11
|
const args = process.argv.slice(2);
|
|
12
|
+
const command = args[0] || 'build';
|
|
13
|
+
|
|
13
14
|
const getArg = (flag) => {
|
|
14
15
|
const idx = args.indexOf(flag);
|
|
15
16
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null;
|
|
@@ -19,27 +20,37 @@ const mode = getArg('--mode') || 'dev';
|
|
|
19
20
|
const inputDir = getArg('--input') || './';
|
|
20
21
|
const outputDir = getArg('--output') || './dist';
|
|
21
22
|
|
|
22
|
-
async function
|
|
23
|
-
|
|
23
|
+
async function main() {
|
|
24
|
+
switch (command) {
|
|
25
|
+
case 'build':
|
|
26
|
+
await runBuild();
|
|
27
|
+
break;
|
|
28
|
+
case 'analyze':
|
|
29
|
+
await runAnalyze();
|
|
30
|
+
break;
|
|
31
|
+
case 'doctor':
|
|
32
|
+
await runDoctor();
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
console.error(`Unknown command: ${command}`);
|
|
36
|
+
console.log('Available commands: build, analyze, doctor');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
console.log(`š Scanning files in ${inputDir}...`);
|
|
41
|
+
async function prepareEngine() {
|
|
27
42
|
const files = getProjectFiles(path.resolve(inputDir));
|
|
28
43
|
const usedClasses = scanFiles(files);
|
|
29
|
-
console.log(`ā
Found ${usedClasses.length} used FingguFlux classes.`);
|
|
30
|
-
|
|
31
|
-
// 2. Initialize Engine
|
|
32
44
|
const engine = new CompilerEngine({ mode });
|
|
33
45
|
engine.setUsedClasses(usedClasses);
|
|
34
46
|
|
|
35
|
-
//
|
|
47
|
+
// Collect CSS source files
|
|
36
48
|
let combinedCSS = '';
|
|
37
49
|
const possiblePaths = [
|
|
38
50
|
path.resolve('./node_modules/@finggujadhav/core'),
|
|
39
51
|
path.resolve('./packages/core'),
|
|
40
52
|
path.resolve('../../packages/core')
|
|
41
53
|
];
|
|
42
|
-
|
|
43
54
|
let corePath = possiblePaths.find(p => fs.existsSync(p));
|
|
44
55
|
if (!corePath) throw new Error('Could not find @finggujadhav/core CSS sources.');
|
|
45
56
|
|
|
@@ -55,32 +66,100 @@ async function runBuild() {
|
|
|
55
66
|
}
|
|
56
67
|
});
|
|
57
68
|
};
|
|
58
|
-
|
|
59
69
|
collectCSS(corePath);
|
|
60
70
|
|
|
61
|
-
// 4. Process CSS
|
|
62
|
-
console.log(`š ļø Processing CSS and applying tree-shaking...`);
|
|
63
71
|
const finalCSS = engine.processCSS(combinedCSS);
|
|
64
|
-
|
|
72
|
+
return { engine, finalCSS, usedClasses };
|
|
73
|
+
}
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
async function runBuild() {
|
|
76
|
+
console.log(`\nš FingguFlux Compiler [Mode: ${mode.toUpperCase()}]`);
|
|
77
|
+
const { engine, finalCSS } = await prepareEngine();
|
|
78
|
+
|
|
79
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
70
80
|
|
|
71
81
|
const cssPath = path.join(outputDir, 'finggu.css');
|
|
72
82
|
const mappingPath = path.join(outputDir, 'mapping.json');
|
|
83
|
+
const reportPath = path.join(outputDir, 'fingguflux-report.json');
|
|
73
84
|
|
|
74
85
|
fs.writeFileSync(cssPath, finalCSS);
|
|
75
|
-
fs.writeFileSync(mappingPath, JSON.stringify(
|
|
86
|
+
fs.writeFileSync(mappingPath, JSON.stringify(engine.getMapping(), null, 2));
|
|
87
|
+
fs.writeFileSync(reportPath, JSON.stringify(engine.generateReport(), null, 2));
|
|
76
88
|
|
|
77
|
-
console.log(
|
|
89
|
+
console.log(`ā
Build Complete!`);
|
|
78
90
|
console.log(`- CSS: ${cssPath} (${Buffer.byteLength(finalCSS)} bytes)`);
|
|
79
|
-
console.log(`-
|
|
80
|
-
|
|
91
|
+
console.log(`- Report: ${reportPath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runAnalyze() {
|
|
95
|
+
console.log(`\nš FingguFlux Intelligence - Analyze Mode`);
|
|
96
|
+
const { engine } = await prepareEngine();
|
|
97
|
+
const stats = engine.getStats();
|
|
98
|
+
|
|
99
|
+
console.log(`-------------------------------------------`);
|
|
100
|
+
console.log(`Total Scanned Classes: ${stats.totalClasses}`);
|
|
101
|
+
console.log(`Used Classes: ${stats.usedClasses}`);
|
|
102
|
+
console.log(`Unused Classes: ${stats.unusedClasses}`);
|
|
103
|
+
console.log(`Extreme Mappings: ${stats.extremeMappings}`);
|
|
104
|
+
console.log(`-------------------------------------------`);
|
|
105
|
+
console.log(`Original Size: ${stats.originalSize} bytes`);
|
|
106
|
+
console.log(`Estimated Final Size: ${stats.finalSize} bytes`);
|
|
107
|
+
console.log(`Gzip Estimate (~30%): ${stats.gzipEstimate} bytes`);
|
|
108
|
+
console.log(`-------------------------------------------`);
|
|
109
|
+
|
|
110
|
+
if (stats.unusedList.length > 0 && args.includes('--verbose')) {
|
|
111
|
+
console.log(`\nš Unused Classes:`);
|
|
112
|
+
stats.unusedList.slice(0, 20).forEach(c => console.log(` - ${c}`));
|
|
113
|
+
if (stats.unusedList.length > 20) console.log(` ... and ${stats.unusedList.length - 20} more.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function runDoctor() {
|
|
118
|
+
console.log(`\n𩺠FingguFlux Intelligence - Doctor Mode`);
|
|
119
|
+
const mappingPath = path.resolve(outputDir, 'mapping.json');
|
|
120
|
+
const corePkgPath = path.resolve('./packages/core/package.json');
|
|
121
|
+
|
|
122
|
+
let healthy = true;
|
|
123
|
+
|
|
124
|
+
// 1. Check mapping
|
|
125
|
+
if (fs.existsSync(mappingPath)) {
|
|
126
|
+
console.log(`ā
mapping.json found.`);
|
|
127
|
+
try {
|
|
128
|
+
const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
|
|
129
|
+
if (Object.keys(mapping).length === 0) {
|
|
130
|
+
console.warn(`ā ļø Warning: mapping.json is empty.`);
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
console.error(`ā Error: mapping.json is corrupted.`);
|
|
134
|
+
healthy = false;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.warn(`ā ļø Warning: mapping.json not found in ${outputDir}. Run build first.`);
|
|
138
|
+
healthy = false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Check Core Version
|
|
142
|
+
if (fs.existsSync(corePkgPath)) {
|
|
143
|
+
const corePkg = JSON.parse(fs.readFileSync(corePkgPath, 'utf8'));
|
|
144
|
+
console.log(`ā
Core version verified: ${corePkg.version}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Extreme Mode Compatibility
|
|
148
|
+
const { usedClasses } = await prepareEngine();
|
|
149
|
+
const invalidClasses = usedClasses.filter(c => !c.startsWith('ff-'));
|
|
150
|
+
if (invalidClasses.length > 0) {
|
|
151
|
+
console.error(`ā Error: ${invalidClasses.length} classes do not follow ff- prefix!`);
|
|
152
|
+
invalidClasses.slice(0, 5).forEach(c => console.log(` - ${c}`));
|
|
153
|
+
healthy = false;
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`ā
All used classes follow ff- prefix.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (healthy) console.log(`\n⨠Project is healthy!`);
|
|
159
|
+
else console.log(`\nā Project has issues. See details above.`);
|
|
81
160
|
}
|
|
82
161
|
|
|
83
|
-
|
|
84
|
-
console.error('
|
|
162
|
+
main().catch(err => {
|
|
163
|
+
console.error('Command failed:', err);
|
|
85
164
|
process.exit(1);
|
|
86
165
|
});
|
package/engine.js
CHANGED
|
@@ -72,6 +72,15 @@ export class CompilerEngine {
|
|
|
72
72
|
|
|
73
73
|
processCSS(cssContent) {
|
|
74
74
|
let processed = cssContent;
|
|
75
|
+
this.stats = {
|
|
76
|
+
totalClasses: 0,
|
|
77
|
+
usedClasses: 0,
|
|
78
|
+
unusedClasses: 0,
|
|
79
|
+
originalSize: Buffer.byteLength(cssContent),
|
|
80
|
+
finalSize: 0,
|
|
81
|
+
extremeMappings: 0,
|
|
82
|
+
unusedList: []
|
|
83
|
+
};
|
|
75
84
|
|
|
76
85
|
// 1. Identify all .ff- selectors in the source
|
|
77
86
|
const classRegex = /\.ff-([\w-]+)/g;
|
|
@@ -80,13 +89,21 @@ export class CompilerEngine {
|
|
|
80
89
|
while ((match = classRegex.exec(cssContent)) !== null) {
|
|
81
90
|
foundInCSS.add(`ff-${match[1]}`);
|
|
82
91
|
}
|
|
92
|
+
this.stats.totalClasses = foundInCSS.size;
|
|
83
93
|
|
|
84
94
|
// 2. Generate mappings only for USED classes
|
|
85
95
|
foundInCSS.forEach(cls => {
|
|
86
96
|
if (this.usedClasses.has(cls)) {
|
|
87
97
|
this.generateMapping(cls);
|
|
98
|
+
} else {
|
|
99
|
+
this.stats.unusedList.push(cls);
|
|
88
100
|
}
|
|
89
101
|
});
|
|
102
|
+
this.stats.usedClasses = Object.keys(this.mapping).length;
|
|
103
|
+
this.stats.unusedClasses = this.stats.totalClasses - this.stats.usedClasses;
|
|
104
|
+
if (this.mode === 'ext') {
|
|
105
|
+
this.stats.extremeMappings = this.stats.usedClasses;
|
|
106
|
+
}
|
|
90
107
|
|
|
91
108
|
// 3. Tree-shaking (Block-level pruning) - DO THIS FIRST while names are original
|
|
92
109
|
const blocks = processed.split('}');
|
|
@@ -99,8 +116,6 @@ export class CompilerEngine {
|
|
|
99
116
|
if (selector.includes('.ff-')) {
|
|
100
117
|
const ffClassesInSelector = selector.match(/\.ff-[\w-]+/g);
|
|
101
118
|
if (ffClassesInSelector) {
|
|
102
|
-
// Prune block if ANY ff- class in the selector is unused
|
|
103
|
-
// (Matches our strict tree-shaking policy for components/utilities)
|
|
104
119
|
const hasUnused = ffClassesInSelector.some(cls => {
|
|
105
120
|
const baseCls = cls.substring(1);
|
|
106
121
|
return !this.usedClasses.has(baseCls);
|
|
@@ -114,8 +129,6 @@ export class CompilerEngine {
|
|
|
114
129
|
processed = filteredBlocks.join('}') + (filteredBlocks.length > 0 ? '}' : '');
|
|
115
130
|
|
|
116
131
|
// 4. Replace selectors with mapped hashes
|
|
117
|
-
// NOTE: We explicitly DO NOT hash CSS variables starting with --ff-
|
|
118
|
-
// This ensures runtime theme switching remains operational.
|
|
119
132
|
const sortedClasses = Object.keys(this.mapping).sort((a, b) => b.length - a.length);
|
|
120
133
|
sortedClasses.forEach(cls => {
|
|
121
134
|
const mapped = this.mapping[cls];
|
|
@@ -126,17 +139,37 @@ export class CompilerEngine {
|
|
|
126
139
|
}
|
|
127
140
|
});
|
|
128
141
|
|
|
129
|
-
// 5. Keyframe Pruning
|
|
130
|
-
|
|
142
|
+
// 5. Keyframe Pruning
|
|
143
|
+
processed = this.pruneKeyframes(processed);
|
|
144
|
+
this.stats.finalSize = Buffer.byteLength(processed);
|
|
145
|
+
return processed;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @returns {Object} v0.9.4 Intelligence Stats
|
|
150
|
+
*/
|
|
151
|
+
getStats() {
|
|
152
|
+
return {
|
|
153
|
+
...this.stats,
|
|
154
|
+
gzipEstimate: Math.round(this.stats.finalSize * 0.3) // Rough estimate for reporting
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
generateReport() {
|
|
159
|
+
return {
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
engineVersion: "0.9.4",
|
|
162
|
+
mode: this.mode,
|
|
163
|
+
stats: this.getStats(),
|
|
164
|
+
mapping: this.getMapping()
|
|
165
|
+
};
|
|
131
166
|
}
|
|
132
167
|
|
|
133
168
|
pruneKeyframes(css) {
|
|
134
|
-
// Find all referenced animation names
|
|
135
169
|
const animationRegex = /animation(?:\-name)?\s*:\s*([^;!}]+)/g;
|
|
136
170
|
const usedAnimations = new Set();
|
|
137
171
|
let match;
|
|
138
172
|
while ((match = animationRegex.exec(css)) !== null) {
|
|
139
|
-
// Split by space/comma and filter out durations/easings/etc
|
|
140
173
|
const parts = match[1].split(/[,\s]+/).map(p => p.trim());
|
|
141
174
|
parts.forEach(p => {
|
|
142
175
|
if (p && !/^\d|ms|s|infinite|linear|ease|both|forwards|backwards/.test(p)) {
|
|
@@ -145,7 +178,6 @@ export class CompilerEngine {
|
|
|
145
178
|
});
|
|
146
179
|
}
|
|
147
180
|
|
|
148
|
-
// Prune unused @keyframes blocks
|
|
149
181
|
const keyframeBlocks = css.split(/@keyframes\s+([\w-]+)\s*\{/);
|
|
150
182
|
if (keyframeBlocks.length <= 1) return css;
|
|
151
183
|
|
|
@@ -154,7 +186,6 @@ export class CompilerEngine {
|
|
|
154
186
|
const name = keyframeBlocks[i];
|
|
155
187
|
const contentAndRest = keyframeBlocks[i + 1];
|
|
156
188
|
|
|
157
|
-
// Find the end of this @keyframes block (handling nested braces if any)
|
|
158
189
|
let braceCount = 1;
|
|
159
190
|
let endOfBlock = -1;
|
|
160
191
|
for (let j = 0; j < contentAndRest.length; j++) {
|
|
@@ -179,7 +210,6 @@ export class CompilerEngine {
|
|
|
179
210
|
}
|
|
180
211
|
|
|
181
212
|
getMapping() {
|
|
182
|
-
// Return sorted mapping for stability
|
|
183
213
|
const sortedMapping = {};
|
|
184
214
|
Object.keys(this.mapping).sort().forEach(key => {
|
|
185
215
|
sortedMapping[key] = this.mapping[key];
|