@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.
- package/dist/diagnostics.d.ts +1 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +8 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/emit-client.js +14 -9
- package/dist/emit-client.js.map +1 -1
- package/dist/emit-compat.js +5 -1
- package/dist/emit-compat.js.map +1 -1
- package/dist/emit-server-stream.d.ts.map +1 -1
- package/dist/emit-server-stream.js +70 -6
- package/dist/emit-server-stream.js.map +1 -1
- package/dist/emit-server.d.ts.map +1 -1
- package/dist/emit-server.js +69 -13
- package/dist/emit-server.js.map +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +7 -4
- package/dist/internal.js.map +1 -1
- package/dist/ir.d.ts +1 -0
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js.map +1 -1
- package/dist/oxc-child-analysis.d.ts.map +1 -1
- package/dist/oxc-child-analysis.js +44 -10
- package/dist/oxc-child-analysis.js.map +1 -1
- package/dist/oxc-component-detection.d.ts +5 -2
- package/dist/oxc-component-detection.d.ts.map +1 -1
- package/dist/oxc-component-detection.js +80 -3
- package/dist/oxc-component-detection.js.map +1 -1
- package/dist/oxc-component-props.d.ts +1 -1
- package/dist/oxc-component-props.d.ts.map +1 -1
- package/dist/oxc-component-props.js +1 -1
- package/dist/oxc-component-props.js.map +1 -1
- package/dist/oxc-component-references.d.ts.map +1 -1
- package/dist/oxc-component-references.js +247 -12
- package/dist/oxc-component-references.js.map +1 -1
- package/dist/oxc-runtime-emit.d.ts.map +1 -1
- package/dist/oxc-runtime-emit.js +10 -2
- package/dist/oxc-runtime-emit.js.map +1 -1
- package/dist/oxc.d.ts.map +1 -1
- package/dist/oxc.js +109 -20
- package/dist/oxc.js.map +1 -1
- package/dist/transform.js +29 -11
- package/dist/transform.js.map +1 -1
- package/package.json +2 -2
- package/src/diagnostics.ts +10 -0
- package/src/emit-client.ts +20 -10
- package/src/emit-compat.ts +6 -1
- package/src/emit-server-stream.ts +96 -6
- package/src/emit-server.ts +93 -29
- package/src/internal.ts +9 -4
- package/src/ir.ts +1 -0
- package/src/oxc-child-analysis.ts +66 -21
- package/src/oxc-component-detection.ts +145 -2
- package/src/oxc-component-props.ts +2 -1
- package/src/oxc-component-references.ts +366 -11
- package/src/oxc-runtime-emit.ts +12 -2
- package/src/oxc.ts +167 -5
- package/src/transform.ts +42 -10
package/src/emit-client.ts
CHANGED
|
@@ -238,7 +238,7 @@ function emitComponent(
|
|
|
238
238
|
|
|
239
239
|
const fragmentName = allocator("_fragment");
|
|
240
240
|
const rootName = allocator("_root");
|
|
241
|
-
const templateHtml =
|
|
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}(
|
|
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}, () =>
|
|
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
|
|
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 =
|
|
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}(
|
|
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
|
-
}
|
package/src/emit-compat.ts
CHANGED
|
@@ -325,7 +325,12 @@ function emitJsxNode(
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
if (node.kind === "conditional") {
|
|
328
|
-
|
|
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 {
|
|
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
|
-
|
|
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 [
|
|
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}, ${
|
|
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}, ${
|
|
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:
|
|
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
|
}
|
package/src/emit-server.ts
CHANGED
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
parseStyleLiteralValue,
|
|
19
19
|
simpleSideEffectFreeExpression,
|
|
20
20
|
} from "./emit-server-shared.js";
|
|
21
|
-
import {
|
|
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
|
-
|
|
365
|
-
`if (${
|
|
369
|
+
statements = [
|
|
370
|
+
`if (${conditionCode}) {`,
|
|
366
371
|
...whenTrueStatements.map((statement) => ` ${statement}`),
|
|
367
372
|
`}`,
|
|
368
373
|
];
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
`
|
|
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
|
-
`
|
|
381
|
-
|
|
382
|
-
|
|
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}, ${
|
|
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
|
-
|
|
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}, ${
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
212
|
+
function isOxcRuntimeComponentBinding(
|
|
205
213
|
tagName: string,
|
|
206
214
|
context: OxcChildAnalysisContext,
|
|
207
215
|
): boolean {
|
|
208
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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;
|