@reckona/mreact-compiler 0.0.96 → 0.0.98

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/dist/diagnostics.d.ts +1 -0
  2. package/dist/diagnostics.d.ts.map +1 -1
  3. package/dist/diagnostics.js +8 -0
  4. package/dist/diagnostics.js.map +1 -1
  5. package/dist/emit-client.js +14 -9
  6. package/dist/emit-client.js.map +1 -1
  7. package/dist/emit-compat.js +5 -1
  8. package/dist/emit-compat.js.map +1 -1
  9. package/dist/emit-server-stream.d.ts.map +1 -1
  10. package/dist/emit-server-stream.js +70 -6
  11. package/dist/emit-server-stream.js.map +1 -1
  12. package/dist/emit-server.d.ts.map +1 -1
  13. package/dist/emit-server.js +69 -13
  14. package/dist/emit-server.js.map +1 -1
  15. package/dist/internal.d.ts.map +1 -1
  16. package/dist/internal.js +7 -4
  17. package/dist/internal.js.map +1 -1
  18. package/dist/ir.d.ts +1 -0
  19. package/dist/ir.d.ts.map +1 -1
  20. package/dist/ir.js.map +1 -1
  21. package/dist/oxc-child-analysis.d.ts.map +1 -1
  22. package/dist/oxc-child-analysis.js +44 -10
  23. package/dist/oxc-child-analysis.js.map +1 -1
  24. package/dist/oxc-component-detection.d.ts +5 -2
  25. package/dist/oxc-component-detection.d.ts.map +1 -1
  26. package/dist/oxc-component-detection.js +80 -3
  27. package/dist/oxc-component-detection.js.map +1 -1
  28. package/dist/oxc-component-props.d.ts +1 -1
  29. package/dist/oxc-component-props.d.ts.map +1 -1
  30. package/dist/oxc-component-props.js +1 -1
  31. package/dist/oxc-component-props.js.map +1 -1
  32. package/dist/oxc-component-references.d.ts.map +1 -1
  33. package/dist/oxc-component-references.js +247 -12
  34. package/dist/oxc-component-references.js.map +1 -1
  35. package/dist/oxc-runtime-emit.d.ts.map +1 -1
  36. package/dist/oxc-runtime-emit.js +10 -2
  37. package/dist/oxc-runtime-emit.js.map +1 -1
  38. package/dist/oxc.d.ts.map +1 -1
  39. package/dist/oxc.js +109 -20
  40. package/dist/oxc.js.map +1 -1
  41. package/dist/transform.js +29 -11
  42. package/dist/transform.js.map +1 -1
  43. package/package.json +2 -2
  44. package/src/diagnostics.ts +10 -0
  45. package/src/emit-client.ts +20 -10
  46. package/src/emit-compat.ts +6 -1
  47. package/src/emit-server-stream.ts +96 -6
  48. package/src/emit-server.ts +93 -29
  49. package/src/internal.ts +9 -4
  50. package/src/ir.ts +1 -0
  51. package/src/oxc-child-analysis.ts +66 -21
  52. package/src/oxc-component-detection.ts +145 -2
  53. package/src/oxc-component-props.ts +2 -1
  54. package/src/oxc-component-references.ts +366 -11
  55. package/src/oxc-runtime-emit.ts +12 -2
  56. package/src/oxc.ts +167 -5
  57. package/src/transform.ts +42 -10
