@shriyanss/js-recon 1.4.1-alpha.2 → 1.4.1-alpha.3

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CLAUDE.md +39 -2
  3. package/README.md +2 -0
  4. package/build/analyze/engine/index.js.map +1 -1
  5. package/build/analyze/helpers/schemas.js +1 -1
  6. package/build/analyze/helpers/schemas.js.map +1 -1
  7. package/build/analyze/index.js +1 -0
  8. package/build/analyze/index.js.map +1 -1
  9. package/build/globalConfig.js +2 -2
  10. package/build/globalConfig.js.map +1 -1
  11. package/build/index.js +73 -3
  12. package/build/index.js.map +1 -1
  13. package/build/lazyLoad/downloadQueue.js +1 -1
  14. package/build/lazyLoad/downloadQueue.js.map +1 -1
  15. package/build/lazyLoad/index.js +25 -0
  16. package/build/lazyLoad/index.js.map +1 -1
  17. package/build/lazyLoad/methodFilter.js +1 -0
  18. package/build/lazyLoad/methodFilter.js.map +1 -1
  19. package/build/lazyLoad/nuxt_js/nuxt_getBuildsManifest.js +64 -0
  20. package/build/lazyLoad/nuxt_js/nuxt_getBuildsManifest.js.map +1 -0
  21. package/build/lazyLoad/react/react_followImports.js +8 -2
  22. package/build/lazyLoad/react/react_followImports.js.map +1 -1
  23. package/build/lazyLoad/svelte/svelte_getFromPageSource.js +16 -0
  24. package/build/lazyLoad/svelte/svelte_getFromPageSource.js.map +1 -1
  25. package/build/lazyLoad/svelte/svelte_getVersionJson.js +40 -0
  26. package/build/lazyLoad/svelte/svelte_getVersionJson.js.map +1 -0
  27. package/build/map/angular_js/getAngularConnections.js +88 -0
  28. package/build/map/angular_js/getAngularConnections.js.map +1 -0
  29. package/build/map/angular_js/interactive.js +4 -0
  30. package/build/map/angular_js/interactive.js.map +1 -0
  31. package/build/map/index.js +38 -0
  32. package/build/map/index.js.map +1 -1
  33. package/build/refactor/index.js +478 -12
  34. package/build/refactor/index.js.map +1 -1
  35. package/build/refactor/react/helpers.js +13 -0
  36. package/build/refactor/react/helpers.js.map +1 -1
  37. package/build/refactor/react/index.js +149 -41
  38. package/build/refactor/react/index.js.map +1 -1
  39. package/build/refactor/react/library-classify.js +232 -1
  40. package/build/refactor/react/library-classify.js.map +1 -1
  41. package/build/refactor/react/transform.js +1046 -34
  42. package/build/refactor/react/transform.js.map +1 -1
  43. package/build/refactor/react-vite/index.js +618 -0
  44. package/build/refactor/react-vite/index.js.map +1 -0
  45. package/build/refactor/react-vite/vendor-analyze.js +336 -0
  46. package/build/refactor/react-vite/vendor-analyze.js.map +1 -0
  47. package/build/refactor/remote/cache.js +211 -0
  48. package/build/refactor/remote/cache.js.map +1 -0
  49. package/build/refactor/remote/config.js +47 -0
  50. package/build/refactor/remote/config.js.map +1 -0
  51. package/build/refactor/remote/hf-client.js +137 -0
  52. package/build/refactor/remote/hf-client.js.map +1 -0
  53. package/build/run/index.js +116 -11
  54. package/build/run/index.js.map +1 -1
  55. package/build/utility/banner.js +68 -0
  56. package/build/utility/banner.js.map +1 -0
  57. package/package.json +2 -1
@@ -2,7 +2,7 @@ import _traverse from "@babel/traverse";
2
2
  import _generator from "@babel/generator";
3
3
  import * as t from "@babel/types";
4
4
  import { tryExtractModuleExportsAssignment, tryExtractExportsAssignment, tryExtractRequireCall, buildModuleExportStatement, makeNamedExportStatement, } from "./helpers.js";
5
- import { REACT_CANONICAL, JSX_RUNTIME_CANONICAL, REACT_DOM_CLIENT_CANONICAL, librarySource, } from "./library-classify.js";
5
+ import { REACT_CANONICAL, JSX_RUNTIME_CANONICAL, REACT_DOM_CLIENT_CANONICAL, REACT_ROUTER_DOM_CANONICAL, librarySource, } from "./library-classify.js";
6
6
  const traverse = _traverse.default;
7
7
  const generate = _generator.default;
