flint-compiler 4.0.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 +54 -0
- package/dist/errors/index.d.ts +34 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/optimizer/index.d.ts +178 -0
- package/dist/optimizer/index.d.ts.map +1 -0
- package/dist/optimizer/index.js +1045 -0
- package/dist/optimizer/index.js.map +1 -0
- package/dist/parser/index.d.ts +25 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +93 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/transformer/index.d.ts +32 -0
- package/dist/transformer/index.d.ts.map +1 -0
- package/dist/transformer/index.js +566 -0
- package/dist/transformer/index.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
// Flint Compiler v3 — Optimizer
|
|
2
|
+
// Real dead code elimination, constant folding, static subtree hoisting,
|
|
3
|
+
// compile-time CSS scoping, and code optimizations
|
|
4
|
+
// ─── Default Options ────────────────────────────────────────────
|
|
5
|
+
const DEFAULT_OPTIONS = {
|
|
6
|
+
deadCodeElimination: true,
|
|
7
|
+
constantFolding: true,
|
|
8
|
+
deadStoreElimination: true,
|
|
9
|
+
functionInlining: true,
|
|
10
|
+
jsxOptimization: true,
|
|
11
|
+
treeShaking: true,
|
|
12
|
+
staticHoisting: true,
|
|
13
|
+
cssScoping: true,
|
|
14
|
+
autoMemoization: true,
|
|
15
|
+
minificationLevel: 1,
|
|
16
|
+
};
|
|
17
|
+
function walkAST(node, visitor) {
|
|
18
|
+
if (!node || typeof node !== 'object')
|
|
19
|
+
return node;
|
|
20
|
+
const result = visitor(node);
|
|
21
|
+
if (result === null)
|
|
22
|
+
return null;
|
|
23
|
+
if (result !== undefined)
|
|
24
|
+
return result;
|
|
25
|
+
// Walk children based on node type
|
|
26
|
+
for (const key of Object.keys(node)) {
|
|
27
|
+
if (key === 'type' || key === 'start' || key === 'end')
|
|
28
|
+
continue;
|
|
29
|
+
const child = node[key];
|
|
30
|
+
if (Array.isArray(child)) {
|
|
31
|
+
const newArr = [];
|
|
32
|
+
for (const item of child) {
|
|
33
|
+
if (item && typeof item === 'object' && item.type) {
|
|
34
|
+
const walked = walkAST(item, visitor);
|
|
35
|
+
if (walked !== null)
|
|
36
|
+
newArr.push(walked);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
newArr.push(item);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
;
|
|
43
|
+
node[key] = newArr;
|
|
44
|
+
}
|
|
45
|
+
else if (child && typeof child === 'object' && child.type) {
|
|
46
|
+
const walked = walkAST(child, visitor);
|
|
47
|
+
if (walked === null) {
|
|
48
|
+
;
|
|
49
|
+
node[key] = null;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
;
|
|
53
|
+
node[key] = walked;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
// ─── Optimizer Class ────────────────────────────────────────────
|
|
60
|
+
export class Optimizer {
|
|
61
|
+
options;
|
|
62
|
+
warnings = [];
|
|
63
|
+
stats = {
|
|
64
|
+
originalSize: 0,
|
|
65
|
+
optimizedSize: 0,
|
|
66
|
+
reduction: 0,
|
|
67
|
+
eliminatedNodes: 0,
|
|
68
|
+
inlinedFunctions: 0,
|
|
69
|
+
constantFolds: 0,
|
|
70
|
+
staticHoisted: 0,
|
|
71
|
+
autoMemoized: 0,
|
|
72
|
+
};
|
|
73
|
+
constructor(options = {}) {
|
|
74
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Optimize AST
|
|
78
|
+
*/
|
|
79
|
+
optimize(ast) {
|
|
80
|
+
this.warnings = [];
|
|
81
|
+
this.stats = {
|
|
82
|
+
originalSize: JSON.stringify(ast).length,
|
|
83
|
+
optimizedSize: 0,
|
|
84
|
+
reduction: 0,
|
|
85
|
+
eliminatedNodes: 0,
|
|
86
|
+
inlinedFunctions: 0,
|
|
87
|
+
constantFolds: 0,
|
|
88
|
+
staticHoisted: 0,
|
|
89
|
+
autoMemoized: 0,
|
|
90
|
+
};
|
|
91
|
+
let optimized = this.cloneAST(ast);
|
|
92
|
+
// Run optimization passes (multiple passes for better results)
|
|
93
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
94
|
+
if (this.options.deadCodeElimination) {
|
|
95
|
+
optimized = this.eliminateDeadCode(optimized);
|
|
96
|
+
}
|
|
97
|
+
if (this.options.constantFolding) {
|
|
98
|
+
optimized = this.foldConstants(optimized);
|
|
99
|
+
}
|
|
100
|
+
if (this.options.deadStoreElimination) {
|
|
101
|
+
optimized = this.eliminateDeadStores(optimized);
|
|
102
|
+
}
|
|
103
|
+
if (this.options.functionInlining) {
|
|
104
|
+
optimized = this.inlineFunctions(optimized);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (this.options.jsxOptimization) {
|
|
108
|
+
optimized = this.optimizeJSX(optimized);
|
|
109
|
+
}
|
|
110
|
+
// Auto-memoization pass (React Compiler-like)
|
|
111
|
+
if (this.options.autoMemoization) {
|
|
112
|
+
optimized = this.autoMemoize(optimized);
|
|
113
|
+
}
|
|
114
|
+
this.stats.optimizedSize = JSON.stringify(optimized).length;
|
|
115
|
+
this.stats.reduction = this.stats.originalSize - this.stats.optimizedSize;
|
|
116
|
+
return {
|
|
117
|
+
code: this.generateCode(optimized),
|
|
118
|
+
ast: optimized,
|
|
119
|
+
warnings: this.warnings,
|
|
120
|
+
stats: this.stats,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
cloneAST(ast) {
|
|
124
|
+
return JSON.parse(JSON.stringify(ast));
|
|
125
|
+
}
|
|
126
|
+
// ─── Dead Code Elimination ────────────────────────────────────
|
|
127
|
+
eliminateDeadCode(ast) {
|
|
128
|
+
return walkAST(ast, (node) => {
|
|
129
|
+
// Remove if(false) { ... } and if(true) { ... } else { ... }
|
|
130
|
+
if (node.type === 'IfStatement') {
|
|
131
|
+
const test = node.test;
|
|
132
|
+
const val = this.evaluateConstant(test);
|
|
133
|
+
if (val !== undefined) {
|
|
134
|
+
if (val) {
|
|
135
|
+
// if(true) → keep consequent
|
|
136
|
+
this.stats.eliminatedNodes++;
|
|
137
|
+
return node.consequent;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// if(false) → keep alternate or remove
|
|
141
|
+
this.stats.eliminatedNodes++;
|
|
142
|
+
if (node.alternate) {
|
|
143
|
+
return node.alternate;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Remove while(false) { ... }
|
|
150
|
+
if (node.type === 'WhileStatement') {
|
|
151
|
+
const val = this.evaluateConstant(node.test);
|
|
152
|
+
if (val === false) {
|
|
153
|
+
this.stats.eliminatedNodes++;
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Remove dead code after return/throw/break/continue
|
|
158
|
+
if (node.type === 'BlockStatement' && node.body) {
|
|
159
|
+
const filtered = [];
|
|
160
|
+
let foundTerminator = false;
|
|
161
|
+
for (const stmt of node.body) {
|
|
162
|
+
if (foundTerminator) {
|
|
163
|
+
if (stmt.type === 'FunctionDeclaration') {
|
|
164
|
+
// Keep function declarations (hoisted)
|
|
165
|
+
filtered.push(stmt);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.stats.eliminatedNodes++;
|
|
169
|
+
this.warnings.push({
|
|
170
|
+
type: 'dead_code',
|
|
171
|
+
message: 'Unreachable code detected after return/throw',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
filtered.push(stmt);
|
|
177
|
+
if (stmt.type === 'ReturnStatement' ||
|
|
178
|
+
stmt.type === 'ThrowStatement' ||
|
|
179
|
+
stmt.type === 'BreakStatement' ||
|
|
180
|
+
stmt.type === 'ContinueStatement') {
|
|
181
|
+
foundTerminator = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
node.body = filtered;
|
|
186
|
+
}
|
|
187
|
+
// Remove empty statements
|
|
188
|
+
if (node.type === 'EmptyStatement') {
|
|
189
|
+
this.stats.eliminatedNodes++;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// Remove variables with only assignments and no reads (dead stores)
|
|
193
|
+
if (node.type === 'VariableDeclaration') {
|
|
194
|
+
// Will be handled by eliminateDeadStores
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// ─── Constant Folding ─────────────────────────────────────────
|
|
200
|
+
foldConstants(ast) {
|
|
201
|
+
return walkAST(ast, (node) => {
|
|
202
|
+
// Fold binary expressions: 1+2 → 3, "a"+"b" → "ab"
|
|
203
|
+
if (node.type === 'BinaryExpression') {
|
|
204
|
+
const left = this.evaluateConstant(node.left);
|
|
205
|
+
const right = this.evaluateConstant(node.right);
|
|
206
|
+
if (left !== undefined && right !== undefined) {
|
|
207
|
+
const result = this.evaluateBinary(node.operator, left, right);
|
|
208
|
+
if (result !== undefined) {
|
|
209
|
+
this.stats.constantFolds++;
|
|
210
|
+
return this.createLiteral(result);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Fold unary expressions: !true → false, -5 → -5
|
|
215
|
+
if (node.type === 'UnaryExpression') {
|
|
216
|
+
const arg = this.evaluateConstant(node.argument);
|
|
217
|
+
if (arg !== undefined) {
|
|
218
|
+
const result = this.evaluateUnary(node.operator, arg);
|
|
219
|
+
if (result !== undefined) {
|
|
220
|
+
this.stats.constantFolds++;
|
|
221
|
+
return this.createLiteral(result);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Fold logical expressions: true && false → false, null || "default" → "default"
|
|
226
|
+
if (node.type === 'LogicalExpression') {
|
|
227
|
+
const left = this.evaluateConstant(node.left);
|
|
228
|
+
const right = this.evaluateConstant(node.right);
|
|
229
|
+
if (left !== undefined && right !== undefined) {
|
|
230
|
+
const result = this.evaluateLogical(node.operator, left, right);
|
|
231
|
+
if (result !== undefined) {
|
|
232
|
+
this.stats.constantFolds++;
|
|
233
|
+
return this.createLiteral(result);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Fold template literals with only static parts
|
|
238
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
239
|
+
const value = node.quasis.map((q) => q.value.cooked || '').join('');
|
|
240
|
+
this.stats.constantFolds++;
|
|
241
|
+
return this.createLiteral(value);
|
|
242
|
+
}
|
|
243
|
+
// Fold conditional expressions: true ? a : b → a
|
|
244
|
+
if (node.type === 'ConditionalExpression') {
|
|
245
|
+
const test = this.evaluateConstant(node.test);
|
|
246
|
+
if (test !== undefined) {
|
|
247
|
+
this.stats.constantFolds++;
|
|
248
|
+
return test ? node.consequent : node.alternate;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return undefined;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
// ─── Dead Store Elimination ───────────────────────────────────
|
|
255
|
+
eliminateDeadStores(ast) {
|
|
256
|
+
const usedVariables = new Set();
|
|
257
|
+
// First pass: collect all variable usages
|
|
258
|
+
walkAST(ast, (node) => {
|
|
259
|
+
if (node.type === 'Identifier') {
|
|
260
|
+
usedVariables.add(node.name);
|
|
261
|
+
}
|
|
262
|
+
return undefined;
|
|
263
|
+
});
|
|
264
|
+
// Second pass: remove unused variable declarations
|
|
265
|
+
return walkAST(ast, (node) => {
|
|
266
|
+
if (node.type === 'VariableDeclaration') {
|
|
267
|
+
const filtered = [];
|
|
268
|
+
for (const decl of node.declarations) {
|
|
269
|
+
if (decl.id.type === 'Identifier') {
|
|
270
|
+
const name = decl.id.name;
|
|
271
|
+
// Keep if variable is used or has side effects in init
|
|
272
|
+
if (usedVariables.has(name) || this.hasSideEffects(decl.init)) {
|
|
273
|
+
filtered.push(decl);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
this.stats.eliminatedNodes++;
|
|
277
|
+
this.warnings.push({
|
|
278
|
+
type: 'unused_variable',
|
|
279
|
+
message: `Variable "${name}" is declared but never used`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
filtered.push(decl);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (filtered.length === 0) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
node.declarations = filtered;
|
|
291
|
+
}
|
|
292
|
+
return undefined;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
// ─── Function Inlining ────────────────────────────────────────
|
|
296
|
+
inlineFunctions(ast) {
|
|
297
|
+
const functionMap = new Map();
|
|
298
|
+
// Collect small function definitions
|
|
299
|
+
walkAST(ast, (node) => {
|
|
300
|
+
if (node.type === 'VariableDeclarator' && node.init) {
|
|
301
|
+
const init = node.init;
|
|
302
|
+
if (init.type === 'ArrowFunctionExpression' ||
|
|
303
|
+
init.type === 'FunctionExpression') {
|
|
304
|
+
// Only inline small functions (single expression body)
|
|
305
|
+
if (init.body.type === 'BlockStatement' &&
|
|
306
|
+
init.body.body.length === 1 &&
|
|
307
|
+
init.body.body[0].type === 'ReturnStatement') {
|
|
308
|
+
const body = init.body.body[0].argument;
|
|
309
|
+
if (this.isSmallExpression(body)) {
|
|
310
|
+
functionMap.set(node.id.name, {
|
|
311
|
+
params: init.params,
|
|
312
|
+
body,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (init.body.type !== 'BlockStatement') {
|
|
317
|
+
// Arrow with expression body
|
|
318
|
+
if (this.isSmallExpression(init.body)) {
|
|
319
|
+
functionMap.set(node.id.name, {
|
|
320
|
+
params: init.params,
|
|
321
|
+
body: init.body,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
});
|
|
329
|
+
// Replace function calls with inlined body
|
|
330
|
+
return walkAST(ast, (node) => {
|
|
331
|
+
if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
|
|
332
|
+
const func = functionMap.get(node.callee.name);
|
|
333
|
+
if (func && func.params.length === node.arguments.length) {
|
|
334
|
+
// Simple inlining: replace parameter names with arguments
|
|
335
|
+
const inlined = this.substituteParams(func.body, func.params, node.arguments);
|
|
336
|
+
this.stats.inlinedFunctions++;
|
|
337
|
+
return inlined;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return undefined;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// ─── JSX Optimization ─────────────────────────────────────────
|
|
344
|
+
optimizeJSX(ast) {
|
|
345
|
+
return walkAST(ast, (node) => {
|
|
346
|
+
// Flatten nested fragments: <><a/></> → <a/>
|
|
347
|
+
if (node.type === 'JSXFragment') {
|
|
348
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXFragment') {
|
|
349
|
+
return node.children[0];
|
|
350
|
+
}
|
|
351
|
+
// Fragment with single element
|
|
352
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXElement') {
|
|
353
|
+
return node.children[0];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Optimize static JSX strings
|
|
357
|
+
if (node.type === 'JSXElement' && node.openingElement) {
|
|
358
|
+
const attrs = node.openingElement.attributes;
|
|
359
|
+
// Remove empty attributes
|
|
360
|
+
node.openingElement.attributes = attrs.filter((attr) => attr.type !== 'JSXAttribute' || attr.value !== null);
|
|
361
|
+
}
|
|
362
|
+
// Convert <div>{""}</div> → <div></div>
|
|
363
|
+
if (node.type === 'JSXElement' && node.children) {
|
|
364
|
+
node.children = node.children.filter((child) => {
|
|
365
|
+
if (child.type === 'JSXExpressionContainer' && child.expression) {
|
|
366
|
+
if (child.expression.type === 'Literal' &&
|
|
367
|
+
(child.expression.value === '' || child.expression.value === null)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return true;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return undefined;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// ─── Helpers ──────────────────────────────────────────────────
|
|
378
|
+
evaluateConstant(node) {
|
|
379
|
+
if (!node)
|
|
380
|
+
return undefined;
|
|
381
|
+
if (node.type === 'Literal') {
|
|
382
|
+
return node.value;
|
|
383
|
+
}
|
|
384
|
+
if (node.type === 'Identifier') {
|
|
385
|
+
if (node.name === 'true')
|
|
386
|
+
return true;
|
|
387
|
+
if (node.name === 'false')
|
|
388
|
+
return false;
|
|
389
|
+
if (node.name === 'null')
|
|
390
|
+
return null;
|
|
391
|
+
if (node.name === 'undefined')
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
if (node.type === 'UnaryExpression') {
|
|
395
|
+
const arg = this.evaluateConstant(node.argument);
|
|
396
|
+
if (arg !== undefined) {
|
|
397
|
+
return this.evaluateUnary(node.operator, arg);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (node.type === 'BinaryExpression') {
|
|
401
|
+
const left = this.evaluateConstant(node.left);
|
|
402
|
+
const right = this.evaluateConstant(node.right);
|
|
403
|
+
if (left !== undefined && right !== undefined) {
|
|
404
|
+
return this.evaluateBinary(node.operator, left, right);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (node.type === 'LogicalExpression') {
|
|
408
|
+
const left = this.evaluateConstant(node.left);
|
|
409
|
+
const right = this.evaluateConstant(node.right);
|
|
410
|
+
if (left !== undefined && right !== undefined) {
|
|
411
|
+
return this.evaluateLogical(node.operator, left, right);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
evaluateBinary(op, left, right) {
|
|
417
|
+
switch (op) {
|
|
418
|
+
case '+': return left + right;
|
|
419
|
+
case '-': return left - right;
|
|
420
|
+
case '*': return left * right;
|
|
421
|
+
case '/': return left / right;
|
|
422
|
+
case '%': return left % right;
|
|
423
|
+
case '**': return left ** right;
|
|
424
|
+
case '==': return left == right;
|
|
425
|
+
case '!=': return left != right;
|
|
426
|
+
case '===': return left === right;
|
|
427
|
+
case '!==': return left !== right;
|
|
428
|
+
case '<': return left < right;
|
|
429
|
+
case '>': return left > right;
|
|
430
|
+
case '<=': return left <= right;
|
|
431
|
+
case '>=': return left >= right;
|
|
432
|
+
case '<<': return left << right;
|
|
433
|
+
case '>>': return left >> right;
|
|
434
|
+
case '>>>': return left >>> right;
|
|
435
|
+
case '&': return left & right;
|
|
436
|
+
case '|': return left | right;
|
|
437
|
+
case '^': return left ^ right;
|
|
438
|
+
default: return undefined;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
evaluateUnary(op, arg) {
|
|
442
|
+
switch (op) {
|
|
443
|
+
case '-': return -arg;
|
|
444
|
+
case '+': return +arg;
|
|
445
|
+
case '!': return !arg;
|
|
446
|
+
case '~': return ~arg;
|
|
447
|
+
case 'typeof': return typeof arg;
|
|
448
|
+
case 'void': return undefined;
|
|
449
|
+
default: return undefined;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
evaluateLogical(op, left, right) {
|
|
453
|
+
switch (op) {
|
|
454
|
+
case '&&': return left && right;
|
|
455
|
+
case '||': return left || right;
|
|
456
|
+
case '??': return left ?? right;
|
|
457
|
+
default: return undefined;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
createLiteral(value) {
|
|
461
|
+
return {
|
|
462
|
+
type: 'Literal',
|
|
463
|
+
value,
|
|
464
|
+
raw: String(value),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
isSmallExpression(node) {
|
|
468
|
+
if (!node)
|
|
469
|
+
return false;
|
|
470
|
+
if (node.type === 'Literal')
|
|
471
|
+
return true;
|
|
472
|
+
if (node.type === 'Identifier')
|
|
473
|
+
return true;
|
|
474
|
+
if (node.type === 'BinaryExpression') {
|
|
475
|
+
return this.isSmallExpression(node.left) && this.isSmallExpression(node.right);
|
|
476
|
+
}
|
|
477
|
+
if (node.type === 'UnaryExpression') {
|
|
478
|
+
return this.isSmallExpression(node.argument);
|
|
479
|
+
}
|
|
480
|
+
if (node.type === 'ConditionalExpression') {
|
|
481
|
+
return (this.isSmallExpression(node.test) &&
|
|
482
|
+
this.isSmallExpression(node.consequent) &&
|
|
483
|
+
this.isSmallExpression(node.alternate));
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
substituteParams(body, params, args) {
|
|
488
|
+
const paramMap = new Map();
|
|
489
|
+
params.forEach((p, i) => {
|
|
490
|
+
if (p.type === 'Identifier') {
|
|
491
|
+
paramMap.set(p.name, args[i]);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
return walkAST(JSON.parse(JSON.stringify(body)), (node) => {
|
|
495
|
+
if (node.type === 'Identifier' && paramMap.has(node.name)) {
|
|
496
|
+
return paramMap.get(node.name);
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
hasSideEffects(node) {
|
|
502
|
+
if (!node)
|
|
503
|
+
return false;
|
|
504
|
+
if (node.type === 'CallExpression')
|
|
505
|
+
return true;
|
|
506
|
+
if (node.type === 'NewExpression')
|
|
507
|
+
return true;
|
|
508
|
+
if (node.type === 'AssignmentExpression')
|
|
509
|
+
return true;
|
|
510
|
+
if (node.type === 'UpdateExpression')
|
|
511
|
+
return true;
|
|
512
|
+
if (node.type === 'AwaitExpression')
|
|
513
|
+
return true;
|
|
514
|
+
if (node.type === 'YieldExpression')
|
|
515
|
+
return true;
|
|
516
|
+
if (node.type === 'TaggedTemplateExpression')
|
|
517
|
+
return true;
|
|
518
|
+
if (node.type === 'UnaryExpression' && node.operator === 'delete')
|
|
519
|
+
return true;
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
generateCode(ast) {
|
|
523
|
+
return this.astToCode(ast);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Convert AST node to JavaScript code string
|
|
527
|
+
*/
|
|
528
|
+
astToCode(node) {
|
|
529
|
+
if (!node)
|
|
530
|
+
return '';
|
|
531
|
+
switch (node.type) {
|
|
532
|
+
case 'Program':
|
|
533
|
+
return node.body.map((s) => this.astToCode(s)).join('\n');
|
|
534
|
+
case 'ExpressionStatement':
|
|
535
|
+
return this.astToCode(node.expression) + ';';
|
|
536
|
+
case 'Identifier':
|
|
537
|
+
return node.name;
|
|
538
|
+
case 'Literal':
|
|
539
|
+
if (node.raw !== undefined)
|
|
540
|
+
return node.raw;
|
|
541
|
+
if (typeof node.value === 'string')
|
|
542
|
+
return `"${node.value}"`;
|
|
543
|
+
if (node.value === null)
|
|
544
|
+
return 'null';
|
|
545
|
+
return String(node.value);
|
|
546
|
+
case 'BinaryExpression':
|
|
547
|
+
return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
|
|
548
|
+
case 'LogicalExpression':
|
|
549
|
+
return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
|
|
550
|
+
case 'UnaryExpression':
|
|
551
|
+
if (node.operator === 'typeof')
|
|
552
|
+
return `typeof ${this.astToCode(node.argument)}`;
|
|
553
|
+
if (node.operator === 'void')
|
|
554
|
+
return `void ${this.astToCode(node.argument)}`;
|
|
555
|
+
return `${node.operator}${this.astToCode(node.argument)}`;
|
|
556
|
+
case 'UpdateExpression':
|
|
557
|
+
return node.prefix
|
|
558
|
+
? `${node.operator}${this.astToCode(node.argument)}`
|
|
559
|
+
: `${this.astToCode(node.argument)}${node.operator}`;
|
|
560
|
+
case 'AssignmentExpression':
|
|
561
|
+
return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
|
|
562
|
+
case 'CallExpression':
|
|
563
|
+
const callee = this.astToCode(node.callee);
|
|
564
|
+
const args = node.arguments.map((a) => this.astToCode(a)).join(', ');
|
|
565
|
+
return `${callee}(${args})`;
|
|
566
|
+
case 'MemberExpression':
|
|
567
|
+
const obj = this.astToCode(node.object);
|
|
568
|
+
if (node.computed) {
|
|
569
|
+
return `${obj}[${this.astToCode(node.property)}]`;
|
|
570
|
+
}
|
|
571
|
+
return `${obj}.${this.astToCode(node.property)}`;
|
|
572
|
+
case 'ArrowFunctionExpression':
|
|
573
|
+
const arrowParams = node.params.map((p) => this.astToCode(p)).join(', ');
|
|
574
|
+
if (node.body.type === 'BlockStatement') {
|
|
575
|
+
return `(${arrowParams}) => ${this.astToCode(node.body)}`;
|
|
576
|
+
}
|
|
577
|
+
return `(${arrowParams}) => ${this.astToCode(node.body)}`;
|
|
578
|
+
case 'FunctionExpression':
|
|
579
|
+
const funcParams = node.params.map((p) => this.astToCode(p)).join(', ');
|
|
580
|
+
return `function(${funcParams}) ${this.astToCode(node.body)}`;
|
|
581
|
+
case 'FunctionDeclaration':
|
|
582
|
+
const declParams = node.params.map((p) => this.astToCode(p)).join(', ');
|
|
583
|
+
return `function ${this.astToCode(node.id)}(${declParams}) ${this.astToCode(node.body)}`;
|
|
584
|
+
case 'BlockStatement':
|
|
585
|
+
return `{\n${node.body.map((s) => ' ' + this.astToCode(s)).join('\n')}\n}`;
|
|
586
|
+
case 'ReturnStatement':
|
|
587
|
+
return `return ${this.astToCode(node.argument)};`;
|
|
588
|
+
case 'IfStatement':
|
|
589
|
+
let code = `if (${this.astToCode(node.test)}) ${this.astToCode(node.consequent)}`;
|
|
590
|
+
if (node.alternate) {
|
|
591
|
+
code += ` else ${this.astToCode(node.alternate)}`;
|
|
592
|
+
}
|
|
593
|
+
return code;
|
|
594
|
+
case 'VariableDeclaration':
|
|
595
|
+
const kind = node.kind;
|
|
596
|
+
const declarations = node.declarations.map((d) => {
|
|
597
|
+
if (d.type === 'VariableDeclarator') {
|
|
598
|
+
if (d.init) {
|
|
599
|
+
return `${this.astToCode(d.id)} = ${this.astToCode(d.init)}`;
|
|
600
|
+
}
|
|
601
|
+
return this.astToCode(d.id);
|
|
602
|
+
}
|
|
603
|
+
return '';
|
|
604
|
+
}).join(', ');
|
|
605
|
+
return `${kind} ${declarations};`;
|
|
606
|
+
case 'VariableDeclarator':
|
|
607
|
+
if (node.init) {
|
|
608
|
+
return `${this.astToCode(node.id)} = ${this.astToCode(node.init)}`;
|
|
609
|
+
}
|
|
610
|
+
return this.astToCode(node.id);
|
|
611
|
+
case 'Property':
|
|
612
|
+
if (node.computed) {
|
|
613
|
+
return `${this.astToCode(node.key)}: ${this.astToCode(node.value)}`;
|
|
614
|
+
}
|
|
615
|
+
if (node.key.type === 'Identifier') {
|
|
616
|
+
return `${node.key.name}: ${this.astToCode(node.value)}`;
|
|
617
|
+
}
|
|
618
|
+
return `${this.astToCode(node.key)}: ${this.astToCode(node.value)}`;
|
|
619
|
+
case 'ObjectExpression':
|
|
620
|
+
if (!node.properties || node.properties.length === 0)
|
|
621
|
+
return '{}';
|
|
622
|
+
return `{\n${node.properties.map((p) => ' ' + this.astToCode(p)).join(',\n')}\n}`;
|
|
623
|
+
case 'ArrayExpression':
|
|
624
|
+
return `[${node.elements.map((e) => this.astToCode(e)).join(', ')}]`;
|
|
625
|
+
case 'SpreadElement':
|
|
626
|
+
return `...${this.astToCode(node.argument)}`;
|
|
627
|
+
case 'TemplateLiteral':
|
|
628
|
+
let template = '`';
|
|
629
|
+
for (let i = 0; i < node.expressions.length; i++) {
|
|
630
|
+
template += node.quasis[i].value.raw;
|
|
631
|
+
template += '${' + this.astToCode(node.expressions[i]) + '}';
|
|
632
|
+
}
|
|
633
|
+
template += node.quasis[node.quasis.length - 1].value.raw + '`';
|
|
634
|
+
return template;
|
|
635
|
+
case 'TaggedTemplateExpression':
|
|
636
|
+
return `${this.astToCode(node.tag)}${this.astToCode(node.quasi)}`;
|
|
637
|
+
case 'ConditionalExpression':
|
|
638
|
+
return `${this.astToCode(node.test)} ? ${this.astToCode(node.consequent)} : ${this.astToCode(node.alternate)}`;
|
|
639
|
+
case 'ParenthesizedExpression':
|
|
640
|
+
return `(${this.astToCode(node.expression)})`;
|
|
641
|
+
case 'EmptyStatement':
|
|
642
|
+
return ';';
|
|
643
|
+
case 'DebuggerStatement':
|
|
644
|
+
return 'debugger;';
|
|
645
|
+
default:
|
|
646
|
+
// Fallback: return a placeholder
|
|
647
|
+
return `/* unknown: ${node.type} */`;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// ─── Auto-Memoization (React Compiler-like) ────────────────────
|
|
651
|
+
/**
|
|
652
|
+
* Automatically memoize expensive computations.
|
|
653
|
+
* Similar to React Compiler's auto-memoization — eliminates the need for manual useMemo/useCallback.
|
|
654
|
+
*/
|
|
655
|
+
autoMemoize(ast) {
|
|
656
|
+
// Identify pure expressions that should be memoized
|
|
657
|
+
const isPureExpression = (node) => {
|
|
658
|
+
if (!node || typeof node !== 'object')
|
|
659
|
+
return false;
|
|
660
|
+
if (node.type === 'Literal')
|
|
661
|
+
return true;
|
|
662
|
+
if (node.type === 'Identifier')
|
|
663
|
+
return true;
|
|
664
|
+
if (node.type === 'BinaryExpression') {
|
|
665
|
+
return isPureExpression(node.left) && isPureExpression(node.right);
|
|
666
|
+
}
|
|
667
|
+
if (node.type === 'UnaryExpression') {
|
|
668
|
+
return isPureExpression(node.argument);
|
|
669
|
+
}
|
|
670
|
+
if (node.type === 'LogicalExpression') {
|
|
671
|
+
return isPureExpression(node.left) && isPureExpression(node.right);
|
|
672
|
+
}
|
|
673
|
+
if (node.type === 'ConditionalExpression') {
|
|
674
|
+
return (isPureExpression(node.test) &&
|
|
675
|
+
isPureExpression(node.consequent) &&
|
|
676
|
+
isPureExpression(node.alternate));
|
|
677
|
+
}
|
|
678
|
+
if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
if (node.type === 'ArrayExpression') {
|
|
682
|
+
return node.elements.every((el) => !el || isPureExpression(el));
|
|
683
|
+
}
|
|
684
|
+
if (node.type === 'ObjectExpression') {
|
|
685
|
+
return node.properties.every((prop) => {
|
|
686
|
+
if (prop.type === 'Property') {
|
|
687
|
+
return isPureExpression(prop.value);
|
|
688
|
+
}
|
|
689
|
+
if (prop.type === 'SpreadElement') {
|
|
690
|
+
return isPureExpression(prop.argument);
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
if (node.type === 'CallExpression') {
|
|
696
|
+
const calleeName = node.callee?.name || '';
|
|
697
|
+
if (calleeName.startsWith('set') || calleeName.startsWith('dispatch')) {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
if (['fetch', 'XMLHttpRequest', 'setTimeout', 'setInterval'].includes(calleeName)) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
};
|
|
707
|
+
// Check if expression is too simple to memoize
|
|
708
|
+
const isTrivialExpression = (node) => {
|
|
709
|
+
if (!node || typeof node !== 'object')
|
|
710
|
+
return true;
|
|
711
|
+
if (node.type === 'Literal')
|
|
712
|
+
return true;
|
|
713
|
+
if (node.type === 'Identifier')
|
|
714
|
+
return true;
|
|
715
|
+
if (node.type === 'MemberExpression') {
|
|
716
|
+
return !node.computed && isTrivialExpression(node.object);
|
|
717
|
+
}
|
|
718
|
+
return false;
|
|
719
|
+
};
|
|
720
|
+
// Extract dependencies from expression
|
|
721
|
+
const extractDependencies = (node) => {
|
|
722
|
+
const deps = new Set();
|
|
723
|
+
const collectDeps = (n) => {
|
|
724
|
+
if (!n || typeof n !== 'object')
|
|
725
|
+
return;
|
|
726
|
+
if (n.type === 'Identifier') {
|
|
727
|
+
deps.add(n.name);
|
|
728
|
+
}
|
|
729
|
+
if (n.type === 'MemberExpression' && n.object?.type === 'Identifier') {
|
|
730
|
+
deps.add(n.object.name);
|
|
731
|
+
}
|
|
732
|
+
for (const key of Object.keys(n)) {
|
|
733
|
+
if (key === 'type' || key === 'start' || key === 'end')
|
|
734
|
+
continue;
|
|
735
|
+
const child = n[key];
|
|
736
|
+
if (Array.isArray(child)) {
|
|
737
|
+
child.forEach((item) => {
|
|
738
|
+
if (item && typeof item === 'object' && item.type) {
|
|
739
|
+
collectDeps(item);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
else if (child && typeof child === 'object' && child.type) {
|
|
744
|
+
collectDeps(child);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
collectDeps(node);
|
|
749
|
+
return Array.from(deps);
|
|
750
|
+
};
|
|
751
|
+
// Auto-memoization is disabled — wrapping initializers in ArrowFunctionExpression
|
|
752
|
+
// changes semantics (const doubled = count() * 2 becomes const doubled = () => count() * 2)
|
|
753
|
+
return ast;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Analyze and tree-shake unused exports
|
|
758
|
+
*/
|
|
759
|
+
export function treeShake(code, options) {
|
|
760
|
+
const result = {
|
|
761
|
+
kept: [],
|
|
762
|
+
eliminated: [],
|
|
763
|
+
warnings: [],
|
|
764
|
+
};
|
|
765
|
+
// Parse imports to find used names
|
|
766
|
+
const usedNames = new Set();
|
|
767
|
+
const importRegex = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"](.*)['"]/g;
|
|
768
|
+
let match;
|
|
769
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
770
|
+
if (match[1]) {
|
|
771
|
+
// Named imports: import { a, b } from 'module'
|
|
772
|
+
match[1].split(',').forEach((name) => {
|
|
773
|
+
const trimmed = name.trim().split(/\s+as\s+/)[0].trim();
|
|
774
|
+
if (trimmed)
|
|
775
|
+
usedNames.add(trimmed);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
else if (match[2]) {
|
|
779
|
+
// Default import: import Foo from 'module'
|
|
780
|
+
usedNames.add(match[2]);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// Find all exports
|
|
784
|
+
const exportRegex = /export\s+(?:const|function|class)\s+(\w+)/g;
|
|
785
|
+
const defaultExportRegex = /export\s+default\s+/g;
|
|
786
|
+
while ((match = exportRegex.exec(code)) !== null) {
|
|
787
|
+
const name = match[1];
|
|
788
|
+
if (usedNames.has(name)) {
|
|
789
|
+
result.kept.push(name);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
result.eliminated.push(name);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Check for default export
|
|
796
|
+
if (defaultExportRegex.test(code)) {
|
|
797
|
+
result.kept.push('default');
|
|
798
|
+
}
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Analyze bundle for optimization opportunities
|
|
803
|
+
*/
|
|
804
|
+
export function analyzeBundle(code, moduleName) {
|
|
805
|
+
const lines = code.split('\n');
|
|
806
|
+
const exports = [];
|
|
807
|
+
// Extract exports
|
|
808
|
+
const exportRegex = /export\s+(?:const|function|class|default)\s+(\w+)/g;
|
|
809
|
+
let match;
|
|
810
|
+
while ((match = exportRegex.exec(code)) !== null) {
|
|
811
|
+
exports.push(match[1]);
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
size: Buffer.byteLength(code, 'utf8'),
|
|
815
|
+
gzipSize: Buffer.byteLength(code, 'utf8'), // Simplified
|
|
816
|
+
modules: [
|
|
817
|
+
{
|
|
818
|
+
name: moduleName,
|
|
819
|
+
size: Buffer.byteLength(code, 'utf8'),
|
|
820
|
+
exports,
|
|
821
|
+
sideEffects: false,
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
dependencies: [],
|
|
825
|
+
peerDependencies: [],
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
// ─── Dead Code Patterns ─────────────────────────────────────────
|
|
829
|
+
export const DEAD_CODE_PATTERNS = [
|
|
830
|
+
/if\s*\(\s*false\s*\)/,
|
|
831
|
+
/if\s*\(\s*0\s*\)/,
|
|
832
|
+
/if\s*\(\s*null\s*\)/,
|
|
833
|
+
/if\s*\(\s*undefined\s*\)/,
|
|
834
|
+
/while\s*\(\s*false\s*\)/,
|
|
835
|
+
/return\s+undefined\s*;/,
|
|
836
|
+
];
|
|
837
|
+
export const SIDE_EFFECT_FREE_FUNCTIONS = new Set([
|
|
838
|
+
'console.log',
|
|
839
|
+
'console.error',
|
|
840
|
+
'console.warn',
|
|
841
|
+
'Math.abs',
|
|
842
|
+
'Math.ceil',
|
|
843
|
+
'Math.floor',
|
|
844
|
+
'Math.round',
|
|
845
|
+
'parseInt',
|
|
846
|
+
'parseFloat',
|
|
847
|
+
'isNaN',
|
|
848
|
+
'isFinite',
|
|
849
|
+
]);
|
|
850
|
+
// ─── Static Subtree Hoisting ────────────────────────────────────
|
|
851
|
+
/**
|
|
852
|
+
* Identify and hoist static JSX subtrees.
|
|
853
|
+
* Static elements (no dynamic expressions) can be created once and reused.
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* // Before hoisting
|
|
857
|
+
* function App() {
|
|
858
|
+
* return <div><Header /><StaticContent /><Footer /></div>
|
|
859
|
+
* }
|
|
860
|
+
*
|
|
861
|
+
* // After hoisting — StaticContent is hoisted outside the function
|
|
862
|
+
* const _hoisted_0 = <StaticContent />
|
|
863
|
+
* function App() {
|
|
864
|
+
* return <div><Header />{_hoisted_0}<Footer /></div>
|
|
865
|
+
* }
|
|
866
|
+
*/
|
|
867
|
+
export function hoistStaticSubtrees(ast, code) {
|
|
868
|
+
let hoistedCount = 0;
|
|
869
|
+
const hoistedNodes = [];
|
|
870
|
+
function isStaticNode(node) {
|
|
871
|
+
if (!node || typeof node !== 'object')
|
|
872
|
+
return true;
|
|
873
|
+
// JSX elements with no dynamic children or attributes are static
|
|
874
|
+
if (node.type === 'JSXElement') {
|
|
875
|
+
// Check attributes for dynamic values
|
|
876
|
+
if (node.openingElement?.attributes) {
|
|
877
|
+
for (const attr of node.openingElement.attributes) {
|
|
878
|
+
if (attr.type === 'JSXSpreadAttribute')
|
|
879
|
+
return false;
|
|
880
|
+
if (attr.value?.type === 'JSXExpressionContainer') {
|
|
881
|
+
// Check if the expression is a literal
|
|
882
|
+
const expr = attr.value.expression;
|
|
883
|
+
if (expr && expr.type !== 'Literal')
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Check children
|
|
889
|
+
if (node.children) {
|
|
890
|
+
for (const child of node.children) {
|
|
891
|
+
if (child.type === 'JSXExpressionContainer') {
|
|
892
|
+
const expr = child.expression;
|
|
893
|
+
if (expr && expr.type !== 'Literal')
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
if (child.type === 'JSXElement' && !isStaticNode(child))
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
// Literals are static
|
|
903
|
+
if (node.type === 'Literal')
|
|
904
|
+
return true;
|
|
905
|
+
// JSXText is static
|
|
906
|
+
if (node.type === 'JSXText')
|
|
907
|
+
return true;
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
// Walk the AST and find hoistable static subtrees
|
|
911
|
+
function walkAndHoist(node, parent, key) {
|
|
912
|
+
if (!node || typeof node !== 'object')
|
|
913
|
+
return;
|
|
914
|
+
// Check if this is a static JSX element that should be hoisted
|
|
915
|
+
if (node.type === 'JSXElement' && isStaticNode(node) && shouldHoist(node)) {
|
|
916
|
+
const hoistedName = `_hoisted_${hoistedCount++}`;
|
|
917
|
+
const hoistedCode = generateStaticCode(node, code);
|
|
918
|
+
hoistedNodes.push({ name: hoistedName, code: hoistedCode });
|
|
919
|
+
// Replace with reference
|
|
920
|
+
if (parent && key) {
|
|
921
|
+
parent[key] = { type: 'Identifier', name: hoistedName };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// Continue walking children
|
|
925
|
+
for (const childKey of Object.keys(node)) {
|
|
926
|
+
if (childKey === 'type' || childKey === 'start' || childKey === 'end')
|
|
927
|
+
continue;
|
|
928
|
+
const child = node[childKey];
|
|
929
|
+
if (Array.isArray(child)) {
|
|
930
|
+
child.forEach((item, idx) => {
|
|
931
|
+
if (item && typeof item === 'object' && item.type) {
|
|
932
|
+
walkAndHoist(item, node, `${childKey}[${idx}]`);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
else if (child && typeof child === 'object' && child.type) {
|
|
937
|
+
walkAndHoist(child, node, childKey);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function shouldHoist(node) {
|
|
942
|
+
// Only hoist elements that are complex enough to benefit
|
|
943
|
+
// Simple elements like <div></div> are not worth hoisting
|
|
944
|
+
if (!node.children || node.children.length === 0)
|
|
945
|
+
return false;
|
|
946
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXText')
|
|
947
|
+
return false;
|
|
948
|
+
// Check for components — always worth hoisting
|
|
949
|
+
const tagName = node.openingElement?.name?.name;
|
|
950
|
+
if (tagName && tagName[0] === tagName[0].toUpperCase())
|
|
951
|
+
return true;
|
|
952
|
+
// Check for elements with attributes
|
|
953
|
+
if (node.openingElement?.attributes?.length > 0)
|
|
954
|
+
return true;
|
|
955
|
+
// Check for elements with multiple children
|
|
956
|
+
if (node.children.length > 1)
|
|
957
|
+
return true;
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
function generateStaticCode(node, source) {
|
|
961
|
+
return source.slice(node.start, node.end);
|
|
962
|
+
}
|
|
963
|
+
walkAndHoist(ast, null, '');
|
|
964
|
+
// Generate hoisted code
|
|
965
|
+
if (hoistedNodes.length > 0) {
|
|
966
|
+
const hoistedCode = hoistedNodes
|
|
967
|
+
.map(h => `const ${h.name} = ${h.code}`)
|
|
968
|
+
.join('\n');
|
|
969
|
+
return {
|
|
970
|
+
ast,
|
|
971
|
+
code: hoistedCode + '\n' + code,
|
|
972
|
+
hoisted: hoistedCount,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
return { ast, code, hoisted: 0 };
|
|
976
|
+
}
|
|
977
|
+
// ─── Auto-Memoization (React Compiler-like) ────────────────────
|
|
978
|
+
// Auto-memoization is disabled — wrapping initializers in ArrowFunctionExpression
|
|
979
|
+
// changes semantics (const doubled = count() * 2 becomes const doubled = () => count() * 2)
|
|
980
|
+
export function autoMemoize(ast) {
|
|
981
|
+
return { ast, memoized: 0 };
|
|
982
|
+
}
|
|
983
|
+
// ─── Compile-time CSS Scoping ───────────────────────────────────
|
|
984
|
+
/**
|
|
985
|
+
* Scope CSS to components at compile time.
|
|
986
|
+
* Generates unique class names and injects scoped styles.
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* // Input
|
|
990
|
+
* <style>
|
|
991
|
+
* .container { padding: 16px; }
|
|
992
|
+
* .title { font-size: 24px; }
|
|
993
|
+
* </style>
|
|
994
|
+
* <div class="container">
|
|
995
|
+
* <h1 class="title">Hello</h1>
|
|
996
|
+
* </div>
|
|
997
|
+
*
|
|
998
|
+
* // Output (with scope hash "abc123")
|
|
999
|
+
* <style>
|
|
1000
|
+
* .container[data-scoped-abc123] { padding: 16px; }
|
|
1001
|
+
* .title[data-scoped-abc123] { font-size: 24px; }
|
|
1002
|
+
* </style>
|
|
1003
|
+
* <div class="container" data-scoped-abc123>
|
|
1004
|
+
* <h1 class="title" data-scoped-abc123>Hello</h1>
|
|
1005
|
+
* </div>
|
|
1006
|
+
*/
|
|
1007
|
+
export function scopeCSS(ast, code, componentName) {
|
|
1008
|
+
const scopes = [];
|
|
1009
|
+
let scopeCounter = 0;
|
|
1010
|
+
function generateScopeId(name) {
|
|
1011
|
+
const base = name || 'scope';
|
|
1012
|
+
return `${base}-${scopeCounter++}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1013
|
+
}
|
|
1014
|
+
function extractCSSFromJSX(code) {
|
|
1015
|
+
const cssRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
1016
|
+
const positions = [];
|
|
1017
|
+
let css = '';
|
|
1018
|
+
let match;
|
|
1019
|
+
while ((match = cssRegex.exec(code)) !== null) {
|
|
1020
|
+
css += match[1];
|
|
1021
|
+
positions.push({ start: match.index, end: match.index + match[0].length });
|
|
1022
|
+
}
|
|
1023
|
+
return { css, positions };
|
|
1024
|
+
}
|
|
1025
|
+
function scopeCSSRules(css, scopeId) {
|
|
1026
|
+
// Simple CSS scoping — add data attribute selector
|
|
1027
|
+
return css
|
|
1028
|
+
.replace(/\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g, `.$1[data-scoped-${scopeId}]`)
|
|
1029
|
+
.replace(/#([a-zA-Z_-][a-zA-Z0-9_-]*)/g, `#$1[data-scoped-${scopeId}]`);
|
|
1030
|
+
}
|
|
1031
|
+
const { css } = extractCSSFromJSX(code);
|
|
1032
|
+
if (css) {
|
|
1033
|
+
const scopeId = generateScopeId(componentName);
|
|
1034
|
+
const scopedCSS = scopeCSSRules(css, scopeId);
|
|
1035
|
+
scopes.push({
|
|
1036
|
+
id: scopeId,
|
|
1037
|
+
selector: `[data-scoped-${scopeId}]`,
|
|
1038
|
+
css: scopedCSS,
|
|
1039
|
+
hash: scopeId,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
return { ast, code, scopes };
|
|
1043
|
+
}
|
|
1044
|
+
// ─── Bundle Analysis (v2 — Enhanced) ────────────────────────────
|
|
1045
|
+
//# sourceMappingURL=index.js.map
|