@@ -238,7 +238,7 @@ function emitComponent(
238
238
 
239
239
  const fragmentName = allocator("_fragment");
240
240
  const rootName = allocator("_root");
241
- const templateHtml = escapeTemplateHtml(renderStaticHtml(component.root));
241
+ const templateHtml = JSON.stringify(renderStaticHtml(component.root));
242
242
  const setup = emitSetup(component.root, rootName, {
243
243
  allocateName: allocator,
244
244
  textIndex: 0,
@@ -246,7 +246,7 @@ function emitComponent(
246
246
  clientBoundaryHelperName,
247
247
  });
248
248
  return [
249
- `const ${templateName} = ${helperNames.createTemplate}("${templateHtml}");`,
249
+ `const ${templateName} = ${helperNames.createTemplate}(${templateHtml});`,
250
250
  `${component.exportDefault === true ? "export default " : component.exported === false ? "" : "export "}function ${component.name}(${parameters}) {`,
251
251
  ...body,
252
252
  ` const ${fragmentName} = ${templateName}();`,
@@ -413,7 +413,7 @@ function emitSetup(
413
413
 
414
414
  if (child.kind === "conditional") {
415
415
  lines.push(
416
- ` ${state.helperNames.insertDynamic}(${path}, ${childPath}, () => (${child.conditionCode}) ? ${emitRenderValueExpression(child.whenTrue, state)} : ${emitRenderValueExpression(child.whenFalse, state)});`,
416
+ ` ${state.helperNames.insertDynamic}(${path}, ${childPath}, () => ${emitConditionalRenderValueExpression(child, state)});`,
417
417
  );
418
418
  childIndex += 1;
419
419
  continue;
@@ -652,7 +652,7 @@ function emitNodeRenderValueExpression(
652
652
  }
653
653
 
654
654
  if (node.kind === "conditional") {
655
- return `((${node.conditionCode}) ? ${emitRenderValueExpression(node.whenTrue, state)} : ${emitRenderValueExpression(node.whenFalse, state)})`;
655
+ return emitConditionalRenderValueExpression(node, state);
656
656
  }
657
657
 
658
658
  if (node.kind === "list") {
@@ -667,13 +667,13 @@ function emitNodeRenderValueExpression(
667
667
  const templateName = state.allocateName("_dynamicTemplate");
668
668
  const fragmentName = state.allocateName("_dynamicFragment");
669
669
  const rootName = state.allocateName("_dynamicRoot");
670
- const templateHtml = escapeTemplateHtml(renderStaticHtml(node));
670
+ const templateHtml = JSON.stringify(renderStaticHtml(node));
671
671
  const setup = emitSetup(node, rootName, state);
672
672
  const setupLines = setup === "" ? [] : setup.split("\n");
673
673
 
674
674
  return [
675
675
  "(() => {",
676
- ` const ${templateName} = ${state.helperNames.createTemplate}("${templateHtml}");`,
676
+ ` const ${templateName} = ${state.helperNames.createTemplate}(${templateHtml});`,
677
677
  ` const ${fragmentName} = ${templateName}();`,
678
678
  ` const ${rootName} = ${fragmentName}.firstChild;`,
679
679
  ...setupLines,
@@ -682,6 +682,20 @@ function emitNodeRenderValueExpression(
682
682
  ].join("\n");
683
683
  }
684
684
 
685
+ function emitConditionalRenderValueExpression(
686
+ node: Extract<JsxNodeIr, { kind: "conditional" }>,
687
+ state: EmitSetupState,
688
+ ): string {
689
+ const whenTrue = emitRenderValueExpression(node.whenTrue, state);
690
+ const whenFalse = emitRenderValueExpression(node.whenFalse, state);
691
+
692
+ if (node.conditionValueName === undefined) {
693
+ return `((${node.conditionCode}) ? ${whenTrue} : ${whenFalse})`;
694
+ }
695
+
696
+ return `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`;
697
+ }
698
+
685
699
  function emitListRenderer(
686
700
  node: Extract<JsxNodeIr, { kind: "list" }>,
687
701
  parameters: string,
@@ -847,7 +861,3 @@ function visit(node: JsxNodeIr, fn: (node: JsxNodeIr) => void): void {
847
861
  }
848
862
  }
849
863
  }
850
-
851
- function escapeTemplateHtml(value: string): string {
852
- return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
853
- }
@@ -325,7 +325,12 @@ function emitJsxNode(
325
325
  }
326
326
 
327
327
  if (node.kind === "conditional") {
328
- return `(${node.conditionCode}) ? ${emitCompatChildren(node.whenTrue, helperNames, dev)} : ${emitCompatChildren(node.whenFalse, helperNames, dev)}`;
328
+ const whenTrue = emitCompatChildren(node.whenTrue, helperNames, dev);
329
+ const whenFalse = emitCompatChildren(node.whenFalse, helperNames, dev);
330
+
331
+ return node.conditionValueName === undefined
332
+ ? `(${node.conditionCode}) ? ${whenTrue} : ${whenFalse}`
333
+ : `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`;
329
334
  }
330
335
 
331
336
  if (node.kind === "list") {
@@ -25,7 +25,10 @@ import {
25
25
  parseStyleLiteralValue,
26
26
  simpleSideEffectFreeExpression,
27
27
  } from "./emit-server-shared.js";
28
- import { oxcServerStringReactNodeRenderHelperPlaceholder } from "./oxc-runtime-emit.js";
28
+ import {
29
+ emitOxcCompatObjectChildren,
30
+ oxcServerStringReactNodeRenderHelperPlaceholder,
31
+ } from "./oxc-runtime-emit.js";
29
32
 
30
33
  export interface EmitServerStreamResult {
31
34
  code: string;
@@ -515,11 +518,25 @@ function emitAppendStatements(
515
518
  return [];
516
519
  }
517
520
 
521
+ const conditionCode = node.conditionValueName ?? node.conditionCode;
522
+ let statements: string[];
523
+
518
524
  if (whenFalse.length === 0) {
519
- return [` if (${node.conditionCode}) {`, ...whenTrue, ` }`];
525
+ statements = [` if (${conditionCode}) {`, ...whenTrue, ` }`];
526
+ } else {
527
+ statements = [` if (${conditionCode}) {`, ...whenTrue, ` } else {`, ...whenFalse, ` }`];
528
+ }
529
+
530
+ if (node.conditionValueName === undefined) {
531
+ return statements;
520
532
  }
521
533
 
522
- return [` if (${node.conditionCode}) {`, ...whenTrue, ` } else {`, ...whenFalse, ` }`];
534
+ return [
535
+ ` {`,
536
+ ` const ${node.conditionValueName} = (${node.conditionCode});`,
537
+ ...statements.map((statement) => ` ${statement}`),
538
+ ` }`,
539
+ ];
523
540
  }
524
541
 
525
542
  const collectState: CollectHtmlState = {
@@ -832,13 +849,16 @@ function tryEmitPartAsStringExpression(
832
849
  return emitListPartAsStringExpression(part, compatRenderToStringHelperName);
833
850
  }
834
851
  if (part.kind === "component" && part.runtime === "compat") {
835
- const rendered = `${compatRenderToStringHelperName}(${part.name}, ${emitPropsObject(part.props, part.children, part.escapeHelperName)})`;
852
+ const rendered = `${compatRenderToStringHelperName}(${part.name}, ${emitCompatRuntimePropsObject(part.props, part.children)})`;
836
853
  if (part.hydrationId === undefined) {
837
854
  return rendered;
838
855
  }
839
856
 
840
857
  return `${stringLiteral(`<!--mreact-h:start:${encodeURIComponent(part.hydrationId)}-->`)} + ${rendered} + ${stringLiteral(`<!--mreact-h:end:${encodeURIComponent(part.hydrationId)}-->`)}`;
841
858
  }
859
+ if (part.kind === "component" && part.hydrationId === undefined) {
860
+ return `${part.name}(${emitPropsObject(part.props, part.children, part.escapeHelperName)})`;
861
+ }
842
862
  // Non-compat component parts require `await sink-write`; lists with
843
863
  // sink-needing children also can't collapse. Signal fallback.
844
864
  return undefined;
@@ -934,7 +954,7 @@ function emitCompatComponentAppendStatements(
934
954
  compatRenderToStringHelperName: string,
935
955
  indent: string,
936
956
  ): string {
937
- const rendered = `${compatRenderToStringHelperName}(${part.name}, ${emitPropsObject(part.props, part.children, part.escapeHelperName)})`;
957
+ const rendered = `${compatRenderToStringHelperName}(${part.name}, ${emitCompatRuntimePropsObject(part.props, part.children)})`;
938
958
  const statements =
939
959
  part.hydrationId === undefined
940
960
  ? [`${sinkName}.append(${rendered});`]
@@ -1096,10 +1116,16 @@ function collectHtmlParts(
1096
1116
  }
1097
1117
 
1098
1118
  if (node.kind === "conditional") {
1119
+ const whenTrue = emitHtmlExpressionFromChildren(node.whenTrue, escapeHelperName);
1120
+ const whenFalse = emitHtmlExpressionFromChildren(node.whenFalse, escapeHelperName);
1121
+
1099
1122
  return [
1100
1123
  {
1101
1124
  kind: "raw-dynamic",
1102
- code: `((${node.conditionCode}) ? ${emitHtmlExpressionFromChildren(node.whenTrue, escapeHelperName)} : ${emitHtmlExpressionFromChildren(node.whenFalse, escapeHelperName)})`,
1125
+ code:
1126
+ node.conditionValueName === undefined
1127
+ ? `((${node.conditionCode}) ? ${whenTrue} : ${whenFalse})`
1128
+ : `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`,
1103
1129
  },
1104
1130
  ];
1105
1131
  }
@@ -1589,6 +1615,15 @@ function collectElementAttributeParts(
1589
1615
  ): HtmlSyncPart[] {
1590
1616
  const escapeBatchHelperName = state.escapeBatchHelperName;
1591
1617
 
1618
+ if (state.dynamicAttributes === "emit" && attrs.some((attr) => attr.kind === "spread-attr")) {
1619
+ return [
1620
+ {
1621
+ kind: "raw-dynamic",
1622
+ code: emitMergedSpreadAttributeExpression(tagName, attrs, attributeScan),
1623
+ },
1624
+ ];
1625
+ }
1626
+
1592
1627
  return attrs.flatMap((attr) =>
1593
1628
  attr.kind !== "spread-attr" &&
1594
1629
  ((tagName === "input" &&
@@ -1607,6 +1642,38 @@ function collectElementAttributeParts(
1607
1642
  );
1608
1643
  }
1609
1644
 
1645
+ function emitMergedSpreadAttributeExpression(
1646
+ tagName: string,
1647
+ attrs: readonly AttributeIr[],
1648
+ attributeScan: ElementAttributeScan,
1649
+ ): string {
1650
+ const statements = attrs.flatMap((attr): string[] => {
1651
+ if (
1652
+ attr.kind !== "spread-attr" &&
1653
+ ((tagName === "input" &&
1654
+ ((attr.name === "defaultValue" && attributeScan.hasExplicitInputValue) ||
1655
+ (attr.name === "defaultChecked" && attributeScan.hasExplicitInputChecked))) ||
1656
+ ((tagName === "textarea" || tagName === "select") &&
1657
+ (attr.name === "value" || attr.name === "defaultValue")))
1658
+ ) {
1659
+ return [];
1660
+ }
1661
+
1662
+ if (attr.kind === "spread-attr") {
1663
+ return [`Object.assign(_props, (${attr.code}) ?? {});`];
1664
+ }
1665
+
1666
+ if (attr.kind === "event" || attr.name === "key" || attr.name === "dangerouslySetInnerHTML") {
1667
+ return [];
1668
+ }
1669
+
1670
+ const valueCode = attr.kind === "static-attr" ? stringLiteral(attr.value) : `(${attr.code})`;
1671
+ return [`_props[${stringLiteral(attr.name)}] = ${valueCode};`];
1672
+ });
1673
+
1674
+ return `(() => { const _props = {}; ${statements.join(" ")} return ${currentSpreadAttributesHelperName}(${stringLiteral(tagName)}, _props); })()`;
1675
+ }
1676
+
1610
1677
  interface ElementAttributeScan {
1611
1678
  hasExplicitInputValue: boolean;
1612
1679
  hasExplicitInputChecked: boolean;
@@ -2420,6 +2487,29 @@ function emitPropsObject(
2420
2487
  return `{ ${entries.join(", ")} }`;
2421
2488
  }
2422
2489
 
2490
+ function emitCompatRuntimePropsObject(
2491
+ props: ComponentPropIr[],
2492
+ children: JsxNodeIr[] = [],
2493
+ ): string {
2494
+ const entries = props.map((prop) => {
2495
+ if (prop.kind === "spread-prop") {
2496
+ return `...(${prop.code})`;
2497
+ }
2498
+
2499
+ if (prop.kind === "render-prop") {
2500
+ return `${emitPropName(prop.name)}: ${emitOxcCompatObjectChildren(prop.children)}`;
2501
+ }
2502
+
2503
+ return `${emitPropName(prop.name)}: (${prop.code})`;
2504
+ });
2505
+
2506
+ if (children.length > 0) {
2507
+ entries.push(`children: ${emitOxcCompatObjectChildren(children)}`);
2508
+ }
2509
+
2510
+ return `{ ${entries.join(", ")} }`;
2511
+ }
2512
+
2423
2513
  function emitPropName(name: string): string {
2424
2514
  return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
2425
2515
  }
@@ -18,7 +18,10 @@ import {
18
18
  parseStyleLiteralValue,
19
19
  simpleSideEffectFreeExpression,
20
20
  } from "./emit-server-shared.js";
21
- import { oxcServerStringReactNodeRenderHelperPlaceholder } from "./oxc-runtime-emit.js";
21
+ import {
22
+ emitOxcCompatObjectChildren,
23
+ oxcServerStringReactNodeRenderHelperPlaceholder,
24
+ } from "./oxc-runtime-emit.js";
22
25
 
23
26
  export interface EmitResult {
24
27
  code: string;
@@ -329,6 +332,7 @@ function collectHtmlStatements(
329
332
  }
330
333
 
331
334
  if (node.kind === "conditional") {
335
+ const conditionCode = node.conditionValueName ?? node.conditionCode;
332
336
  const whenTrueStatements = node.whenTrue.flatMap((child) =>
333
337
  collectHtmlStatements(
334
338
  child,
@@ -360,27 +364,37 @@ function collectHtmlStatements(
360
364
  return [];
361
365
  }
362
366
 
367
+ let statements: string[];
363
368
  if (whenFalseStatements.length === 0) {
364
- return [
365
- `if (${node.conditionCode}) {`,
369
+ statements = [
370
+ `if (${conditionCode}) {`,
366
371
  ...whenTrueStatements.map((statement) => ` ${statement}`),
367
372
  `}`,
368
373
  ];
369
- }
370
-
371
- if (whenTrueStatements.length === 0) {
372
- return [
373
- `if (!(${node.conditionCode})) {`,
374
+ } else if (whenTrueStatements.length === 0) {
375
+ statements = [
376
+ `if (!(${conditionCode})) {`,
377
+ ...whenFalseStatements.map((statement) => ` ${statement}`),
378
+ `}`,
379
+ ];
380
+ } else {
381
+ statements = [
382
+ `if (${conditionCode}) {`,
383
+ ...whenTrueStatements.map((statement) => ` ${statement}`),
384
+ `} else {`,
374
385
  ...whenFalseStatements.map((statement) => ` ${statement}`),
375
386
  `}`,
376
387
  ];
377
388
  }
378
389
 
390
+ if (node.conditionValueName === undefined) {
391
+ return statements;
392
+ }
393
+
379
394
  return [
380
- `if (${node.conditionCode}) {`,
381
- ...whenTrueStatements.map((statement) => ` ${statement}`),
382
- `} else {`,
383
- ...whenFalseStatements.map((statement) => ` ${statement}`),
395
+ `{`,
396
+ ` const ${node.conditionValueName} = (${node.conditionCode});`,
397
+ ...statements.map((statement) => ` ${statement}`),
384
398
  `}`,
385
399
  ];
386
400
  }
@@ -527,16 +541,9 @@ function collectHtmlStatements(
527
541
 
528
542
  if (node.runtime === "compat" && reactNodeRenderHelperName !== undefined) {
529
543
  return [
530
- `${outVar} += ${reactNodeRenderHelperName}(${node.name}, ${emitPropsObject(
544
+ `${outVar} += ${reactNodeRenderHelperName}(${node.name}, ${emitCompatRuntimePropsObject(
531
545
  node.props,
532
546
  node.children,
533
- escapeHelperName,
534
- escapeBatchHelperName,
535
- asyncComponentNames,
536
- dynamicAttributes,
537
- contextProviderHelperName,
538
- contextConsumerHelperName,
539
- reactNodeRenderHelperName,
540
547
  )});`,
541
548
  ];
542
549
  }
@@ -686,8 +693,13 @@ function collectHtmlParts(
686
693
  }
687
694
 
688
695
  if (node.kind === "conditional") {
696
+ const whenTrue = emitHtmlExpressionFromChildren(node.whenTrue, escapeHelperName, escapeBatchHelperName, asyncComponentNames, dynamicAttributes, contextProviderHelperName, contextConsumerHelperName, reactNodeRenderHelperName);
697
+ const whenFalse = emitHtmlExpressionFromChildren(node.whenFalse, escapeHelperName, escapeBatchHelperName, asyncComponentNames, dynamicAttributes, contextProviderHelperName, contextConsumerHelperName, reactNodeRenderHelperName);
698
+
689
699
  return [
690
- `((${node.conditionCode}) ? ${emitHtmlExpressionFromChildren(node.whenTrue, escapeHelperName, escapeBatchHelperName, asyncComponentNames, dynamicAttributes, contextProviderHelperName, contextConsumerHelperName, reactNodeRenderHelperName)} : ${emitHtmlExpressionFromChildren(node.whenFalse, escapeHelperName, escapeBatchHelperName, asyncComponentNames, dynamicAttributes, contextProviderHelperName, contextConsumerHelperName, reactNodeRenderHelperName)})`,
700
+ node.conditionValueName === undefined
701
+ ? `((${node.conditionCode}) ? ${whenTrue} : ${whenFalse})`
702
+ : `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`,
691
703
  ];
692
704
  }
693
705
 
@@ -808,16 +820,9 @@ function collectHtmlParts(
808
820
 
809
821
  if (node.runtime === "compat" && reactNodeRenderHelperName !== undefined) {
810
822
  return [
811
- `${reactNodeRenderHelperName}(${node.name}, ${emitPropsObject(
823
+ `${reactNodeRenderHelperName}(${node.name}, ${emitCompatRuntimePropsObject(
812
824
  node.props,
813
825
  node.children,
814
- escapeHelperName,
815
- escapeBatchHelperName,
816
- asyncComponentNames,
817
- dynamicAttributes,
818
- contextProviderHelperName,
819
- contextConsumerHelperName,
820
- reactNodeRenderHelperName,
821
826
  )})`,
822
827
  ];
823
828
  }
@@ -998,6 +1003,10 @@ function collectElementAttributeParts(
998
1003
  dynamicAttributes: "drop" | "emit",
999
1004
  attributeScan = scanElementAttributes(tagName, attrs),
1000
1005
  ): string[] {
1006
+ if (dynamicAttributes === "emit" && attrs.some((attr) => attr.kind === "spread-attr")) {
1007
+ return [emitMergedSpreadAttributeExpression(tagName, attrs, attributeScan)];
1008
+ }
1009
+
1001
1010
  return attrs.flatMap((attr) =>
1002
1011
  attr.kind !== "spread-attr" &&
1003
1012
  ((tagName === "input" &&
@@ -1016,6 +1025,38 @@ function collectElementAttributeParts(
1016
1025
  );
1017
1026
  }
1018
1027
 
1028
+ function emitMergedSpreadAttributeExpression(
1029
+ tagName: string,
1030
+ attrs: readonly AttributeIr[],
1031
+ attributeScan: ElementAttributeScan,
1032
+ ): string {
1033
+ const statements = attrs.flatMap((attr): string[] => {
1034
+ if (
1035
+ attr.kind !== "spread-attr" &&
1036
+ ((tagName === "input" &&
1037
+ ((attr.name === "defaultValue" && attributeScan.hasExplicitInputValue) ||
1038
+ (attr.name === "defaultChecked" && attributeScan.hasExplicitInputChecked))) ||
1039
+ ((tagName === "textarea" || tagName === "select") &&
1040
+ (attr.name === "value" || attr.name === "defaultValue")))
1041
+ ) {
1042
+ return [];
1043
+ }
1044
+
1045
+ if (attr.kind === "spread-attr") {
1046
+ return [`Object.assign(_props, (${attr.code}) ?? {});`];
1047
+ }
1048
+
1049
+ if (attr.kind === "event" || attr.name === "key" || attr.name === "dangerouslySetInnerHTML") {
1050
+ return [];
1051
+ }
1052
+
1053
+ const valueCode = attr.kind === "static-attr" ? stringLiteral(attr.value) : `(${attr.code})`;
1054
+ return [`_props[${stringLiteral(attr.name)}] = ${valueCode};`];
1055
+ });
1056
+
1057
+ return `(() => { const _props = {}; ${statements.join(" ")} return ${currentSpreadAttributesHelperName}(${stringLiteral(tagName)}, _props); })()`;
1058
+ }
1059
+
1019
1060
  interface ElementAttributeScan {
1020
1061
  hasExplicitInputValue: boolean;
1021
1062
  hasExplicitInputChecked: boolean;
@@ -1406,6 +1447,29 @@ function emitPropsObject(
1406
1447
  return `{ ${entries.join(", ")} }`;
1407
1448
  }
1408
1449
 
1450
+ function emitCompatRuntimePropsObject(
1451
+ props: ComponentPropIr[],
1452
+ children: JsxNodeIr[] = [],
1453
+ ): string {
1454
+ const entries = props.map((prop) => {
1455
+ if (prop.kind === "spread-prop") {
1456
+ return `...(${prop.code})`;
1457
+ }
1458
+
1459
+ if (prop.kind === "render-prop") {
1460
+ return `${emitPropName(prop.name)}: ${emitOxcCompatObjectChildren(prop.children)}`;
1461
+ }
1462
+
1463
+ return `${emitPropName(prop.name)}: (${prop.code})`;
1464
+ });
1465
+
1466
+ if (children.length > 0) {
1467
+ entries.push(`children: ${emitOxcCompatObjectChildren(children)}`);
1468
+ }
1469
+
1470
+ return `{ ${entries.join(", ")} }`;
1471
+ }
1472
+
1409
1473
  function emitComponentCallExpression(
1410
1474
  name: string,
1411
1475
  propsCode: string,
package/src/internal.ts CHANGED
@@ -1049,16 +1049,21 @@ function parseModule(code: string, filename: string | undefined) {
1049
1049
  function parseModuleContext(context: CompilerModuleContext): CompilerModuleContext {
1050
1050
  if (context.parseErrors.length > 0) {
1051
1051
  throw new Error(
1052
- `${context.filename}: ${context.parseErrors
1053
- .map((error) => readObject(error).message)
1054
- .filter((message): message is string => typeof message === "string")
1055
- .join("\n")}`,
1052
+ `${context.filename}: ${context.parseErrors.map(parseErrorMessage).join("\n")}`,
1056
1053
  );
1057
1054
  }
1058
1055
 
1059
1056
  return context;
1060
1057
  }
1061
1058
 
1059
+ function parseErrorMessage(error: unknown): string {
1060
+ const object = readObject(error);
1061
+ const message = typeof object.message === "string" ? object.message : "Parse error";
1062
+ const codeframe = typeof object.codeframe === "string" ? object.codeframe.trimEnd() : undefined;
1063
+
1064
+ return codeframe === undefined ? message : `${message}\n${codeframe}`;
1065
+ }
1066
+
1062
1067
  function programBody(program: unknown): Record<string, unknown>[] {
1063
1068
  const body = readObject(program).body;
1064
1069
  return Array.isArray(body) ? body.map(readObject) : [];
package/src/ir.ts CHANGED
@@ -83,6 +83,7 @@ export interface JsxFragmentIr {
83
83
  export interface ConditionalIr {
84
84
  kind: "conditional";
85
85
  conditionCode: string;
86
+ conditionValueName?: string;
86
87
  whenTrue: JsxNodeIr[];
87
88
  whenFalse: JsxNodeIr[];
88
89
  }
@@ -2,6 +2,7 @@ import {
2
2
  invalidJsxExpressionDiagnostic,
3
3
  unserializableAwaitValueDiagnostic,
4
4
  unsupportedComponentReferenceDiagnostic,
5
+ unsupportedJsxSpreadChildDiagnostic,
5
6
  } from "./diagnostics.js";
6
7
  import type { AsyncBoundaryIr, JsxElementIr, JsxNodeIr } from "./ir.js";
7
8
  import type { OxcBodyStatementJsxMode } from "./oxc-analysis-types.js";
@@ -128,14 +129,21 @@ export function analyzeOxcJsxNode(
128
129
  if (
129
130
  /^[A-Z][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(tagName) ||
130
131
  context.componentNames.has(tagName) ||
131
- isOxcServerRuntimeComponentBinding(tagName, context)
132
+ isOxcRuntimeComponentBinding(tagName, context)
132
133
  ) {
133
134
  const keyCode = findOxcJsxAttributeCode(code, attributes, "key");
134
135
  const allowRef = bodyStatementJsx === "compat-object";
135
136
  const analyzeJsxNode = (
136
137
  child: Record<string, unknown>,
137
138
  childBodyStatementJsx: OxcBodyStatementJsxMode = bodyStatementJsx,
138
- ) => analyzeOxcJsxNode(code, child, context, childBodyStatementJsx);
139
+ shadowNames: readonly string[] = [],
140
+ ) =>
141
+ analyzeOxcJsxNode(
142
+ code,
143
+ child,
144
+ shadowOxcReactiveAliases(context, shadowNames),
145
+ childBodyStatementJsx,
146
+ );
139
147
  const consumerRenderProp = tagName.endsWith(".Consumer")
140
148
  ? readOxcConsumerRenderProp(
141
149
  code,
@@ -201,11 +209,11 @@ export function analyzeOxcJsxNode(
201
209
  } satisfies JsxElementIr;
202
210
  }
203
211
 
204
- function isOxcServerRuntimeComponentBinding(
212
+ function isOxcRuntimeComponentBinding(
205
213
  tagName: string,
206
214
  context: OxcChildAnalysisContext,
207
215
  ): boolean {
208
- if (context.target !== "server" || !/^[A-Z]/.test(tagName)) {
216
+ if (!/^[A-Z]/.test(tagName)) {
209
217
  return false;
210
218
  }
211
219
 
@@ -344,6 +352,11 @@ export function analyzeOxcChildren(
344
352
  );
345
353
  }
346
354
 
355
+ if (object.type === "JSXSpreadChild") {
356
+ context.diagnostics.push(unsupportedJsxSpreadChildDiagnostic(getOxcLocation(code, object)));
357
+ return [];
358
+ }
359
+
347
360
  return [];
348
361
  });
349
362
  }
@@ -401,17 +414,22 @@ export function analyzeOxcExpressionChild(
401
414
  bodyStatementJsx,
402
415
  );
403
416
 
417
+ const leftExpression = readObject(unwrappedExpression.left);
418
+ const conditionValueName = logicalConditionValueName(leftExpression);
419
+
404
420
  if (unwrappedExpression.operator === "&&") {
405
421
  return [
406
422
  {
407
423
  kind: "conditional",
408
- conditionCode: readOxcReactiveExpressionCode(
409
- code,
410
- readObject(unwrappedExpression.left),
411
- context,
412
- ),
424
+ conditionCode: readOxcReactiveExpressionCode(code, leftExpression, context),
425
+ conditionValueName,
413
426
  whenTrue: rightBranch,
414
- whenFalse: [],
427
+ whenFalse: [
428
+ {
429
+ kind: "expr",
430
+ code: renderableFalsyConditionValueCode(conditionValueName),
431
+ },
432
+ ],
415
433
  },
416
434
  ];
417
435
  }
@@ -420,19 +438,12 @@ export function analyzeOxcExpressionChild(
420
438
  return [
421
439
  {
422
440
  kind: "conditional",
423
- conditionCode: readOxcReactiveExpressionCode(
424
- code,
425
- readObject(unwrappedExpression.left),
426
- context,
427
- ),
441
+ conditionCode: readOxcReactiveExpressionCode(code, leftExpression, context),
442
+ conditionValueName,
428
443
  whenTrue: [
429
444
  {
430
445
  kind: "expr",
431
- code: readOxcReactiveExpressionCode(
432
- code,
433
- readObject(unwrappedExpression.left),
434
- context,
435
- ),
446
+ code: conditionValueName,
436
447
  },
437
448
  ],
438
449
  whenFalse: rightBranch,
@@ -613,7 +624,11 @@ function analyzeOxcListExpression(
613
624
  const itemName = String(readObject(readArray(renderer.params)[0]).name ?? "_item");
614
625
  const indexName = readObject(readArray(renderer.params)[1]).name;
615
626
  const arrayName = readObject(readArray(renderer.params)[2]).name;
616
- const rendererBody = analyzeOxcListRenderer(code, renderer, context, bodyStatementJsx);
627
+ const rendererContext = shadowOxcReactiveAliases(
628
+ context,
629
+ [itemName, indexName, arrayName].filter((name): name is string => typeof name === "string"),
630
+ );
631
+ const rendererBody = analyzeOxcListRenderer(code, renderer, rendererContext, bodyStatementJsx);
617
632
 
618
633
  if (rendererBody === undefined) {
619
634
  return undefined;
@@ -802,6 +817,36 @@ function resolveOxcBodyStatementJsx(context: OxcChildAnalysisContext): OxcBodySt
802
817
  return context.bodyStatementJsx ?? (context.target === "server" ? "server-string" : "dom-node");
803
818
  }
804
819
 
820
+ function logicalConditionValueName(expression: Record<string, unknown>): string {
821
+ return `__mreactLogical_${typeof expression.start === "number" ? expression.start : "value"}`;
822
+ }
823
+
824
+ function renderableFalsyConditionValueCode(name: string): string {
825
+ return `((typeof ${name} === "number" || typeof ${name} === "bigint") ? ${name} : null)`;
826
+ }
827
+
828
+ function shadowOxcReactiveAliases(
829
+ context: OxcChildAnalysisContext,
830
+ names: readonly string[],
831
+ ): OxcChildAnalysisContext {
832
+ if (context.reactiveAliasBindings === undefined || names.length === 0) {
833
+ return context;
834
+ }
835
+
836
+ let aliases: Map<string, string> | undefined;
837
+
838
+ for (const name of names) {
839
+ if (!context.reactiveAliasBindings.has(name)) {
840
+ continue;
841
+ }
842
+
843
+ aliases ??= new Map(context.reactiveAliasBindings);
844
+ aliases.delete(name);
845
+ }
846
+
847
+ return aliases === undefined ? context : { ...context, reactiveAliasBindings: aliases };
848
+ }
849
+
805
850
  function isOxcJsxCommentExpression(code: string, expression: Record<string, unknown>): boolean {
806
851
  if (typeof expression.start !== "number" || typeof expression.end !== "number") {
807
852
  return false;