8
8
  /**
@@ -55,6 +55,92 @@ export const transformModule = (mod) => {
55
55
  }
56
56
  body.body = next;
57
57
  }
58
+ // Pass 1.5 — handle webpack ES module registration patterns:
59
+ // `<requireParam>.r(<exportsParam>)` → drop (marks module as ES module, not needed in ESM)
60
+ // `<requireParam>.d(<exportsParam>, {name: ()=>binding, ...})` → named exports
61
+ // This handles the common lazy-chunk pattern where webpack emits r.r(t), r.d(t, {default:()=>X})
62
+ // at the top of the module body instead of the older t.exports = X form.
63
+ if (requireParam && exportsParam) {
64
+ const processWebpackRegExpr = (expr) => {
65
+ if (!t.isCallExpression(expr))
66
+ return null;
67
+ const callee = expr.callee;
68
+ if (!t.isMemberExpression(callee) || callee.computed)
69
+ return null;
70
+ if (!t.isIdentifier(callee.object, { name: requireParam }))
71
+ return null;
72
+ const method = t.isIdentifier(callee.property) ? callee.property.name : null;
73
+ if (!method)
74
+ return null;
75
+ // r.r(t) → drop
76
+ if (method === "r" &&
77
+ expr.arguments.length === 1 &&
78
+ t.isIdentifier(expr.arguments[0], { name: exportsParam })) {
79
+ return [];
80
+ }
81
+ // r.d(t, {name: ()=>binding, ...}) → named export statements
82
+ if (method === "d" &&
83
+ expr.arguments.length >= 2 &&
84
+ t.isIdentifier(expr.arguments[0], { name: exportsParam }) &&
85
+ t.isObjectExpression(expr.arguments[1])) {
86
+ const stmts = [];
87
+ for (const prop of expr.arguments[1].properties) {
88
+ if (!t.isObjectProperty(prop) || prop.computed)
89
+ continue;
90
+ const key = prop.key;
91
+ const val = prop.value;
92
+ let propName = null;
93
+ if (t.isIdentifier(key))
94
+ propName = key.name;
95
+ else if (t.isStringLiteral(key))
96
+ propName = key.value;
97
+ if (!propName)
98
+ continue;
99
+ // val is typically `() => binding` — unwrap the arrow function body
100
+ const exportedVal = t.isArrowFunctionExpression(val) && !t.isBlockStatement(val.body)
101
+ ? val.body
102
+ : val;
103
+ stmts.push(makeNamedExportStatement(propName, exportedVal));
104
+ }
105
+ return stmts;
106
+ }
107
+ return null;
108
+ };
109
+ const next = [];
110
+ for (const stmt of body.body) {
111
+ if (!t.isExpressionStatement(stmt)) {
112
+ next.push(stmt);
113
+ continue;
114
+ }
115
+ const expr = stmt.expression;
116
+ if (t.isSequenceExpression(expr)) {
117
+ let hadMatch = false;
118
+ const splitted = [];
119
+ for (const sub of expr.expressions) {
120
+ const handled = processWebpackRegExpr(sub);
121
+ if (handled !== null) {
122
+ hadMatch = true;
123
+ splitted.push(...handled);
124
+ }
125
+ else {
126
+ splitted.push(t.expressionStatement(sub));
127
+ }
128
+ }
129
+ if (hadMatch) {
130
+ next.push(...splitted);
131
+ continue;
132
+ }
133
+ }
134
+ const handled = processWebpackRegExpr(expr);
135
+ if (handled !== null) {
136
+ next.push(...handled);
137
+ }
138
+ else {
139
+ next.push(stmt);
140
+ }
141
+ }
142
+ body.body = next;
143
+ }
58
144
  // Pass 2 — handle `<exportsParam>.<propName> = <rhs>` at the top level only.
59
145
  if (exportsParam) {
60
146
  const next = [];
@@ -142,6 +228,58 @@ export const transformModule = (mod) => {
142
228
  },
143
229
  });
144
230
  }
231
+ // Pass 4.5 — convert webpack async chunk loading to a true dynamic import:
232
+ // requireParam.e(N).then(requireParam.bind(requireParam, N)) → import('./N.js')
233
+ // This pattern is emitted by webpack for React.lazy() code-split points. After the
234
+ // outer module wrapper is stripped, `requireParam` is no longer in scope, so the
235
+ // expression must be replaced before Step 5 removes it.
236
+ if (requireParam) {
237
+ fnPath.traverse({
238
+ CallExpression(callPath) {
239
+ const node = callPath.node;
240
+ // Outer call must be X.then(bindExpr) — non-computed member
241
+ if (!t.isMemberExpression(node.callee) || node.callee.computed)
242
+ return;
243
+ if (!t.isIdentifier(node.callee.property, { name: "then" }))
244
+ return;
245
+ // X must be requireParam.e(numId)
246
+ const eCallNode = node.callee.object;
247
+ if (!t.isCallExpression(eCallNode))
248
+ return;
249
+ const eCallee = eCallNode.callee;
250
+ if (!t.isMemberExpression(eCallee) || eCallee.computed)
251
+ return;
252
+ if (!t.isIdentifier(eCallee.object, { name: requireParam }))
253
+ return;
254
+ if (!t.isIdentifier(eCallee.property, { name: "e" }))
255
+ return;
256
+ const eArgs = eCallNode.arguments;
257
+ if (eArgs.length !== 1 || !t.isNumericLiteral(eArgs[0]))
258
+ return;
259
+ const numId = eArgs[0].value;
260
+ // The .then() argument must be requireParam.bind(requireParam, numId)
261
+ if (node.arguments.length !== 1 || !t.isCallExpression(node.arguments[0]))
262
+ return;
263
+ const bindNode = node.arguments[0];
264
+ if (!t.isMemberExpression(bindNode.callee) || bindNode.callee.computed)
265
+ return;
266
+ if (!t.isIdentifier(bindNode.callee.object, { name: requireParam }))
267
+ return;
268
+ if (!t.isIdentifier(bindNode.callee.property, { name: "bind" }))
269
+ return;
270
+ const bindArgs = bindNode.arguments;
271
+ if (bindArgs.length !== 2)
272
+ return;
273
+ if (!t.isIdentifier(bindArgs[0], { name: requireParam }))
274
+ return;
275
+ if (!t.isNumericLiteral(bindArgs[1]) || bindArgs[1].value !== numId)
276
+ return;
277
+ // Replace with import('./N.js') — a dynamic import expression
278
+ callPath.replaceWith(t.callExpression(t.import(), [t.stringLiteral(`./${numId}.js`)]));
279
+ callPath.skip();
280
+ },
281
+ });
282
+ }
145
283
  // Step 5 — strip outer function wrapper, prepend hoisted static imports.
146
284
  const importStmts = [];
147
285
  for (const [spec, name] of hoistedImports) {
@@ -194,6 +332,8 @@ function resolveLibraryProp(varName, prop, varToLib) {
194
332
  return JSX_RUNTIME_CANONICAL.has(name) || name === "Fragment";
195
333
  if (info.type === "react-dom-client")
196
334
  return REACT_DOM_CLIENT_CANONICAL.has(name);
335
+ if (info.type === "react-router-dom")
336
+ return REACT_ROUTER_DOM_CANONICAL.has(name);
197
337
  return false;
198
338
  };
199
339
  // Check exportMap: only trust the value if it IS a canonical name.
@@ -281,6 +421,36 @@ function rewriteLibraryCalls(statements, varToLib) {
281
421
  p.replaceWith(t.identifier(resolved.canonical));
282
422
  p.skip();
283
423
  },
424
+ // Rewrite JSX member expressions: <ns.Component> → <CanonicalName>
425
+ // These are JSXMemberExpression nodes (not MemberExpression), so the visitor above misses them.
426
+ JSXOpeningElement(p) {
427
+ const elem = p.node;
428
+ if (!t.isJSXMemberExpression(elem.name))
429
+ return;
430
+ const obj = elem.name.object;
431
+ const prop = elem.name.property;
432
+ if (!t.isJSXIdentifier(obj) || !t.isJSXIdentifier(prop))
433
+ return;
434
+ const resolved = resolveLibraryProp(obj.name, prop.name, varToLib);
435
+ if (!resolved)
436
+ return;
437
+ record(resolved.libType, resolved.canonical);
438
+ elem.name = t.jsxIdentifier(resolved.canonical);
439
+ },
440
+ JSXClosingElement(p) {
441
+ const elem = p.node;
442
+ if (!t.isJSXMemberExpression(elem.name))
443
+ return;
444
+ const obj = elem.name.object;
445
+ const prop = elem.name.property;
446
+ if (!t.isJSXIdentifier(obj) || !t.isJSXIdentifier(prop))
447
+ return;
448
+ const resolved = resolveLibraryProp(obj.name, prop.name, varToLib);
449
+ if (!resolved)
450
+ return;
451
+ // Don't record() again — already recorded from JSXOpeningElement.
452
+ elem.name = t.jsxIdentifier(resolved.canonical);
453
+ },
284
454
  });
285
455
  return usedExports;
286
456
  }
@@ -384,11 +554,174 @@ function collapseSlicedToArray(statements) {
384
554
  }
385
555
  return out;
386
556
  }
557
+ // ---------------------------------------------------------------------------
558
+ // Pass E.2 — collapse named slicedToArray helper calls
559
+ // ---------------------------------------------------------------------------
560
+ /**
561
+ * Detects a named slicedToArray helper function by the TypeError throw at the
562
+ * end of its body. Shape: function(e, t) { return ... || (() => { throw new TypeError("...non-iterable...") })() }
563
+ */
564
+ function isNamedSlicedToArrayHelper(stmt) {
565
+ if (!t.isFunctionDeclaration(stmt) || !stmt.id || stmt.params.length !== 2)
566
+ return null;
567
+ // Direct name match: _slicedToArray is always a Babel array-destructure helper.
568
+ if (stmt.id.name === "_slicedToArray" || stmt.id.name === "slicedToArray")
569
+ return stmt.id.name;
570
+ const body = stmt.body.body;
571
+ if (body.length === 0)
572
+ return null;
573
+ const last = body[body.length - 1];
574
+ if (!t.isReturnStatement(last) || !last.argument)
575
+ return null;
576
+ // The return value must (somewhere in its || chain) throw new TypeError
577
+ const hasTypeError = (node) => {
578
+ if (t.isCallExpression(node) &&
579
+ (t.isFunctionExpression(node.callee) || t.isArrowFunctionExpression(node.callee))) {
580
+ const innerBody = node.callee.body;
581
+ if (t.isBlockStatement(innerBody)) {
582
+ return innerBody.body.some((s) => t.isThrowStatement(s) &&
583
+ t.isNewExpression(s.argument) &&
584
+ t.isIdentifier(s.argument.callee, { name: "TypeError" }) &&
585
+ s.argument.arguments.some((a) => t.isStringLiteral(a) && a.value.includes("non-iterable")));
586
+ }
587
+ }
588
+ if (t.isLogicalExpression(node) && node.operator === "||")
589
+ return hasTypeError(node.right);
590
+ return false;
591
+ };
592
+ return hasTypeError(last.argument) ? stmt.id.name : null;
593
+ }
594
+ /**
595
+ * Collapses `const temp = helper(expr, n), a = temp[0], b = temp[1]` into
596
+ * `const [a, b] = expr` for a given list of statements, knowing which function
597
+ * names are slicedToArray helpers (passed in as `helperNames`).
598
+ */
599
+ function collapseSlicedToArrayCalls(stmts, helperNames) {
600
+ if (helperNames.size === 0)
601
+ return stmts;
602
+ const out = [];
603
+ for (const stmt of stmts) {
604
+ if (!t.isVariableDeclaration(stmt)) {
605
+ out.push(stmt);
606
+ continue;
607
+ }
608
+ const decls = stmt.declarations;
609
+ const newDecls = [];
610
+ let i = 0;
611
+ while (i < decls.length) {
612
+ const d = decls[i];
613
+ if (!t.isIdentifier(d.id) || !d.init || !t.isCallExpression(d.init)) {
614
+ newDecls.push(d);
615
+ i++;
616
+ continue;
617
+ }
618
+ const callExpr = d.init;
619
+ const callee = callExpr.callee;
620
+ if (!t.isIdentifier(callee) || !helperNames.has(callee.name)) {
621
+ newDecls.push(d);
622
+ i++;
623
+ continue;
624
+ }
625
+ const tempName = d.id.name;
626
+ let actualExpr = callExpr.arguments[0];
627
+ // If actualExpr references the immediately preceding declarator, inline its init
628
+ // so we get `const [a, b] = actualCall` instead of `const _x = actualCall, [a, b] = _x`.
629
+ if (t.isIdentifier(actualExpr) &&
630
+ newDecls.length > 0 &&
631
+ t.isIdentifier(newDecls[newDecls.length - 1].id) &&
632
+ newDecls[newDecls.length - 1].id.name === actualExpr.name &&
633
+ newDecls[newDecls.length - 1].init != null) {
634
+ actualExpr = newDecls[newDecls.length - 1].init;
635
+ newDecls.pop(); // remove the now-inlined declarator
636
+ }
637
+ const targets = [];
638
+ let j = i + 1;
639
+ while (j < decls.length) {
640
+ const nd = decls[j];
641
+ if (!t.isIdentifier(nd.id) ||
642
+ !nd.init ||
643
+ !t.isMemberExpression(nd.init) ||
644
+ !nd.init.computed ||
645
+ !t.isIdentifier(nd.init.object, { name: tempName }) ||
646
+ !t.isNumericLiteral(nd.init.property))
647
+ break;
648
+ targets.push({
649
+ id: nd.id,
650
+ index: nd.init.property.value,
651
+ });
652
+ j++;
653
+ }
654
+ if (targets.length > 0) {
655
+ const sorted = [...targets].sort((a, b) => a.index - b.index);
656
+ const pattern = t.arrayPattern(sorted.map((tgt) => tgt.id));
657
+ newDecls.push(t.variableDeclarator(pattern, actualExpr));
658
+ i = j;
659
+ }
660
+ else {
661
+ newDecls.push(d);
662
+ i++;
663
+ }
664
+ }
665
+ if (newDecls.length > 0)
666
+ out.push(t.variableDeclaration(stmt.kind, newDecls));
667
+ }
668
+ return out;
669
+ }
670
+ /**
671
+ * Given a list of statements, finds any named slicedToArray helpers, then:
672
+ * 1. Collapses `const temp = helper(expr, n), a = temp[0], b = temp[1]` → `const [a, b] = expr`
673
+ * 2. Rewrites inline `helper(expr, n)[k]` → `expr[k]` (single-element extraction with chained access)
674
+ * 3. Removes the helper declarations once they are no longer referenced.
675
+ */
676
+ function collapseNamedSlicedToArray(statements) {
677
+ const helperNames = new Set();
678
+ for (const stmt of statements) {
679
+ const name = isNamedSlicedToArrayHelper(stmt);
680
+ if (name)
681
+ helperNames.add(name);
682
+ }
683
+ if (helperNames.size === 0)
684
+ return statements;
685
+ // Remove helper declarations and collapse top-level declarator-sequence usages
686
+ const withoutHelpers = statements.filter((s) => !(t.isFunctionDeclaration(s) && s.id && helperNames.has(s.id.name)));
687
+ const topCollapsed = collapseSlicedToArrayCalls(withoutHelpers, helperNames);
688
+ // Recurse into nested function bodies via traverse — collapse declarator sequences
689
+ const syntheticFile = t.file(t.program(topCollapsed, [], "module"));
690
+ traverse(syntheticFile, {
691
+ BlockStatement(p) {
692
+ const collapsed = collapseSlicedToArrayCalls(p.node.body, helperNames);
693
+ if (collapsed !== p.node.body)
694
+ p.node.body = collapsed;
695
+ },
696
+ // Rewrite inline `helper(expr, n)[k]` → `expr[k]`
697
+ MemberExpression(p) {
698
+ const node = p.node;
699
+ if (!node.computed)
700
+ return;
701
+ if (!t.isNumericLiteral(node.property))
702
+ return;
703
+ if (!t.isCallExpression(node.object))
704
+ return;
705
+ const call = node.object;
706
+ if (!t.isIdentifier(call.callee) || !helperNames.has(call.callee.name))
707
+ return;
708
+ const actualExpr = call.arguments[0];
709
+ if (!actualExpr)
710
+ return;
711
+ p.replaceWith(t.memberExpression(actualExpr, node.property, true));
712
+ p.skip();
713
+ },
714
+ });
715
+ return topCollapsed;
716
+ }
387
717
  /** Traverse all function bodies in a statement list and collapse slicedToArray inside them. */
388
718
  function collapseSlicedToArrayDeep(statements) {
389
- // First collapse top-level
390
- const top = collapseSlicedToArray(statements);
391
- // Then recurse into function bodies via a traverse
719
+ // First collapse named helpers (module-level factored-out slicedToArray function).
720
+ // This also recurses into all nested function bodies via traverse internally.
721
+ const afterNamed = collapseNamedSlicedToArray(statements);
722
+ // Collapse the inline expansion pattern at top level
723
+ const top = collapseSlicedToArray(afterNamed);
724
+ // Then recurse into function bodies via a traverse for the inline pattern
392
725
  const syntheticFile = t.file(t.program(top, [], "module"));
393
726
  traverse(syntheticFile, {
394
727
  BlockStatement(p) {
@@ -404,8 +737,15 @@ function collapseSlicedToArrayDeep(statements) {
404
737
  // Pass F — JSX recovery
405
738
  // ---------------------------------------------------------------------------
406
739
  function exprToJsxName(expr) {
740
+ var _a;
407
741
  if (t.isStringLiteral(expr))
408
742
  return t.jsxIdentifier(expr.value);
743
+ // Handle template literals with no expressions: `div` → "div"
744
+ if (t.isTemplateLiteral(expr) && expr.expressions.length === 0 && expr.quasis.length === 1) {
745
+ const raw = (_a = expr.quasis[0].value.cooked) !== null && _a !== void 0 ? _a : expr.quasis[0].value.raw;
746
+ if (raw)
747
+ return t.jsxIdentifier(raw);
748
+ }
409
749
  if (t.isIdentifier(expr))
410
750
  return t.jsxIdentifier(expr.name);
411
751
  if (t.isMemberExpression(expr) && !expr.computed && t.isIdentifier(expr.property)) {
@@ -421,8 +761,15 @@ function exprToJsxAttrValue(expr) {
421
761
  return t.jsxExpressionContainer(expr);
422
762
  }
423
763
  function childToJsxChild(child) {
764
+ var _a;
424
765
  if (t.isStringLiteral(child))
425
766
  return t.jsxText(child.value);
767
+ // Template literal with no expressions: `Dashboard` → JSXText
768
+ if (t.isTemplateLiteral(child) && child.expressions.length === 0 && child.quasis.length === 1) {
769
+ const raw = (_a = child.quasis[0].value.cooked) !== null && _a !== void 0 ? _a : child.quasis[0].value.raw;
770
+ if (raw !== undefined)
771
+ return t.jsxText(raw);
772
+ }
426
773
  if (t.isJSXElement(child) || t.isJSXFragment(child))
427
774
  return child;
428
775
  // Recursively convert nested jsx(...) calls into JSX elements.
@@ -433,23 +780,52 @@ function childToJsxChild(child) {
433
780
  }
434
781
  return t.jsxExpressionContainer(child);
435
782
  }
436
- function tryConvertToJSX(call) {
437
- const callee = call.callee;
438
- if (!t.isIdentifier(callee))
783
+ // Detect Babel's compiled object-spread IIFE:
784
+ // (function(target) { for(var i=1; i<arguments.length; i++){...} return target; })(base, spread1, ...)
785
+ // Returns { base, spreads } if matched, null otherwise.
786
+ function tryUnpackSpreadIIFE(expr) {
787
+ if (!t.isCallExpression(expr))
439
788
  return null;
440
- if (callee.name !== "jsx" && callee.name !== "jsxs" && callee.name !== "jsxDEV")
789
+ const callee = expr.callee;
790
+ if (!t.isFunctionExpression(callee))
441
791
  return null;
442
- if (call.arguments.length < 2)
792
+ if (callee.params.length !== 1 || !t.isIdentifier(callee.params[0]))
443
793
  return null;
444
- const tagArg = call.arguments[0];
445
- const propsArg = call.arguments[1];
446
- const jsxName = exprToJsxName(tagArg);
447
- if (!jsxName)
794
+ const paramName = callee.params[0].name;
795
+ const body = callee.body.body;
796
+ if (body.length < 2)
797
+ return null;
798
+ // Must contain a for loop that iterates `arguments.length`
799
+ const hasArgumentsLoop = body.some((s) => {
800
+ if (!t.isForStatement(s) || !s.test || !t.isBinaryExpression(s.test))
801
+ return false;
802
+ const right = s.test.right;
803
+ return (s.test.operator === "<" &&
804
+ t.isMemberExpression(right) &&
805
+ t.isIdentifier(right.object, { name: "arguments" }) &&
806
+ t.isIdentifier(right.property, { name: "length" }));
807
+ });
808
+ if (!hasArgumentsLoop)
448
809
  return null;
810
+ // Must return the first param
811
+ const hasReturn = body.some((s) => t.isReturnStatement(s) && s.argument && t.isIdentifier(s.argument, { name: paramName }));
812
+ if (!hasReturn)
813
+ return null;
814
+ const args = expr.arguments;
815
+ if (args.length < 1)
816
+ return null;
817
+ return { base: args[0], spreads: args.slice(1) };
818
+ }
819
+ /** Build JSXAttributes + children from an ObjectExpression or a spread-IIFE propsArg. */
820
+ function propsArgToAttrsAndChildren(propsArg) {
449
821
  const attrs = [];
450
- const children = [];
451
- if (t.isObjectExpression(propsArg)) {
452
- for (const prop of propsArg.properties) {
822
+ const childExprs = [];
823
+ const processObjectExpr = (obj) => {
824
+ for (const prop of obj.properties) {
825
+ if (t.isSpreadElement(prop)) {
826
+ attrs.push(t.jsxSpreadAttribute(prop.argument));
827
+ continue;
828
+ }
453
829
  if (!t.isObjectProperty(prop) || prop.computed)
454
830
  continue;
455
831
  const keyNode = prop.key;
@@ -458,35 +834,94 @@ function tryConvertToJSX(call) {
458
834
  if (!keyName)
459
835
  continue;
460
836
  if (keyName === "children") {
461
- // children can be a single value or an array
462
837
  if (t.isArrayExpression(valNode)) {
463
838
  for (const el of valNode.elements) {
464
- if (!el)
465
- continue;
466
- const ch = childToJsxChild(el);
467
- if (ch)
468
- children.push(ch);
839
+ if (el)
840
+ childExprs.push(el);
469
841
  }
470
842
  }
471
843
  else {
472
- const ch = childToJsxChild(valNode);
473
- if (ch)
474
- children.push(ch);
844
+ childExprs.push(valNode);
475
845
  }
476
846
  continue;
477
847
  }
478
- // Normal prop
479
- const attrName = t.jsxIdentifier(keyName);
480
- if (t.isJSXExpressionContainer(valNode) || t.isStringLiteral(valNode)) {
481
- attrs.push(t.jsxAttribute(attrName, exprToJsxAttrValue(valNode)));
848
+ attrs.push(t.jsxAttribute(t.jsxIdentifier(keyName), exprToJsxAttrValue(valNode)));
849
+ }
850
+ };
851
+ if (t.isObjectExpression(propsArg)) {
852
+ processObjectExpr(propsArg);
853
+ }
854
+ else {
855
+ // Try to unpack Babel's compiled object-spread IIFE
856
+ const unpacked = tryUnpackSpreadIIFE(propsArg);
857
+ if (unpacked) {
858
+ if (t.isObjectExpression(unpacked.base)) {
859
+ processObjectExpr(unpacked.base);
482
860
  }
483
861
  else {
484
- attrs.push(t.jsxAttribute(attrName, exprToJsxAttrValue(valNode)));
862
+ attrs.push(t.jsxSpreadAttribute(unpacked.base));
863
+ }
864
+ for (const spreadExpr of unpacked.spreads) {
865
+ attrs.push(t.jsxSpreadAttribute(spreadExpr));
485
866
  }
486
867
  }
868
+ else {
869
+ // Fallback: emit the entire props argument as a single JSX spread
870
+ attrs.push(t.jsxSpreadAttribute(propsArg));
871
+ }
872
+ }
873
+ return { attrs, childExprs };
874
+ }
875
+ const JSX_METHOD_NAMES = new Set(["jsx", "jsxs", "jsxDEV"]);
876
+ // Extract the jsx method name from a callee that may be:
877
+ // - bare identifier: `jsx`
878
+ // - member expression: `ns.jsx`
879
+ // - sequence (0, ns.jsx) after a `(0, ...)()` call
880
+ // Returns the method name string, or null if not a jsx call.
881
+ function getJsxMethodName(callee) {
882
+ if (t.isIdentifier(callee) && JSX_METHOD_NAMES.has(callee.name)) {
883
+ return callee.name;
487
884
  }
885
+ if (t.isMemberExpression(callee) && !callee.computed) {
886
+ const prop = callee.property;
887
+ if (t.isIdentifier(prop) && JSX_METHOD_NAMES.has(prop.name)) {
888
+ return prop.name;
889
+ }
890
+ }
891
+ // (0, ns.jsx) sequence expression
892
+ if (t.isSequenceExpression(callee)) {
893
+ const exprs = callee.expressions;
894
+ const last = exprs[exprs.length - 1];
895
+ if (last && t.isMemberExpression(last) && !last.computed) {
896
+ const prop = last.property;
897
+ if (t.isIdentifier(prop) && JSX_METHOD_NAMES.has(prop.name)) {
898
+ return prop.name;
899
+ }
900
+ }
901
+ }
902
+ return null;
903
+ }
904
+ function tryConvertToJSX(call) {
905
+ const callee = call.callee;
906
+ const methodName = getJsxMethodName(callee);
907
+ if (!methodName)
908
+ return null;
909
+ if (call.arguments.length < 2)
910
+ return null;
911
+ const tagArg = call.arguments[0];
912
+ const propsArg = call.arguments[1];
913
+ const jsxName = exprToJsxName(tagArg);
914
+ if (!jsxName)
915
+ return null;
916
+ const { attrs, childExprs } = propsArgToAttrsAndChildren(propsArg);
917
+ const children = childExprs.map((e) => childToJsxChild(e)).filter(Boolean);
918
+ // Fragment identifier → JSXFragment shorthand (<>...</>)
919
+ if (t.isJSXIdentifier(jsxName) && jsxName.name === "Fragment") {
920
+ return t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), children);
921
+ }
922
+ const jsxAttrs = attrs.filter((a) => t.isJSXAttribute(a) || t.isJSXSpreadAttribute(a));
488
923
  const selfClosing = children.length === 0;
489
- const openingElement = t.jsxOpeningElement(jsxName, attrs, selfClosing);
924
+ const openingElement = t.jsxOpeningElement(jsxName, jsxAttrs, selfClosing);
490
925
  const closingElement = selfClosing ? null : t.jsxClosingElement(jsxName);
491
926
  return t.jsxElement(openingElement, closingElement, children, selfClosing);
492
927
  }
@@ -499,7 +934,7 @@ function recoverJSX(statements) {
499
934
  if (!jsxEl)
500
935
  return;
501
936
  p.replaceWith(jsxEl);
502
- p.skip();
937
+ // Do NOT skip — nested jsx calls inside lambdas/expression containers need visiting.
503
938
  },
504
939
  });
505
940
  }
@@ -617,6 +1052,50 @@ function isBabelObjectSpreadHelper(stmt) {
617
1052
  const bodyStr = JSON.stringify(body);
618
1053
  return bodyStr.includes('"getOwnPropertySymbols"');
619
1054
  }
1055
+ // Detect `_objectSpread2` — Babel's 1-param spread helper that uses arguments.length
1056
+ // and Object.defineProperties to merge source objects into the target.
1057
+ function isBabelObjectSpread2Helper(stmt) {
1058
+ if (!t.isFunctionDeclaration(stmt))
1059
+ return false;
1060
+ const params = stmt.params;
1061
+ if (params.length !== 1)
1062
+ return false;
1063
+ const body = stmt.body.body;
1064
+ // Must have a for loop that reads arguments.length, then calls Object.defineProperties
1065
+ const hasArgumentsLength = JSON.stringify(body).includes('"arguments"');
1066
+ const hasDefineProperties = JSON.stringify(body).includes('"defineProperties"') || JSON.stringify(body).includes('"defineProperty"');
1067
+ return hasArgumentsLength && hasDefineProperties;
1068
+ }
1069
+ // Detect `_ownKeys` / `ownKeys` — companion helper to _objectSpread2.
1070
+ // Shape: 1- or 2-param function whose first statement declares a var via Object.keys(param)
1071
+ // and whose body references getOwnPropertySymbols.
1072
+ function isBabelOwnKeysHelper(stmt) {
1073
+ if (!t.isFunctionDeclaration(stmt))
1074
+ return false;
1075
+ const params = stmt.params;
1076
+ if (params.length < 1 || params.length > 2)
1077
+ return false;
1078
+ const body = stmt.body.body;
1079
+ if (body.length === 0)
1080
+ return false;
1081
+ // First statement declares a var via Object.keys(param)
1082
+ const first = body[0];
1083
+ if (!t.isVariableDeclaration(first))
1084
+ return false;
1085
+ const bodyStr = JSON.stringify(body);
1086
+ return bodyStr.includes('"keys"') && bodyStr.includes('"getOwnPropertySymbols"');
1087
+ }
1088
+ // Named Babel array-destructure companion helpers that appear as FunctionDeclarations.
1089
+ const NAMED_BABEL_ARRAY_HELPERS = new Set([
1090
+ "_arrayWithHoles",
1091
+ "_arrayLikeToArray",
1092
+ "_iterableToArrayLimit",
1093
+ "_unsupportedIterableToArray",
1094
+ "_nonIterableRest",
1095
+ ]);
1096
+ function isNamedBabelArrayHelper(stmt) {
1097
+ return t.isFunctionDeclaration(stmt) && stmt.id != null && NAMED_BABEL_ARRAY_HELPERS.has(stmt.id.name);
1098
+ }
620
1099
  /** Remove `var x = {}` declarations where every declarator has an empty-object init.
621
1100
  * These are webpack module-cache variables. */
622
1101
  function dropEmptyObjectVars(stmts) {
@@ -692,6 +1171,481 @@ function pruneUnusedNamedImports(importStmts, bodyStmts) {
692
1171
  .filter(Boolean);
693
1172
  }
694
1173
  // ---------------------------------------------------------------------------
1174
+ // Pass D for module files — library-aware import rewriting
1175
+ // ---------------------------------------------------------------------------
1176
+ /**
1177
+ * Applies Pass D to an already-transformed module file whose imports are in the
1178
+ * form `import * as ns from './N.js'`. Looks up each module ID `N` in
1179
+ * `libModuleMap`, rewrites call-sites from `(0, ns.hook)(...)` to `hook(...)`,
1180
+ * replaces namespace imports with named imports from the real library path, and
1181
+ * prunes unused specifiers.
1182
+ *
1183
+ * Returns the rewritten statement list (imports first, then body).
1184
+ * If `libModuleMap` is empty / not provided, returns `statements` unchanged.
1185
+ */
1186
+ export const applyLibraryImportRewriting = (statements, libModuleMap) => {
1187
+ if (!libModuleMap || libModuleMap.size === 0)
1188
+ return statements;
1189
+ // Separate existing import declarations from body statements
1190
+ const importDecls = [];
1191
+ const bodyStmts = [];
1192
+ for (const stmt of statements) {
1193
+ if (t.isImportDeclaration(stmt))
1194
+ importDecls.push(stmt);
1195
+ else
1196
+ bodyStmts.push(stmt);
1197
+ }
1198
+ // Build varName → moduleId from `import * as ns from './N.js'`
1199
+ const varToModuleId = new Map(); // ns → numericModuleId
1200
+ for (const decl of importDecls) {
1201
+ const source = decl.source.value;
1202
+ const match = source.match(/^\.\/(\d+)\.js$/);
1203
+ if (!match)
1204
+ continue;
1205
+ const numId = parseInt(match[1], 10);
1206
+ for (const spec of decl.specifiers) {
1207
+ if (t.isImportNamespaceSpecifier(spec)) {
1208
+ varToModuleId.set(spec.local.name, numId);
1209
+ }
1210
+ }
1211
+ }
1212
+ // Build varName → LibraryModuleInfo
1213
+ const varToLib = new Map();
1214
+ for (const [varName, numId] of varToModuleId) {
1215
+ const info = libModuleMap.get(String(numId));
1216
+ if (info)
1217
+ varToLib.set(varName, info);
1218
+ }
1219
+ if (varToLib.size === 0)
1220
+ return statements;
1221
+ // --- CSS injection stripping ---
1222
+ // Collect CSS-typed import vars (style-loader, css-module)
1223
+ const cssImportVars = new Set();
1224
+ for (const [varName, info] of varToLib) {
1225
+ if (info.type === "style-loader" || info.type === "css-module") {
1226
+ cssImportVars.add(varName);
1227
+ }
1228
+ }
1229
+ if (cssImportVars.size > 0) {
1230
+ const CSS_INJECT_PROPS = new Set([
1231
+ "styleTagTransform",
1232
+ "setAttributes",
1233
+ "insert",
1234
+ "domAPI",
1235
+ "insertStyleElement",
1236
+ ]);
1237
+ // Find CSS wrapper vars: var x = <expr>.n(libVar) for any library import.
1238
+ // webpack's .n() (interopRequireDefault) only appears in the CSS injection
1239
+ // setup in React apps; it's safe to strip all library-import .n() wrappers.
1240
+ const allLibVars = new Set(varToLib.keys());
1241
+ const cssWrapperVars = new Set();
1242
+ for (const stmt of bodyStmts) {
1243
+ if (!t.isVariableDeclaration(stmt))
1244
+ continue;
1245
+ for (const decl of stmt.declarations) {
1246
+ if (!t.isIdentifier(decl.id) || !decl.init)
1247
+ continue;
1248
+ if (!t.isCallExpression(decl.init))
1249
+ continue;
1250
+ const callee = decl.init.callee;
1251
+ const args = decl.init.arguments;
1252
+ if (t.isMemberExpression(callee) &&
1253
+ !callee.computed &&
1254
+ t.isIdentifier(callee.property, { name: "n" }) &&
1255
+ args.length === 1 &&
1256
+ t.isIdentifier(args[0]) &&
1257
+ allLibVars.has(args[0].name)) {
1258
+ cssWrapperVars.add(decl.id.name);
1259
+ }
1260
+ }
1261
+ }
1262
+ // Find CSS config object vars: var g = {} where g is assigned CSS injection props
1263
+ const cssConfigObjVars = new Set();
1264
+ for (const stmt of bodyStmts) {
1265
+ if (!t.isExpressionStatement(stmt))
1266
+ continue;
1267
+ const expr = stmt.expression;
1268
+ if (!t.isAssignmentExpression(expr))
1269
+ continue;
1270
+ const lhs = expr.left;
1271
+ if (!t.isMemberExpression(lhs) || lhs.computed)
1272
+ continue;
1273
+ const prop = lhs.property;
1274
+ if (t.isIdentifier(prop) && CSS_INJECT_PROPS.has(prop.name)) {
1275
+ if (t.isIdentifier(lhs.object))
1276
+ cssConfigObjVars.add(lhs.object.name);
1277
+ }
1278
+ }
1279
+ const isCssMemberRef = (e) => {
1280
+ if (!t.isMemberExpression(e) || e.computed)
1281
+ return false;
1282
+ return (t.isIdentifier(e.object) &&
1283
+ cssImportVars.has(e.object.name));
1284
+ };
1285
+ const hasLogicalCssRef = (e) => {
1286
+ if (isCssMemberRef(e))
1287
+ return true;
1288
+ if (t.isLogicalExpression(e))
1289
+ return (hasLogicalCssRef(e.left) ||
1290
+ hasLogicalCssRef(e.right));
1291
+ return false;
1292
+ };
1293
+ const isCssInjectionStmt = (stmt) => {
1294
+ if (!t.isExpressionStatement(stmt))
1295
+ return false;
1296
+ const expr = stmt.expression;
1297
+ // g.styleTagTransform = ..., g.setAttributes = ..., etc.
1298
+ if (t.isAssignmentExpression(expr) &&
1299
+ t.isMemberExpression(expr.left) &&
1300
+ !expr.left.computed &&
1301
+ t.isIdentifier(expr.left.property) &&
1302
+ CSS_INJECT_PROPS.has(expr.left.property.name))
1303
+ return true;
1304
+ // cssWrapperVar()(cssModuleVar.A, configObj)
1305
+ if (t.isCallExpression(expr) && t.isCallExpression(expr.callee)) {
1306
+ const innerCallee = expr.callee.callee;
1307
+ if (t.isIdentifier(innerCallee) && cssWrapperVars.has(innerCallee.name))
1308
+ return true;
1309
+ }
1310
+ // cssModuleVar.A && cssModuleVar.A.locals && cssModuleVar.A.locals
1311
+ if (t.isLogicalExpression(expr) && hasLogicalCssRef(expr))
1312
+ return true;
1313
+ return false;
1314
+ };
1315
+ const filteredStmts = [];
1316
+ for (const stmt of bodyStmts) {
1317
+ if (isCssInjectionStmt(stmt))
1318
+ continue;
1319
+ if (t.isVariableDeclaration(stmt)) {
1320
+ const remaining = stmt.declarations.filter((decl) => {
1321
+ if (!t.isIdentifier(decl.id))
1322
+ return true;
1323
+ const name = decl.id.name;
1324
+ if (cssWrapperVars.has(name))
1325
+ return false;
1326
+ if (cssConfigObjVars.has(name) &&
1327
+ decl.init &&
1328
+ t.isObjectExpression(decl.init) &&
1329
+ decl.init.properties.length === 0)
1330
+ return false;
1331
+ return true;
1332
+ });
1333
+ if (remaining.length === 0)
1334
+ continue;
1335
+ filteredStmts.push(remaining.length < stmt.declarations.length ? t.variableDeclaration(stmt.kind, remaining) : stmt);
1336
+ continue;
1337
+ }
1338
+ filteredStmts.push(stmt);
1339
+ }
1340
+ bodyStmts.splice(0, bodyStmts.length, ...filteredStmts);
1341
+ }
1342
+ // Rewrite call-sites in bodyStmts; collect used exports per library
1343
+ const usedExports = rewriteLibraryCalls(bodyStmts, varToLib);
1344
+ // Build replacement import declarations
1345
+ const finalImportStmts = [];
1346
+ const handledSpecs = new Set();
1347
+ for (const [varName, info] of varToLib) {
1348
+ const src = librarySource(info.type);
1349
+ if (!src)
1350
+ continue;
1351
+ if (!handledSpecs.has(src)) {
1352
+ handledSpecs.add(src);
1353
+ const used = usedExports.get(src);
1354
+ if (used && used.size > 0) {
1355
+ const specifiers = [...used]
1356
+ .sort()
1357
+ .map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
1358
+ finalImportStmts.push(t.importDeclaration(specifiers, t.stringLiteral(src)));
1359
+ }
1360
+ }
1361
+ }
1362
+ // Keep non-library namespace imports unchanged.
1363
+ // For library namespace imports: keep only if the namespace variable is still used as
1364
+ // the object of a MemberExpression or JSXMemberExpression (i.e., namespace access).
1365
+ // Using collectReferencedNames would falsely keep imports when a local variable in a
1366
+ // closure happens to share the namespace name (e.g. `const n = e.toLowerCase()`).
1367
+ const namespaceObjRefs = new Set();
1368
+ {
1369
+ const syntheticFile = t.file(t.program(bodyStmts, [], "module"));
1370
+ traverse(syntheticFile, {
1371
+ MemberExpression(p) {
1372
+ if (!p.node.computed && t.isIdentifier(p.node.object)) {
1373
+ namespaceObjRefs.add(p.node.object.name);
1374
+ }
1375
+ },
1376
+ JSXMemberExpression(p) {
1377
+ if (t.isJSXIdentifier(p.node.object)) {
1378
+ namespaceObjRefs.add(p.node.object.name);
1379
+ }
1380
+ },
1381
+ });
1382
+ }
1383
+ for (const decl of importDecls) {
1384
+ const source = decl.source.value;
1385
+ const match = source.match(/^\.\/(\d+)\.js$/);
1386
+ if (!match) {
1387
+ finalImportStmts.push(decl);
1388
+ continue;
1389
+ }
1390
+ const numId = parseInt(match[1], 10);
1391
+ const isLibrary = [...varToModuleId.entries()].some(([vn, id]) => id === numId && varToLib.has(vn));
1392
+ if (!isLibrary) {
1393
+ finalImportStmts.push(decl);
1394
+ continue;
1395
+ }
1396
+ // Library module: keep the namespace import only if the namespace var is
1397
+ // still used as a namespace object (i.e. some exports were not resolved).
1398
+ const hasRemainingRefs = decl.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec) && namespaceObjRefs.has(spec.local.name));
1399
+ if (hasRemainingRefs)
1400
+ finalImportStmts.push(decl);
1401
+ }
1402
+ // Pass H — prune named imports that are no longer referenced
1403
+ const prunedImports = pruneUnusedNamedImports(finalImportStmts, bodyStmts);
1404
+ return [...prunedImports, ...bodyStmts];
1405
+ };
1406
+ // ---------------------------------------------------------------------------
1407
+ // Module cleanup passes — applies E/F/G standalone (no webpack require context needed)
1408
+ // ---------------------------------------------------------------------------
1409
+ /**
1410
+ * Applies JSX recovery, slicedToArray collapse, and Babel helper removal to
1411
+ * any list of statements. Used for both individual module files and index.js.
1412
+ * Does NOT require a webpack require-function name or library module map.
1413
+ */
1414
+ export const applyModuleCleanupPasses = (statements) => {
1415
+ // Pass E — collapse Babel slicedToArray expansions.
1416
+ const afterE = collapseSlicedToArrayDeep(statements);
1417
+ // Pass F — JSX recovery.
1418
+ recoverJSX(afterE);
1419
+ // Pass G — remove top-level Babel helper functions and webpack module-cache vars.
1420
+ const afterG = dropEmptyObjectVars(afterE.filter((stmt) => !isBabelArrayLikeToArrayHelper(stmt) &&
1421
+ !isBabelTypeofHelper(stmt) &&
1422
+ !isBabelDefinePropertyHelper(stmt) &&
1423
+ !isBabelObjectSpreadHelper(stmt) &&
1424
+ !isBabelObjectSpread2Helper(stmt) &&
1425
+ !isBabelOwnKeysHelper(stmt) &&
1426
+ !isNamedBabelArrayHelper(stmt)));
1427
+ return afterG;
1428
+ };
1429
+ // ---------------------------------------------------------------------------
1430
+ // Route-aware component renaming
1431
+ // ---------------------------------------------------------------------------
1432
+ /** Derives a component name from a React Router path string. */
1433
+ function pathToComponentName(fullPath) {
1434
+ if (fullPath === "/" || fullPath === "")
1435
+ return "Home";
1436
+ const isIndex = fullPath.endsWith("/index");
1437
+ const base = isIndex ? fullPath.slice(0, -"/index".length) : fullPath;
1438
+ const segments = base
1439
+ .replace(/^\//, "")
1440
+ .split("/")
1441
+ .filter((s) => s && !s.startsWith(":"));
1442
+ if (segments.length === 0)
1443
+ return "Home";
1444
+ const name = segments
1445
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase()))
1446
+ .join("");
1447
+ return isIndex ? `${name}Dashboard` : name;
1448
+ }
1449
+ /**
1450
+ * Post-pass that renames minified component variables to meaningful names derived
1451
+ * from React Router route paths. Intended to run after JSX recovery (Pass F) so
1452
+ * that `<Route path="..." element={<X />} />` patterns are visible as JSX.
1453
+ *
1454
+ * Also renames:
1455
+ * - The root App component (the one that contains `<Routes>`)
1456
+ * - The Suspense fallback component (if it's a locally-defined function)
1457
+ */
1458
+ export const renameRouteComponents = (statements) => {
1459
+ // Step 1: collect lazy-import variable names → spec (e.g. '_' → './45.js')
1460
+ const lazyVars = new Map(); // varName → importSpec
1461
+ for (const stmt of statements) {
1462
+ if (!t.isVariableDeclaration(stmt))
1463
+ continue;
1464
+ for (const decl of stmt.declarations) {
1465
+ if (!t.isIdentifier(decl.id) || !decl.init)
1466
+ continue;
1467
+ if (!t.isCallExpression(decl.init))
1468
+ continue;
1469
+ const callExpr = decl.init;
1470
+ if (!t.isIdentifier(callExpr.callee, { name: "lazy" }))
1471
+ continue;
1472
+ if (callExpr.arguments.length !== 1)
1473
+ continue;
1474
+ const arg = callExpr.arguments[0];
1475
+ if (!t.isArrowFunctionExpression(arg) && !t.isFunctionExpression(arg))
1476
+ continue;
1477
+ const body = arg.body;
1478
+ // body is `import('./N.js')` — a CallExpression with an Import callee
1479
+ const importCall = t.isBlockStatement(body)
1480
+ ? body.body.length === 1 &&
1481
+ t.isReturnStatement(body.body[0])
1482
+ ? body.body[0].argument
1483
+ : null
1484
+ : body;
1485
+ if (!importCall || !t.isCallExpression(importCall))
1486
+ continue;
1487
+ if (!t.isImport(importCall.callee))
1488
+ continue;
1489
+ const importArgs = importCall.arguments;
1490
+ if (importArgs.length !== 1 || !t.isStringLiteral(importArgs[0]))
1491
+ continue;
1492
+ lazyVars.set(decl.id.name, importArgs[0].value);
1493
+ }
1494
+ }
1495
+ if (lazyVars.size === 0)
1496
+ return statements;
1497
+ // Step 2: traverse JSX Route elements, tracking parent route paths for full-path assembly.
1498
+ const varToPath = new Map(); // varName → full route path
1499
+ const syntheticFile = t.file(t.program(statements, [], "module"));
1500
+ const pathStack = [];
1501
+ traverse(syntheticFile, {
1502
+ JSXElement: {
1503
+ enter(p) {
1504
+ const opening = p.node.openingElement;
1505
+ if (!t.isJSXIdentifier(opening.name, { name: "Route" }))
1506
+ return;
1507
+ let routePath = null;
1508
+ let isIndex = false;
1509
+ let elementVarName = null;
1510
+ for (const attr of opening.attributes) {
1511
+ if (!t.isJSXAttribute(attr))
1512
+ continue;
1513
+ const keyName = t.isJSXIdentifier(attr.name) ? attr.name.name : null;
1514
+ if (keyName === "path" && t.isStringLiteral(attr.value)) {
1515
+ routePath = attr.value.value;
1516
+ }
1517
+ else if (keyName === "index") {
1518
+ isIndex = true;
1519
+ }
1520
+ else if (keyName === "element") {
1521
+ const val = attr.value;
1522
+ const jsxEl = t.isJSXExpressionContainer(val)
1523
+ ? t.isJSXElement(val.expression)
1524
+ ? val.expression
1525
+ : null
1526
+ : t.isJSXElement(val)
1527
+ ? val
1528
+ : null;
1529
+ if (jsxEl) {
1530
+ const tagName = jsxEl.openingElement.name;
1531
+ if (t.isJSXIdentifier(tagName))
1532
+ elementVarName = tagName.name;
1533
+ }
1534
+ }
1535
+ }
1536
+ const parentPath = pathStack.length > 0 ? pathStack[pathStack.length - 1] : "";
1537
+ let fullPath;
1538
+ if (routePath !== null) {
1539
+ fullPath = routePath.startsWith("/") ? routePath : `${parentPath}/${routePath}`;
1540
+ }
1541
+ else if (isIndex) {
1542
+ fullPath = `${parentPath}/index`;
1543
+ }
1544
+ else {
1545
+ fullPath = parentPath;
1546
+ }
1547
+ pathStack.push(fullPath);
1548
+ if (elementVarName && lazyVars.has(elementVarName)) {
1549
+ varToPath.set(elementVarName, fullPath);
1550
+ }
1551
+ },
1552
+ exit(p) {
1553
+ if (t.isJSXIdentifier(p.node.openingElement.name, { name: "Route" })) {
1554
+ pathStack.pop();
1555
+ }
1556
+ },
1557
+ },
1558
+ });
1559
+ // Step 3: generate semantic names from route paths
1560
+ const renames = new Map(); // oldName → newName
1561
+ const usedNames = new Set();
1562
+ for (const [varName, fullPath] of varToPath) {
1563
+ let name = pathToComponentName(fullPath);
1564
+ if (usedNames.has(name)) {
1565
+ let i = 2;
1566
+ while (usedNames.has(`${name}${i}`))
1567
+ i++;
1568
+ name = `${name}${i}`;
1569
+ }
1570
+ usedNames.add(name);
1571
+ renames.set(varName, name);
1572
+ }
1573
+ // Step 4: find the root App component — the function whose JSX body contains <Routes>
1574
+ traverse(syntheticFile, {
1575
+ JSXElement(p) {
1576
+ if (!t.isJSXIdentifier(p.node.openingElement.name, { name: "Routes" }))
1577
+ return;
1578
+ let ancestor = p.parentPath;
1579
+ while (ancestor) {
1580
+ if (ancestor.isFunctionDeclaration() ||
1581
+ ancestor.isFunctionExpression() ||
1582
+ ancestor.isArrowFunctionExpression()) {
1583
+ if (ancestor.isFunctionDeclaration()) {
1584
+ const id = ancestor.node.id;
1585
+ if (id && !renames.has(id.name))
1586
+ renames.set(id.name, "App");
1587
+ }
1588
+ else {
1589
+ const par = ancestor.parentPath;
1590
+ if (par === null || par === void 0 ? void 0 : par.isVariableDeclarator()) {
1591
+ const declId = par.node.id;
1592
+ if (t.isIdentifier(declId) && !renames.has(declId.name)) {
1593
+ renames.set(declId.name, "App");
1594
+ }
1595
+ }
1596
+ }
1597
+ break;
1598
+ }
1599
+ if (!ancestor.parentPath)
1600
+ break;
1601
+ ancestor = ancestor.parentPath;
1602
+ }
1603
+ p.skip();
1604
+ },
1605
+ });
1606
+ // Step 5: find the Suspense fallback component
1607
+ traverse(syntheticFile, {
1608
+ JSXAttribute(p) {
1609
+ if (!t.isJSXIdentifier(p.node.name, { name: "fallback" }))
1610
+ return;
1611
+ const val = p.node.value;
1612
+ if (!t.isJSXExpressionContainer(val))
1613
+ return;
1614
+ const expr = val.expression;
1615
+ if (!t.isJSXElement(expr))
1616
+ return;
1617
+ const tagName = expr.openingElement.name;
1618
+ if (!t.isJSXIdentifier(tagName))
1619
+ return;
1620
+ const varName = tagName.name;
1621
+ if (!renames.has(varName) && !lazyVars.has(varName)) {
1622
+ renames.set(varName, "Loading");
1623
+ }
1624
+ },
1625
+ });
1626
+ if (renames.size === 0)
1627
+ return statements;
1628
+ // Step 6: rename bindings via scope analysis, then patch any remaining JSXIdentifiers
1629
+ traverse(syntheticFile, {
1630
+ Program(p) {
1631
+ for (const [oldName, newName] of renames) {
1632
+ if (p.scope.hasBinding(oldName)) {
1633
+ p.scope.rename(oldName, newName);
1634
+ }
1635
+ }
1636
+ },
1637
+ });
1638
+ // JSXIdentifier nodes may not be tracked by scope — patch them explicitly as a fallback
1639
+ traverse(syntheticFile, {
1640
+ JSXIdentifier(p) {
1641
+ const newName = renames.get(p.node.name);
1642
+ if (newName)
1643
+ p.node.name = newName;
1644
+ },
1645
+ });
1646
+ return statements;
1647
+ };
1648
+ // ---------------------------------------------------------------------------
695
1649
  // Main export
696
1650
  // ---------------------------------------------------------------------------
697
1651
  /**
@@ -720,13 +1674,68 @@ export const transformIndexStatements = (statements, libModuleMap) => {
720
1674
  }
721
1675
  if (requireFnNames.size === 0)
722
1676
  return statements; // nothing to do
1677
+ // Pass A.1 — strip webpack runtime bootstrap statements.
1678
+ // Any statement that references requireFn.X (assignments, calls, or var inits)
1679
+ // is webpack infrastructure. Also strip the runtime temp-var declarations that
1680
+ // have no init / empty-collection init and 3+ declarators.
1681
+ const containsRequireFnMember = (node, fnName) => {
1682
+ if (t.isMemberExpression(node) && t.isIdentifier(node.object, { name: fnName }))
1683
+ return true;
1684
+ for (const key of Object.keys(node)) {
1685
+ const child = node[key];
1686
+ if (child && typeof child === "object") {
1687
+ if (Array.isArray(child)) {
1688
+ for (const item of child) {
1689
+ if (item &&
1690
+ typeof item.type === "string" &&
1691
+ containsRequireFnMember(item, fnName))
1692
+ return true;
1693
+ }
1694
+ }
1695
+ else if (typeof child.type === "string") {
1696
+ if (containsRequireFnMember(child, fnName))
1697
+ return true;
1698
+ }
1699
+ }
1700
+ }
1701
+ return false;
1702
+ };
1703
+ const isRuntimeTempVarDecl = (stmt) => {
1704
+ if (!t.isVariableDeclaration(stmt) || stmt.declarations.length < 3)
1705
+ return false;
1706
+ return stmt.declarations.every((decl) => {
1707
+ if (!decl.init)
1708
+ return true;
1709
+ if (t.isObjectExpression(decl.init) && decl.init.properties.length === 0)
1710
+ return true;
1711
+ if (t.isArrayExpression(decl.init) && decl.init.elements.length === 0)
1712
+ return true;
1713
+ if (t.isUnaryExpression(decl.init) && decl.init.operator === "void")
1714
+ return true;
1715
+ return false;
1716
+ });
1717
+ };
1718
+ const afterA1 = [];
1719
+ for (const stmt of afterA) {
1720
+ let isRuntime = isRuntimeTempVarDecl(stmt);
1721
+ if (!isRuntime) {
1722
+ for (const fnName of requireFnNames) {
1723
+ if (containsRequireFnMember(stmt, fnName)) {
1724
+ isRuntime = true;
1725
+ break;
1726
+ }
1727
+ }
1728
+ }
1729
+ if (!isRuntime)
1730
+ afterA1.push(stmt);
1731
+ }
723
1732
  const hoistedImports = new Map();
724
1733
  const importNameByNumId = new Map();
725
1734
  // Pass B — hoist top-level `var x = requireFn(N)` to static imports.
726
1735
  // Also build varName → moduleId so Pass D can look up library identity.
727
1736
  const varToModuleId = new Map(); // localVarName → numericModuleId
728
1737
  const afterB = [];
729
- for (const stmt of afterA) {
1738
+ for (const stmt of afterA1) {
730
1739
  if (!t.isVariableDeclaration(stmt)) {
731
1740
  afterB.push(stmt);
732
1741
  continue;
@@ -830,7 +1839,10 @@ export const transformIndexStatements = (statements, libModuleMap) => {
830
1839
  const afterG = dropEmptyObjectVars(afterE.filter((stmt) => !isBabelArrayLikeToArrayHelper(stmt) &&
831
1840
  !isBabelTypeofHelper(stmt) &&
832
1841
  !isBabelDefinePropertyHelper(stmt) &&
833
- !isBabelObjectSpreadHelper(stmt)));
1842
+ !isBabelObjectSpreadHelper(stmt) &&
1843
+ !isBabelObjectSpread2Helper(stmt) &&
1844
+ !isBabelOwnKeysHelper(stmt) &&
1845
+ !isNamedBabelArrayHelper(stmt)));
834
1846
  // Pass H — prune named imports whose local name is no longer referenced
835
1847
  // (e.g. jsx/jsxs after JSX recovery, namespace imports that were cleared).
836
1848
  const prunedImports = pruneUnusedNamedImports(finalImportStmts, afterG);