octane 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/constants.cjs +3 -0
- package/dist/cjs/dom-binding-handoff.cjs +2 -1
- package/dist/cjs/dom-binding-protocol.cjs +4 -3
- package/dist/cjs/dom-binding-styles.cjs +3 -2
- package/dist/cjs/dom-bindings.cjs +24 -23
- package/dist/cjs/error-codes.client.generated.cjs +7 -1
- package/dist/cjs/error-codes.server.generated.cjs +27 -1
- package/dist/cjs/hydration/event-capture.cjs +2 -1
- package/dist/cjs/independent-hydration-protocol.cjs +3 -2
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +866 -370
- package/dist/cjs/runtime.server.cjs +250 -62
- package/dist/cjs/server/index.cjs +2 -0
- package/dist/cjs/server/rpc-stream.cjs +19 -12
- package/dist/cjs/server/streamed-signals.cjs +18 -1
- package/dist/cjs/shared-value-helpers.cjs +24 -2
- package/dist/cjs/signals/control-binding.cjs +7 -6
- package/dist/cjs/signals/derived-facade.cjs +2 -1
- package/dist/cjs/signals/document-owner.cjs +4 -3
- package/dist/cjs/signals/facade.cjs +10 -9
- package/dist/cjs/signals/graph.cjs +5 -4
- package/dist/cjs/signals/native-read-client.cjs +4 -3
- package/dist/cjs/signals/owner-context.cjs +4 -3
- package/dist/cjs/signals/query-attempt-observer.cjs +2 -1
- package/dist/cjs/signals/query-facade.cjs +2 -1
- package/dist/cjs/signals/scalar-computations.cjs +2 -1
- package/dist/cjs/signals/transition-action.cjs +10 -9
- package/dist/cjs/signals/transition-candidate.cjs +2 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/arm-exits.js +151 -0
- package/dist/compiler/compile-universal.js +262 -65
- package/dist/compiler/compile.js +1929 -306
- package/dist/compiler/deferred-snapshots.js +791 -0
- package/dist/compiler/strong-mode.js +59 -10
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +2 -0
- package/dist/error-codes.client.generated.d.ts +1 -0
- package/dist/error-codes.client.generated.js +5 -0
- package/dist/error-codes.server.generated.d.ts +5 -0
- package/dist/error-codes.server.generated.js +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/node/aria-diagnostics.js +92 -0
- package/dist/node/behavior-form-submissions.js +228 -0
- package/dist/node/behavior-root-types.js +0 -0
- package/dist/node/behavior-root.js +746 -0
- package/dist/node/class-names.js +25 -0
- package/dist/node/compatibility.js +10 -0
- package/dist/node/component-flags.js +23 -0
- package/dist/node/constants.js +115 -0
- package/dist/node/context-epoch.js +13 -0
- package/dist/node/context-identity.js +46 -0
- package/dist/node/css.js +80 -0
- package/dist/node/data-encoding.js +134 -0
- package/dist/node/devtools-hook.js +229 -0
- package/dist/node/dom-binding-claims.js +4 -0
- package/dist/node/dom-binding-classes.js +152 -0
- package/dist/node/dom-binding-controls.js +6 -0
- package/dist/node/dom-binding-handoff.js +77 -0
- package/dist/node/dom-binding-program.js +1617 -0
- package/dist/node/dom-binding-projections.js +204 -0
- package/dist/node/dom-binding-protocol.js +130 -0
- package/dist/node/dom-binding-signals.js +135 -0
- package/dist/node/dom-binding-styles.js +280 -0
- package/dist/node/dom-bindings.js +1087 -0
- package/dist/node/dom-focused-move.js +46 -0
- package/dist/node/dom-stage.js +878 -0
- package/dist/node/dom-tables.js +382 -0
- package/dist/node/error-codes.client.generated.js +1071 -0
- package/dist/node/error-codes.server.generated.js +761 -0
- package/dist/node/error-message.js +27 -0
- package/dist/node/event-names.js +15 -0
- package/dist/node/form-diagnostics.js +96 -0
- package/dist/node/form-submission.js +43 -0
- package/dist/node/has-own.js +4 -0
- package/dist/node/head-ownership.js +31 -0
- package/dist/node/hook-slot-cache.js +91 -0
- package/dist/node/host-property-diagnostics.js +189 -0
- package/dist/node/html-tree-validation.js +193 -0
- package/dist/node/hydration/condition.js +15 -0
- package/dist/node/hydration/control-capture.js +237 -0
- package/dist/node/hydration/document-lifecycle.js +89 -0
- package/dist/node/hydration/event-capture.js +362 -0
- package/dist/node/hydration/idle.js +23 -0
- package/dist/node/hydration/independent-island.js +317 -0
- package/dist/node/hydration/index.js +74 -0
- package/dist/node/hydration/interaction-config.js +61 -0
- package/dist/node/hydration/interaction.js +35 -0
- package/dist/node/hydration/load.js +15 -0
- package/dist/node/hydration/media.js +33 -0
- package/dist/node/hydration/native-intent.js +68 -0
- package/dist/node/hydration/never.js +12 -0
- package/dist/node/hydration/stream-delivery.js +345 -0
- package/dist/node/hydration/stream-receiver.js +190 -0
- package/dist/node/hydration/stream-result-receiver.js +276 -0
- package/dist/node/hydration/streamed-signals.js +186 -0
- package/dist/node/hydration/types.js +0 -0
- package/dist/node/hydration/visible.js +69 -0
- package/dist/node/hydration-markers.js +44 -0
- package/dist/node/independent-hydration-protocol.js +91 -0
- package/dist/node/index.js +463 -0
- package/dist/node/internal/client.js +7 -0
- package/dist/node/internal/context.js +5 -0
- package/dist/node/internal/server.js +5 -0
- package/dist/node/internal/signal-read.js +4 -0
- package/dist/node/method-dep.js +25 -0
- package/dist/node/profiling.js +432 -0
- package/dist/node/public-types.js +0 -0
- package/dist/node/react/fiber-adapter.js +57 -0
- package/dist/node/react/index.js +397 -0
- package/dist/node/react/react-compat-envelope.js +78 -0
- package/dist/node/react/react-compat-server.js +129 -0
- package/dist/node/react/react-compat-shared.js +104 -0
- package/dist/node/react/react-compat.js +223 -0
- package/dist/node/react/server.js +81 -0
- package/dist/node/react/shared.js +75 -0
- package/dist/node/renderer-bridge.js +35 -0
- package/dist/node/resize-observer.js +105 -0
- package/dist/node/resource-hint-diagnostics.js +67 -0
- package/dist/node/runtime-tags.js +22 -0
- package/dist/node/runtime.js +29364 -0
- package/dist/node/runtime.server.js +8200 -0
- package/dist/node/sanitize-url.js +34 -0
- package/dist/node/server/early-signals.js +54 -0
- package/dist/node/server/index.js +338 -0
- package/dist/node/server/rpc-batch.js +129 -0
- package/dist/node/server/rpc-stream.js +132 -0
- package/dist/node/server/rpc.js +48 -0
- package/dist/node/server/signal-query-observation.js +134 -0
- package/dist/node/server/streamed-region.js +31 -0
- package/dist/node/server/streamed-signals.js +574 -0
- package/dist/node/server-call.js +75 -0
- package/dist/node/server-rpc-batch-client.js +119 -0
- package/dist/node/server-rpc-client.js +108 -0
- package/dist/node/server-rpc-protocol.js +109 -0
- package/dist/node/server-rpc-stream-client.js +98 -0
- package/dist/node/shared-value-helpers.js +60 -0
- package/dist/node/signals/actions.js +463 -0
- package/dist/node/signals/client.js +22 -0
- package/dist/node/signals/computations.js +452 -0
- package/dist/node/signals/control-binding.js +388 -0
- package/dist/node/signals/control-handoff.js +12 -0
- package/dist/node/signals/derived-facade.js +25 -0
- package/dist/node/signals/document-owner.js +48 -0
- package/dist/node/signals/early-values.js +124 -0
- package/dist/node/signals/encoding.js +6 -0
- package/dist/node/signals/engine.js +668 -0
- package/dist/node/signals/errors.js +54 -0
- package/dist/node/signals/facade.js +441 -0
- package/dist/node/signals/graph.js +817 -0
- package/dist/node/signals/handle-protocol.js +11 -0
- package/dist/node/signals/index.js +87 -0
- package/dist/node/signals/native-read-client.js +319 -0
- package/dist/node/signals/native-read-collector.js +132 -0
- package/dist/node/signals/native-read-events.js +45 -0
- package/dist/node/signals/native-read-inspection.js +20 -0
- package/dist/node/signals/native-read-retry.js +30 -0
- package/dist/node/signals/native-read-seeds.js +243 -0
- package/dist/node/signals/native-read-server.js +99 -0
- package/dist/node/signals/owner-context.js +88 -0
- package/dist/node/signals/query-attempt-observer.js +42 -0
- package/dist/node/signals/query-facade.js +41 -0
- package/dist/node/signals/read-protocol.js +125 -0
- package/dist/node/signals/requests.js +716 -0
- package/dist/node/signals/scalar-computations.js +57 -0
- package/dist/node/signals/scope-streams.js +147 -0
- package/dist/node/signals/server.js +22 -0
- package/dist/node/signals/transition-action.js +555 -0
- package/dist/node/signals/transition-candidate.js +101 -0
- package/dist/node/signals/transition-coordinator.js +255 -0
- package/dist/node/signals/transition-state.js +82 -0
- package/dist/node/signals/types.js +20 -0
- package/dist/node/static/index.js +10 -0
- package/dist/node/stream-protocol.js +114 -0
- package/dist/node/streamed-signals-protocol.js +130 -0
- package/dist/node/style-values.js +96 -0
- package/dist/node/sub-slot.js +35 -0
- package/dist/node/testing.js +14 -0
- package/dist/node/trusted-html.js +6 -0
- package/dist/node/tsrx-iterable.js +5 -0
- package/dist/node/tsrx-spread.js +10 -0
- package/dist/node/universal-core.js +10138 -0
- package/dist/node/universal-dom-boundary.js +237 -0
- package/dist/node/universal-native.js +28 -0
- package/dist/node/universal.js +17 -0
- package/dist/node/version.js +4 -0
- package/dist/runtime.d.ts +219 -8
- package/dist/runtime.js +714 -220
- package/dist/runtime.server.d.ts +33 -2
- package/dist/runtime.server.js +217 -29
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/rpc-stream.js +19 -12
- package/dist/server/streamed-signals.js +18 -1
- package/dist/shared-value-helpers.d.ts +11 -0
- package/dist/shared-value-helpers.js +21 -1
- package/dist/universal-core.d.ts +3 -1
- package/dist/universal-core.js +178 -50
- package/dist/version.js +1 -1
- package/package.json +62 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -51,6 +51,7 @@ import { decodeHTMLStrict } from 'entities';
|
|
|
51
51
|
import { print as esrapPrint } from 'esrap';
|
|
52
52
|
import esrapTsx from 'esrap/languages/tsx';
|
|
53
53
|
import { buildFatSegments } from './fat-segments.js';
|
|
54
|
+
import { markDeferredLive, markDeferredRead, snapshotDeferredReads } from './deferred-snapshots.js';
|
|
54
55
|
import {
|
|
55
56
|
METHOD_DEP_IMPORT,
|
|
56
57
|
analyzeCallbackDependencies,
|
|
@@ -76,6 +77,12 @@ import {
|
|
|
76
77
|
UNIVERSAL_THREAD_RUNTIME_IMPORTS,
|
|
77
78
|
} from './compile-universal.js';
|
|
78
79
|
import { compileValdi, VALDI_COMPILER_RUNTIME_IMPORTS } from './compile-valdi.js';
|
|
80
|
+
import {
|
|
81
|
+
ARM_BREAK_MESSAGE,
|
|
82
|
+
ARM_VALUE_RETURN_MESSAGE,
|
|
83
|
+
armJump,
|
|
84
|
+
assertTemplateJumps,
|
|
85
|
+
} from './arm-exits.js';
|
|
79
86
|
import { HOOK_NAMES, NATIVE_SIGNAL_HOOK_NAMES } from './hook-names.js';
|
|
80
87
|
export { HOOK_NAMES } from './hook-names.js';
|
|
81
88
|
import {
|
|
@@ -667,9 +674,14 @@ function directSignalSite(ctx, node, kind = 'binding') {
|
|
|
667
674
|
}
|
|
668
675
|
|
|
669
676
|
function componentInvocationSite(ctx, node) {
|
|
670
|
-
const position = node?.start ?? `${node?.loc?.start?.line ?? 0}:${node?.loc?.start?.column ?? 0}`;
|
|
671
677
|
// Authored positions identify call sites independently of the server/client
|
|
672
678
|
// helper names introduced for loops, branches, and value-position fragments.
|
|
679
|
+
// Hash line:col, not the parser offset: one call site in returned JSX reaches
|
|
680
|
+
// the client as the raw JSXElement but the server as a normalized template
|
|
681
|
+
// Element, and normalization keeps only `loc`.
|
|
682
|
+
const start = node?.loc?.start;
|
|
683
|
+
const position =
|
|
684
|
+
start != null ? `${start.line ?? 0}:${start.column ?? 0}` : (node?.start ?? '0:0');
|
|
673
685
|
return `c:${strongHash(
|
|
674
686
|
`octane:component-invocation-site:1\0${normalizeTextTypeFilename(ctx.filename) ?? ctx.filename}\0${position}`,
|
|
675
687
|
)}`;
|
|
@@ -1051,6 +1063,90 @@ function rejectTextareaValueChildren(tag, node, ctx) {
|
|
|
1051
1063
|
);
|
|
1052
1064
|
}
|
|
1053
1065
|
|
|
1066
|
+
const TEXTAREA_CHILD_KINDS = {
|
|
1067
|
+
Element: 'an element',
|
|
1068
|
+
TSRXExpression: 'a JSX expression',
|
|
1069
|
+
IfStatement: 'an `@if` block',
|
|
1070
|
+
ForOfStatement: 'an `@for` block or a mapped JSX list',
|
|
1071
|
+
SwitchStatement: 'an `@switch` block',
|
|
1072
|
+
TryStatement: 'an `@try` block',
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
// A `<textarea>`'s content is RCDATA: the HTML parser keeps markup and comments
|
|
1076
|
+
// inside it as literal text, so neither the client template nor the server can
|
|
1077
|
+
// place a `<!>` placeholder, a `<!-- -->` separator or a `<!--[-->` frame there.
|
|
1078
|
+
// Its children are therefore text on both sides: they fold into ONE text value
|
|
1079
|
+
// that the client binds as the host's only Text node (runtime `textareaText`)
|
|
1080
|
+
// and the server serializes markerless (`ssrTextareaText`). Returns the parts in
|
|
1081
|
+
// source order, or null when the ordinary only-child path already emits
|
|
1082
|
+
// markerless text on both sides (one static literal or one proven text hole).
|
|
1083
|
+
// Throws for a child that cannot be text, on both emit paths.
|
|
1084
|
+
function textareaTextParts(tag, ns, children, ctx) {
|
|
1085
|
+
if (tag !== 'textarea' || (ns !== 'html' && ns !== 'opaque') || children.length === 0) {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
const parts = [];
|
|
1089
|
+
for (const child of children) {
|
|
1090
|
+
if (child.type !== 'Text') {
|
|
1091
|
+
const element = child.type === 'HeadHoist' ? child.element : child;
|
|
1092
|
+
const kind =
|
|
1093
|
+
element.type === 'JSXElement' || (element.type === 'Element' && !isComponentTag(element))
|
|
1094
|
+
? `\`<${jsxTagName(element) || elementTagName(element)}>\``
|
|
1095
|
+
: (TEXTAREA_CHILD_KINDS[child.type] ?? 'a non-text child');
|
|
1096
|
+
const l = (element.loc ?? child.expression?.loc)?.start;
|
|
1097
|
+
const at = l
|
|
1098
|
+
? ` (${ctx.mapSourceName ? ctx.mapSourceName + ':' : ''}${l.line}:${l.column})`
|
|
1099
|
+
: '';
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`\`<textarea>\` children must be text, but it contains ${kind}. A textarea's content ` +
|
|
1102
|
+
'is its default value and the HTML parser keeps markup inside it as literal ' +
|
|
1103
|
+
`text: move the markup outside the textarea, or pass a string expression.${at}`,
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
// Authored binding views address each hole by its own marker range.
|
|
1107
|
+
if (
|
|
1108
|
+
child._octaneBindingText ||
|
|
1109
|
+
child._octaneBindingOpaque ||
|
|
1110
|
+
child._octaneBindingValue ||
|
|
1111
|
+
child._octaneBindingSlot
|
|
1112
|
+
) {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
const literal = staticTextLiteral(child.expression);
|
|
1116
|
+
if (literal === '') continue;
|
|
1117
|
+
const previous = parts.at(-1);
|
|
1118
|
+
if (literal !== null && previous?.kind === 'static') {
|
|
1119
|
+
parts[parts.length - 1] = { ...previous, value: previous.value + literal };
|
|
1120
|
+
} else if (literal !== null) {
|
|
1121
|
+
parts.push({ kind: 'static', value: literal, node: child });
|
|
1122
|
+
} else {
|
|
1123
|
+
const text =
|
|
1124
|
+
isDirectSignalHandleExpression(child.expression) ||
|
|
1125
|
+
isKnownTextChildExpression(child.expression, ctx.knownStringChildLocals);
|
|
1126
|
+
parts.push({ kind: text ? 'text' : 'child', expr: child.expression, node: child });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// Static text alone is baked into the template and serialized as one merged
|
|
1130
|
+
// run; a sole text hole mounts with htext and serializes unframed.
|
|
1131
|
+
if (parts.every((part) => part.kind === 'static')) return null;
|
|
1132
|
+
if (children.length === 1 && parts[0].kind === 'text') return null;
|
|
1133
|
+
return parts;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// The runtime call's arguments: the parts array, then a 't' at each
|
|
1137
|
+
// `{x as string}` part (omitted when there is none).
|
|
1138
|
+
function textareaTextArgs(parts, lowerHole) {
|
|
1139
|
+
const values = parts.map((part) =>
|
|
1140
|
+
part.kind === 'static'
|
|
1141
|
+
? b.literal(part.value, JSON.stringify(part.value))
|
|
1142
|
+
: lowerHole(part.expr, part.kind === 'text', part.node),
|
|
1143
|
+
);
|
|
1144
|
+
const textHoles = parts.map((part) => (part.kind === 'text' ? 't' : '-')).join('');
|
|
1145
|
+
return textHoles.includes('t')
|
|
1146
|
+
? [b.array(values), b.literal(textHoles, JSON.stringify(textHoles))]
|
|
1147
|
+
: [b.array(values)];
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1054
1150
|
// React's raw-HTML contract is mutually exclusive with a non-nullish child.
|
|
1055
1151
|
// Static TSRX can reject definitely contradictory shapes before either the
|
|
1056
1152
|
// client or server renderer runs them. Preserve React's accepted null/undefined
|
|
@@ -1452,6 +1548,8 @@ const INTERNAL_CLIENT_RUNTIME_HELPERS = new Set([
|
|
|
1452
1548
|
'enableSignalBindings',
|
|
1453
1549
|
'createElementAt',
|
|
1454
1550
|
'createElementFromConfig',
|
|
1551
|
+
'isRenderCall',
|
|
1552
|
+
'deferRecord',
|
|
1455
1553
|
'bindSignalText',
|
|
1456
1554
|
'bindSignalChild',
|
|
1457
1555
|
'bindSignalAttribute',
|
|
@@ -1491,6 +1589,7 @@ const INTERNAL_SERVER_RUNTIME_HELPERS = new Set([
|
|
|
1491
1589
|
'enableServerSignalBindings',
|
|
1492
1590
|
'createElementAt',
|
|
1493
1591
|
'createElementFromConfig',
|
|
1592
|
+
'isRenderCall',
|
|
1494
1593
|
'ssrSignalValue',
|
|
1495
1594
|
'ssrSignalControlValue',
|
|
1496
1595
|
'ssrSignalControlAttrs',
|
|
@@ -2686,13 +2785,88 @@ function collectComponentLocals(componentNode) {
|
|
|
2686
2785
|
for (const stmt of stmts) {
|
|
2687
2786
|
if (stmt.type === 'VariableDeclaration') {
|
|
2688
2787
|
for (const d of stmt.declarations || []) collectBindings(d.id, locals);
|
|
2689
|
-
} else if (stmt.type === 'FunctionDeclaration') {
|
|
2788
|
+
} else if (stmt.type === 'FunctionDeclaration' || stmt.type === 'ClassDeclaration') {
|
|
2690
2789
|
if (stmt.id) locals.add(stmt.id.name);
|
|
2691
2790
|
}
|
|
2692
2791
|
}
|
|
2693
2792
|
return locals;
|
|
2694
2793
|
}
|
|
2695
2794
|
|
|
2795
|
+
// Every name a callback binds for code in its own body: its params, a function
|
|
2796
|
+
// expression's own name, and each declaration at ANY statement depth, where
|
|
2797
|
+
// collectComponentLocals reads only the top level. A name from a sibling block is
|
|
2798
|
+
// included too, which is sound only because a name is threaded when code reads it
|
|
2799
|
+
// free: that read already means whatever the name resolves to at the call site.
|
|
2800
|
+
// collectFreeIdentifiers must therefore never report a nested scope's own names
|
|
2801
|
+
// (a `@{ … }` block's locals, a `@catch` param) as free. Nested functions and
|
|
2802
|
+
// render bodies bind their own names, and are visited on their own.
|
|
2803
|
+
function collectCallbackBindings(fn) {
|
|
2804
|
+
const names = new Set();
|
|
2805
|
+
for (const p of fn.params || []) collectBindings(p, names);
|
|
2806
|
+
if (fn.type === 'FunctionExpression' && fn.id) names.add(fn.id.name);
|
|
2807
|
+
visit(fn.body);
|
|
2808
|
+
return names;
|
|
2809
|
+
|
|
2810
|
+
function visit(node) {
|
|
2811
|
+
if (node == null || typeof node !== 'object') return;
|
|
2812
|
+
if (Array.isArray(node)) {
|
|
2813
|
+
for (const statement of node) visit(statement);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
switch (node.type) {
|
|
2817
|
+
case 'VariableDeclaration':
|
|
2818
|
+
for (const d of node.declarations || []) collectBindings(d.id, names);
|
|
2819
|
+
return;
|
|
2820
|
+
case 'FunctionDeclaration':
|
|
2821
|
+
case 'ClassDeclaration':
|
|
2822
|
+
if (node.id) names.add(node.id.name);
|
|
2823
|
+
return;
|
|
2824
|
+
case 'BlockStatement':
|
|
2825
|
+
visit(node.body);
|
|
2826
|
+
return;
|
|
2827
|
+
case 'IfStatement':
|
|
2828
|
+
visit(node.consequent);
|
|
2829
|
+
visit(node.alternate);
|
|
2830
|
+
return;
|
|
2831
|
+
case 'ForStatement':
|
|
2832
|
+
visit(node.init);
|
|
2833
|
+
visit(node.body);
|
|
2834
|
+
return;
|
|
2835
|
+
case 'ForInStatement':
|
|
2836
|
+
case 'ForOfStatement':
|
|
2837
|
+
visit(node.left);
|
|
2838
|
+
visit(node.body);
|
|
2839
|
+
return;
|
|
2840
|
+
case 'WhileStatement':
|
|
2841
|
+
case 'DoWhileStatement':
|
|
2842
|
+
case 'LabeledStatement':
|
|
2843
|
+
visit(node.body);
|
|
2844
|
+
return;
|
|
2845
|
+
case 'TryStatement':
|
|
2846
|
+
visit(node.block);
|
|
2847
|
+
if (node.handler?.param) collectBindings(node.handler.param, names);
|
|
2848
|
+
visit(node.handler?.body);
|
|
2849
|
+
visit(node.finalizer);
|
|
2850
|
+
return;
|
|
2851
|
+
case 'SwitchStatement':
|
|
2852
|
+
for (const c of node.cases || []) visit(c.consequent);
|
|
2853
|
+
return;
|
|
2854
|
+
default:
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
// Whether `node` reads a name an enclosing callback binds. Such a name is in scope
|
|
2861
|
+
// where `node` is written, but not in the owning body a hoisted helper lands in.
|
|
2862
|
+
function readsCallbackScope(node, callbackScope) {
|
|
2863
|
+
if (callbackScope == null || callbackScope.size === 0) return false;
|
|
2864
|
+
for (const name of collectFreeIdentifiers(node, [])) {
|
|
2865
|
+
if (callbackScope.has(name)) return true;
|
|
2866
|
+
}
|
|
2867
|
+
return false;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2696
2870
|
/**
|
|
2697
2871
|
* Compute the set of component-local names that are guaranteed STABLE across
|
|
2698
2872
|
* renders. Used by the auto-callback pass below to decide which `const X =
|
|
@@ -3953,6 +4127,12 @@ function collectFreeIdentifiers(root, initiallyBound, ignoreNodes = null) {
|
|
|
3953
4127
|
walk(n.body, scope);
|
|
3954
4128
|
return;
|
|
3955
4129
|
}
|
|
4130
|
+
// The compiler's own renderable child hole (a normalized `{expr}` or
|
|
4131
|
+
// `@{ … }` child) shares the prefix, but its expression is runtime code.
|
|
4132
|
+
if (t === 'TSRXExpression') {
|
|
4133
|
+
walk(n.expression, scope);
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
3956
4136
|
if (t.startsWith('TS')) return;
|
|
3957
4137
|
|
|
3958
4138
|
if (t === 'Identifier') {
|
|
@@ -4056,15 +4236,37 @@ function collectFreeIdentifiers(root, initiallyBound, ignoreNodes = null) {
|
|
|
4056
4236
|
return;
|
|
4057
4237
|
}
|
|
4058
4238
|
|
|
4059
|
-
// CatchClause introduces its param
|
|
4239
|
+
// CatchClause introduces its param, and a `@catch (error, reset)` clause
|
|
4240
|
+
// also its reset param.
|
|
4060
4241
|
if (t === 'CatchClause') {
|
|
4061
4242
|
const newScope = new Set(scope);
|
|
4062
4243
|
if (n.param) collectBindings(n.param, newScope);
|
|
4244
|
+
if (n.resetParam) collectBindings(n.resetParam, newScope);
|
|
4063
4245
|
if (n.param) walkPatternExpressions(n.param, newScope);
|
|
4064
4246
|
walk(n.body, newScope);
|
|
4065
4247
|
return;
|
|
4066
4248
|
}
|
|
4067
4249
|
|
|
4250
|
+
// A `@{ … }` block's setup declarations are scoped to its setup and render
|
|
4251
|
+
// output, like a block statement's. Otherwise a block-local reads as a
|
|
4252
|
+
// capture of any outer name it shares, which a caller would thread from a
|
|
4253
|
+
// call site where that outer name may not be in scope.
|
|
4254
|
+
if (t === 'JSXCodeBlock') {
|
|
4255
|
+
const newScope = new Set(scope);
|
|
4256
|
+
for (const stmt of n.body || []) {
|
|
4257
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
4258
|
+
for (const d of stmt.declarations || []) collectBindings(d.id, newScope);
|
|
4259
|
+
} else if (stmt.type === 'FunctionDeclaration' && stmt.id) {
|
|
4260
|
+
newScope.add(stmt.id.name);
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
for (const key in n) {
|
|
4264
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
4265
|
+
walk(n[key], newScope);
|
|
4266
|
+
}
|
|
4267
|
+
return;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4068
4270
|
// for / for-in / for-of — a DECLARATION left introduces bindings, but a
|
|
4069
4271
|
// bare left-hand side is an ASSIGNMENT to something already in scope.
|
|
4070
4272
|
if (t === 'ForStatement' || t === 'ForInStatement' || t === 'ForOfStatement') {
|
|
@@ -7620,20 +7822,39 @@ function isPlainHostRoot(node) {
|
|
|
7620
7822
|
);
|
|
7621
7823
|
}
|
|
7622
7824
|
|
|
7825
|
+
/**
|
|
7826
|
+
* True when `node` compiles to exactly one template element in place, in both
|
|
7827
|
+
* compile modes. Every single-root proof (keyed rows, sole-root components,
|
|
7828
|
+
* host-only @if/@switch arms, and the SSR ranges that mirror them) uses this
|
|
7829
|
+
* rather than isPlainHostRoot, because normalizeChildren plans some plain
|
|
7830
|
+
* hosts differently:
|
|
7831
|
+
* - keyed, `noscript`/document, and parser-repaired hosts lower to a
|
|
7832
|
+
* descriptor hole, which is an anchor plus the element;
|
|
7833
|
+
* - head-hoisted metadata and Float resources render nothing in place.
|
|
7834
|
+
* The server writes a parser-repaired host from its template but frames it in
|
|
7835
|
+
* the same hole range, so both modes reject it too. The planner exempts SVG
|
|
7836
|
+
* and `<noscript>` content from some of these rules; ignoring that context
|
|
7837
|
+
* here only keeps a marker pair it could have skipped.
|
|
7838
|
+
*/
|
|
7839
|
+
function isSingleTemplateHost(node) {
|
|
7840
|
+
return (
|
|
7841
|
+
isPlainHostRoot(node) &&
|
|
7842
|
+
node._octaneImperativeHost !== true &&
|
|
7843
|
+
!alwaysImperativeHost(node) &&
|
|
7844
|
+
!isHoistableHeadElementNode(node) &&
|
|
7845
|
+
headResourceKind(node) === null
|
|
7846
|
+
);
|
|
7847
|
+
}
|
|
7848
|
+
|
|
7623
7849
|
/**
|
|
7624
7850
|
* A direct body host can adopt its enclosing control-flow range on hydration.
|
|
7625
7851
|
*
|
|
7626
7852
|
* Keep this narrower than the general single-root proof: setup statements,
|
|
7627
|
-
* components, nested directives
|
|
7628
|
-
*
|
|
7853
|
+
* components, and nested directives cannot guarantee that the range's first
|
|
7854
|
+
* child is the branch's own hydratable element.
|
|
7629
7855
|
*/
|
|
7630
7856
|
function canBorrowSsrHostBranchRange(statements) {
|
|
7631
|
-
|
|
7632
|
-
return false;
|
|
7633
|
-
}
|
|
7634
|
-
const host = statements[0];
|
|
7635
|
-
const tag = host.id?.name ?? host.openingElement?.name?.name;
|
|
7636
|
-
return tag !== 'title' && tag !== 'meta' && tag !== 'link';
|
|
7857
|
+
return statements != null && statements.length === 1 && isSingleTemplateHost(statements[0]);
|
|
7637
7858
|
}
|
|
7638
7859
|
|
|
7639
7860
|
function statementsOf(node) {
|
|
@@ -7659,16 +7880,19 @@ function hasSwitchCaseLocalBinding(statements) {
|
|
|
7659
7880
|
}
|
|
7660
7881
|
|
|
7661
7882
|
/**
|
|
7662
|
-
* A directive @if/@else whose every reachable arm emits exactly one
|
|
7663
|
-
* host. The chosen tag may change, but the item always
|
|
7664
|
-
* runtime propagates a branch replacement to enclosing
|
|
7883
|
+
* A directive @if/@else whose every reachable arm emits exactly one template
|
|
7884
|
+
* host (isSingleTemplateHost). The chosen tag may change, but the item always
|
|
7885
|
+
* owns one element; the runtime propagates a branch replacement to enclosing
|
|
7886
|
+
* shared boundaries.
|
|
7665
7887
|
*/
|
|
7666
7888
|
function isSingleHostIfRoot(node) {
|
|
7667
7889
|
if (!isIfDirective(node) || node.alternate == null) return false;
|
|
7668
7890
|
const armIsSingleHost = (arm) => {
|
|
7669
7891
|
if (isIfDirective(arm)) return isSingleHostIfRoot(arm);
|
|
7670
|
-
const
|
|
7671
|
-
|
|
7892
|
+
const statements = statementsOf(arm);
|
|
7893
|
+
if (armMayExit(statements)) return false;
|
|
7894
|
+
const render = statements.filter((s) => isJsxNode(s) || isIfDirective(s));
|
|
7895
|
+
return render.length === 1 && isSingleTemplateHost(render[0]);
|
|
7672
7896
|
};
|
|
7673
7897
|
return armIsSingleHost(node.consequent) && armIsSingleHost(node.alternate);
|
|
7674
7898
|
}
|
|
@@ -7699,7 +7923,7 @@ function singleRootComponentArmName(node, locals, ctx) {
|
|
|
7699
7923
|
/**
|
|
7700
7924
|
* Transitive definition-site single-root proof for a void `@{}` body whose
|
|
7701
7925
|
* sole root is an exhaustive `@if`/`@else` or `@switch` tree: every reachable
|
|
7702
|
-
* arm must render exactly one
|
|
7926
|
+
* arm must render exactly one template host OR one qualifying same-module
|
|
7703
7927
|
* component call (see
|
|
7704
7928
|
* singleRootComponentArmName). Returns the set of callee names the proof
|
|
7705
7929
|
* depends on (empty when every arm is a host), or null when the shape does
|
|
@@ -7723,12 +7947,13 @@ function collectSingleRootIfDeps(render, locals, ctx) {
|
|
|
7723
7947
|
if (isSwitchDirective(arm)) return switchOk(arm);
|
|
7724
7948
|
const statements = Array.isArray(arm) ? arm : statementsOf(arm);
|
|
7725
7949
|
if (insideSwitch && hasSwitchCaseLocalBinding(statements)) return false;
|
|
7950
|
+
if (armMayExit(statements)) return false;
|
|
7726
7951
|
const out = statements.filter((s) => isJsxNode(s) || isIfDirective(s) || isSwitchDirective(s));
|
|
7727
7952
|
if (out.length !== 1) return false;
|
|
7728
7953
|
const sole = out[0];
|
|
7729
7954
|
if (isIfDirective(sole)) return ifOk(sole, insideSwitch);
|
|
7730
7955
|
if (isSwitchDirective(sole)) return switchOk(sole);
|
|
7731
|
-
if (
|
|
7956
|
+
if (isSingleTemplateHost(sole)) return true;
|
|
7732
7957
|
const name = singleRootComponentArmName(sole, locals, ctx);
|
|
7733
7958
|
if (name === null) return false;
|
|
7734
7959
|
deps.add(name);
|
|
@@ -7828,12 +8053,13 @@ function anchorlessRootShape(node) {
|
|
|
7828
8053
|
* mount, but its current SSR representation still begins with that construct's
|
|
7829
8054
|
* own hydration markers. A direct host always begins with the row element, so
|
|
7830
8055
|
* the hydrator can use it as the keyed item boundary without an item pair.
|
|
8056
|
+
* `itemBody` is forItemTemplateBody's result, whose root key has already been
|
|
8057
|
+
* folded into the item key when that is safe.
|
|
7831
8058
|
*/
|
|
7832
|
-
function isSsrMarkerlessForItem(node) {
|
|
7833
|
-
if (node?._octaneBindingSite !== undefined) return false;
|
|
7834
|
-
const
|
|
7835
|
-
|
|
7836
|
-
return jsxChildren.length === 1 && isPlainHostRoot(jsxChildren[0]);
|
|
8059
|
+
function isSsrMarkerlessForItem(node, itemBody) {
|
|
8060
|
+
if (node?._octaneBindingSite !== undefined || armMayExit(itemBody)) return false;
|
|
8061
|
+
const jsxChildren = itemBody.filter((s) => isJsxNode(s));
|
|
8062
|
+
return jsxChildren.length === 1 && isSingleTemplateHost(jsxChildren[0]);
|
|
7837
8063
|
}
|
|
7838
8064
|
|
|
7839
8065
|
/**
|
|
@@ -7843,9 +8069,10 @@ function isSsrMarkerlessForItem(node) {
|
|
|
7843
8069
|
* again. Its component call then adopts the host through the established
|
|
7844
8070
|
* singleRoot path. Explicit keys, spread/children overrides, imported or
|
|
7845
8071
|
* dynamic callees, and additional item statements cannot use that proof.
|
|
8072
|
+
* `body` is forItemTemplateBody's output, which the client's single-root proof
|
|
8073
|
+
* also reads, so a removed row-key attribute leaves both sides in agreement.
|
|
7846
8074
|
*/
|
|
7847
|
-
function canShareSsrComponentItemRange(
|
|
7848
|
-
const body = node?.body?.body || [];
|
|
8075
|
+
function canShareSsrComponentItemRange(body, ctx) {
|
|
7849
8076
|
if (body.length !== 1) return false;
|
|
7850
8077
|
const component = body[0];
|
|
7851
8078
|
if (
|
|
@@ -7929,6 +8156,13 @@ const HOST_MOUNT_SAFE_TAGS = new Set([
|
|
|
7929
8156
|
'ul',
|
|
7930
8157
|
]);
|
|
7931
8158
|
|
|
8159
|
+
// Attributes whose writes carry their own lifecycle or ordering keep a row on
|
|
8160
|
+
// forBlock. `style` is a deliberate, conservative member: its writers also run
|
|
8161
|
+
// hydration comparison, hidden-Activity display enforcement and transition
|
|
8162
|
+
// snapshots, none of which the direct mount was audited against. The cost is
|
|
8163
|
+
// confined to refilling an already-mounted, empty list with at least
|
|
8164
|
+
// FAST_HOST_LIST_MIN_ITEMS rows: the first mount and every update, including
|
|
8165
|
+
// each virtualized scroll, run through forBlock whichever helper is named.
|
|
7932
8166
|
const HOST_MOUNT_UNSAFE_ATTRIBUTES = new Set([
|
|
7933
8167
|
'autofocus',
|
|
7934
8168
|
'checked',
|
|
@@ -8065,14 +8299,18 @@ function alwaysCompletesAbruptly(statements) {
|
|
|
8065
8299
|
* template, so normalize every component-level early return through `?? null`.
|
|
8066
8300
|
* This preserves one evaluation and every renderable value while making bare or
|
|
8067
8301
|
* explicitly-undefined returns an unambiguous empty output. Nested functions are
|
|
8068
|
-
* separate execution scopes and remain untouched.
|
|
8302
|
+
* separate execution scopes and remain untouched. So is JSX: a return inside a
|
|
8303
|
+
* directive arm is that arm's early exit, which the arm lowers exactly as it
|
|
8304
|
+
* does in a template body, so the server and client ranges agree.
|
|
8069
8305
|
*/
|
|
8070
8306
|
function normalizeOwnRenderableReturns(statement, preserveJsx = false) {
|
|
8071
8307
|
return mapAst(statement, (node) => {
|
|
8072
8308
|
if (
|
|
8073
8309
|
node.type === 'FunctionDeclaration' ||
|
|
8074
8310
|
node.type === 'FunctionExpression' ||
|
|
8075
|
-
node.type === 'ArrowFunctionExpression'
|
|
8311
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
8312
|
+
JSX_CHILDREN_BEARING_TYPES.has(node.type) ||
|
|
8313
|
+
SETUP_VALUE_DIRECTIVE_TYPES.has(node.type)
|
|
8076
8314
|
)
|
|
8077
8315
|
return node;
|
|
8078
8316
|
if (node.type !== 'ReturnStatement') return null;
|
|
@@ -8166,7 +8404,7 @@ export function hasOnlyLowerableNullishExits(node) {
|
|
|
8166
8404
|
}
|
|
8167
8405
|
|
|
8168
8406
|
/**
|
|
8169
|
-
* Prove that a component always returns one host element.
|
|
8407
|
+
* Prove that a component always returns one template host element.
|
|
8170
8408
|
*
|
|
8171
8409
|
* TSRX's `@{}` form carries that output as `body.render`. Ordinary TSX keeps a
|
|
8172
8410
|
* real `return`; accept only a final host-element return and reject any other
|
|
@@ -8175,12 +8413,13 @@ export function hasOnlyLowerableNullishExits(node) {
|
|
|
8175
8413
|
*/
|
|
8176
8414
|
function singleHostComponentRoot(node) {
|
|
8177
8415
|
if (node?.body?.type === 'JSXCodeBlock') {
|
|
8178
|
-
return isVoidJsxCodeBlockFunction(node) &&
|
|
8416
|
+
return isVoidJsxCodeBlockFunction(node) && isSingleTemplateHost(node.body.render);
|
|
8179
8417
|
}
|
|
8180
8418
|
if (node?.body?.type !== 'BlockStatement') return false;
|
|
8181
8419
|
const stmts = node.body.body || [];
|
|
8182
8420
|
const final = stmts[stmts.length - 1];
|
|
8183
|
-
if (!final || final.type !== 'ReturnStatement' || !
|
|
8421
|
+
if (!final || final.type !== 'ReturnStatement' || !isSingleTemplateHost(final.argument))
|
|
8422
|
+
return false;
|
|
8184
8423
|
|
|
8185
8424
|
let returns = 0;
|
|
8186
8425
|
const seen = new WeakSet();
|
|
@@ -9771,6 +10010,7 @@ function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
|
9771
10010
|
normalizeAuthoredJsxLiterals(parseModule(source, cleanFilename)),
|
|
9772
10011
|
);
|
|
9773
10012
|
analyzeTsrx(analyzedAst, cleanFilename);
|
|
10013
|
+
assertTemplateJumps(analyzedAst, source, cleanFilename);
|
|
9774
10014
|
adoptParserAst(analyzedAst);
|
|
9775
10015
|
assertNoLegacyContextProviders(analyzedAst, source, cleanFilename);
|
|
9776
10016
|
options = nativeReadOptions(analyzedAst, options);
|
|
@@ -10241,6 +10481,9 @@ function compileInternal(
|
|
|
10241
10481
|
const errorBoundaryLowering = lowerImportedErrorBoundaries(ast);
|
|
10242
10482
|
ast = errorBoundaryLowering.ast;
|
|
10243
10483
|
const consumedRuntimeLocals = errorBoundaryLowering.consumed;
|
|
10484
|
+
// A module-level `@{ … }` function that code calls directly also gets its
|
|
10485
|
+
// returned-JSX form, before the passes below rewrite its authored returns.
|
|
10486
|
+
ast = splitDirectlyCalledComponents(ast);
|
|
10244
10487
|
// A null-only shorthand guard is template control flow, not an arbitrary
|
|
10245
10488
|
// JavaScript return value. Lower it in every compiler mode so SSR, hydration,
|
|
10246
10489
|
// and HMR share one DOM-range shape. HMR still emits the generic component
|
|
@@ -10249,6 +10492,8 @@ function compileInternal(
|
|
|
10249
10492
|
// Same contract for React-style conditional JSX returns: branch-selected
|
|
10250
10493
|
// output compiles as template control flow instead of de-opt descriptors.
|
|
10251
10494
|
ast = lowerJsxReturnBranchComponents(ast);
|
|
10495
|
+
// A nested `@{ … }` function that code calls directly returns JSX instead.
|
|
10496
|
+
ast = lowerDirectlyCalledTemplateFunctions(ast);
|
|
10252
10497
|
// Omitted dependency lists are compiler-owned: infer reactive captures
|
|
10253
10498
|
// before any component splitting/hoisting so every lexical binding is still
|
|
10254
10499
|
// visible to the shared TSRX/TSX analysis. Explicit arrays and `null` pass
|
|
@@ -10599,8 +10844,7 @@ function compileInternal(
|
|
|
10599
10844
|
if (
|
|
10600
10845
|
item.id?.type === 'Identifier' &&
|
|
10601
10846
|
init?.type === 'CallExpression' &&
|
|
10602
|
-
init.callee
|
|
10603
|
-
memoImportNames.has(init.callee.name) &&
|
|
10847
|
+
isOctaneMemoCallee(init.callee, ctx, memoImportNames) &&
|
|
10604
10848
|
(init.arguments?.length ?? 0) === 1
|
|
10605
10849
|
) {
|
|
10606
10850
|
ctx.defaultMemoBindings.add(item.id.name);
|
|
@@ -10708,7 +10952,9 @@ function compileInternal(
|
|
|
10708
10952
|
(isComponentFunction(node.declaration) || isReturnJsxFunction(node.declaration))
|
|
10709
10953
|
)
|
|
10710
10954
|
compNode = node.declaration;
|
|
10711
|
-
|
|
10955
|
+
// A component's returned-JSX twin runs only for direct calls, never as a
|
|
10956
|
+
// component.
|
|
10957
|
+
if (compNode && compNode.id && compNode._octaneDirectCallTwin !== true) {
|
|
10712
10958
|
ctx.componentInfo.set(compNode.id.name, {
|
|
10713
10959
|
eligible: false,
|
|
10714
10960
|
autoMemoSafe: false,
|
|
@@ -11779,10 +12025,12 @@ function compileServer(
|
|
|
11779
12025
|
const errorBoundaryLowering = lowerImportedErrorBoundaries(ast);
|
|
11780
12026
|
ast = errorBoundaryLowering.ast;
|
|
11781
12027
|
const consumedRuntimeLocals = errorBoundaryLowering.consumed;
|
|
12028
|
+
ast = splitDirectlyCalledComponents(ast);
|
|
11782
12029
|
// Mirror the client transform so SSR emits the same control-flow ranges
|
|
11783
12030
|
// hydration expects in both development and production.
|
|
11784
12031
|
ast = lowerNullishComponentExits(ast);
|
|
11785
12032
|
ast = lowerJsxReturnBranchComponents(ast);
|
|
12033
|
+
ast = lowerDirectlyCalledTemplateFunctions(ast);
|
|
11786
12034
|
// Mirror the client transform exactly. Effects are server no-ops, but
|
|
11787
12035
|
// useMemo/useCallback execute during SSR and must receive the same inferred
|
|
11788
12036
|
// dependency shape as hydration's client compile.
|
|
@@ -12087,6 +12335,11 @@ function compileServerComponent(node, ctx) {
|
|
|
12087
12335
|
const beforeCss = ctx.cssInjections.length;
|
|
12088
12336
|
const scoping = applyStyleScopes(node, ctx);
|
|
12089
12337
|
node = scoping.node;
|
|
12338
|
+
// A plain return-JSX declaration can also be called directly; its returned
|
|
12339
|
+
// JSX then needs the same deferred value the client emits.
|
|
12340
|
+
if (node.body?.type === 'BlockStatement' && !node._octaneBindingView) {
|
|
12341
|
+
node = { ...node, _octaneDirectCallReturn: true };
|
|
12342
|
+
}
|
|
12090
12343
|
const cssHash = scoping.cssHash;
|
|
12091
12344
|
const cssEntries = [...ctx.moduleCssInjections, ...ctx.cssInjections.slice(beforeCss)].sort(
|
|
12092
12345
|
(a, b) => a.order - b.order,
|
|
@@ -12120,6 +12373,7 @@ function compileServerComponent(node, ctx) {
|
|
|
12120
12373
|
// its body (serverThemeTouchStatements) so their CSS precedes its own.
|
|
12121
12374
|
scoping.runtimeApplied,
|
|
12122
12375
|
);
|
|
12376
|
+
fn = withDirectCallGuard(fn, node, ctx);
|
|
12123
12377
|
} finally {
|
|
12124
12378
|
ctx.currentComponentLocals = prevLocals;
|
|
12125
12379
|
ctx.knownStringLocals = prevKnownStr;
|
|
@@ -12144,8 +12398,11 @@ function compileServerComponent(node, ctx) {
|
|
|
12144
12398
|
// hoisting instead of a TDZ `const` binding. Server and client compiles must
|
|
12145
12399
|
// agree, or the same route module renders on one side and crashes on the other.
|
|
12146
12400
|
// Writable declarations also keep their module binding inside their own body
|
|
12147
|
-
// and preserve declaration-form live default exports.
|
|
12401
|
+
// and preserve declaration-form live default exports. A component's
|
|
12402
|
+
// returned-JSX twin is hoisted like the client's, because a direct call of
|
|
12403
|
+
// its component can run while the module evaluates.
|
|
12148
12404
|
if (
|
|
12405
|
+
node._octaneDirectCallTwin === true ||
|
|
12149
12406
|
componentReferencedAboveDeclaration(ctx, node, name) ||
|
|
12150
12407
|
isReassignedComponentDeclaration(node, ctx)
|
|
12151
12408
|
) {
|
|
@@ -12211,7 +12468,9 @@ function ssrReturnedJsxNode(argument, ctx) {
|
|
|
12211
12468
|
argument.type === 'JSXFragment' ||
|
|
12212
12469
|
argument.type === 'Fragment' ||
|
|
12213
12470
|
isFragmentLongForm(argument, ctx) ||
|
|
12214
|
-
(
|
|
12471
|
+
(returnedHostRoot
|
|
12472
|
+
? isRangedReturnedHost(argument)
|
|
12473
|
+
: requiresTemplateNormalization(argument, 'html', true, ctx))
|
|
12215
12474
|
) {
|
|
12216
12475
|
return {
|
|
12217
12476
|
type: 'TSRXExpression',
|
|
@@ -12280,6 +12539,7 @@ function ssrCompileBodyWithMapTemps(
|
|
|
12280
12539
|
|
|
12281
12540
|
let statements;
|
|
12282
12541
|
let jsxNodes;
|
|
12542
|
+
let directCallRoot = null;
|
|
12283
12543
|
if (node.body && node.body.type === 'JSXCodeBlock') {
|
|
12284
12544
|
statements = returnedOutput
|
|
12285
12545
|
? (node.body.body || []).map(normalizeOwnRenderableReturns)
|
|
@@ -12293,11 +12553,18 @@ function ssrCompileBodyWithMapTemps(
|
|
|
12293
12553
|
// itself. rewriteEarlyExits wants the array.
|
|
12294
12554
|
const bodyStmts =
|
|
12295
12555
|
node.body && node.body.type === 'BlockStatement' ? node.body.body || [] : node.body || [];
|
|
12296
|
-
|
|
12556
|
+
// A statement array is a synthetic arm body (see splitArmExits); a function
|
|
12557
|
+
// body keeps its returns on the generic value ABI.
|
|
12558
|
+
const bodyRewritten = Array.isArray(node.body)
|
|
12559
|
+
? splitArmExits(unwrapOutputCodeBlock(bodyStmts), ctx)
|
|
12560
|
+
: rewriteEarlyExits(unwrapOutputCodeBlock(bodyStmts));
|
|
12297
12561
|
statements = [];
|
|
12298
12562
|
jsxNodes = [];
|
|
12299
12563
|
for (const child of bodyRewritten) {
|
|
12300
12564
|
if (child.type === 'ReturnStatement' && child.argument && isJsxNode(child.argument)) {
|
|
12565
|
+
if (directCallRoot === null && node._octaneDirectCallReturn === true) {
|
|
12566
|
+
directCallRoot = child.argument;
|
|
12567
|
+
}
|
|
12301
12568
|
// return-JSX form: the returned host element (+ its directive children) is
|
|
12302
12569
|
// the render output — route it to jsxNodes so it flows through ssrEmitNode
|
|
12303
12570
|
// (byte-identical SSR to the `@{}` form), not printed as `return <jsx>`.
|
|
@@ -12425,6 +12692,51 @@ function ssrCompileBodyWithMapTemps(
|
|
|
12425
12692
|
ctx._returnedFragmentTemplate = prevReturnedFragmentTemplate;
|
|
12426
12693
|
ctx._tsxValuePos = prevValuePos;
|
|
12427
12694
|
ctx._valueDirectiveLowering = prevValueDirectiveLowering;
|
|
12695
|
+
// Mirrors compileReturnJsxFunction: a direct call defers the record that a
|
|
12696
|
+
// body call renders now. Its fragment sub is declared only in that branch, and
|
|
12697
|
+
// it is lowered after the body template so body-call output is unchanged.
|
|
12698
|
+
let directCall = null;
|
|
12699
|
+
const directCallKind = directCallRoot === null ? null : directCallReturnKind(directCallRoot, ctx);
|
|
12700
|
+
if (directCallKind === 'value') {
|
|
12701
|
+
// Directives in the returned value fold into helpers owned by the direct
|
|
12702
|
+
// call, so a body call never declares them.
|
|
12703
|
+
const subs = [];
|
|
12704
|
+
const previousLowering = ctx._valueDirectiveLowering;
|
|
12705
|
+
ctx._valueDirectiveLowering = serverValueDirectiveFold(ctx, name, subs, cssHash);
|
|
12706
|
+
try {
|
|
12707
|
+
const returned = rewriteHookCalls(b.return(directCallRoot), ctx, name, localSetupSlots);
|
|
12708
|
+
// Like the client's deferred record, the value reads bindings when it renders.
|
|
12709
|
+
directCall = { subs, value: markDeferredLive(rewriteJsxValues(returned.argument, ctx)) };
|
|
12710
|
+
} finally {
|
|
12711
|
+
ctx._valueDirectiveLowering = previousLowering;
|
|
12712
|
+
}
|
|
12713
|
+
} else if (directCallKind === 'fragment') {
|
|
12714
|
+
const subs = [];
|
|
12715
|
+
const record = serverValueDirectiveFold(
|
|
12716
|
+
ctx,
|
|
12717
|
+
name,
|
|
12718
|
+
subs,
|
|
12719
|
+
cssHash,
|
|
12720
|
+
false,
|
|
12721
|
+
'html',
|
|
12722
|
+
)(directCallRoot);
|
|
12723
|
+
ctx.runtimeNeeded.add('createScopedValue');
|
|
12724
|
+
directCall = {
|
|
12725
|
+
subs,
|
|
12726
|
+
value: inheritOriginLoc(
|
|
12727
|
+
b.call(rtAlias('createScopedValue'), b.arrow([], record)),
|
|
12728
|
+
directCallRoot,
|
|
12729
|
+
),
|
|
12730
|
+
};
|
|
12731
|
+
}
|
|
12732
|
+
const renderCallTest =
|
|
12733
|
+
directCall === null
|
|
12734
|
+
? null
|
|
12735
|
+
: () =>
|
|
12736
|
+
inheritOriginLoc(
|
|
12737
|
+
b.call(requireRuntimeForContext(ctx, 'isRenderCall'), b.id('__s')),
|
|
12738
|
+
directCallRoot,
|
|
12739
|
+
);
|
|
12428
12740
|
|
|
12429
12741
|
const body = [];
|
|
12430
12742
|
body.push(...serverThemeTouchStatements(themeTouches ?? [], ctx, node));
|
|
@@ -12488,14 +12800,55 @@ function ssrCompileBodyWithMapTemps(
|
|
|
12488
12800
|
}
|
|
12489
12801
|
}
|
|
12490
12802
|
}
|
|
12491
|
-
|
|
12492
|
-
//
|
|
12803
|
+
// A direct call's deferred fragment registers its own head elements when it
|
|
12804
|
+
// renders, so the body-call registrations run only for a body call.
|
|
12805
|
+
const guardHead = (statements) =>
|
|
12806
|
+
renderCallTest === null || statements.length === 0
|
|
12807
|
+
? statements
|
|
12808
|
+
: [inheritOriginLoc(b.if(renderCallTest(), b.block(statements), null), statements[0])];
|
|
12809
|
+
body.push(...guardHead(frontHead), ...rewritten, ...inlinedSubs, ...guardHead(deferredHead));
|
|
12810
|
+
if (directCall !== null) {
|
|
12811
|
+
const scopeNames = collectFunctionScopeBindings(
|
|
12812
|
+
node.params.length > 0 ? node.params : [b.id('__props')],
|
|
12813
|
+
[...rewritten, ...inlinedSubs],
|
|
12814
|
+
new Set([...mapTemps, '__s', '__extra']),
|
|
12815
|
+
);
|
|
12816
|
+
const captures = directCallCaptures(node, scopeNames, directCall.subs, directCall.value);
|
|
12817
|
+
const branch =
|
|
12818
|
+
captures === null
|
|
12819
|
+
? [...directCall.subs, b.return(directCall.value)]
|
|
12820
|
+
: [
|
|
12821
|
+
b.return(
|
|
12822
|
+
hoistCapturingHelper(
|
|
12823
|
+
ctx,
|
|
12824
|
+
`${name}$direct`,
|
|
12825
|
+
captures,
|
|
12826
|
+
directCall.subs,
|
|
12827
|
+
directCall.value,
|
|
12828
|
+
directCallRoot,
|
|
12829
|
+
),
|
|
12830
|
+
),
|
|
12831
|
+
];
|
|
12832
|
+
body.push(
|
|
12833
|
+
inheritOriginLoc(b.if(b.unary('!', renderCallTest()), b.block(branch), null), directCallRoot),
|
|
12834
|
+
);
|
|
12835
|
+
}
|
|
12836
|
+
// PROPS-FIRST ABI (matches the client): `(props, __s, __extra)`. A leading
|
|
12493
12837
|
// `__props` placeholder stands in when there are no user params, so a verbatim
|
|
12494
12838
|
// `function Foo(props)` and a compiled component both bind props from arg 0.
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12839
|
+
// A synthetic sub-body (a statement array) is called by its construct instead,
|
|
12840
|
+
// with its own leading arguments: `(item, index, __s, __extra)` for a @for
|
|
12841
|
+
// item, `(error, reset, __s, __extra)` for a @catch arm.
|
|
12842
|
+
const { params, bindings: paramBindings } = Array.isArray(node.body)
|
|
12843
|
+
? {
|
|
12844
|
+
params: [
|
|
12845
|
+
...(node.params.length > 0 ? node.params : [b.id('__props')]),
|
|
12846
|
+
b.id('__s'),
|
|
12847
|
+
b.id('__extra'),
|
|
12848
|
+
],
|
|
12849
|
+
bindings: [],
|
|
12850
|
+
}
|
|
12851
|
+
: compiledBodyParams(node.params);
|
|
12499
12852
|
// Private loop items feed only HTML concatenation, so their serialized tail
|
|
12500
12853
|
// needs no carrier. Other bodies can cross a component/value boundary, where
|
|
12501
12854
|
// the carrier distinguishes compiled HTML from authored text returns. This
|
|
@@ -12519,14 +12872,11 @@ function ssrCompileBodyWithMapTemps(
|
|
|
12519
12872
|
: ([...(node.params || []), ...(Array.isArray(node.body) ? node.body : [])].find(
|
|
12520
12873
|
(part) => part?.loc != null,
|
|
12521
12874
|
) ?? ctx._moduleOrigin);
|
|
12875
|
+
const scopedBody = ctx.nativeReads
|
|
12876
|
+
? wrapNativeReadScope(body, b.id('__s'), nativeReadNames(ctx))
|
|
12877
|
+
: body;
|
|
12522
12878
|
return inheritOriginLoc(
|
|
12523
|
-
b.function_declaration(
|
|
12524
|
-
b.id(name),
|
|
12525
|
-
params,
|
|
12526
|
-
b.block(
|
|
12527
|
-
ctx.nativeReads ? wrapNativeReadScope(body, b.id('__s'), nativeReadNames(ctx)) : body,
|
|
12528
|
-
),
|
|
12529
|
-
),
|
|
12879
|
+
b.function_declaration(b.id(name), params, b.block([...paramBindings, ...scopedBody])),
|
|
12530
12880
|
origin,
|
|
12531
12881
|
);
|
|
12532
12882
|
}
|
|
@@ -12932,6 +13282,14 @@ function ssrEmitNode(
|
|
|
12932
13282
|
case 'Element':
|
|
12933
13283
|
if (isComponentTag(node))
|
|
12934
13284
|
return ssrEmitComponent(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
|
|
13285
|
+
if (node.parserRepairRange === true) {
|
|
13286
|
+
ctx.runtimeNeeded.add('ssrBlock');
|
|
13287
|
+
return ssrCall(
|
|
13288
|
+
'ssrBlock',
|
|
13289
|
+
[ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs)],
|
|
13290
|
+
node,
|
|
13291
|
+
);
|
|
13292
|
+
}
|
|
12935
13293
|
return ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
|
|
12936
13294
|
case 'TSRXExpression':
|
|
12937
13295
|
return ssrEmitTsrxExpression(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
|
|
@@ -13926,15 +14284,37 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
13926
14284
|
// still gets a `<!--[-->…<!--]-->` block). Must match the client's only-child
|
|
13927
14285
|
// markerless condition exactly so both sides agree for hydration: a single `Text`
|
|
13928
14286
|
// child that is neither a static literal (baked into HTML) nor proven text
|
|
13929
|
-
// (emitted via `ssrText`).
|
|
14287
|
+
// (emitted via `ssrText`). A spread or raw-HTML writer does not change the
|
|
14288
|
+
// client's shape, so it must not change this one: inside RCDATA (<textarea>)
|
|
14289
|
+
// a frame around a primitive would parse as literal text.
|
|
13930
14290
|
const onlyChild0 =
|
|
13931
14291
|
normChildren.length === 1 && normChildren[0].type === 'Text' ? normChildren[0] : null;
|
|
14292
|
+
const textareaParts = textareaTextParts(tag, selfNs, normChildren, ctx);
|
|
13932
14293
|
let childrenExpr;
|
|
13933
14294
|
if (authoredStaticScriptContent !== undefined) {
|
|
13934
14295
|
const content = escapeInlineScriptContent(authoredStaticScriptContent);
|
|
13935
14296
|
childrenExpr = ssrHtmlTemplate(content === '' ? [] : [content], node, ctx);
|
|
14297
|
+
} else if (textareaParts !== null) {
|
|
14298
|
+
// The client binds these as one Text node (see textareaTextParts).
|
|
14299
|
+
ctx.runtimeNeeded.add('ssrTextareaText');
|
|
14300
|
+
childrenExpr = ssrCall(
|
|
14301
|
+
'ssrTextareaText',
|
|
14302
|
+
textareaTextArgs(textareaParts, (expression, text, part) =>
|
|
14303
|
+
ssrSignalValue(
|
|
14304
|
+
resolveStyleExpr(
|
|
14305
|
+
text
|
|
14306
|
+
? rewriteHookCalls(expression, ctx, name)
|
|
14307
|
+
: rewriteJsxValues(rewriteHookCalls(expression, ctx, name), ctx),
|
|
14308
|
+
cssHash,
|
|
14309
|
+
),
|
|
14310
|
+
ctx,
|
|
14311
|
+
part,
|
|
14312
|
+
true,
|
|
14313
|
+
),
|
|
14314
|
+
),
|
|
14315
|
+
node,
|
|
14316
|
+
);
|
|
13936
14317
|
} else if (
|
|
13937
|
-
htmlSources.length === 0 &&
|
|
13938
14318
|
onlyChild0 !== null &&
|
|
13939
14319
|
!onlyChild0._octaneBindingOpaque &&
|
|
13940
14320
|
!onlyChild0._octaneBindingValue &&
|
|
@@ -14008,7 +14388,15 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
14008
14388
|
ctx.runtimeNeeded.add('ssrChildrenSources');
|
|
14009
14389
|
childrenExpr = ssrCall(
|
|
14010
14390
|
'ssrChildrenSources',
|
|
14011
|
-
[
|
|
14391
|
+
[
|
|
14392
|
+
b.array(effectiveChildrenPropSources),
|
|
14393
|
+
ssrThunk(childrenExpr, node),
|
|
14394
|
+
b.id('__s'),
|
|
14395
|
+
// A textarea's children prop is text too (see textareaTextParts).
|
|
14396
|
+
...(tag === 'textarea' && (selfNs === 'html' || selfNs === 'opaque')
|
|
14397
|
+
? [b.literal(true, 'true')]
|
|
14398
|
+
: []),
|
|
14399
|
+
],
|
|
14012
14400
|
node,
|
|
14013
14401
|
);
|
|
14014
14402
|
}
|
|
@@ -14722,21 +15110,8 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
14722
15110
|
),
|
|
14723
15111
|
node,
|
|
14724
15112
|
);
|
|
14725
|
-
let explicitKey = null;
|
|
14726
15113
|
let keyDeclaration = null;
|
|
14727
|
-
const
|
|
14728
|
-
(child) => child.type === 'Element' || child.type === 'JSXElement',
|
|
14729
|
-
);
|
|
14730
|
-
if (firstEl) {
|
|
14731
|
-
const keyAttr = (firstEl.attributes || firstEl.openingElement?.attributes || []).find(
|
|
14732
|
-
(attr) => (attr.name?.name || attr.name) === 'key',
|
|
14733
|
-
);
|
|
14734
|
-
if (keyAttr?.value != null) {
|
|
14735
|
-
explicitKey =
|
|
14736
|
-
keyAttr.value.type === 'JSXExpressionContainer' ? keyAttr.value.expression : keyAttr.value;
|
|
14737
|
-
}
|
|
14738
|
-
}
|
|
14739
|
-
if (explicitKey === null) explicitKey = node.key || null;
|
|
15114
|
+
const explicitKey = forRowKeyAttribute(node, ctx) ?? node.key ?? null;
|
|
14740
15115
|
if (explicitKey !== null) {
|
|
14741
15116
|
const keyParams = [itemId];
|
|
14742
15117
|
if (node.index) keyParams.push(node.index);
|
|
@@ -14751,7 +15126,7 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
14751
15126
|
} else if (node.index) {
|
|
14752
15127
|
itemKey = inheritOriginLoc(b.id('__i'), node.index);
|
|
14753
15128
|
}
|
|
14754
|
-
const markerlessItem = isSsrMarkerlessForItem(node);
|
|
15129
|
+
const markerlessItem = isSsrMarkerlessForItem(node, itemBody);
|
|
14755
15130
|
// ssrArm exists solely to make use()/component identity distinct per item.
|
|
14756
15131
|
// Skip it when the item is compiler-proven synchronous and transparent:
|
|
14757
15132
|
// no render-time calls, nested components/control flow, or renderable child
|
|
@@ -14772,7 +15147,7 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
14772
15147
|
node,
|
|
14773
15148
|
);
|
|
14774
15149
|
const sharedItemRange =
|
|
14775
|
-
!bindingSite && (markerlessItem || canShareSsrComponentItemRange(
|
|
15150
|
+
!bindingSite && (markerlessItem || canShareSsrComponentItemRange(itemBody, ctx));
|
|
14776
15151
|
const itemHtml =
|
|
14777
15152
|
sharedItemRange || bindingSite ? itemCall : ssrCall('ssrBlock', [itemCall], node);
|
|
14778
15153
|
let renderItem = itemNeedsIdentity
|
|
@@ -15103,12 +15478,15 @@ function ssrEmitTsrxExpression(node, ctx, name, inlinedSubs, parentNs, cssHash,
|
|
|
15103
15478
|
node,
|
|
15104
15479
|
);
|
|
15105
15480
|
}
|
|
15106
|
-
if (
|
|
15481
|
+
if (
|
|
15482
|
+
node.returnedJsxValue === true &&
|
|
15483
|
+
(isRangedReturnedHost(expr) || requiresTemplateNormalization(expr, 'html', true, ctx))
|
|
15484
|
+
) {
|
|
15107
15485
|
// A returned JSX value that contains template-only syntax is one compiled
|
|
15108
15486
|
// renderer on the client (rather than a descriptor array whose value
|
|
15109
15487
|
// lowering cannot represent directives, sentinels, head hoists, or child
|
|
15110
|
-
// code blocks)
|
|
15111
|
-
// sub-function keeps access to the
|
|
15488
|
+
// code blocks), as is an imperatively built host root. Mirror that component
|
|
15489
|
+
// boundary on the server: the local sub-function keeps access to the
|
|
15112
15490
|
// enclosing return component's props/locals, while ssrComponent emits the
|
|
15113
15491
|
// range the client descriptor adopts during hydration.
|
|
15114
15492
|
// The final flag mirrors extractFragment's component-child decision inside
|
|
@@ -15136,7 +15514,10 @@ function ssrEmitTsrxExpression(node, ctx, name, inlinedSubs, parentNs, cssHash,
|
|
|
15136
15514
|
b.literal(false),
|
|
15137
15515
|
undefinedNode(),
|
|
15138
15516
|
undefinedNode(),
|
|
15139
|
-
|
|
15517
|
+
// Hash the authored JSX, as the client's lowerHostFragment does, not
|
|
15518
|
+
// the synthetic wrapper: every signal instance below this renderer
|
|
15519
|
+
// chains its key from this site.
|
|
15520
|
+
b.literal(componentInvocationSite(ctx, expr)),
|
|
15140
15521
|
],
|
|
15141
15522
|
node,
|
|
15142
15523
|
);
|
|
@@ -15328,13 +15709,25 @@ function singleRootInitializer(ctx, component) {
|
|
|
15328
15709
|
return markPure(b.call('_$__s', component));
|
|
15329
15710
|
}
|
|
15330
15711
|
|
|
15712
|
+
function isOctaneMemoCallee(callee, ctx, memoImportNames) {
|
|
15713
|
+
if (callee?.type === 'Identifier') return memoImportNames.has(callee.name);
|
|
15714
|
+
return (
|
|
15715
|
+
callee?.type === 'MemberExpression' &&
|
|
15716
|
+
!callee.computed &&
|
|
15717
|
+
callee.object?.type === 'Identifier' &&
|
|
15718
|
+
callee.property?.type === 'Identifier' &&
|
|
15719
|
+
callee.property.name === 'memo' &&
|
|
15720
|
+
ctx.octaneImportNamespaces?.has(callee.object.name) === true
|
|
15721
|
+
);
|
|
15722
|
+
}
|
|
15723
|
+
|
|
15331
15724
|
// An exact public memo wrapper preserves the already-proven host output of its
|
|
15332
15725
|
// immutable local component. Stamp only the fresh compiler-owned wrapper:
|
|
15333
15726
|
// probing arbitrary component metadata would invoke observable getters, and
|
|
15334
15727
|
// dev/HMR, custom comparators, imported components, and renderer units remain
|
|
15335
15728
|
// deliberately opaque.
|
|
15336
15729
|
function markSingleRootMemoInitializers(node, ctx, memoImportNames) {
|
|
15337
|
-
if (ctx.hmr || ctx.dev || ctx.profile ||
|
|
15730
|
+
if (ctx.hmr || ctx.dev || ctx.profile || ctx.defaultMemoBindings.size === 0) return node;
|
|
15338
15731
|
const exported = node.type === 'ExportNamedDeclaration';
|
|
15339
15732
|
const declaration = exported ? node.declaration : node;
|
|
15340
15733
|
if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') return node;
|
|
@@ -15346,8 +15739,7 @@ function markSingleRootMemoInitializers(node, ctx, memoImportNames) {
|
|
|
15346
15739
|
item.id?.type !== 'Identifier' ||
|
|
15347
15740
|
!ctx.defaultMemoBindings.has(item.id.name) ||
|
|
15348
15741
|
init?.type !== 'CallExpression' ||
|
|
15349
|
-
init.callee
|
|
15350
|
-
!memoImportNames.has(init.callee.name) ||
|
|
15742
|
+
!isOctaneMemoCallee(init.callee, ctx, memoImportNames) ||
|
|
15351
15743
|
init.arguments.length !== 1 ||
|
|
15352
15744
|
wrapped?.type !== 'Identifier' ||
|
|
15353
15745
|
!ctx.moduleFunctionDeclarations.has(wrapped.name) ||
|
|
@@ -15533,11 +15925,15 @@ function compileComponent(node, ctx, options) {
|
|
|
15533
15925
|
// other inner compileFunctionBody calls leave their arrows untouched
|
|
15534
15926
|
// (they rarely declare arrow consts; if they do, the stability oracle
|
|
15535
15927
|
// would need to be redefined relative to the inner scope).
|
|
15536
|
-
fnNode =
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15928
|
+
fnNode = withDirectCallGuard(
|
|
15929
|
+
compileFunctionBody(node, ctx, name, 'opaque', cssHash, {
|
|
15930
|
+
autoCallback: true,
|
|
15931
|
+
localHookSlots: true,
|
|
15932
|
+
returnedOutput,
|
|
15933
|
+
}),
|
|
15934
|
+
node,
|
|
15935
|
+
ctx,
|
|
15936
|
+
);
|
|
15541
15937
|
} finally {
|
|
15542
15938
|
ctx.currentComponentLocals = prevLocals;
|
|
15543
15939
|
ctx.currentAutoMemoCallsitesSafe = prevAutoMemoCallsitesSafe;
|
|
@@ -15990,11 +16386,21 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
15990
16386
|
// Keep the old split + rewriteEarlyExits path for these.
|
|
15991
16387
|
let statements;
|
|
15992
16388
|
let jsxNodes;
|
|
16389
|
+
const previousComponentLocals = ctx.currentComponentLocals;
|
|
15993
16390
|
if (node.body && node.body.type === 'JSXCodeBlock') {
|
|
15994
16391
|
statements = node.body.body || [];
|
|
15995
16392
|
jsxNodes = node.body.render ? [node.body.render] : [];
|
|
15996
16393
|
} else {
|
|
15997
|
-
const bodyRewritten =
|
|
16394
|
+
const bodyRewritten = splitArmExits(unwrapOutputCodeBlock(node.body), ctx);
|
|
16395
|
+
// The split can declare a name of its own (a hooked tail's handoff), which
|
|
16396
|
+
// the arms nested in this body capture like any other local of it.
|
|
16397
|
+
if (previousComponentLocals) {
|
|
16398
|
+
const declared = new Set();
|
|
16399
|
+
for (const statement of bodyRewritten) collectStatementBindings(statement, declared);
|
|
16400
|
+
if ([...declared].some((name) => !previousComponentLocals.has(name))) {
|
|
16401
|
+
ctx.currentComponentLocals = new Set([...previousComponentLocals, ...declared]);
|
|
16402
|
+
}
|
|
16403
|
+
}
|
|
15998
16404
|
statements = [];
|
|
15999
16405
|
jsxNodes = [];
|
|
16000
16406
|
for (const child of bodyRewritten) {
|
|
@@ -16196,11 +16602,11 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
16196
16602
|
// Rewrite hook calls and `<tsrx>` blocks in statements before printing them.
|
|
16197
16603
|
// A `<tsrx>` block at expression position (e.g. `const f = <tsrx>...</tsrx>`)
|
|
16198
16604
|
// is hoisted as a render function in inlinedSubs and replaced with an
|
|
16199
|
-
// identifier reference.
|
|
16200
|
-
//
|
|
16605
|
+
// identifier reference. One that reads a setup callback's names stays for
|
|
16606
|
+
// rewriteJsxValues, which compiles it in place inside that callback.
|
|
16201
16607
|
const rewrittenStatements = preparedStatements
|
|
16202
16608
|
.map((s) => rewriteHookCalls(s, ctx, name, options?.localHookSlots === true))
|
|
16203
|
-
.map((s) => rewriteTsrxBlocks(s, ctx, name, inlinedSubs))
|
|
16609
|
+
.map((s) => rewriteTsrxBlocks(s, ctx, name, inlinedSubs, 'html', null, true))
|
|
16204
16610
|
// JSX component element at VALUE position in setup (e.g. `const el = <App/>`)
|
|
16205
16611
|
// → createElement(App, props). Output JSX (jsxNodes) was already split off.
|
|
16206
16612
|
.map((s) => rewriteJsxValues(s, ctx));
|
|
@@ -16535,11 +16941,10 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
16535
16941
|
);
|
|
16536
16942
|
presentationHostEnd++;
|
|
16537
16943
|
}
|
|
16538
|
-
// PROPS-FIRST convention: `(
|
|
16944
|
+
// PROPS-FIRST convention: `(props, __s, __extra)`. The scope is the 2nd arg
|
|
16539
16945
|
// (a placeholder leads when there are no user params), so a plain function
|
|
16540
16946
|
// `App(props)` binds `props`, while compiled bodies still read `__s` by name.
|
|
16541
|
-
const
|
|
16542
|
-
const fnParams = [...userParams, b.id('__s'), b.id('__extra')];
|
|
16947
|
+
const { params: fnParams, bindings: paramBindings } = compiledBodyParams(node.params ?? []);
|
|
16543
16948
|
ctx.currentAutoMemoOffset = prevAutoMemoOffset;
|
|
16544
16949
|
ctx.currentAutoMemoCacheName = prevAutoMemoCacheName;
|
|
16545
16950
|
ctx.currentAutoMemoCommittedName = prevAutoMemoCommittedName;
|
|
@@ -16548,6 +16953,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
16548
16953
|
ctx.currentHookMemoNames = prevHookMemoNames;
|
|
16549
16954
|
ctx.currentHookMemoOwnerSafe = prevHookMemoOwnerSafe;
|
|
16550
16955
|
ctx.currentMapTemps = prevMapTemps;
|
|
16956
|
+
ctx.currentComponentLocals = previousComponentLocals;
|
|
16551
16957
|
// ONE FunctionDeclaration node — the caller prints it (once, with the full
|
|
16552
16958
|
// esrap map for top-level components) or embeds it in an enclosing body.
|
|
16553
16959
|
const presentationBody = preparePresentationHydration(
|
|
@@ -16558,14 +16964,14 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
16558
16964
|
);
|
|
16559
16965
|
ctx.scalarBindingClaims = previousScalarBindingClaims;
|
|
16560
16966
|
ctx.presentationHydration = previousPresentationHydration;
|
|
16967
|
+
const scopedBody =
|
|
16968
|
+
ctx.nativeReads && !isStaticHostComponent(node)
|
|
16969
|
+
? wrapNativeReadScope(presentationBody, b.id('__s'), nativeReadNames(ctx))
|
|
16970
|
+
: presentationBody;
|
|
16561
16971
|
const emittedFunction = b.function_declaration(
|
|
16562
16972
|
b.id(name, node.id ?? node),
|
|
16563
16973
|
fnParams,
|
|
16564
|
-
b.block(
|
|
16565
|
-
ctx.nativeReads && !isStaticHostComponent(node)
|
|
16566
|
-
? wrapNativeReadScope(presentationBody, b.id('__s'), nativeReadNames(ctx))
|
|
16567
|
-
: presentationBody,
|
|
16568
|
-
),
|
|
16974
|
+
b.block([...paramBindings, ...scopedBody]),
|
|
16569
16975
|
);
|
|
16570
16976
|
return inheritOriginLoc(
|
|
16571
16977
|
hookMemoOpaqueOwner
|
|
@@ -19990,6 +20396,9 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
19990
20396
|
// their closure over setup locals/props — and only their values + the control
|
|
19991
20397
|
// expression are threaded into the renderer as `props.hN` holes.
|
|
19992
20398
|
const compInlinedSubs = [];
|
|
20399
|
+
// Set once a returned record needs a separate direct-call value.
|
|
20400
|
+
let renderCall = null;
|
|
20401
|
+
const returnedRecords = [];
|
|
19993
20402
|
// Fold a directive found at value position into a hoisted renderer owned by THIS
|
|
19994
20403
|
// body — the same fold the `@{}` body and the server emitter build.
|
|
19995
20404
|
const lowerBodyValueDirective =
|
|
@@ -20088,17 +20497,20 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
20088
20497
|
// represented render scope, not captured into fragment props while
|
|
20089
20498
|
// the factory runs. Reuse value lowering in both emitters so its
|
|
20090
20499
|
// public type/props and deferred children also stay inspectable.
|
|
20500
|
+
// Like other returned records, it reads variables when it renders.
|
|
20091
20501
|
if (ctx.nativeReads) {
|
|
20092
20502
|
return {
|
|
20093
20503
|
...h,
|
|
20094
|
-
argument:
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20100
|
-
|
|
20101
|
-
|
|
20504
|
+
argument: markDeferredLive(
|
|
20505
|
+
nativeReturnedJsxValue(
|
|
20506
|
+
h.argument,
|
|
20507
|
+
ctx,
|
|
20508
|
+
name,
|
|
20509
|
+
compInlinedSubs,
|
|
20510
|
+
cssHash,
|
|
20511
|
+
cssEntries,
|
|
20512
|
+
scoping.runtimeApplied,
|
|
20513
|
+
),
|
|
20102
20514
|
),
|
|
20103
20515
|
};
|
|
20104
20516
|
}
|
|
@@ -20121,7 +20533,33 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
20121
20533
|
};
|
|
20122
20534
|
}
|
|
20123
20535
|
}
|
|
20124
|
-
|
|
20536
|
+
const record = lowerReturnJsx(h.argument, ctx, compInlinedSubs, cssHash);
|
|
20537
|
+
if (node._octaneBindingView || !directCallNeedsClientBranch(h.argument, node, ctx)) {
|
|
20538
|
+
return { ...h, argument: record };
|
|
20539
|
+
}
|
|
20540
|
+
// A component's returned-JSX twin runs only for direct calls, so it
|
|
20541
|
+
// needs no Scope parameter and returns only the deferred value.
|
|
20542
|
+
renderCall ??= node._octaneDirectCallTwin
|
|
20543
|
+
? { params: node.params, scope: null }
|
|
20544
|
+
: renderCallScopeParam(node, ctx);
|
|
20545
|
+
// Built once the function's top-level bindings are all known.
|
|
20546
|
+
const placeholder = inheritOriginLoc(b.id('__octaneReturnedRecord'), h.argument);
|
|
20547
|
+
returnedRecords.push({
|
|
20548
|
+
placeholder,
|
|
20549
|
+
record,
|
|
20550
|
+
origin: h.argument,
|
|
20551
|
+
test:
|
|
20552
|
+
renderCall.scope === null
|
|
20553
|
+
? null
|
|
20554
|
+
: inheritOriginLoc(
|
|
20555
|
+
b.call(
|
|
20556
|
+
requireRuntimeForContext(ctx, 'isRenderCall'),
|
|
20557
|
+
cloneAstNode(renderCall.scope),
|
|
20558
|
+
),
|
|
20559
|
+
h.argument,
|
|
20560
|
+
),
|
|
20561
|
+
});
|
|
20562
|
+
return { ...h, argument: placeholder };
|
|
20125
20563
|
}
|
|
20126
20564
|
return rewriteJsxValues(h, ctx);
|
|
20127
20565
|
});
|
|
@@ -20133,6 +20571,20 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
20133
20571
|
ctx.currentAutoCalculatedRenderableRefs = prevAutoCalculatedRenderableRefs;
|
|
20134
20572
|
ctx.currentMapTemps = prevMapTemps;
|
|
20135
20573
|
}
|
|
20574
|
+
if (returnedRecords.length > 0) {
|
|
20575
|
+
const scopeNames = collectFunctionScopeBindings(
|
|
20576
|
+
renderCall.params,
|
|
20577
|
+
[...compInlinedSubs, ...newStatements],
|
|
20578
|
+
new Set(mapTemps),
|
|
20579
|
+
);
|
|
20580
|
+
const replacements = new Map(
|
|
20581
|
+
returnedRecords.map((entry) => [
|
|
20582
|
+
entry.placeholder,
|
|
20583
|
+
splitReturnedRecord(ctx, node, name, scopeNames, entry),
|
|
20584
|
+
]),
|
|
20585
|
+
);
|
|
20586
|
+
newStatements = mapAst(newStatements, (candidate) => replacements.get(candidate) ?? null);
|
|
20587
|
+
}
|
|
20136
20588
|
if (
|
|
20137
20589
|
ctx.autoMemo &&
|
|
20138
20590
|
ctx.privateUnescapedComponents.has(name) &&
|
|
@@ -20184,7 +20636,7 @@ function compileReturnJsxFunction(node, ctx, options) {
|
|
|
20184
20636
|
}
|
|
20185
20637
|
const emittedFunction = b.function_declaration(
|
|
20186
20638
|
node.id,
|
|
20187
|
-
node.params,
|
|
20639
|
+
renderCall?.params ?? node.params,
|
|
20188
20640
|
b.block(
|
|
20189
20641
|
ctx.nativeReads ? wrapNativeReadScope(returnBody, b.void0, nativeReadNames(ctx)) : returnBody,
|
|
20190
20642
|
),
|
|
@@ -20532,6 +20984,321 @@ function requiresTemplateNormalization(
|
|
|
20532
20984
|
);
|
|
20533
20985
|
}
|
|
20534
20986
|
|
|
20987
|
+
// A named return-JSX declaration is also a component. Its body call evaluates
|
|
20988
|
+
// the returned record in the scope that renders it. A direct call returns a JSX
|
|
20989
|
+
// value instead, which must resolve where it renders, like the value an arrow
|
|
20990
|
+
// function returns. Both emitters classify the same authored root: 'fragment'
|
|
20991
|
+
// defers the compiled fragment record, 'value' uses ordinary value lowering, and
|
|
20992
|
+
// null marks static host markup that renders the same anywhere. The server's
|
|
20993
|
+
// body call renders the whole tree to HTML, so every non-literal hole and every
|
|
20994
|
+
// component below the root needs deferral there.
|
|
20995
|
+
function directCallReturnKind(node, ctx, stableNames = null) {
|
|
20996
|
+
if (VALUE_DIRECTIVE_ARM_TYPES.has(node.type)) return 'fragment';
|
|
20997
|
+
if (requiresTemplateNormalization(node, 'html', true, ctx)) return 'fragment';
|
|
20998
|
+
if (node.type !== 'Element' && node.type !== 'JSXElement') {
|
|
20999
|
+
return jsxValueChildrenNeedRenderScope(node, false, stableNames) ? 'value' : null;
|
|
21000
|
+
}
|
|
21001
|
+
if (isComponentTag(node)) return 'value';
|
|
21002
|
+
return jsxValueRootNeedsRenderScope(node, stableNames) ||
|
|
21003
|
+
jsxValueChildrenNeedRenderScope(node, false, stableNames)
|
|
21004
|
+
? 'fragment'
|
|
21005
|
+
: null;
|
|
21006
|
+
}
|
|
21007
|
+
|
|
21008
|
+
// Whether the client's body-call record evaluates anything a direct call must
|
|
21009
|
+
// defer. The client record only stores hole values and interprets them where
|
|
21010
|
+
// it mounts, so reading an unreassigned local is the same during the call and
|
|
21011
|
+
// at render. Value-lowered roots already defer, except a component's own props.
|
|
21012
|
+
function directCallNeedsClientBranch(node, fn, ctx) {
|
|
21013
|
+
const stable = directCallStableNames(fn);
|
|
21014
|
+
const kind = directCallReturnKind(node, ctx, stable);
|
|
21015
|
+
if (kind === 'fragment') return true;
|
|
21016
|
+
return (
|
|
21017
|
+
kind === 'value' &&
|
|
21018
|
+
(node.type === 'Element' || node.type === 'JSXElement') &&
|
|
21019
|
+
jsxValueRootNeedsRenderScope(node, stable)
|
|
21020
|
+
);
|
|
21021
|
+
}
|
|
21022
|
+
|
|
21023
|
+
// Names that code anywhere in the function assigns after declaring them.
|
|
21024
|
+
const DIRECT_CALL_ASSIGNED_NAMES = new WeakMap();
|
|
21025
|
+
function directCallAssignedNames(fn) {
|
|
21026
|
+
const cached = DIRECT_CALL_ASSIGNED_NAMES.get(fn);
|
|
21027
|
+
if (cached !== undefined) return cached;
|
|
21028
|
+
const assigned = new Set();
|
|
21029
|
+
const target = (node) => {
|
|
21030
|
+
if (node?.type === 'Identifier') assigned.add(node.name);
|
|
21031
|
+
else if (node != null && typeof node === 'object') collectBindings(node, assigned);
|
|
21032
|
+
};
|
|
21033
|
+
mapAst(fn.body, (node) => {
|
|
21034
|
+
if (node.type === 'AssignmentExpression') target(node.left);
|
|
21035
|
+
else if (node.type === 'UpdateExpression') target(node.argument);
|
|
21036
|
+
else if (
|
|
21037
|
+
(node.type === 'ForInStatement' || node.type === 'ForOfStatement') &&
|
|
21038
|
+
node.left?.type !== 'VariableDeclaration'
|
|
21039
|
+
) {
|
|
21040
|
+
target(node.left);
|
|
21041
|
+
}
|
|
21042
|
+
return null;
|
|
21043
|
+
});
|
|
21044
|
+
DIRECT_CALL_ASSIGNED_NAMES.set(fn, assigned);
|
|
21045
|
+
return assigned;
|
|
21046
|
+
}
|
|
21047
|
+
|
|
21048
|
+
// Parameters and top-level declarations that nothing in the function assigns.
|
|
21049
|
+
// Reading one runs no user code and yields the same value during the call and
|
|
21050
|
+
// at render, so the client record needs no deferral for it.
|
|
21051
|
+
function directCallStableNames(fn) {
|
|
21052
|
+
const names = collectComponentLocals(fn);
|
|
21053
|
+
for (const name of directCallAssignedNames(fn)) names.delete(name);
|
|
21054
|
+
return names;
|
|
21055
|
+
}
|
|
21056
|
+
|
|
21057
|
+
// A direct call's deferred value reads the function's locals after the call has
|
|
21058
|
+
// returned. Declaring its closure inside the function would make those locals
|
|
21059
|
+
// context slots, allocating a context on every call, body calls included, so
|
|
21060
|
+
// the value is built by a module function that receives them as arguments.
|
|
21061
|
+
// Returns those arguments, or null when a copy could differ from the live
|
|
21062
|
+
// binding: a captured authored local that the function reassigns, or a read of
|
|
21063
|
+
// `this` or `arguments`.
|
|
21064
|
+
function directCallCaptures(fn, scopeNames, statements, value) {
|
|
21065
|
+
const body = b.block([...statements, b.return(value)]);
|
|
21066
|
+
let contextual = false;
|
|
21067
|
+
mapAst(body, (node) => {
|
|
21068
|
+
if (
|
|
21069
|
+
node.type === 'ThisExpression' ||
|
|
21070
|
+
node.type === 'Super' ||
|
|
21071
|
+
node.type === 'MetaProperty' ||
|
|
21072
|
+
(node.type === 'Identifier' && node.name === 'arguments')
|
|
21073
|
+
) {
|
|
21074
|
+
contextual = true;
|
|
21075
|
+
}
|
|
21076
|
+
return contextual ? node : null;
|
|
21077
|
+
});
|
|
21078
|
+
if (contextual) return null;
|
|
21079
|
+
const captures = [...collectFreeIdentifiers(body, new Set())]
|
|
21080
|
+
.filter((identifier) => scopeNames.has(identifier))
|
|
21081
|
+
.sort();
|
|
21082
|
+
const assigned = directCallAssignedNames(fn);
|
|
21083
|
+
return captures.some((identifier) => assigned.has(identifier)) ? null : captures;
|
|
21084
|
+
}
|
|
21085
|
+
|
|
21086
|
+
// Declare `function helper(captures) { ...statements; return value; }` among the
|
|
21087
|
+
// module's hoisted helpers and return a call to it.
|
|
21088
|
+
function hoistCapturingHelper(ctx, preferred, captures, statements, value, origin) {
|
|
21089
|
+
const helper = allocCompilerName(ctx, preferred);
|
|
21090
|
+
const ids = () => captures.map((identifier) => inheritOriginLoc(b.id(identifier), origin));
|
|
21091
|
+
ctx.hoistedHelpers.push(
|
|
21092
|
+
inheritOriginLoc(
|
|
21093
|
+
b.function_declaration(b.id(helper), ids(), b.block([...statements, b.return(value)])),
|
|
21094
|
+
origin,
|
|
21095
|
+
),
|
|
21096
|
+
);
|
|
21097
|
+
return inheritOriginLoc(b.call(helper, ...ids()), origin);
|
|
21098
|
+
}
|
|
21099
|
+
|
|
21100
|
+
// The client's returned record, split between a body call and a direct call.
|
|
21101
|
+
// The record is printed once, in a module function the body call invokes; a
|
|
21102
|
+
// direct call defers that same function until the value renders. Without a
|
|
21103
|
+
// body-call `test`, the function only ever returns the direct call's value.
|
|
21104
|
+
function splitReturnedRecord(ctx, fn, name, scopeNames, { record, test, origin }) {
|
|
21105
|
+
const captures = directCallCaptures(fn, scopeNames, [], record);
|
|
21106
|
+
if (captures === null) {
|
|
21107
|
+
ctx.runtimeNeeded.add('createScopedValue');
|
|
21108
|
+
// A direct call's record reads bindings when it renders, as the server's does.
|
|
21109
|
+
const direct = inheritOriginLoc(
|
|
21110
|
+
b.call(
|
|
21111
|
+
rtAlias('createScopedValue'),
|
|
21112
|
+
b.arrow([], markDeferredLive(test === null ? record : cloneAstNode(record))),
|
|
21113
|
+
),
|
|
21114
|
+
origin,
|
|
21115
|
+
);
|
|
21116
|
+
return test === null ? direct : inheritOriginLoc(b.conditional(test, record, direct), origin);
|
|
21117
|
+
}
|
|
21118
|
+
const read = hoistCapturingHelper(ctx, `${name}$record`, captures, [], record, origin);
|
|
21119
|
+
const direct = inheritOriginLoc(
|
|
21120
|
+
b.call(
|
|
21121
|
+
requireRuntimeForContext(ctx, 'deferRecord'),
|
|
21122
|
+
b.id(read.callee.name),
|
|
21123
|
+
...read.arguments.map((argument) => cloneAstNode(argument)),
|
|
21124
|
+
),
|
|
21125
|
+
origin,
|
|
21126
|
+
);
|
|
21127
|
+
return test === null ? direct : inheritOriginLoc(b.conditional(test, read, direct), origin);
|
|
21128
|
+
}
|
|
21129
|
+
|
|
21130
|
+
// Names an emitted function body binds in its own scope: parameters, top-level
|
|
21131
|
+
// declarations, and `var` declarations in nested blocks.
|
|
21132
|
+
function collectFunctionScopeBindings(params, statements, into) {
|
|
21133
|
+
for (const param of params) collectBindings(param, into);
|
|
21134
|
+
const visit = (statement, topLevel) => {
|
|
21135
|
+
if (statement == null || typeof statement !== 'object') return;
|
|
21136
|
+
switch (statement.type) {
|
|
21137
|
+
case 'VariableDeclaration':
|
|
21138
|
+
if (topLevel || statement.kind === 'var') {
|
|
21139
|
+
for (const declaration of statement.declarations || []) {
|
|
21140
|
+
collectBindings(declaration.id, into);
|
|
21141
|
+
}
|
|
21142
|
+
}
|
|
21143
|
+
return;
|
|
21144
|
+
case 'FunctionDeclaration':
|
|
21145
|
+
case 'ClassDeclaration':
|
|
21146
|
+
if (topLevel && statement.id) into.add(statement.id.name);
|
|
21147
|
+
return;
|
|
21148
|
+
case 'BlockStatement':
|
|
21149
|
+
for (const child of statement.body || []) visit(child, false);
|
|
21150
|
+
return;
|
|
21151
|
+
case 'IfStatement':
|
|
21152
|
+
visit(statement.consequent, false);
|
|
21153
|
+
visit(statement.alternate, false);
|
|
21154
|
+
return;
|
|
21155
|
+
case 'ForStatement':
|
|
21156
|
+
visit(statement.init, false);
|
|
21157
|
+
visit(statement.body, false);
|
|
21158
|
+
return;
|
|
21159
|
+
case 'ForInStatement':
|
|
21160
|
+
case 'ForOfStatement':
|
|
21161
|
+
visit(statement.left, false);
|
|
21162
|
+
visit(statement.body, false);
|
|
21163
|
+
return;
|
|
21164
|
+
case 'WhileStatement':
|
|
21165
|
+
case 'DoWhileStatement':
|
|
21166
|
+
case 'LabeledStatement':
|
|
21167
|
+
visit(statement.body, false);
|
|
21168
|
+
return;
|
|
21169
|
+
case 'TryStatement':
|
|
21170
|
+
visit(statement.block, false);
|
|
21171
|
+
visit(statement.handler?.body, false);
|
|
21172
|
+
visit(statement.finalizer, false);
|
|
21173
|
+
return;
|
|
21174
|
+
case 'SwitchStatement':
|
|
21175
|
+
for (const clause of statement.cases || []) {
|
|
21176
|
+
for (const child of clause.consequent || []) visit(child, false);
|
|
21177
|
+
}
|
|
21178
|
+
return;
|
|
21179
|
+
default:
|
|
21180
|
+
return;
|
|
21181
|
+
}
|
|
21182
|
+
};
|
|
21183
|
+
for (const statement of statements) visit(statement, true);
|
|
21184
|
+
return into;
|
|
21185
|
+
}
|
|
21186
|
+
|
|
21187
|
+
// A compiled body's parameters. The runtime calls every body as
|
|
21188
|
+
// `body(props, scope, extra)`, so the body takes `(props, __s, __extra)`: its
|
|
21189
|
+
// first authored parameter, or a `__props` placeholder, then the scope and the
|
|
21190
|
+
// extra. Each later authored parameter is a leading `var` bound to the argument
|
|
21191
|
+
// at its authored position, so it holds what the returned-JSX form's parameter
|
|
21192
|
+
// holds: a direct call's own argument, and for a body call the scope, the extra,
|
|
21193
|
+
// and then nothing. A rest parameter binds every argument from its position
|
|
21194
|
+
// on. The slice names no global, which a module may shadow
|
|
21195
|
+
// (`import { Array } from 'effect'`). A TypeScript `this` parameter types the
|
|
21196
|
+
// receiver and takes no argument.
|
|
21197
|
+
function compiledBodyParams(authored) {
|
|
21198
|
+
const receiver = authored[0]?.type === 'Identifier' && authored[0].name === 'this' ? 1 : 0;
|
|
21199
|
+
const own = authored.slice(receiver);
|
|
21200
|
+
const first = own.length > 0 && own[0].type !== 'RestElement' ? own[0] : null;
|
|
21201
|
+
const params = [
|
|
21202
|
+
...authored.slice(0, receiver),
|
|
21203
|
+
first ?? b.id('__props'),
|
|
21204
|
+
b.id('__s'),
|
|
21205
|
+
b.id('__extra'),
|
|
21206
|
+
];
|
|
21207
|
+
const bindings = [];
|
|
21208
|
+
for (let i = first === null ? 0 : 1; i < own.length; i++) {
|
|
21209
|
+
const param = own[i];
|
|
21210
|
+
const argument = () =>
|
|
21211
|
+
i === 1
|
|
21212
|
+
? b.id('__s')
|
|
21213
|
+
: i === 2
|
|
21214
|
+
? b.id('__extra')
|
|
21215
|
+
: b.member(b.id('arguments'), b.literal(i), true);
|
|
21216
|
+
let binding;
|
|
21217
|
+
if (param.type === 'RestElement') {
|
|
21218
|
+
const slice = b.member(b.member(b.array([]), 'slice'), 'call');
|
|
21219
|
+
binding = b.declarator(
|
|
21220
|
+
param.argument,
|
|
21221
|
+
b.call(slice, b.id('arguments'), ...(i > 0 ? [b.literal(i)] : [])),
|
|
21222
|
+
);
|
|
21223
|
+
} else if (param.type === 'AssignmentPattern') {
|
|
21224
|
+
binding = b.declarator(
|
|
21225
|
+
param.left,
|
|
21226
|
+
b.conditional(b.binary('===', argument(), b.void0), param.right, argument()),
|
|
21227
|
+
);
|
|
21228
|
+
} else {
|
|
21229
|
+
binding = b.declarator(param, argument());
|
|
21230
|
+
}
|
|
21231
|
+
bindings.push({
|
|
21232
|
+
...inheritOriginLoc(b.declaration('var', [binding]), param),
|
|
21233
|
+
_octaneParamBinding: true,
|
|
21234
|
+
});
|
|
21235
|
+
}
|
|
21236
|
+
return { params, bindings };
|
|
21237
|
+
}
|
|
21238
|
+
|
|
21239
|
+
// Argument 1 of a function whose own parameters already reach it: the second
|
|
21240
|
+
// parameter when the function never reassigns it, `arguments[1]` otherwise.
|
|
21241
|
+
function secondArgument(fn) {
|
|
21242
|
+
const params = fn.params || [];
|
|
21243
|
+
const second = params[1]?.type === 'AssignmentPattern' ? params[1].left : params[1];
|
|
21244
|
+
return second?.type === 'Identifier' &&
|
|
21245
|
+
params[0]?.type !== 'RestElement' &&
|
|
21246
|
+
!(params[0]?.type === 'Identifier' && params[0].name === 'this') &&
|
|
21247
|
+
!directCallAssignedNames(fn).has(second.name)
|
|
21248
|
+
? b.id(second.name)
|
|
21249
|
+
: b.member(b.id('arguments'), b.literal(1), true);
|
|
21250
|
+
}
|
|
21251
|
+
|
|
21252
|
+
// The argument a body call receives its Scope in. A spare default parameter
|
|
21253
|
+
// keeps the function's `length` and avoids an `arguments` object; functions
|
|
21254
|
+
// that cannot take one read `arguments[1]`.
|
|
21255
|
+
function renderCallScopeParam(node, ctx) {
|
|
21256
|
+
const params = node.params || [];
|
|
21257
|
+
const statements = node.body?.body || [];
|
|
21258
|
+
const strictBody = statements.some(
|
|
21259
|
+
(statement) => statement.type === 'ExpressionStatement' && statement.directive === 'use strict',
|
|
21260
|
+
);
|
|
21261
|
+
if (
|
|
21262
|
+
params.length > 1 ||
|
|
21263
|
+
strictBody ||
|
|
21264
|
+
params.some(
|
|
21265
|
+
(param) =>
|
|
21266
|
+
param.type === 'RestElement' || (param.type === 'Identifier' && param.name === 'this'),
|
|
21267
|
+
)
|
|
21268
|
+
) {
|
|
21269
|
+
return { params, scope: inheritOriginLoc(secondArgument(node), node) };
|
|
21270
|
+
}
|
|
21271
|
+
const scope = inheritOriginLoc(b.id(allocCompilerName(ctx, '__renderScope')), node);
|
|
21272
|
+
const spare = (identifier) => inheritOriginLoc(b.assignment_pattern(identifier, b.void0), node);
|
|
21273
|
+
return {
|
|
21274
|
+
params:
|
|
21275
|
+
params.length === 0
|
|
21276
|
+
? [spare(inheritOriginLoc(b.id(allocCompilerName(ctx, '__props')), node)), spare(scope)]
|
|
21277
|
+
: [...params, spare(scope)],
|
|
21278
|
+
scope,
|
|
21279
|
+
};
|
|
21280
|
+
}
|
|
21281
|
+
|
|
21282
|
+
/**
|
|
21283
|
+
* A returned host root that keeps its fragment renderer's own range.
|
|
21284
|
+
*
|
|
21285
|
+
* The client renders every returned host root through a compiled fragment. That
|
|
21286
|
+
* fragment mounts markerless when the host is its one template element, so the
|
|
21287
|
+
* server inlines the host without a range. A host the client builds
|
|
21288
|
+
* imperatively (keyed, `noscript`, document, or parser-repaired) is a descriptor
|
|
21289
|
+
* hole inside the fragment instead, and the server frames that hole with its
|
|
21290
|
+
* own range. A markerless fragment would adopt that inner range as its own
|
|
21291
|
+
* during hydration, so both sides give this root the fragment range that
|
|
21292
|
+
* returned fragments keep.
|
|
21293
|
+
*/
|
|
21294
|
+
function isRangedReturnedHost(node) {
|
|
21295
|
+
return (
|
|
21296
|
+
(node.type === 'Element' || node.type === 'JSXElement') &&
|
|
21297
|
+
!isComponentTag(node) &&
|
|
21298
|
+
(node._octaneImperativeHost === true || alwaysImperativeHost(node))
|
|
21299
|
+
);
|
|
21300
|
+
}
|
|
21301
|
+
|
|
20535
21302
|
// Lower JSX at return position. Host roots always use the compiled-fragment
|
|
20536
21303
|
// path. Any other root whose subtree contains template-only syntax uses it too.
|
|
20537
21304
|
// Ordinary component/Fragment values retain descriptor lowering and its identity
|
|
@@ -20670,6 +21437,11 @@ function rewriteExtractedFragmentHole(expression, ctx, parentNs) {
|
|
|
20670
21437
|
}
|
|
20671
21438
|
const fold = ctx._foldCtx;
|
|
20672
21439
|
if (fold?.compInlinedSubs === undefined) return lowered;
|
|
21440
|
+
// The owner's body sits outside any callback the directive is written in, so a
|
|
21441
|
+
// sub-template reading that callback's names compiles in place instead. The
|
|
21442
|
+
// hole props are built at the call site, inside the callback, so its closure
|
|
21443
|
+
// sees them.
|
|
21444
|
+
if (readsCallbackScope(lowered, ctx._callbackScopeNames)) return rewriteJsxValues(lowered, ctx);
|
|
20673
21445
|
return rewriteTsrxBlocks(lowered, ctx, 'fragment', fold.compInlinedSubs, parentNs, fold.cssHash);
|
|
20674
21446
|
}
|
|
20675
21447
|
|
|
@@ -20741,7 +21513,18 @@ function isStaticReturnedFragmentComponent(node, ctx) {
|
|
|
20741
21513
|
// text, nested host elements/fragments) stays in the template; nested component
|
|
20742
21514
|
// children become renderable holes. The result is a self-contained fragment whose
|
|
20743
21515
|
// only inputs are its props — compilable as an ordinary renderer.
|
|
20744
|
-
|
|
21516
|
+
//
|
|
21517
|
+
// The renderer builds a keyed, `noscript`/document, or parser-repaired host as a
|
|
21518
|
+
// descriptor (isDescriptorBuiltHost), and a descriptor child must be a value: a
|
|
21519
|
+
// FoldedDirective or template-only component placeholder under it would be
|
|
21520
|
+
// dropped. Inside such a host (`inDescriptor`), directives, components, and child
|
|
21521
|
+
// `@{}` blocks lower to value holes here in the owning component, as a `@{}` body
|
|
21522
|
+
// and the server lower them. Misreading a template host as a descriptor host costs
|
|
21523
|
+
// only the template fast path; the reverse drops children.
|
|
21524
|
+
function extractFragment(node, ctx, holeProps, parentNs = 'html', inDescriptor = false) {
|
|
21525
|
+
const descriptor =
|
|
21526
|
+
inDescriptor ||
|
|
21527
|
+
(node.type === 'JSXElement' && isDescriptorBuiltHost(node, parentNs === 'svg', ctx));
|
|
20745
21528
|
const attrs = node.attributes || node.openingElement?.attributes || [];
|
|
20746
21529
|
const newAttrs = [];
|
|
20747
21530
|
const mergedFragmentSpread = isFragmentLongForm(node, ctx) && hasJsxSpreadAttribute(node);
|
|
@@ -20851,14 +21634,16 @@ function extractFragment(node, ctx, holeProps, parentNs = 'html') {
|
|
|
20851
21634
|
// Lower `{xs.map(item => <jsx key/>)}` children to a synthetic `@for`
|
|
20852
21635
|
// (ForOfStatement) up front so they take the directive fold path below
|
|
20853
21636
|
// (forBlock) instead of becoming a childSlot descriptor-array hole. Only when
|
|
20854
|
-
// we have a fold context (always true on the .tsx host-fragment path)
|
|
20855
|
-
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
21637
|
+
// we have a fold context (always true on the .tsx host-fragment path) and the
|
|
21638
|
+
// children are template children.
|
|
21639
|
+
const fragChildren =
|
|
21640
|
+
ctx._foldCtx && !descriptor
|
|
21641
|
+
? (node.children || []).map((child) =>
|
|
21642
|
+
child && child.type === 'JSXExpressionContainer'
|
|
21643
|
+
? mapCallToForOf(child.expression, ctx) || child
|
|
21644
|
+
: child,
|
|
21645
|
+
)
|
|
21646
|
+
: node.children || [];
|
|
20862
21647
|
const nodeTag = jsxTagName(node) || elementTagName(node);
|
|
20863
21648
|
const childNs =
|
|
20864
21649
|
typeof nodeTag === 'string' && !isComponentTag(node)
|
|
@@ -20908,9 +21693,12 @@ function extractFragment(node, ctx, holeProps, parentNs = 'html') {
|
|
|
20908
21693
|
newChildren.push({ ...child, expression: rendered });
|
|
20909
21694
|
}
|
|
20910
21695
|
} else if (t === 'Element' || t === 'JSXElement') {
|
|
20911
|
-
if (
|
|
21696
|
+
if (descriptor && !isComponentTag(child)) {
|
|
21697
|
+
newChildren.push(extractFragment(child, ctx, holeProps, childNs, true));
|
|
21698
|
+
} else if (!descriptor && isLongFormTemplateSentinel(child, childNs, true, ctx)) {
|
|
20912
21699
|
newChildren.push(extractFragment(child, ctx, holeProps, childNs));
|
|
20913
21700
|
} else if (
|
|
21701
|
+
!descriptor &&
|
|
20914
21702
|
isComponentTag(child) &&
|
|
20915
21703
|
(child._octaneBindingSite ||
|
|
20916
21704
|
(ctx._foldCtx?.templateComponentChildren === true &&
|
|
@@ -20955,7 +21743,24 @@ function extractFragment(node, ctx, holeProps, parentNs = 'html') {
|
|
|
20955
21743
|
// A fragment nested inside the returned fragment shares the hoisted
|
|
20956
21744
|
// renderer. Extract its dynamic values/directives too; leaving it raw would
|
|
20957
21745
|
// make authored outer locals resolve against the renderer's hole-props object.
|
|
20958
|
-
newChildren.push(extractFragment(child, ctx, holeProps, childNs));
|
|
21746
|
+
newChildren.push(extractFragment(child, ctx, holeProps, childNs, descriptor));
|
|
21747
|
+
} else if (descriptor && VALUE_DIRECTIVE_ARM_TYPES.has(t)) {
|
|
21748
|
+
// lowerJsxChild folds the directive into a renderer owned by this
|
|
21749
|
+
// component, the value a `@{}` body passes the same descriptor.
|
|
21750
|
+
const hn = `h${holeProps.length}`;
|
|
21751
|
+
holeProps.push(objectProp(hn, lowerJsxChild(child, ctx)));
|
|
21752
|
+
newChildren.push(b.jsx_expression_container(memberProps(hn, child)));
|
|
21753
|
+
} else if (descriptor && t === 'JSXCodeBlock') {
|
|
21754
|
+
// lowerJsxChild lowers the block as it does under a `@{}` body's descriptor:
|
|
21755
|
+
// a render-only block transparently, and a setup-bearing one through this
|
|
21756
|
+
// component's fold. The renderer must never fold it, since that would move
|
|
21757
|
+
// the block's setup away from the locals it closes over.
|
|
21758
|
+
const value = lowerJsxChild(child, ctx);
|
|
21759
|
+
if (value !== null) {
|
|
21760
|
+
const hn = `h${holeProps.length}`;
|
|
21761
|
+
holeProps.push(objectProp(hn, value));
|
|
21762
|
+
newChildren.push(b.jsx_expression_container(memberProps(hn, child)));
|
|
21763
|
+
}
|
|
20959
21764
|
} else if (t === 'JSXCodeBlock') {
|
|
20960
21765
|
const body = child.body || [];
|
|
20961
21766
|
if (body.length === 0) {
|
|
@@ -21389,11 +22194,16 @@ function lowerHostFragment(
|
|
|
21389
22194
|
node,
|
|
21390
22195
|
);
|
|
21391
22196
|
const renderer = compileFunctionBody(synthFn, ctx, fragName, parentNs, cssHash);
|
|
21392
|
-
if (
|
|
22197
|
+
if (
|
|
22198
|
+
(node.type === 'Element' || node.type === 'JSXElement') &&
|
|
22199
|
+
!isComponentTag(node) &&
|
|
22200
|
+
!isRangedReturnedHost(node)
|
|
22201
|
+
) {
|
|
21393
22202
|
// A host fragment is a SINGLE root element, so it can mount markerless (the
|
|
21394
22203
|
// element self-delimits) — matching `@{}`'s inline render exactly (no extra
|
|
21395
22204
|
// comment markers), which is required for byte-equal DOM when folding `@{}`.
|
|
21396
|
-
// A returned JSX fragment may have multiple roots and must retain its range
|
|
22205
|
+
// A returned JSX fragment may have multiple roots and must retain its range,
|
|
22206
|
+
// as must an imperatively built host (see isRangedReturnedHost).
|
|
21397
22207
|
ctx.hoistedHelpers.push(
|
|
21398
22208
|
inheritOriginLoc(
|
|
21399
22209
|
b.const(fragName, singleRootInitializer(ctx, functionExpressionFromDeclaration(renderer))),
|
|
@@ -21428,8 +22238,10 @@ function lowerHostFragment(
|
|
|
21428
22238
|
*
|
|
21429
22239
|
* In both cases the helper is added to `inlinedSubs` (visible in the
|
|
21430
22240
|
* surrounding component-body scope) so it captures the parent component's
|
|
21431
|
-
* locals via closure.
|
|
21432
|
-
*
|
|
22241
|
+
* locals via closure. The names a callback inside `node` binds are not in scope
|
|
22242
|
+
* there, so with `leaveCallbackReads` a sub-template that reads one is left in
|
|
22243
|
+
* place, for the rewriteJsxValues pass that must follow to compile inside that
|
|
22244
|
+
* callback.
|
|
21433
22245
|
*/
|
|
21434
22246
|
function rewriteTsrxBlocks(
|
|
21435
22247
|
node,
|
|
@@ -21438,8 +22250,15 @@ function rewriteTsrxBlocks(
|
|
|
21438
22250
|
inlinedSubs,
|
|
21439
22251
|
parentNs = 'html',
|
|
21440
22252
|
cssHash = null,
|
|
22253
|
+
leaveCallbackReads = false,
|
|
21441
22254
|
) {
|
|
21442
|
-
return
|
|
22255
|
+
return rewrite(node, null);
|
|
22256
|
+
|
|
22257
|
+
function rewrite(root, callbackScope) {
|
|
22258
|
+
return mapAst(root, (n) => visit(n, callbackScope));
|
|
22259
|
+
}
|
|
22260
|
+
|
|
22261
|
+
function visit(n, callbackScope) {
|
|
21443
22262
|
if (n.type === 'Tsrx' || n.type === 'Tsx') {
|
|
21444
22263
|
const helperName = `__tsrx$${ctx.nextHelperId++}`;
|
|
21445
22264
|
const fakeBody = {
|
|
@@ -21453,6 +22272,8 @@ function rewriteTsrxBlocks(
|
|
|
21453
22272
|
return inheritOriginLoc(b.id(helperName), n);
|
|
21454
22273
|
}
|
|
21455
22274
|
if (n.type === 'ArrowFunctionExpression' && n.body && n.body.type === 'JSXCodeBlock') {
|
|
22275
|
+
// The callback's names do not exist in the body the helper would join.
|
|
22276
|
+
if (readsCallbackScope(n, callbackScope)) return n;
|
|
21456
22277
|
// `() => @{ … }` — new sub-template form. Hoist as a regular component
|
|
21457
22278
|
// body so its body.body (setup) + body.render (JSX) feed back through
|
|
21458
22279
|
// the standard compileFunctionBody path.
|
|
@@ -21466,11 +22287,90 @@ function rewriteTsrxBlocks(
|
|
|
21466
22287
|
);
|
|
21467
22288
|
fakeBody.generator = n.generator;
|
|
21468
22289
|
if (n.returnType !== undefined) fakeBody.returnType = n.returnType;
|
|
21469
|
-
inlinedSubs.push(
|
|
22290
|
+
inlinedSubs.push(
|
|
22291
|
+
withNestedTemplateScope(fakeBody, ctx, () =>
|
|
22292
|
+
compileFunctionBody(fakeBody, ctx, helperName, parentNs, cssHash),
|
|
22293
|
+
),
|
|
22294
|
+
);
|
|
21470
22295
|
return inheritOriginLoc(b.id(helperName), n);
|
|
21471
22296
|
}
|
|
22297
|
+
if (leaveCallbackReads && isFunctionNode(n)) {
|
|
22298
|
+
const scope = new Set(callbackScope);
|
|
22299
|
+
for (const name of collectCallbackBindings(n)) scope.add(name);
|
|
22300
|
+
let out = n;
|
|
22301
|
+
for (const key in n) {
|
|
22302
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
22303
|
+
const child = n[key];
|
|
22304
|
+
if (child === null || typeof child !== 'object') continue;
|
|
22305
|
+
const mapped = rewrite(child, scope);
|
|
22306
|
+
if (mapped !== child) {
|
|
22307
|
+
if (out === n) out = { ...n };
|
|
22308
|
+
out[key] = mapped;
|
|
22309
|
+
}
|
|
22310
|
+
}
|
|
22311
|
+
return out;
|
|
22312
|
+
}
|
|
21472
22313
|
return null;
|
|
21473
|
-
}
|
|
22314
|
+
}
|
|
22315
|
+
}
|
|
22316
|
+
|
|
22317
|
+
/**
|
|
22318
|
+
* Compile a template function nested inside another body — a `() => @{ … }`
|
|
22319
|
+
* sub-template (including the scoped child a setup-bearing `@{ … }` child
|
|
22320
|
+
* lowers to) or a `function F() @{ … }` declared in a component — with its own
|
|
22321
|
+
* params and locals visible to the constructs inside it.
|
|
22322
|
+
*
|
|
22323
|
+
* The nested function closes over the enclosing body, but its `@if`/`@for`/
|
|
22324
|
+
* `@switch`/`@try` arms and lifted event bundles hoist to module scope and
|
|
22325
|
+
* receive what they read through an env tuple that `helperCaptures` builds
|
|
22326
|
+
* from `ctx.currentComponentLocals`. Every name visible at those call sites
|
|
22327
|
+
* must be in that set: the enclosing body's, which the function still reaches,
|
|
22328
|
+
* plus the function's own. A missing name is left out of the tuple and the
|
|
22329
|
+
* hoisted arm reads it as a free identifier.
|
|
22330
|
+
*
|
|
22331
|
+
* A name the function introduces can shadow an enclosing lifetime-invariant
|
|
22332
|
+
* binding with one that changes between renders, so it must not inherit that
|
|
22333
|
+
* proof. `compileFunctionBody` recomputes the nested body's own invariants.
|
|
22334
|
+
*
|
|
22335
|
+
* Inside a module-level callback (`untracked`), nothing tracks the names the
|
|
22336
|
+
* enclosing callbacks bind, so no env tuple can carry them. The whole nested
|
|
22337
|
+
* compile, including the templates nested in it, then runs with no component
|
|
22338
|
+
* context, which keeps every arm inline where it closes over those names.
|
|
22339
|
+
*/
|
|
22340
|
+
function withNestedTemplateScope(fn, ctx, compile, untracked = false) {
|
|
22341
|
+
const prevLocals = ctx.currentComponentLocals;
|
|
22342
|
+
const prevInvariantLocals = ctx.currentInvariantLocals;
|
|
22343
|
+
const prevEventInvariantLocals = ctx.currentEventInvariantLocals;
|
|
22344
|
+
const prevUntracked = ctx._untrackedScope;
|
|
22345
|
+
const introduced = collectComponentLocals(fn);
|
|
22346
|
+
if (prevLocals == null && (untracked || prevUntracked === true)) {
|
|
22347
|
+
ctx._untrackedScope = true;
|
|
22348
|
+
} else {
|
|
22349
|
+
const locals = new Set(prevLocals);
|
|
22350
|
+
for (const name of introduced) locals.add(name);
|
|
22351
|
+
ctx.currentComponentLocals = locals;
|
|
22352
|
+
}
|
|
22353
|
+
ctx.currentInvariantLocals = withoutShadowedNames(prevInvariantLocals, introduced);
|
|
22354
|
+
ctx.currentEventInvariantLocals = withoutShadowedNames(prevEventInvariantLocals, introduced);
|
|
22355
|
+
try {
|
|
22356
|
+
return compile();
|
|
22357
|
+
} finally {
|
|
22358
|
+
ctx._untrackedScope = prevUntracked;
|
|
22359
|
+
ctx.currentComponentLocals = prevLocals;
|
|
22360
|
+
ctx.currentInvariantLocals = prevInvariantLocals;
|
|
22361
|
+
ctx.currentEventInvariantLocals = prevEventInvariantLocals;
|
|
22362
|
+
}
|
|
22363
|
+
}
|
|
22364
|
+
|
|
22365
|
+
function withoutShadowedNames(names, introduced) {
|
|
22366
|
+
if (names == null || introduced.size === 0) return names;
|
|
22367
|
+
let out = names;
|
|
22368
|
+
for (const name of introduced) {
|
|
22369
|
+
if (!out.has(name)) continue;
|
|
22370
|
+
if (out === names) out = new Set(names);
|
|
22371
|
+
out.delete(name);
|
|
22372
|
+
}
|
|
22373
|
+
return out;
|
|
21474
22374
|
}
|
|
21475
22375
|
|
|
21476
22376
|
const SETUP_VALUE_DIRECTIVE_TYPES = new Set([
|
|
@@ -21485,7 +22385,7 @@ const SETUP_VALUE_DIRECTIVE_TYPES = new Set([
|
|
|
21485
22385
|
// `@{ … }` block is also folded as a setup value, but it is a sub-template rather
|
|
21486
22386
|
// than a set of arms — `rewriteTsrxBlocks` owns its expression-position handling,
|
|
21487
22387
|
// and the unowned-directive diagnostic's advice does not apply to it — so it is
|
|
21488
|
-
// deliberately absent here.
|
|
22388
|
+
// deliberately absent here. With no owning body, lowerJsxChild compiles it in place.
|
|
21489
22389
|
const VALUE_DIRECTIVE_ARM_TYPES = new Set([
|
|
21490
22390
|
'JSXIfExpression',
|
|
21491
22391
|
'JSXForExpression',
|
|
@@ -21841,18 +22741,18 @@ function rewriteJsxValues(node, ctx, eagerMapCallbackRoots = false, eagerMapCall
|
|
|
21841
22741
|
const t = n && n.type;
|
|
21842
22742
|
if (isFunctionNode(n) && n.body?.type === 'JSXCodeBlock') {
|
|
21843
22743
|
const name = n.id?.name ?? allocCompilerName(ctx, '__template');
|
|
21844
|
-
const previousLocals = ctx.currentComponentLocals;
|
|
21845
22744
|
// A nested body must not replace the enclosing component's warm plan.
|
|
21846
22745
|
const previousWarm = ctx._pendingWarm;
|
|
21847
|
-
|
|
22746
|
+
const compile = () =>
|
|
22747
|
+
ctx.mode === 'server'
|
|
22748
|
+
? ssrCompileBody(n, ctx, name, null, [], 'opaque')
|
|
22749
|
+
: compileFunctionBody(n, ctx, name, 'opaque');
|
|
21848
22750
|
try {
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
: compileFunctionBody(n, ctx, name, 'opaque');
|
|
22751
|
+
// A missing fold marks a module-level callback, whose names only this
|
|
22752
|
+
// function's closure can reach.
|
|
22753
|
+
const compiled = withNestedTemplateScope(n, ctx, compile, lower == null);
|
|
21853
22754
|
return functionExpressionFromDeclaration({ ...compiled, id: n.id ?? null }, n);
|
|
21854
22755
|
} finally {
|
|
21855
|
-
ctx.currentComponentLocals = previousLocals;
|
|
21856
22756
|
ctx._pendingWarm = previousWarm;
|
|
21857
22757
|
}
|
|
21858
22758
|
}
|
|
@@ -21926,10 +22826,13 @@ function rewriteJsxValues(node, ctx, eagerMapCallbackRoots = false, eagerMapCall
|
|
|
21926
22826
|
// arms beside values they cannot reach. Drop the fold for this subtree so
|
|
21927
22827
|
// re-entries below (an attribute value re-enters rewriteJsxValues, with no
|
|
21928
22828
|
// function node left in view) cannot pick it back up, and the directive
|
|
21929
|
-
// reaches the unowned diagnostic instead of folding into the wrong scope.
|
|
22829
|
+
// reaches the unowned diagnostic instead of folding into the wrong scope. A
|
|
22830
|
+
// `@{ … }` child block has no arms, so lowerJsxChild compiles it in place.
|
|
21930
22831
|
ctx._valueDirectiveLowering = null;
|
|
21931
22832
|
} else {
|
|
21932
|
-
|
|
22833
|
+
// Including names bound in the callback's nested blocks, which a fold
|
|
22834
|
+
// written in one of those blocks reads just the same.
|
|
22835
|
+
const introduced = collectCallbackBindings(n);
|
|
21933
22836
|
const extended = new Set(previousLocals);
|
|
21934
22837
|
for (const name of introduced) extended.add(name);
|
|
21935
22838
|
ctx.currentComponentLocals = extended;
|
|
@@ -22000,6 +22903,13 @@ function rewriteJsxValues(node, ctx, eagerMapCallbackRoots = false, eagerMapCall
|
|
|
22000
22903
|
function lowerInspectableJsxChild(child, ctx) {
|
|
22001
22904
|
const fold = ctx._valueDirectiveLowering;
|
|
22002
22905
|
if (fold == null) return lowerJsxChild(child, ctx);
|
|
22906
|
+
// A render-only @{} block is transparent grouping: lowerJsxChild unwraps it
|
|
22907
|
+
// to its render root. Letting lowerSetupValueDirectives fold it would produce
|
|
22908
|
+
// a component range on the server while the descriptor path emits the
|
|
22909
|
+
// unwrapped children on the client, breaking hydration.
|
|
22910
|
+
if (child && child.type === 'JSXCodeBlock' && (child.body?.length ?? 0) === 0) {
|
|
22911
|
+
return lowerJsxChild(child, ctx);
|
|
22912
|
+
}
|
|
22003
22913
|
const prepared = lowerSetupValueDirectives(child, fold);
|
|
22004
22914
|
const t = prepared && prepared.type;
|
|
22005
22915
|
if (
|
|
@@ -22039,6 +22949,19 @@ function lowerJsxChild(child, ctx) {
|
|
|
22039
22949
|
if (fold != null) return fold(child);
|
|
22040
22950
|
rejectUnownedValueDirective(child);
|
|
22041
22951
|
}
|
|
22952
|
+
if (t === 'JSXCodeBlock') {
|
|
22953
|
+
// A render-only block is transparent grouping, as under a template host.
|
|
22954
|
+
if ((child.body?.length ?? 0) === 0) {
|
|
22955
|
+
return child.render ? lowerJsxChild(child.render, ctx) : null;
|
|
22956
|
+
}
|
|
22957
|
+
// A setup-bearing or code-only block is its own render scope. The owning
|
|
22958
|
+
// body's fold compiles it into a renderer that closes over that body, the
|
|
22959
|
+
// value both targets build. With no owning body, as inside a module-level
|
|
22960
|
+
// callback, it compiles in place as the `() => @{ … }` child that
|
|
22961
|
+
// normalizeChildren makes of it, closing over the callback's params.
|
|
22962
|
+
const fold = ctx._valueDirectiveLowering;
|
|
22963
|
+
return fold != null ? fold(child) : rewriteJsxValues(childCodeBlockArrow(child), ctx);
|
|
22964
|
+
}
|
|
22042
22965
|
if (t === 'JSXFragment' || t === 'Fragment') {
|
|
22043
22966
|
const els = [];
|
|
22044
22967
|
for (const c of child.children || []) {
|
|
@@ -22066,7 +22989,7 @@ function lowerJsxChild(child, ctx) {
|
|
|
22066
22989
|
scopedElement,
|
|
22067
22990
|
inheritOriginLoc(b.id(rtAlias('Fragment')), child),
|
|
22068
22991
|
inheritOriginLoc(b.object([]), child),
|
|
22069
|
-
inheritOriginLoc(
|
|
22992
|
+
inheritOriginLoc(deferredRead(ctx, children), child),
|
|
22070
22993
|
),
|
|
22071
22994
|
child,
|
|
22072
22995
|
);
|
|
@@ -22096,18 +23019,19 @@ function jsxNameToExpr(name) {
|
|
|
22096
23019
|
// hit the TDZ, while a member, coercion, spread, or computed key can invoke
|
|
22097
23020
|
// user code without containing a call. Function expressions are values here;
|
|
22098
23021
|
// their bodies still run only when an event/render-prop consumer invokes them.
|
|
22099
|
-
function isStaticJsxValueExpression(expression) {
|
|
23022
|
+
function isStaticJsxValueExpression(expression, stableNames = null) {
|
|
22100
23023
|
const value = unwrapTsExpr(expression);
|
|
22101
23024
|
return (
|
|
22102
23025
|
isInvariantLiteral(value) ||
|
|
22103
23026
|
value?.type === 'ArrowFunctionExpression' ||
|
|
22104
|
-
value?.type === 'FunctionExpression'
|
|
23027
|
+
value?.type === 'FunctionExpression' ||
|
|
23028
|
+
(stableNames !== null && value?.type === 'Identifier' && stableNames.has(value.name))
|
|
22105
23029
|
);
|
|
22106
23030
|
}
|
|
22107
23031
|
|
|
22108
23032
|
// A root member tag, spread, or dynamic prop can invoke user code before its
|
|
22109
23033
|
// descriptor exists. Defer the complete record rather than just its children.
|
|
22110
|
-
function jsxValueRootNeedsRenderScope(node) {
|
|
23034
|
+
function jsxValueRootNeedsRenderScope(node, stableNames = null) {
|
|
22111
23035
|
const name = node.openingElement?.name || node.id;
|
|
22112
23036
|
if (
|
|
22113
23037
|
name?.type === 'MemberExpression' ||
|
|
@@ -22120,7 +23044,7 @@ function jsxValueRootNeedsRenderScope(node) {
|
|
|
22120
23044
|
if (attr.type === 'SpreadAttribute' || attr.type === 'JSXSpreadAttribute') return true;
|
|
22121
23045
|
if (attr.type !== 'Attribute' && attr.type !== 'JSXAttribute') continue;
|
|
22122
23046
|
if (attr.value?.type !== 'JSXExpressionContainer') continue;
|
|
22123
|
-
if (!isStaticJsxValueExpression(attr.value.expression)) return true;
|
|
23047
|
+
if (!isStaticJsxValueExpression(attr.value.expression, stableNames)) return true;
|
|
22124
23048
|
}
|
|
22125
23049
|
return false;
|
|
22126
23050
|
}
|
|
@@ -22129,21 +23053,27 @@ function jsxValueRootNeedsRenderScope(node) {
|
|
|
22129
23053
|
// syntax is effectful. Nested component/tag resolution and dynamic attributes
|
|
22130
23054
|
// belong to their represented parent, so they make that parent's children lazy
|
|
22131
23055
|
// even when the nested element has no expression children of its own.
|
|
22132
|
-
function jsxValueChildrenNeedRenderScope(
|
|
23056
|
+
function jsxValueChildrenNeedRenderScope(
|
|
23057
|
+
node,
|
|
23058
|
+
descendantElementsOwnChildren = false,
|
|
23059
|
+
stableNames = null,
|
|
23060
|
+
) {
|
|
22133
23061
|
for (const child of node.children || []) {
|
|
22134
23062
|
if (child == null || child.type === 'JSXText' || child.type === 'Text') continue;
|
|
22135
23063
|
if (child.type === 'JSXExpressionContainer') {
|
|
22136
23064
|
if (
|
|
22137
23065
|
child.expression &&
|
|
22138
23066
|
child.expression.type !== 'JSXEmptyExpression' &&
|
|
22139
|
-
!isStaticJsxValueExpression(child.expression)
|
|
23067
|
+
!isStaticJsxValueExpression(child.expression, stableNames)
|
|
22140
23068
|
) {
|
|
22141
23069
|
return true;
|
|
22142
23070
|
}
|
|
22143
23071
|
continue;
|
|
22144
23072
|
}
|
|
22145
23073
|
if (child.type === 'Fragment' || child.type === 'JSXFragment') {
|
|
22146
|
-
if (jsxValueChildrenNeedRenderScope(child, descendantElementsOwnChildren))
|
|
23074
|
+
if (jsxValueChildrenNeedRenderScope(child, descendantElementsOwnChildren, stableNames)) {
|
|
23075
|
+
return true;
|
|
23076
|
+
}
|
|
22147
23077
|
continue;
|
|
22148
23078
|
}
|
|
22149
23079
|
if (child.type !== 'Element' && child.type !== 'JSXElement') return true;
|
|
@@ -22152,11 +23082,16 @@ function jsxValueChildrenNeedRenderScope(node, descendantElementsOwnChildren = f
|
|
|
22152
23082
|
if (attr.type === 'SpreadAttribute' || attr.type === 'JSXSpreadAttribute') return true;
|
|
22153
23083
|
if (attr.type !== 'Attribute' && attr.type !== 'JSXAttribute') return true;
|
|
22154
23084
|
if (attr.value?.type !== 'JSXExpressionContainer') continue;
|
|
22155
|
-
if (!isStaticJsxValueExpression(attr.value.expression)) return true;
|
|
23085
|
+
if (!isStaticJsxValueExpression(attr.value.expression, stableNames)) return true;
|
|
22156
23086
|
}
|
|
22157
23087
|
// A fragment can retain its inspectable positional array when only a nested
|
|
22158
23088
|
// element's descendants are dynamic: that element already owns their scope.
|
|
22159
|
-
if (
|
|
23089
|
+
if (
|
|
23090
|
+
!descendantElementsOwnChildren &&
|
|
23091
|
+
jsxValueChildrenNeedRenderScope(child, false, stableNames)
|
|
23092
|
+
) {
|
|
23093
|
+
return true;
|
|
23094
|
+
}
|
|
22160
23095
|
}
|
|
22161
23096
|
return false;
|
|
22162
23097
|
}
|
|
@@ -22452,8 +23387,9 @@ function jsxElementToCreateElement(node, ctx, eagerRoot = false) {
|
|
|
22452
23387
|
const memoizedDescriptor = inheritOriginLoc(
|
|
22453
23388
|
b.call(
|
|
22454
23389
|
requireRuntimeForContext(ctx, ctx.nativeReads ? 'nativePuMemo' : 'useMemo'),
|
|
22455
|
-
|
|
22456
|
-
|
|
23390
|
+
// Runs synchronously inside readChildren, so it reads the same snapshots.
|
|
23391
|
+
deferredRead(
|
|
23392
|
+
ctx,
|
|
22457
23393
|
b.sequence([b.assignment('=', b.id(freshName), b.literal(true)), childrenValue]),
|
|
22458
23394
|
),
|
|
22459
23395
|
b.array([rows, ...(captures ?? []).map((name) => inheritOriginLoc(b.id(name), host))]),
|
|
@@ -22504,7 +23440,10 @@ function jsxElementToCreateElement(node, ctx, eagerRoot = false) {
|
|
|
22504
23440
|
const scopedElement = ctx.nativeReads
|
|
22505
23441
|
? requireRuntimeForContext(ctx, 'nativeCreateScopedElement')
|
|
22506
23442
|
: rtAlias('createScopedElement');
|
|
22507
|
-
const readChildren = inheritOriginLoc(
|
|
23443
|
+
const readChildren = inheritOriginLoc(
|
|
23444
|
+
deferredRead(ctx, memoizedChildrenBody ?? childrenValue),
|
|
23445
|
+
node,
|
|
23446
|
+
);
|
|
22508
23447
|
descriptor = inheritOriginLoc(
|
|
22509
23448
|
b.call(
|
|
22510
23449
|
scopedElement,
|
|
@@ -22538,11 +23477,19 @@ function jsxElementToCreateElement(node, ctx, eagerRoot = false) {
|
|
|
22538
23477
|
? requireRuntimeForContext(ctx, 'nativeCreateScopedValue')
|
|
22539
23478
|
: rtAlias('createScopedValue');
|
|
22540
23479
|
return inheritOriginLoc(
|
|
22541
|
-
b.call(scopedValue, inheritOriginLoc(
|
|
23480
|
+
b.call(scopedValue, inheritOriginLoc(deferredRead(ctx, descriptor), node)),
|
|
22542
23481
|
node,
|
|
22543
23482
|
);
|
|
22544
23483
|
}
|
|
22545
23484
|
|
|
23485
|
+
// A value-position JSX thunk: its body runs when Octane renders or inspects the
|
|
23486
|
+
// element. printNodeWithMap snapshots the reassigned bindings it reads, so it
|
|
23487
|
+
// sees their values from when the JSX evaluated (see deferred-snapshots.js).
|
|
23488
|
+
function deferredRead(ctx, body) {
|
|
23489
|
+
ctx.deferredReads = true;
|
|
23490
|
+
return markDeferredRead(b.arrow([], body));
|
|
23491
|
+
}
|
|
23492
|
+
|
|
22546
23493
|
// Short, unique, path-free slot description for non-HMR output: a djb2 hash of
|
|
22547
23494
|
// the module filename + the per-module hook index. The DESCRIPTION is
|
|
22548
23495
|
// load-bearing beyond debugging: resolveSlot/currentPathSlot (runtime) compose
|
|
@@ -23398,9 +24345,17 @@ function normalizeAuthoredJsxLiterals(ast) {
|
|
|
23398
24345
|
}
|
|
23399
24346
|
|
|
23400
24347
|
function requiresImperativeHostTree(root, ctx) {
|
|
24348
|
+
return (
|
|
24349
|
+
(ctx.mode !== 'server' && root._octaneImperativeHost === true) || alwaysImperativeHost(root)
|
|
24350
|
+
);
|
|
24351
|
+
}
|
|
24352
|
+
|
|
24353
|
+
// Hosts that both compile modes build imperatively. The server writes a
|
|
24354
|
+
// parser-repaired tree as a string, so repair cannot move its bindings there;
|
|
24355
|
+
// normalizeChildren frames it in the hole range the client hydrates instead.
|
|
24356
|
+
function alwaysImperativeHost(root) {
|
|
23401
24357
|
const tag = jsxTagName(root) || elementTagName(root);
|
|
23402
24358
|
return (
|
|
23403
|
-
(ctx.mode !== 'server' && root._octaneImperativeHost === true) ||
|
|
23404
24359
|
hasJsxAttribute(root, 'key') ||
|
|
23405
24360
|
tag === 'noscript' ||
|
|
23406
24361
|
tag === 'html' ||
|
|
@@ -23485,6 +24440,17 @@ function rewriteImperativeHeadElements(node, ctx, namespace = 'html', inNoscript
|
|
|
23485
24440
|
};
|
|
23486
24441
|
}
|
|
23487
24442
|
|
|
24443
|
+
// A JSXElement that normalizeChildren lowers with jsxElementToCreateElement
|
|
24444
|
+
// instead of the template compiler, so its whole subtree becomes descriptor
|
|
24445
|
+
// values. extractFragment uses the same test to keep that subtree's children in
|
|
24446
|
+
// value form.
|
|
24447
|
+
function isDescriptorBuiltHost(node, inSvg, ctx) {
|
|
24448
|
+
return (
|
|
24449
|
+
!isComponentTag(node) &&
|
|
24450
|
+
(hasJsxAttribute(node, 'key') || (!inSvg && requiresImperativeHostTree(node, ctx)))
|
|
24451
|
+
);
|
|
24452
|
+
}
|
|
24453
|
+
|
|
23488
24454
|
/**
|
|
23489
24455
|
* Normalize a list of JSX child nodes into the shapes the emitters consume:
|
|
23490
24456
|
* - Indentation-only JSXText / JSX comments (`{…}` empty containers) → dropped
|
|
@@ -23632,12 +24598,7 @@ function normalizeChildren(
|
|
|
23632
24598
|
// Keys need a reconciliation boundary. Parser-sensitive host trees need
|
|
23633
24599
|
// imperative construction so HTML repair cannot change binding paths.
|
|
23634
24600
|
// The shared descriptor path provides both without taxing ordinary templates.
|
|
23635
|
-
if (
|
|
23636
|
-
ctx &&
|
|
23637
|
-
allowImperative &&
|
|
23638
|
-
!isComponentTag(n) &&
|
|
23639
|
-
(hasJsxAttribute(n, 'key') || (!inSvg && requiresImperativeHostTree(n, ctx)))
|
|
23640
|
-
) {
|
|
24601
|
+
if (ctx && allowImperative && isDescriptorBuiltHost(n, inSvg, ctx)) {
|
|
23641
24602
|
out.push(
|
|
23642
24603
|
inheritOriginLoc(
|
|
23643
24604
|
{
|
|
@@ -23691,6 +24652,13 @@ function normalizeChildren(
|
|
|
23691
24652
|
// `unstable_Activity` starts with a lowercase letter but is a component
|
|
23692
24653
|
// when it resolves to the builtin. Keep that fact on this rare node only.
|
|
23693
24654
|
if (isActivityLongForm(n, ctx)) element.activityDescriptor = true;
|
|
24655
|
+
// The client builds a parser-repaired host through the descriptor hole
|
|
24656
|
+
// above. The server keeps its template string and nesting diagnostics,
|
|
24657
|
+
// but frames it as that hole's range: the browser's repair then stays
|
|
24658
|
+
// inside a range the client can adopt or discard, and later siblings
|
|
24659
|
+
// keep their positions.
|
|
24660
|
+
if (ctx?.mode === 'server' && allowImperative && !inSvg && n._octaneImperativeHost === true)
|
|
24661
|
+
element.parserRepairRange = true;
|
|
23694
24662
|
// Preserve @tsrx/core's raw-text script discriminator without changing
|
|
23695
24663
|
// the normalized object shape of every ordinary element.
|
|
23696
24664
|
if (elementTagName(element) === 'script' && typeof n.content === 'string') {
|
|
@@ -25756,7 +26724,10 @@ function planJsx(
|
|
|
25756
26724
|
// Const-seeded straight into the bag factory args — no mount statement.
|
|
25757
26725
|
if (cc.isChild && !noTemplate) {
|
|
25758
26726
|
bag.constField(`_chv$${cc.id}`, 'null');
|
|
25759
|
-
|
|
26727
|
+
// An only-child hole's first render must reach childTextHole even for
|
|
26728
|
+
// `undefined`: while hydrating, that call reconciles the host's server
|
|
26729
|
+
// children, which no other binding owns.
|
|
26730
|
+
bag.constField(`_chp$${cc.id}`, cc.onlyChildText ? 'unset' : 'undefined');
|
|
25760
26731
|
}
|
|
25761
26732
|
},
|
|
25762
26733
|
});
|
|
@@ -25801,15 +26772,23 @@ function planJsx(
|
|
|
25801
26772
|
);
|
|
25802
26773
|
}
|
|
25803
26774
|
// Const-seeded fields keep their registry strings until the factory call.
|
|
25804
|
-
//
|
|
25805
|
-
//
|
|
25806
|
-
//
|
|
26775
|
+
// An `unset` field starts at its private owning scope, which no rendered
|
|
26776
|
+
// value equals, so its first write still runs for null/undefined. For a
|
|
26777
|
+
// mixed-style scalar, the same identity lets its setter distinguish a fresh
|
|
26778
|
+
// mount from a preserved suspended retry.
|
|
25807
26779
|
const constArgNode = (expr) =>
|
|
25808
|
-
expr === 'null' ? b.literal(null) : expr === '
|
|
25809
|
-
const bagFieldValue = (f) =>
|
|
26780
|
+
expr === 'null' ? b.literal(null) : expr === 'unset' ? b.id('__s') : b.id(expr);
|
|
26781
|
+
const bagFieldValue = (f) =>
|
|
26782
|
+
f.hostVar !== null
|
|
26783
|
+
? b.id(f.hostVar)
|
|
26784
|
+
: f.constExpr !== null
|
|
26785
|
+
? constArgNode(f.constExpr)
|
|
26786
|
+
: b.id(f.local);
|
|
25810
26787
|
const rootArg = () => (single ? b.id('_root') : b.literal(null));
|
|
25811
26788
|
if (bag.fields.length <= BAG_FACTORY_MAX) {
|
|
25812
26789
|
ctx.runtimeNeeded.add(`bag${bag.fields.length}`);
|
|
26790
|
+
// The arity factory's object literal still declares every field, so an
|
|
26791
|
+
// omitted trailing `undefined` seed keeps the same bag shape.
|
|
25813
26792
|
mountLines.push(
|
|
25814
26793
|
inheritOriginLoc(
|
|
25815
26794
|
b.stmt(
|
|
@@ -25820,7 +26799,9 @@ function planJsx(
|
|
|
25820
26799
|
`_$bag${bag.fields.length}`,
|
|
25821
26800
|
b.id('__s'),
|
|
25822
26801
|
rootArg(),
|
|
25823
|
-
...
|
|
26802
|
+
...optionalCallArgs(
|
|
26803
|
+
...bag.fields.map((f) => (f.constExpr === 'undefined' ? null : bagFieldValue(f))),
|
|
26804
|
+
),
|
|
25824
26805
|
),
|
|
25825
26806
|
),
|
|
25826
26807
|
),
|
|
@@ -26461,10 +27442,12 @@ function planJsx(
|
|
|
26461
27442
|
cc.valueExpr,
|
|
26462
27443
|
b.literal(cc.signalSite),
|
|
26463
27444
|
childAnchor ?? b.literal(null),
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
27445
|
+
...optionalCallArgs(
|
|
27446
|
+
cc.anchorVar ? b.literal(true) : null,
|
|
27447
|
+
null,
|
|
27448
|
+
cc.coalesceRange ? b.literal(true) : null,
|
|
27449
|
+
cc.onlyChildText ? b.literal(true) : null,
|
|
27450
|
+
),
|
|
26468
27451
|
),
|
|
26469
27452
|
),
|
|
26470
27453
|
),
|
|
@@ -26662,15 +27645,13 @@ function planJsx(
|
|
|
26662
27645
|
);
|
|
26663
27646
|
const memoAnchor = anchorNodeFor(cc, 'compAnchor');
|
|
26664
27647
|
const trailing = liteMemo
|
|
26665
|
-
?
|
|
26666
|
-
:
|
|
26667
|
-
memoAnchor ?? undefinedNode(),
|
|
26668
|
-
undefinedNode(),
|
|
26669
|
-
cc.singleRoot ? b.literal(true) : undefinedNode(),
|
|
26670
|
-
cc.inheritRange ? b.literal(true) : undefinedNode(),
|
|
26671
|
-
undefinedNode(),
|
|
27648
|
+
? optionalCallArgs(b.literal(cc.invocationSite), memoAnchor)
|
|
27649
|
+
: optionalCallArgs(
|
|
26672
27650
|
b.literal(cc.invocationSite),
|
|
26673
|
-
|
|
27651
|
+
memoAnchor,
|
|
27652
|
+
cc.singleRoot ? b.literal(true) : null,
|
|
27653
|
+
cc.inheritRange ? b.literal(true) : null,
|
|
27654
|
+
);
|
|
26674
27655
|
const witnessMiss = cc.autoMemoWitnesses.length
|
|
26675
27656
|
? witnessMissChain(cc.autoMemoWitnesses)
|
|
26676
27657
|
: null;
|
|
@@ -26711,7 +27692,7 @@ function planJsx(
|
|
|
26711
27692
|
continue;
|
|
26712
27693
|
}
|
|
26713
27694
|
// M3 inherit-range: the sole comp-call root of a `@{}` body — the slot
|
|
26714
|
-
// BORROWS the enclosing block's marker range (
|
|
27695
|
+
// BORROWS the enclosing block's marker range (the `inherit` argument), so it
|
|
26715
27696
|
// mints nothing and the server skips the child's frame pair at the same
|
|
26716
27697
|
// site (ssrEmitComponent reads the same predicate). Supersedes lite (the
|
|
26717
27698
|
// borrow needs a real Block behind the slot) and singleRoot (the borrow
|
|
@@ -26722,7 +27703,6 @@ function planJsx(
|
|
|
26722
27703
|
if (cc.inheritRange) {
|
|
26723
27704
|
const componentHelper = cc.voidComponent ? '_$componentSlotVoid' : '_$componentSlot';
|
|
26724
27705
|
ctx.runtimeNeeded.add(cc.voidComponent ? 'componentSlotVoid' : 'componentSlot');
|
|
26725
|
-
const inheritAnchor = anchorNodeFor(cc, 'compAnchor') ?? undefinedNode();
|
|
26726
27706
|
pushAfterStmt(
|
|
26727
27707
|
cc.id,
|
|
26728
27708
|
org,
|
|
@@ -26734,12 +27714,12 @@ function planJsx(
|
|
|
26734
27714
|
hostExpr(),
|
|
26735
27715
|
cc.compNode,
|
|
26736
27716
|
cc.propsExpr,
|
|
26737
|
-
|
|
26738
|
-
|
|
26739
|
-
|
|
26740
|
-
|
|
26741
|
-
|
|
26742
|
-
|
|
27717
|
+
...optionalCallArgs(
|
|
27718
|
+
b.literal(cc.invocationSite),
|
|
27719
|
+
anchorNodeFor(cc, 'compAnchor'),
|
|
27720
|
+
null,
|
|
27721
|
+
b.literal(true),
|
|
27722
|
+
),
|
|
26743
27723
|
),
|
|
26744
27724
|
),
|
|
26745
27725
|
);
|
|
@@ -26766,8 +27746,7 @@ function planJsx(
|
|
|
26766
27746
|
hostExpr(),
|
|
26767
27747
|
cc.compNode,
|
|
26768
27748
|
cc.propsExpr,
|
|
26769
|
-
|
|
26770
|
-
b.literal(cc.invocationSite),
|
|
27749
|
+
...optionalCallArgs(b.literal(cc.invocationSite), liteAnchor),
|
|
26771
27750
|
),
|
|
26772
27751
|
),
|
|
26773
27752
|
);
|
|
@@ -26780,14 +27759,14 @@ function planJsx(
|
|
|
26780
27759
|
// unmount move the slot DOM along with the block; an element host with
|
|
26781
27760
|
// no in-template anchor can safely append).
|
|
26782
27761
|
const compAnchor = anchorNodeFor(cc, 'compAnchor');
|
|
26783
|
-
const trailing =
|
|
26784
|
-
compAnchor ?? undefinedNode(),
|
|
26785
|
-
cc.keyExpr ?? undefinedNode(),
|
|
26786
|
-
cc.singleRoot ? b.literal(true) : cc.maybeSingleRoot ? b.literal(2) : undefinedNode(),
|
|
26787
|
-
undefinedNode(),
|
|
26788
|
-
cc.keyExpr != null ? b.literal(true) : undefinedNode(),
|
|
27762
|
+
const trailing = optionalCallArgs(
|
|
26789
27763
|
b.literal(cc.invocationSite),
|
|
26790
|
-
|
|
27764
|
+
compAnchor,
|
|
27765
|
+
cc.singleRoot ? b.literal(true) : cc.maybeSingleRoot ? b.literal(2) : null,
|
|
27766
|
+
null,
|
|
27767
|
+
cc.keyExpr ?? null,
|
|
27768
|
+
cc.keyExpr != null ? b.literal(true) : null,
|
|
27769
|
+
);
|
|
26791
27770
|
let invocation = b.call(
|
|
26792
27771
|
componentHelper,
|
|
26793
27772
|
b.id('__s'),
|
|
@@ -26990,8 +27969,11 @@ const DEFERRABLE_MOUNT_KINDS = new Set([
|
|
|
26990
27969
|
// field names are object properties, so unlike locals a minifier can never
|
|
26991
27970
|
// shorten them; 1-char names are the shipped-bytes win. Each field is either
|
|
26992
27971
|
// LOCAL-backed (the mount path assigns a pre-declared `_mN` local; the runtime
|
|
26993
|
-
// bag factory receives it positionally)
|
|
26994
|
-
// seeds pass straight to the factory — no local, no mount statement)
|
|
27972
|
+
// bag factory receives it positionally), CONST-seeded (`null`/`undefined`
|
|
27973
|
+
// seeds pass straight to the factory — no local, no mount statement), or
|
|
27974
|
+
// HOST-backed (a DOM host already held by the mount block's own `const _root` /
|
|
27975
|
+
// `const _elN`, which ensureVar declares before any binding mount and so before
|
|
27976
|
+
// the factory call: the factory reads that const directly, with no alias local).
|
|
26995
27977
|
// Registration order = mount-write order = the factory's positional args =
|
|
26996
27978
|
// the letter sequence, so `_$bagN(s, root, v0, v1, …)` builds `{a: v0, b: v1,
|
|
26997
27979
|
// …}` with every field carrying its REAL mount value. `letter()` throws for a
|
|
@@ -27002,6 +27984,9 @@ const BAG_UC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
27002
27984
|
// Highest shared-factory arity (bag0..bag16 in the runtime); bigger bags fall
|
|
27003
27985
|
// back to `bagOf(__s, root, { … })` with an inline literal of real values.
|
|
27004
27986
|
const BAG_FACTORY_MAX = 16;
|
|
27987
|
+
// Hosts the mount block declares as top-level consts. `__block.parentNode` is a
|
|
27988
|
+
// property read, so it keeps an ordinary local.
|
|
27989
|
+
const BAG_CONST_HOST = /^(?:_root|_el\d+)$/;
|
|
27005
27990
|
function bagLetter(i) {
|
|
27006
27991
|
if (i < 26) return BAG_LC[i];
|
|
27007
27992
|
if (i < 52) return BAG_UC[i - 26];
|
|
@@ -27024,6 +28009,7 @@ function makeBag() {
|
|
|
27024
28009
|
name: bagLetter(i),
|
|
27025
28010
|
local: constExpr === undefined ? `_m${i}` : null,
|
|
27026
28011
|
constExpr: constExpr === undefined ? null : constExpr,
|
|
28012
|
+
hostVar: null,
|
|
27027
28013
|
};
|
|
27028
28014
|
fields.push(r);
|
|
27029
28015
|
byKey.set(key, r);
|
|
@@ -27031,16 +28017,34 @@ function makeBag() {
|
|
|
27031
28017
|
return r;
|
|
27032
28018
|
};
|
|
27033
28019
|
return {
|
|
27034
|
-
/**
|
|
27035
|
-
|
|
27036
|
-
|
|
28020
|
+
/**
|
|
28021
|
+
* Mount-time reference for `key` — the pre-declared local, or a host-backed
|
|
28022
|
+
* field's const (read-only: host() returned false, so nothing assigns it).
|
|
28023
|
+
*/
|
|
28024
|
+
local: (key) => {
|
|
28025
|
+
const r = reg(key, undefined);
|
|
28026
|
+
return r.hostVar ?? r.local;
|
|
28027
|
+
},
|
|
28028
|
+
/**
|
|
28029
|
+
* Register one immutable DOM-host field; aliases reuse its first mount write.
|
|
28030
|
+
* Returns true when the caller must emit `local(key) = host`.
|
|
28031
|
+
*/
|
|
27037
28032
|
host: (key, host) => {
|
|
27038
28033
|
const existing = byHost.get(host);
|
|
27039
28034
|
if (existing !== undefined) {
|
|
27040
28035
|
byKey.set(key, existing);
|
|
27041
28036
|
return false;
|
|
27042
28037
|
}
|
|
27043
|
-
|
|
28038
|
+
// Only a field this call creates can be host-backed: a key already read
|
|
28039
|
+
// through local() may have handed its `_mN` name to emitted code.
|
|
28040
|
+
const fresh = !byKey.has(key);
|
|
28041
|
+
const r = reg(key, undefined);
|
|
28042
|
+
byHost.set(host, r);
|
|
28043
|
+
if (fresh && BAG_CONST_HOST.test(host)) {
|
|
28044
|
+
r.local = null;
|
|
28045
|
+
r.hostVar = host;
|
|
28046
|
+
return false;
|
|
28047
|
+
}
|
|
27044
28048
|
return true;
|
|
27045
28049
|
},
|
|
27046
28050
|
/** Seed `key` with a constant expression (no local, no mount write). */
|
|
@@ -27078,6 +28082,19 @@ function tsrxExprNode(node, ctx, componentName, inlinedSubs, parentNs = 'html',
|
|
|
27078
28082
|
const undefinedNode = () => b.id('undefined');
|
|
27079
28083
|
const nullNode = () => b.literal(null);
|
|
27080
28084
|
|
|
28085
|
+
// Optional positional arguments of a runtime writer whose parameters default to
|
|
28086
|
+
// undefined. Pass `null` for an absent entry: trailing absent entries are
|
|
28087
|
+
// omitted and interior ones become explicit `undefined` placeholders. Only use
|
|
28088
|
+
// it for writers that never inspect `arguments.length` (bindSignalText does).
|
|
28089
|
+
// componentSlot, componentSlotVoid, and componentSlotLite order their tail after
|
|
28090
|
+
// `props` by how often a call site supplies it — (invocationSite, anchor,
|
|
28091
|
+
// singleRoot, inherit, key, hasKey) — so the common call keeps two of the six.
|
|
28092
|
+
function optionalCallArgs(...entries) {
|
|
28093
|
+
let end = entries.length;
|
|
28094
|
+
while (end > 0 && entries[end - 1] == null) end--;
|
|
28095
|
+
return entries.slice(0, end).map((entry) => entry ?? undefinedNode());
|
|
28096
|
+
}
|
|
28097
|
+
|
|
27081
28098
|
// Reference a compiled mount local / host var by NAME. ensureVar hands back
|
|
27082
28099
|
// `__block.parentNode` for bagless hosts — resolve the member chain; every
|
|
27083
28100
|
// other name (`_root`, `_el3`) is a plain identifier.
|
|
@@ -27263,7 +28280,7 @@ function emitDeferredMount(bind, elVar, bag) {
|
|
|
27263
28280
|
: bind.kind === 'style' || bind.kind === 'styleProperties'
|
|
27264
28281
|
? `_sty$${bind.id}`
|
|
27265
28282
|
: `_prev$${bind.id}`,
|
|
27266
|
-
bind.kind === 'styleProperty' || bind.kind === 'styleProperties' ? '
|
|
28283
|
+
bind.kind === 'styleProperty' || bind.kind === 'styleProperties' ? 'unset' : 'undefined',
|
|
27267
28284
|
);
|
|
27268
28285
|
if (bind.kind === 'styleProperties') {
|
|
27269
28286
|
if (bind.spread) bag.constField(`_styFull$${bind.id}`, 'undefined');
|
|
@@ -27297,22 +28314,22 @@ function directSignalBindingHelper(bind) {
|
|
|
27297
28314
|
function directSignalBindingArgs(bind, host, previous, value = bind.expr, previousValue) {
|
|
27298
28315
|
const common = [b.id('__s'), previous, host];
|
|
27299
28316
|
if (bind.kind === 'text' || bind.kind === 'textOnlyChild') {
|
|
28317
|
+
// (…, onlyChild, previousValue, seededText, bindingMarker). An ordinary
|
|
28318
|
+
// update passes only its raw-value cache: seededText matters only before
|
|
28319
|
+
// the first write. A binding-view marker stays on every call because
|
|
28320
|
+
// presentation hydration can replay a retry through the update path.
|
|
27300
28321
|
return [
|
|
27301
28322
|
...common,
|
|
27302
28323
|
value,
|
|
27303
28324
|
b.literal(bind.signalSite, JSON.stringify(bind.signalSite)),
|
|
27304
28325
|
b.literal(bind.kind === 'textOnlyChild'),
|
|
27305
|
-
...(
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
? [bind.seededText ? b.literal(1) : undefinedNode(), b.literal(bind.bindingMarker)]
|
|
27313
|
-
: bind.seededText
|
|
27314
|
-
? [b.literal(1)]
|
|
27315
|
-
: []),
|
|
28326
|
+
...optionalCallArgs(
|
|
28327
|
+
previousValue ?? null,
|
|
28328
|
+
bind.seededText && (previousValue === undefined || bind.bindingMarker)
|
|
28329
|
+
? b.literal(1)
|
|
28330
|
+
: null,
|
|
28331
|
+
bind.bindingMarker ? b.literal(bind.bindingMarker) : null,
|
|
28332
|
+
),
|
|
27316
28333
|
];
|
|
27317
28334
|
}
|
|
27318
28335
|
if (
|
|
@@ -29918,6 +30935,9 @@ function emitElementHtml(
|
|
|
29918
30935
|
}
|
|
29919
30936
|
|
|
29920
30937
|
let children = normalizeChildren(sourceChildren, childNs === 'svg', ctx, tag === 'noscript');
|
|
30938
|
+
// Before metadata lifting, so a `<title>` inside a textarea is rejected here
|
|
30939
|
+
// exactly as the server rejects it.
|
|
30940
|
+
const textareaParts = textareaTextParts(tag, hostNs, children, ctx);
|
|
29921
30941
|
// NESTED document metadata / Float resources are zero-DOM children: lift
|
|
29922
30942
|
// them to the enclosing plan's head list (mounted out-of-band by
|
|
29923
30943
|
// emitHeadClient — scope-owned metadata, global resources), mirroring the
|
|
@@ -29933,8 +30953,51 @@ function emitElementHtml(
|
|
|
29933
30953
|
}
|
|
29934
30954
|
if (hasNestedHoist) children = children.filter((n) => n.type !== 'HeadHoist');
|
|
29935
30955
|
}
|
|
29936
|
-
|
|
29937
|
-
|
|
30956
|
+
if (textareaParts !== null) {
|
|
30957
|
+
// Every child folds into one text binding (see textareaTextParts). It is a
|
|
30958
|
+
// direct signal binding when any hole may carry a handle: the runtime then
|
|
30959
|
+
// returns one derived handle over the parts.
|
|
30960
|
+
let signalCapable = false;
|
|
30961
|
+
for (const part of textareaParts) {
|
|
30962
|
+
if (part.kind === 'static') continue;
|
|
30963
|
+
if (canCarryDirectSignalHandle(part.expr)) signalCapable = true;
|
|
30964
|
+
else if (canCarryDirectSignalHandle(part.expr, false, true)) ctx.signalBindingsUsed = true;
|
|
30965
|
+
if (isDirectSignalHandleExpression(part.expr)) ctx.signalBindingsEager = true;
|
|
30966
|
+
}
|
|
30967
|
+
if (signalCapable) ctx.signalBindingsUsed = true;
|
|
30968
|
+
const expr = inheritOriginLoc(
|
|
30969
|
+
b.call(
|
|
30970
|
+
requireRuntimeForContext(ctx, 'textareaText'),
|
|
30971
|
+
...textareaTextArgs(textareaParts, (expression, text) =>
|
|
30972
|
+
text
|
|
30973
|
+
? resolveStyleExpr(expression, cssHash)
|
|
30974
|
+
: tsrxExprNode(
|
|
30975
|
+
resolveStyleExpr(rewriteChildHoleValue(expression, ctx), cssHash),
|
|
30976
|
+
ctx,
|
|
30977
|
+
componentName,
|
|
30978
|
+
inlinedSubs,
|
|
30979
|
+
childNs,
|
|
30980
|
+
cssHash,
|
|
30981
|
+
),
|
|
30982
|
+
),
|
|
30983
|
+
),
|
|
30984
|
+
node,
|
|
30985
|
+
);
|
|
30986
|
+
// A customized built-in's constructor may inspect children while cloned.
|
|
30987
|
+
const seededText = !directPropNames.has('is');
|
|
30988
|
+
bindings.push({
|
|
30989
|
+
id: bindings.length,
|
|
30990
|
+
kind: 'textOnlyChild',
|
|
30991
|
+
expr,
|
|
30992
|
+
path,
|
|
30993
|
+
seededText,
|
|
30994
|
+
...(signalCapable
|
|
30995
|
+
? { signalDirect: true, signalSite: directSignalSite(ctx, node, 'binding') }
|
|
30996
|
+
: {}),
|
|
30997
|
+
});
|
|
30998
|
+
if (seededText) appendTemplatePart(html, ' ', 'text');
|
|
30999
|
+
} else if (
|
|
31000
|
+
// Special case: a single Text child (only-child text fast path).
|
|
29938
31001
|
children.length === 1 &&
|
|
29939
31002
|
children[0].type === 'Text' &&
|
|
29940
31003
|
!children[0]._octaneBindingText &&
|
|
@@ -32003,77 +33066,100 @@ function keyedSelectionDepIndex(itemName, keyBody, subStmts, runtimeDepNames, ct
|
|
|
32003
33066
|
return runtimeDepNames.indexOf(selected.name);
|
|
32004
33067
|
}
|
|
32005
33068
|
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
//
|
|
32009
|
-
//
|
|
32010
|
-
//
|
|
33069
|
+
const isKeyAttribute = (attribute) => (attribute.name?.name || attribute.name) === 'key';
|
|
33070
|
+
|
|
33071
|
+
// The legacy row-key spelling: a valued `key` attribute on the first element of
|
|
33072
|
+
// an @for body. It takes precedence over a header key, as in @tsrx/core's React
|
|
33073
|
+
// target. The reconciler reads row keys before the body runs, so the key can
|
|
33074
|
+
// read the item, the index and names outside the loop, but nothing the body
|
|
33075
|
+
// declares: the hoisted key function would throw a ReferenceError at runtime or
|
|
33076
|
+
// silently read an outer binding of the same name.
|
|
33077
|
+
function forRowKeyAttribute(node, ctx) {
|
|
33078
|
+
const statements = node.body.body;
|
|
33079
|
+
const element = statements.find((n) => n.type === 'Element' || n.type === 'JSXElement');
|
|
33080
|
+
const attribute = (element?.attributes || element?.openingElement?.attributes || []).find(
|
|
33081
|
+
isKeyAttribute,
|
|
33082
|
+
);
|
|
33083
|
+
// A valueless `<li key>` or a comment-only `key={}` carries no expression and
|
|
33084
|
+
// falls through to the header key, the index, or the `x.id ?? x` default.
|
|
33085
|
+
if (attribute?.value == null) return null;
|
|
33086
|
+
const expression =
|
|
33087
|
+
attribute.value.type === 'JSXExpressionContainer'
|
|
33088
|
+
? attribute.value.expression
|
|
33089
|
+
: attribute.value;
|
|
33090
|
+
if (expression.type === 'JSXEmptyExpression') return null;
|
|
33091
|
+
// The keyed element is the row's only output node, so every other statement
|
|
33092
|
+
// is setup that runs in the row function.
|
|
33093
|
+
const setup = statements.filter((statement) => statement !== element);
|
|
33094
|
+
if (setup.length === 0) return expression;
|
|
33095
|
+
// Analyze the setup with the key placed after it, as the body sees it. Only
|
|
33096
|
+
// row declarations are in scope, so a key identifier that resolves to the
|
|
33097
|
+
// row block, or to the row function a `var` hoists into, reads one. A
|
|
33098
|
+
// binding the key declares itself, such as a callback parameter, resolves to
|
|
33099
|
+
// a scope of its own.
|
|
33100
|
+
const probe = b.stmt(expression);
|
|
33101
|
+
const lexical = createLexicalAnalysis(b.block([...setup, probe]));
|
|
33102
|
+
const rowScope = lexical.nodeScopes.get(probe);
|
|
33103
|
+
const visit = (child, parent, key) => {
|
|
33104
|
+
// Array holes, as in `[, row.id]`, arrive as null entries.
|
|
33105
|
+
if (!child || typeof child !== 'object') return;
|
|
33106
|
+
if (child.type === 'Identifier' && isIdentifierReference(child, parent, key, lexical)) {
|
|
33107
|
+
const binding = lexical.resolveBinding(lexical.nodeScopes.get(child) ?? rowScope, child.name);
|
|
33108
|
+
if (binding?.scope === rowScope || binding?.scope === lexical.rootScope) {
|
|
33109
|
+
const l = attribute.loc && attribute.loc.start;
|
|
33110
|
+
const at = l
|
|
33111
|
+
? ` (${ctx.mapSourceName ? ctx.mapSourceName + ':' : ''}${l.line}:${l.column})`
|
|
33112
|
+
: '';
|
|
33113
|
+
throw new Error(
|
|
33114
|
+
`The \`key\` attribute on this \`@for\` row reads \`${child.name}\`, which is declared ` +
|
|
33115
|
+
'inside the loop body. Row keys are computed before the body runs, so they can only ' +
|
|
33116
|
+
'read the item, its `index` binding, and names from outside the loop. Derive the key ' +
|
|
33117
|
+
`from the item in the loop header instead: \`@for (const item of items; key …)\`.${at}`,
|
|
33118
|
+
);
|
|
33119
|
+
}
|
|
33120
|
+
}
|
|
33121
|
+
forEachRuntimeAstChild(child, (grandchild, childKey) => visit(grandchild, child, childKey));
|
|
33122
|
+
};
|
|
33123
|
+
visit(expression, probe, 'expression');
|
|
33124
|
+
return expression;
|
|
33125
|
+
}
|
|
33126
|
+
|
|
33127
|
+
// A `key` on the only output root of an @for body names the row, never a
|
|
33128
|
+
// separate element: a valued key is the row key above, and the root cannot
|
|
33129
|
+
// change identity inside a row that shares it. Leaving the key on the root
|
|
33130
|
+
// would give it a boundary that only repeats the row key: an intrinsic root
|
|
33131
|
+
// lowers to a keyed descriptor instead of the native template, and a component
|
|
33132
|
+
// root loses the row memo and its shared single-root range. Remove it so the
|
|
33133
|
+
// row compiles exactly as the header spelling does. Like a header key, the
|
|
33134
|
+
// reconciler reads it; an input that changes while rows render takes effect at
|
|
33135
|
+
// the next reconcile. Keyed elements below the root keep their own boundaries.
|
|
32011
33136
|
function forItemTemplateBody(node, ctx) {
|
|
32012
33137
|
const body = node.body.body;
|
|
32013
|
-
if (ctx._universalRuntimeUnit != null
|
|
32014
|
-
|
|
32015
|
-
|
|
33138
|
+
if (ctx._universalRuntimeUnit != null) return body;
|
|
33139
|
+
let root = null;
|
|
33140
|
+
for (const statement of body) {
|
|
33141
|
+
if (!isJsxNode(statement)) continue;
|
|
33142
|
+
if (root !== null) return body;
|
|
33143
|
+
root = statement;
|
|
33144
|
+
}
|
|
33145
|
+
if (
|
|
33146
|
+
(root?.type !== 'Element' && root?.type !== 'JSXElement') ||
|
|
33147
|
+
(!isPlainHostRoot(root) && !isComponentTag(root)) ||
|
|
33148
|
+
isActivityLongForm(root, ctx) ||
|
|
33149
|
+
isFragmentLongForm(root, ctx)
|
|
33150
|
+
)
|
|
32016
33151
|
return body;
|
|
32017
33152
|
const attrs = root.attributes || root.openingElement?.attributes || [];
|
|
32018
|
-
|
|
32019
|
-
|
|
32020
|
-
|
|
32021
|
-
|
|
32022
|
-
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
const key = value.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
32029
|
-
if (!isDeferralSafeBundleArg(key)) return body;
|
|
32030
|
-
// Destructuring must not invoke defaults, computed reads or an iterator.
|
|
32031
|
-
if (!isAutoMemoPropsParam(node.left.declarations[0].id)) return body;
|
|
32032
|
-
function stableHost(host) {
|
|
32033
|
-
if (!isPlainHostRoot(host) || isActivityLongForm(host, ctx) || isFragmentLongForm(host, ctx))
|
|
32034
|
-
return false;
|
|
32035
|
-
const tag = host.id?.name ?? host.openingElement?.name?.name;
|
|
32036
|
-
if (tag.includes('-') || tag === 'script') return false;
|
|
32037
|
-
const attributes = host.attributes || host.openingElement?.attributes || [];
|
|
32038
|
-
if (
|
|
32039
|
-
attributes.some(
|
|
32040
|
-
(attr) =>
|
|
32041
|
-
attr.type === 'SpreadAttribute' ||
|
|
32042
|
-
attr.type === 'JSXSpreadAttribute' ||
|
|
32043
|
-
jsxAttrRawName(attr) === 'is' ||
|
|
32044
|
-
jsxAttrRawName(attr) === 'children',
|
|
32045
|
-
)
|
|
32046
|
-
)
|
|
32047
|
-
return false;
|
|
32048
|
-
// Inspect values so deferred event/ref callbacks remain eligible.
|
|
32049
|
-
const values = attributes.map((attr) => attr.value);
|
|
32050
|
-
if (containsRenderCall(values) || containsAutoMemoUnsafeStructure(values)) return false;
|
|
32051
|
-
for (const child of host.children || []) {
|
|
32052
|
-
if (child.type === 'JSXText') continue;
|
|
32053
|
-
if (child.type === 'Text' || child.type === 'JSXExpressionContainer') {
|
|
32054
|
-
const expr = child.expression;
|
|
32055
|
-
if (!expr || expr.type === 'JSXEmptyExpression') continue;
|
|
32056
|
-
if (
|
|
32057
|
-
!isKnownTextChildExpression(expr) ||
|
|
32058
|
-
containsRenderCall([expr]) ||
|
|
32059
|
-
containsAutoMemoUnsafeStructure([expr])
|
|
32060
|
-
)
|
|
32061
|
-
return false;
|
|
32062
|
-
} else if (!stableHost(child)) return false;
|
|
32063
|
-
}
|
|
32064
|
-
return true;
|
|
32065
|
-
}
|
|
32066
|
-
if (!stableHost(root)) return body;
|
|
32067
|
-
const kept = attrs.filter((_, index) => index !== keyIndex);
|
|
32068
|
-
return [
|
|
32069
|
-
root.openingElement
|
|
32070
|
-
? {
|
|
32071
|
-
...root,
|
|
32072
|
-
...(root.attributes === undefined ? {} : { attributes: kept }),
|
|
32073
|
-
openingElement: { ...root.openingElement, attributes: kept },
|
|
32074
|
-
}
|
|
32075
|
-
: { ...root, attributes: kept },
|
|
32076
|
-
];
|
|
33153
|
+
const kept = attrs.filter((attribute) => !isKeyAttribute(attribute));
|
|
33154
|
+
if (kept.length === attrs.length) return body;
|
|
33155
|
+
const unkeyed = root.openingElement
|
|
33156
|
+
? {
|
|
33157
|
+
...root,
|
|
33158
|
+
...(root.attributes === undefined ? {} : { attributes: kept }),
|
|
33159
|
+
openingElement: { ...root.openingElement, attributes: kept },
|
|
33160
|
+
}
|
|
33161
|
+
: { ...root, attributes: kept };
|
|
33162
|
+
return body.map((statement) => (statement === root ? unkeyed : statement));
|
|
32077
33163
|
}
|
|
32078
33164
|
|
|
32079
33165
|
function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
|
|
@@ -32128,26 +33214,10 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32128
33214
|
return inheritOriginLoc(b.arrow(params, keyExpr), keyExpr);
|
|
32129
33215
|
}
|
|
32130
33216
|
|
|
32131
|
-
let keyFn = null;
|
|
32132
33217
|
// New TSRX surfaces `key` on the JSXForExpression itself (read via `node.key`
|
|
32133
|
-
// below).
|
|
32134
|
-
|
|
32135
|
-
|
|
32136
|
-
// here when the body wasn't routed through normalizeChildren.
|
|
32137
|
-
const firstEl = node.body.body.find((n) => n.type === 'Element' || n.type === 'JSXElement');
|
|
32138
|
-
if (firstEl) {
|
|
32139
|
-
const keyAttr = (firstEl.attributes || firstEl.openingElement?.attributes || []).find(
|
|
32140
|
-
(a) => (a.name?.name || a.name) === 'key',
|
|
32141
|
-
);
|
|
32142
|
-
// A valueless `<li key>` carries no expression — skip it (mirroring
|
|
32143
|
-
// makeCompCall's null-value handling) and fall through to the header key /
|
|
32144
|
-
// index / `x.id ?? x` default instead of crashing on `keyAttr.value.type`.
|
|
32145
|
-
if (keyAttr && keyAttr.value != null) {
|
|
32146
|
-
const inner =
|
|
32147
|
-
keyAttr.value.type === 'JSXExpressionContainer' ? keyAttr.value.expression : keyAttr.value;
|
|
32148
|
-
keyFn = mkKeyFn(inner);
|
|
32149
|
-
}
|
|
32150
|
-
}
|
|
33218
|
+
// below). The legacy `<li key={…}>` attribute spelling takes precedence.
|
|
33219
|
+
const attributeKey = forRowKeyAttribute(node, ctx);
|
|
33220
|
+
let keyFn = attributeKey === null ? null : mkKeyFn(attributeKey);
|
|
32151
33221
|
if (!keyFn && node.key) {
|
|
32152
33222
|
keyFn = mkKeyFn(node.key);
|
|
32153
33223
|
}
|
|
@@ -32555,10 +33625,10 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32555
33625
|
// parent is laid out per child (e.g. <tbody> in js-framework-benchmark).
|
|
32556
33626
|
// In addition to a direct host root, accept only narrowly-proven sole
|
|
32557
33627
|
// component and host-vs-host conditional roots; all other shapes keep item
|
|
32558
|
-
// ranges.
|
|
33628
|
+
// ranges. An item that can exit early renders no root when it does.
|
|
32559
33629
|
let singleRoot = false;
|
|
32560
33630
|
let singleRootExpr = null;
|
|
32561
|
-
{
|
|
33631
|
+
if (!armMayExit(subStmts)) {
|
|
32562
33632
|
const jsxChildren = subStmts.filter((s) => isJsxNode(s));
|
|
32563
33633
|
if (jsxChildren.length === 1) {
|
|
32564
33634
|
const c = jsxChildren[0];
|
|
@@ -32567,7 +33637,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32567
33637
|
// row itself is the block-boundary host, no Comment markers needed).
|
|
32568
33638
|
if (isSingleHostIfRoot(c)) {
|
|
32569
33639
|
singleRoot = true;
|
|
32570
|
-
} else if (
|
|
33640
|
+
} else if (isSingleTemplateHost(c)) {
|
|
32571
33641
|
singleRoot = true;
|
|
32572
33642
|
} else if ((c.type === 'Element' || c.type === 'JSXElement') && isComponentTag(c)) {
|
|
32573
33643
|
// A sole component item can share the component's proven host root as
|
|
@@ -32607,7 +33677,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
32607
33677
|
singleRoot = false;
|
|
32608
33678
|
singleRootExpr = null;
|
|
32609
33679
|
}
|
|
32610
|
-
const ssrMarkerless = isSsrMarkerlessForItem(node);
|
|
33680
|
+
const ssrMarkerless = isSsrMarkerlessForItem(node, subStmts);
|
|
32611
33681
|
const hostRootTag = subStmts[0]?.id?.name ?? subStmts[0]?.openingElement?.name?.name;
|
|
32612
33682
|
const hostMountSafe =
|
|
32613
33683
|
ctx.autoMemo === true &&
|
|
@@ -32856,6 +33926,232 @@ function rewriteEarlyExits(body, allowExplicitNull = false) {
|
|
|
32856
33926
|
return out;
|
|
32857
33927
|
}
|
|
32858
33928
|
|
|
33929
|
+
/**
|
|
33930
|
+
* Split a directive arm (an @if/@else, @switch case, @try/@pending/@catch, or
|
|
33931
|
+
* @for/@empty body) into its guard-nested form. The arm renders its final node
|
|
33932
|
+
* unless it exits first, and an exit is `return;`, `return null;`, or a
|
|
33933
|
+
* `continue;` that targets the `@for`. Wherever the exit sits in the arm's
|
|
33934
|
+
* setup, it ends only that arm's output, exactly like the top-level guard
|
|
33935
|
+
* rewriteEarlyExits nests. An exit inside a setup statement sets a flag and
|
|
33936
|
+
* breaks out of that statement, and the flag becomes the guard:
|
|
33937
|
+
*
|
|
33938
|
+
* if (a) { if (b) return null; f(); } rest
|
|
33939
|
+
* ⇒
|
|
33940
|
+
* let __exit = false;
|
|
33941
|
+
* __exit: if (a) { if (b) { __exit = true; break __exit; } f(); }
|
|
33942
|
+
* if (!__exit) { rest }
|
|
33943
|
+
*
|
|
33944
|
+
* Jumps that target a loop, `switch`, or label inside the arm, and returns in
|
|
33945
|
+
* a nested function, stay JavaScript. Statements the split renders (directives
|
|
33946
|
+
* and JSX-bearing `if`/`for`/`try`) are arms themselves and lower their own
|
|
33947
|
+
* exits when they compile. Legacy synthetic bodies only: a real function body
|
|
33948
|
+
* returns through the generic value ABI.
|
|
33949
|
+
*/
|
|
33950
|
+
function splitArmExits(body, ctx) {
|
|
33951
|
+
const tail = hookedTailStart(body);
|
|
33952
|
+
if (tail !== -1) return retainTailHookState(body, tail, ctx);
|
|
33953
|
+
let out = null;
|
|
33954
|
+
for (let i = 0; i < body.length; i++) {
|
|
33955
|
+
const stmt = body[i];
|
|
33956
|
+
let lowered = stmt;
|
|
33957
|
+
if (!isEarlyExitIf(stmt, true) && !isJsxNode(stmt)) {
|
|
33958
|
+
// An unconditional exit: nothing after it runs.
|
|
33959
|
+
if (isEarlyExitStatement(stmt, true)) return rewriteEarlyExits(out ?? body.slice(0, i), true);
|
|
33960
|
+
let flag = null;
|
|
33961
|
+
const replaced = replaceArmExits(stmt, false, false, ctx, (exit) => {
|
|
33962
|
+
flag ??= allocCompilerName(ctx, '__exit');
|
|
33963
|
+
return inheritOriginLoc(
|
|
33964
|
+
b.block([
|
|
33965
|
+
b.stmt(b.assignment('=', b.id(flag), b.literal(true, 'true'))),
|
|
33966
|
+
{ type: 'BreakStatement', label: b.id(flag) },
|
|
33967
|
+
]),
|
|
33968
|
+
exit,
|
|
33969
|
+
);
|
|
33970
|
+
});
|
|
33971
|
+
if (flag !== null) {
|
|
33972
|
+
lowered = [
|
|
33973
|
+
inheritOriginLoc(b.let(b.id(flag), b.literal(false, 'false')), stmt),
|
|
33974
|
+
inheritOriginLoc(b.labeled(flag, replaced), stmt),
|
|
33975
|
+
inheritOriginLoc(b.if(b.id(flag), b.return(), null), stmt),
|
|
33976
|
+
];
|
|
33977
|
+
}
|
|
33978
|
+
}
|
|
33979
|
+
if (out === null && lowered !== stmt) out = body.slice(0, i);
|
|
33980
|
+
if (out !== null) Array.isArray(lowered) ? out.push(...lowered) : out.push(lowered);
|
|
33981
|
+
}
|
|
33982
|
+
return rewriteEarlyExits(out ?? body, true);
|
|
33983
|
+
}
|
|
33984
|
+
|
|
33985
|
+
/**
|
|
33986
|
+
* Keep the state of hooks declared after an early exit. A hook's state lives
|
|
33987
|
+
* as long as the component, `@for` row, or directive arm that calls it, and an
|
|
33988
|
+
* exit only skips the rest of one render, exactly like a plain `if` around the
|
|
33989
|
+
* hook, a custom hook that returns early, or a value-returning component. The
|
|
33990
|
+
* guard-nested form would give the tail its own arm scope, which unmounts, and
|
|
33991
|
+
* resets that state, whenever the exit is taken. So when a slot-keyed hook
|
|
33992
|
+
* follows the first exit, only the tail's output moves into the guarded arm.
|
|
33993
|
+
* Its setup stays in this body as ordinary guard-nested JavaScript and hands
|
|
33994
|
+
* the output the tail names it reads:
|
|
33995
|
+
*
|
|
33996
|
+
* if (x) return; const [s] = useState(0); <b>{s}</b>
|
|
33997
|
+
* ⇒
|
|
33998
|
+
* let __tail = false;
|
|
33999
|
+
* if (!x) { const [s] = useState(0); __tail = [s]; }
|
|
34000
|
+
* if (__tail) { const [s] = __tail; <b>{s}</b> }
|
|
34001
|
+
*
|
|
34002
|
+
* An effect the exit skips still disconnects and reconnects, like any effect
|
|
34003
|
+
* whose call site a completed render does not reach. `first` is the index
|
|
34004
|
+
* hookedTailStart found.
|
|
34005
|
+
*/
|
|
34006
|
+
function retainTailHookState(body, first, ctx) {
|
|
34007
|
+
const setup = [];
|
|
34008
|
+
const output = [];
|
|
34009
|
+
for (let i = first; i < body.length; i++) (isJsxNode(body[i]) ? output : setup).push(body[i]);
|
|
34010
|
+
const declared = new Set();
|
|
34011
|
+
for (const statement of setup) collectStatementBindings(statement, declared);
|
|
34012
|
+
const read = collectFreeIdentifiers(b.block(output), []);
|
|
34013
|
+
const names = [...declared].filter((name) => read.has(name)).sort();
|
|
34014
|
+
const tail = allocCompilerName(ctx, '__tail');
|
|
34015
|
+
const origin = body[first];
|
|
34016
|
+
const handoff = names.length === 0 ? b.literal(true, 'true') : b.array(names.map((n) => b.id(n)));
|
|
34017
|
+
const lowered = splitArmExits(
|
|
34018
|
+
[
|
|
34019
|
+
...body.slice(0, first),
|
|
34020
|
+
...setup,
|
|
34021
|
+
inheritOriginLoc(b.stmt(b.assignment('=', b.id(tail), handoff)), origin),
|
|
34022
|
+
],
|
|
34023
|
+
ctx,
|
|
34024
|
+
);
|
|
34025
|
+
const unpack =
|
|
34026
|
+
names.length === 0
|
|
34027
|
+
? []
|
|
34028
|
+
: [inheritOriginLoc(b.const(b.array_pattern(names.map((n) => b.id(n))), b.id(tail)), origin)];
|
|
34029
|
+
return [
|
|
34030
|
+
inheritOriginLoc(b.let(b.id(tail), b.literal(false, 'false')), origin),
|
|
34031
|
+
...lowered,
|
|
34032
|
+
inheritOriginLoc(b.if(b.id(tail), b.block([...unpack, ...output]), null), origin),
|
|
34033
|
+
];
|
|
34034
|
+
}
|
|
34035
|
+
|
|
34036
|
+
/**
|
|
34037
|
+
* Index of the first statement that can exit the body when a slot-keyed hook
|
|
34038
|
+
* call follows it in setup and output follows it too, or -1. Otherwise the
|
|
34039
|
+
* guard-nested form already leaves every hook on the body's own scope.
|
|
34040
|
+
*
|
|
34041
|
+
* The tail's output must also follow its last exit. An authored arm or `@{}`
|
|
34042
|
+
* body always ends in its one output node, but a statement list with output
|
|
34043
|
+
* ahead of a later exit renders that output under only the exits before it:
|
|
34044
|
+
* the guard-nested form expresses that, and one guarded output cannot.
|
|
34045
|
+
*/
|
|
34046
|
+
function hookedTailStart(body) {
|
|
34047
|
+
const first = body.findIndex(
|
|
34048
|
+
(statement) => !isJsxNode(statement) && ownsArmJump(statement, false, false),
|
|
34049
|
+
);
|
|
34050
|
+
if (first === -1) return -1;
|
|
34051
|
+
let output = false;
|
|
34052
|
+
let hook = false;
|
|
34053
|
+
for (let i = first; i < body.length; i++) {
|
|
34054
|
+
const statement = body[i];
|
|
34055
|
+
if (isJsxNode(statement)) {
|
|
34056
|
+
output = true;
|
|
34057
|
+
continue;
|
|
34058
|
+
}
|
|
34059
|
+
if (output && ownsArmJump(statement, false, false)) return -1;
|
|
34060
|
+
hook ||= containsHookCall(statement, true);
|
|
34061
|
+
}
|
|
34062
|
+
return output && hook ? first : -1;
|
|
34063
|
+
}
|
|
34064
|
+
|
|
34065
|
+
/**
|
|
34066
|
+
* Replace each exit a setup statement owns with `exit(node)`. A value return
|
|
34067
|
+
* and a `break` that targets the directive have no arm equivalent, so they are
|
|
34068
|
+
* compile errors instead of literal JavaScript that renders "null"/"undefined",
|
|
34069
|
+
* leaves stale DOM, or breaks out of a function.
|
|
34070
|
+
*/
|
|
34071
|
+
function replaceArmExits(node, loop, breakable, ctx, exit) {
|
|
34072
|
+
if (node === null || typeof node !== 'object') return node;
|
|
34073
|
+
if (Array.isArray(node)) {
|
|
34074
|
+
let out = null;
|
|
34075
|
+
for (let i = 0; i < node.length; i++) {
|
|
34076
|
+
const mapped = replaceArmExits(node[i], loop, breakable, ctx, exit);
|
|
34077
|
+
if (out === null && mapped !== node[i]) out = node.slice(0, i);
|
|
34078
|
+
if (out !== null) out.push(mapped);
|
|
34079
|
+
}
|
|
34080
|
+
return out ?? node;
|
|
34081
|
+
}
|
|
34082
|
+
const type = node.type;
|
|
34083
|
+
if (isArmJumpBoundary(type)) return node;
|
|
34084
|
+
const jump = armJump(node, loop, breakable);
|
|
34085
|
+
if (jump === 'exit') return exit(node);
|
|
34086
|
+
if (jump === 'value') {
|
|
34087
|
+
throw armExitError(ctx, node, ARM_VALUE_RETURN_MESSAGE);
|
|
34088
|
+
}
|
|
34089
|
+
if (jump === 'break') {
|
|
34090
|
+
throw armExitError(ctx, node, ARM_BREAK_MESSAGE);
|
|
34091
|
+
}
|
|
34092
|
+
const innerLoop = loop || LOOP_TYPES.has(type);
|
|
34093
|
+
const innerBreakable = breakable || innerLoop || type === 'SwitchStatement';
|
|
34094
|
+
let out = null;
|
|
34095
|
+
for (const key in node) {
|
|
34096
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
34097
|
+
const child = node[key];
|
|
34098
|
+
if (child === null || typeof child !== 'object') continue;
|
|
34099
|
+
const mapped = replaceArmExits(child, innerLoop, innerBreakable, ctx, exit);
|
|
34100
|
+
if (mapped !== child) (out ??= { ...node })[key] = mapped;
|
|
34101
|
+
}
|
|
34102
|
+
return out ?? node;
|
|
34103
|
+
}
|
|
34104
|
+
|
|
34105
|
+
/**
|
|
34106
|
+
* Whether an arm's setup can end it before its output node, so the output is
|
|
34107
|
+
* conditional. Single-root and markerless proofs that pick an arm's output by
|
|
34108
|
+
* its JSX statements must reject such an arm: when the exit is taken there is
|
|
34109
|
+
* no host to serve as the boundary.
|
|
34110
|
+
*/
|
|
34111
|
+
function armMayExit(statements) {
|
|
34112
|
+
return statements.some(
|
|
34113
|
+
(statement) => !isJsxNode(statement) && ownsArmJump(statement, false, false),
|
|
34114
|
+
);
|
|
34115
|
+
}
|
|
34116
|
+
|
|
34117
|
+
function ownsArmJump(node, loop, breakable) {
|
|
34118
|
+
if (node === null || typeof node !== 'object') return false;
|
|
34119
|
+
if (Array.isArray(node)) return node.some((child) => ownsArmJump(child, loop, breakable));
|
|
34120
|
+
const type = node.type;
|
|
34121
|
+
if (isArmJumpBoundary(type)) return false;
|
|
34122
|
+
if (armJump(node, loop, breakable) !== null) return true;
|
|
34123
|
+
const innerLoop = loop || LOOP_TYPES.has(type);
|
|
34124
|
+
const innerBreakable = breakable || innerLoop || type === 'SwitchStatement';
|
|
34125
|
+
for (const key in node) {
|
|
34126
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
34127
|
+
const child = node[key];
|
|
34128
|
+
if (
|
|
34129
|
+
child !== null &&
|
|
34130
|
+
typeof child === 'object' &&
|
|
34131
|
+
ownsArmJump(child, innerLoop, innerBreakable)
|
|
34132
|
+
)
|
|
34133
|
+
return true;
|
|
34134
|
+
}
|
|
34135
|
+
return false;
|
|
34136
|
+
}
|
|
34137
|
+
|
|
34138
|
+
// Nested functions own their returns, and JSX and directives own their arms.
|
|
34139
|
+
function isArmJumpBoundary(type) {
|
|
34140
|
+
return (
|
|
34141
|
+
typeof type !== 'string' ||
|
|
34142
|
+
FN_TYPES.has(type) ||
|
|
34143
|
+
type === 'StaticBlock' ||
|
|
34144
|
+
JSX_CHILDREN_BEARING_TYPES.has(type) ||
|
|
34145
|
+
SETUP_VALUE_DIRECTIVE_TYPES.has(type)
|
|
34146
|
+
);
|
|
34147
|
+
}
|
|
34148
|
+
|
|
34149
|
+
function armExitError(ctx, node, message) {
|
|
34150
|
+
const l = node.loc && node.loc.start;
|
|
34151
|
+
const at = l ? ` (${ctx.mapSourceName ? ctx.mapSourceName + ':' : ''}${l.line}:${l.column})` : '';
|
|
34152
|
+
return new Error(message + at);
|
|
34153
|
+
}
|
|
34154
|
+
|
|
32859
34155
|
/**
|
|
32860
34156
|
* Turn compiler-proven shorthand guards into ordinary template control flow:
|
|
32861
34157
|
*
|
|
@@ -32870,6 +34166,9 @@ function rewriteEarlyExits(body, allowExplicitNull = false) {
|
|
|
32870
34166
|
function lowerNullishComponentExits(ast) {
|
|
32871
34167
|
const statements = ast.body || [];
|
|
32872
34168
|
let out = null;
|
|
34169
|
+
// Compiler-name allocation for retainTailHookState, before the compile
|
|
34170
|
+
// context exists. That context later collects these names from the AST.
|
|
34171
|
+
let names = null;
|
|
32873
34172
|
for (let i = 0; i < statements.length; i++) {
|
|
32874
34173
|
const statement = statements[i];
|
|
32875
34174
|
let replacement = statement;
|
|
@@ -32879,7 +34178,15 @@ function lowerNullishComponentExits(ast) {
|
|
|
32879
34178
|
if (hasOnlyLowerableNullishExits(node)) {
|
|
32880
34179
|
const body = node.body;
|
|
32881
34180
|
const sequence = [...(body.body || []), ...(body.render ? [body.render] : [])];
|
|
32882
|
-
const
|
|
34181
|
+
const tail = hookedTailStart(sequence);
|
|
34182
|
+
const rewritten =
|
|
34183
|
+
tail === -1
|
|
34184
|
+
? rewriteEarlyExits(sequence, true)
|
|
34185
|
+
: retainTailHookState(
|
|
34186
|
+
sequence,
|
|
34187
|
+
tail,
|
|
34188
|
+
(names ??= { usedCompilerNames: collectIdentifierNames(ast) }),
|
|
34189
|
+
);
|
|
32883
34190
|
const renderIndex = rewritten.findIndex(isJsxNode);
|
|
32884
34191
|
if (renderIndex !== -1 && !rewritten.slice(renderIndex + 1).some(isJsxNode)) {
|
|
32885
34192
|
const loweredNode = {
|
|
@@ -32963,13 +34270,16 @@ function jsxArmRoot(node) {
|
|
|
32963
34270
|
* separate scopes and excluded). Content that moves into an if/else ARM must
|
|
32964
34271
|
* not call hooks: on the value path a hook behind an early return keeps its
|
|
32965
34272
|
* slot-keyed state on the COMPONENT scope across branch flips, while an arm's
|
|
32966
|
-
* scope is torn down with the arm.
|
|
34273
|
+
* scope is torn down with the arm. `slotKeyed` skips `use` and `useContext`,
|
|
34274
|
+
* which are keyed by call order and context identity and hold no such state.
|
|
32967
34275
|
*/
|
|
32968
|
-
function containsHookCall(node) {
|
|
34276
|
+
function containsHookCall(node, slotKeyed = false) {
|
|
32969
34277
|
const seen = new WeakSet();
|
|
32970
34278
|
// The shared convention (HOOK_NAME_CONVENTION_RE) — covers `use`, `useX`,
|
|
32971
34279
|
// and the `unstable_`/`UNSTABLE_` prefixed forms.
|
|
32972
|
-
const isHookName =
|
|
34280
|
+
const isHookName = slotKeyed
|
|
34281
|
+
? (name) => name !== 'use' && name !== 'useContext' && isHookCalleeName(name)
|
|
34282
|
+
: isHookCalleeName;
|
|
32973
34283
|
const walk = (value) => {
|
|
32974
34284
|
if (value == null || typeof value !== 'object') return false;
|
|
32975
34285
|
if (Array.isArray(value)) {
|
|
@@ -33190,15 +34500,18 @@ function collectModuleLevelBindings(moduleBody) {
|
|
|
33190
34500
|
}
|
|
33191
34501
|
|
|
33192
34502
|
function collectOctaneComponentWrapperLocals(moduleBody) {
|
|
34503
|
+
return collectOctaneImportLocals(moduleBody, ['memo', 'lazy']);
|
|
34504
|
+
}
|
|
34505
|
+
|
|
34506
|
+
// Local names of the given `octane` named imports.
|
|
34507
|
+
function collectOctaneImportLocals(moduleBody, importedNames) {
|
|
33193
34508
|
const names = new Set();
|
|
33194
34509
|
for (const stmt of moduleBody) {
|
|
33195
34510
|
if (stmt.type !== 'ImportDeclaration' || stmt.source?.value !== 'octane') continue;
|
|
33196
34511
|
for (const spec of stmt.specifiers || []) {
|
|
33197
34512
|
if (spec.type !== 'ImportSpecifier') continue;
|
|
33198
34513
|
const imported = spec.imported?.name ?? spec.imported?.value;
|
|
33199
|
-
if ((imported
|
|
33200
|
-
names.add(spec.local.name);
|
|
33201
|
-
}
|
|
34514
|
+
if (importedNames.includes(imported) && spec.local?.name) names.add(spec.local.name);
|
|
33202
34515
|
}
|
|
33203
34516
|
}
|
|
33204
34517
|
return names;
|
|
@@ -33305,6 +34618,8 @@ function lowerJsxReturnBranchesOf(node, moduleBody, moduleAnalysis = null) {
|
|
|
33305
34618
|
return null;
|
|
33306
34619
|
}
|
|
33307
34620
|
if (!/^[A-Z]/.test(node.id.name)) return null;
|
|
34621
|
+
// A component's returned-JSX twin exists only for direct calls.
|
|
34622
|
+
if (node._octaneDirectCallTwin === true) return null;
|
|
33308
34623
|
const state = { failed: false, totalReturns: 0, branchReturn: false, jsxReturns: 0, roots: [] };
|
|
33309
34624
|
const result = armifyComponentReturnList(node.body.body || [], state, false);
|
|
33310
34625
|
if (result === null || state.failed) return null;
|
|
@@ -33375,6 +34690,295 @@ function lowerJsxReturnBranchComponents(ast) {
|
|
|
33375
34690
|
return out === null ? ast : { ...ast, body: out };
|
|
33376
34691
|
}
|
|
33377
34692
|
|
|
34693
|
+
/**
|
|
34694
|
+
* A `@{ … }` body is shorthand for returning JSX: `(v) => @{ …; <p /> }` means
|
|
34695
|
+
* `(v) => { …; return <p />; }`. A template function declared inside another
|
|
34696
|
+
* function, or written inline as an argument, normally compiles as a render
|
|
34697
|
+
* body instead, which receives its Scope as an argument and renders into it.
|
|
34698
|
+
* That is sound only while the runtime is the one calling it: a `{helper}`
|
|
34699
|
+
* child, a `<Helper />` tag, a render prop, or a portal body. A direct call
|
|
34700
|
+
* `helper(v)` passes no Scope, so the body reads `__s.slots` of undefined, and
|
|
34701
|
+
* `rows.map(helper)` passes the row index where the Scope would go.
|
|
34702
|
+
*
|
|
34703
|
+
* Lower such a function to its returned-JSX form, so a call returns a JSX value
|
|
34704
|
+
* exactly as the `=> <jsx>` form does on the client and the server, whenever
|
|
34705
|
+
* other code may call it:
|
|
34706
|
+
*
|
|
34707
|
+
* - a nested binding that code which can see it calls directly (`helper(v)`,
|
|
34708
|
+
* `helper.call(…)`), or passes to a call (`rows.map(helper)`, `run(helper)`);
|
|
34709
|
+
* - an inline function passed straight to a call or `new`, at any depth,
|
|
34710
|
+
* module scope included: `rows.map((row) => @{ <li /> })`.
|
|
34711
|
+
*
|
|
34712
|
+
* The runtime's own calls render the lowered value too, so this costs only the
|
|
34713
|
+
* template fast path. A call through a shadowing binding of the same name also
|
|
34714
|
+
* lowers the helper. The runtime itself renders the function arguments of
|
|
34715
|
+
* three calls, which keep that path: a `createPortal` body, matched by name as
|
|
34716
|
+
* the portal lowering matches it, and the component or render-function child
|
|
34717
|
+
* given to octane's `memo` or `createElement`.
|
|
34718
|
+
*
|
|
34719
|
+
* Module-level template bindings are components, which this pass leaves to
|
|
34720
|
+
* the component pipeline. Async and generator functions keep their bodies,
|
|
34721
|
+
* because their returned-JSX forms return a promise or an iterator instead of
|
|
34722
|
+
* JSX.
|
|
34723
|
+
*/
|
|
34724
|
+
function lowerDirectlyCalledTemplateFunctions(ast) {
|
|
34725
|
+
// Template function expressions, found anywhere.
|
|
34726
|
+
const expressions = new Set();
|
|
34727
|
+
// Nested template function node → [binding name, the function declaring it].
|
|
34728
|
+
const candidates = new Map();
|
|
34729
|
+
walkWithEnclosingFunctions(ast.body, (node, functions) => {
|
|
34730
|
+
const type = node.type;
|
|
34731
|
+
if (type === 'ArrowFunctionExpression' || type === 'FunctionExpression') {
|
|
34732
|
+
if (isLowerableTemplateFunction(node)) expressions.add(node);
|
|
34733
|
+
return;
|
|
34734
|
+
}
|
|
34735
|
+
if (functions.length === 0) return;
|
|
34736
|
+
let fn = null;
|
|
34737
|
+
let name;
|
|
34738
|
+
if (type === 'FunctionDeclaration') {
|
|
34739
|
+
fn = node;
|
|
34740
|
+
name = node.id?.name;
|
|
34741
|
+
} else if (type === 'VariableDeclarator' && node.id?.type === 'Identifier') {
|
|
34742
|
+
const init = unwrapTsExpr(node.init);
|
|
34743
|
+
if (init?.type === 'ArrowFunctionExpression' || init?.type === 'FunctionExpression') {
|
|
34744
|
+
fn = init;
|
|
34745
|
+
name = node.id.name;
|
|
34746
|
+
}
|
|
34747
|
+
}
|
|
34748
|
+
if (fn !== null && name !== undefined && isLowerableTemplateFunction(fn)) {
|
|
34749
|
+
candidates.set(fn, [name, functions[functions.length - 1]]);
|
|
34750
|
+
}
|
|
34751
|
+
});
|
|
34752
|
+
if (expressions.size === 0 && candidates.size === 0) return ast;
|
|
34753
|
+
|
|
34754
|
+
let renderingCallees = null;
|
|
34755
|
+
// True when the runtime, not the callee, renders the call's function arguments.
|
|
34756
|
+
const rendersArguments = (call) => {
|
|
34757
|
+
const callee = unwrapTsExpr(call.callee);
|
|
34758
|
+
if (callee?.type !== 'Identifier') return false;
|
|
34759
|
+
if (callee.name === 'createPortal') return true;
|
|
34760
|
+
renderingCallees ??= collectOctaneImportLocals(ast.body, ['memo', 'createElement']);
|
|
34761
|
+
return renderingCallees.has(callee.name);
|
|
34762
|
+
};
|
|
34763
|
+
const lowered = new Set();
|
|
34764
|
+
const names = new Set([...candidates.values()].map(([name]) => name));
|
|
34765
|
+
// Function node → candidate names that code inside it calls or passes on.
|
|
34766
|
+
const calledIn = new Map();
|
|
34767
|
+
const markCalled = (functions, name) => {
|
|
34768
|
+
for (const fn of functions) {
|
|
34769
|
+
let called = calledIn.get(fn);
|
|
34770
|
+
if (called === undefined) calledIn.set(fn, (called = new Set()));
|
|
34771
|
+
called.add(name);
|
|
34772
|
+
}
|
|
34773
|
+
};
|
|
34774
|
+
walkWithEnclosingFunctions(ast.body, (node, functions) => {
|
|
34775
|
+
const type = node.type;
|
|
34776
|
+
if (
|
|
34777
|
+
type !== 'CallExpression' &&
|
|
34778
|
+
type !== 'OptionalCallExpression' &&
|
|
34779
|
+
type !== 'NewExpression'
|
|
34780
|
+
) {
|
|
34781
|
+
return;
|
|
34782
|
+
}
|
|
34783
|
+
const name = directlyCalledName(node);
|
|
34784
|
+
if (name !== null && names.has(name)) markCalled(functions, name);
|
|
34785
|
+
// The callee calls a function passed to it, as `rows.map(fn)` does with
|
|
34786
|
+
// the row index where a Scope would go.
|
|
34787
|
+
const args = node.arguments;
|
|
34788
|
+
for (let i = 0; i < args.length; i++) {
|
|
34789
|
+
const arg = unwrapTsExpr(args[i]);
|
|
34790
|
+
if (expressions.has(arg)) {
|
|
34791
|
+
if (!rendersArguments(node)) lowered.add(arg);
|
|
34792
|
+
} else if (arg?.type === 'Identifier' && names.has(arg.name) && !rendersArguments(node)) {
|
|
34793
|
+
markCalled(functions, arg.name);
|
|
34794
|
+
}
|
|
34795
|
+
}
|
|
34796
|
+
});
|
|
34797
|
+
for (const [fn, [name, owner]] of candidates) {
|
|
34798
|
+
if (calledIn.get(owner)?.has(name)) lowered.add(fn);
|
|
34799
|
+
}
|
|
34800
|
+
if (lowered.size === 0) return ast;
|
|
34801
|
+
const lower = (node) =>
|
|
34802
|
+
mapAst(node, (n) => (lowered.has(n) ? returnedJsxFunction(n, lower) : null));
|
|
34803
|
+
return lower(ast);
|
|
34804
|
+
}
|
|
34805
|
+
|
|
34806
|
+
function isLowerableTemplateFunction(fn) {
|
|
34807
|
+
return fn.body?.type === 'JSXCodeBlock' && !fn.async && !fn.generator;
|
|
34808
|
+
}
|
|
34809
|
+
|
|
34810
|
+
/**
|
|
34811
|
+
* The name a call invokes as a function, or null. `helper(…)` and
|
|
34812
|
+
* `helper?.(…)` call it, and so do `helper.call(…)`, `helper.apply(…)`, and
|
|
34813
|
+
* `rows.map(helper)`, which passes the row index where a render body takes its
|
|
34814
|
+
* Scope. Optional calls are an `optional` CallExpression inside a
|
|
34815
|
+
* ChainExpression, or Babel's OptionalCallExpression and
|
|
34816
|
+
* OptionalMemberExpression.
|
|
34817
|
+
*/
|
|
34818
|
+
function directlyCalledName(node) {
|
|
34819
|
+
if (node.type !== 'CallExpression' && node.type !== 'OptionalCallExpression') return null;
|
|
34820
|
+
let callee = unwrapTsExpr(node.callee);
|
|
34821
|
+
if (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression') {
|
|
34822
|
+
const method = callee.computed ? callee.property?.value : callee.property?.name;
|
|
34823
|
+
callee =
|
|
34824
|
+
method === 'call' || method === 'apply'
|
|
34825
|
+
? unwrapTsExpr(callee.object)
|
|
34826
|
+
: method === 'map'
|
|
34827
|
+
? unwrapTsExpr(node.arguments[0])
|
|
34828
|
+
: null;
|
|
34829
|
+
}
|
|
34830
|
+
return callee?.type === 'Identifier' ? callee.name : null;
|
|
34831
|
+
}
|
|
34832
|
+
|
|
34833
|
+
/**
|
|
34834
|
+
* A `@{ … }` function rewritten to its returned-JSX form `{ setup; return
|
|
34835
|
+
* <jsx>; }`. `lower` maps the setup statements and the output first.
|
|
34836
|
+
*/
|
|
34837
|
+
function returnedJsxFunction(fn, lower) {
|
|
34838
|
+
const block = fn.body;
|
|
34839
|
+
const render = block.render;
|
|
34840
|
+
const statements = [...lower(block.body)];
|
|
34841
|
+
if (render != null) {
|
|
34842
|
+
// A directive output returns as a value-position directive.
|
|
34843
|
+
const value = lower(render);
|
|
34844
|
+
// `() => @{ <p /> }` is exactly `() => <p />`.
|
|
34845
|
+
if (statements.length === 0 && fn.type === 'ArrowFunctionExpression') {
|
|
34846
|
+
return { ...fn, body: value, expression: true };
|
|
34847
|
+
}
|
|
34848
|
+
statements.push(inheritOriginLoc(b.return(value), render));
|
|
34849
|
+
}
|
|
34850
|
+
const body = inheritOriginLoc(b.block(statements), block);
|
|
34851
|
+
return fn.type === 'ArrowFunctionExpression'
|
|
34852
|
+
? { ...fn, body, expression: false }
|
|
34853
|
+
: { ...fn, body };
|
|
34854
|
+
}
|
|
34855
|
+
|
|
34856
|
+
/**
|
|
34857
|
+
* A module-level `@{ … }` function is a component: rendering it as `<F />` or
|
|
34858
|
+
* as a `{F}` child calls its compiled body with the Scope it renders into. It is
|
|
34859
|
+
* also shorthand for `function F(v) { …; return <jsx>; }`, whose direct call
|
|
34860
|
+
* `F(v)` returns a JSX value, and a direct call passes no Scope.
|
|
34861
|
+
*
|
|
34862
|
+
* When code in the module calls the function directly, or hands it to `.map`,
|
|
34863
|
+
* declare that returned-JSX form beside it as `F$direct` and mark the
|
|
34864
|
+
* component. Its compiled body then starts with
|
|
34865
|
+
* `if (!isRenderCall(__s)) return F$direct(…)`, so body calls keep the
|
|
34866
|
+
* compiled template while a direct call runs the returned-JSX form: its setup
|
|
34867
|
+
* and hooks run in the caller, and its JSX resolves where the value renders.
|
|
34868
|
+
* The body has already bound the parameters, so the returned-JSX form takes
|
|
34869
|
+
* the names they bind.
|
|
34870
|
+
*
|
|
34871
|
+
* This runs before the passes that rewrite component bodies, because the
|
|
34872
|
+
* returned-JSX form keeps the authored returns: `if (!v) return null;` still
|
|
34873
|
+
* returns null from a direct call. A call through a shadowing binding of the
|
|
34874
|
+
* same name also marks the component, which costs only bytes.
|
|
34875
|
+
*/
|
|
34876
|
+
function splitDirectlyCalledComponents(ast) {
|
|
34877
|
+
const statements = ast.body || [];
|
|
34878
|
+
let names = null;
|
|
34879
|
+
for (const statement of statements) {
|
|
34880
|
+
const node = moduleStatementDeclaration(statement);
|
|
34881
|
+
if (isSplittableComponent(node)) (names ??= new Set()).add(node.id.name);
|
|
34882
|
+
}
|
|
34883
|
+
if (names === null) return ast;
|
|
34884
|
+
const called = new Set();
|
|
34885
|
+
walkWithEnclosingFunctions(statements, (node) => {
|
|
34886
|
+
const name = directlyCalledName(node);
|
|
34887
|
+
if (name !== null && names.has(name)) called.add(name);
|
|
34888
|
+
});
|
|
34889
|
+
if (called.size === 0) return ast;
|
|
34890
|
+
const used = collectIdentifierNames(statements);
|
|
34891
|
+
const out = [];
|
|
34892
|
+
for (const statement of statements) {
|
|
34893
|
+
const node = moduleStatementDeclaration(statement);
|
|
34894
|
+
if (!isSplittableComponent(node) || !called.has(node.id.name)) {
|
|
34895
|
+
out.push(statement);
|
|
34896
|
+
continue;
|
|
34897
|
+
}
|
|
34898
|
+
let twin = `${node.id.name}$direct`;
|
|
34899
|
+
while (used.has(twin)) twin += '$';
|
|
34900
|
+
used.add(twin);
|
|
34901
|
+
const bound = new Set();
|
|
34902
|
+
for (const param of node.params || []) {
|
|
34903
|
+
// A TypeScript `this` parameter declares a type, not an argument.
|
|
34904
|
+
if (param.type !== 'Identifier' || param.name !== 'this') collectBindings(param, bound);
|
|
34905
|
+
}
|
|
34906
|
+
const args = [...bound];
|
|
34907
|
+
const params = args.map((name) => inheritOriginLoc(b.id(name), node));
|
|
34908
|
+
// The two functions compile differently, so they must not share nodes:
|
|
34909
|
+
// module-wide walks skip a node they have already visited, and later
|
|
34910
|
+
// passes key facts by node identity.
|
|
34911
|
+
const returned = returnedJsxFunction(
|
|
34912
|
+
inheritOriginLoc(b.function_declaration(b.id(twin), params, cloneAstNode(node.body)), node),
|
|
34913
|
+
(value) => value,
|
|
34914
|
+
);
|
|
34915
|
+
out.push({ ...returned, _octaneDirectCallTwin: true });
|
|
34916
|
+
const marked = { ...node, _octaneDirectCall: { name: twin, args } };
|
|
34917
|
+
out.push(statement === node ? marked : { ...statement, declaration: marked });
|
|
34918
|
+
}
|
|
34919
|
+
return { ...ast, body: out };
|
|
34920
|
+
}
|
|
34921
|
+
|
|
34922
|
+
function moduleStatementDeclaration(statement) {
|
|
34923
|
+
return statement.type === 'ExportNamedDeclaration' ||
|
|
34924
|
+
statement.type === 'ExportDefaultDeclaration'
|
|
34925
|
+
? statement.declaration
|
|
34926
|
+
: statement;
|
|
34927
|
+
}
|
|
34928
|
+
|
|
34929
|
+
function isSplittableComponent(node) {
|
|
34930
|
+
return (
|
|
34931
|
+
isComponentFunction(node) && node.id != null && node.async !== true && node.generator !== true
|
|
34932
|
+
);
|
|
34933
|
+
}
|
|
34934
|
+
|
|
34935
|
+
// `if (!isRenderCall(__s)) return F$direct(…);` for a component that
|
|
34936
|
+
// splitDirectlyCalledComponents marked.
|
|
34937
|
+
function directCallGuard(node, ctx) {
|
|
34938
|
+
const { name, args } = node._octaneDirectCall;
|
|
34939
|
+
const call = b.call(name, ...args.map((arg) => inheritOriginLoc(b.id(arg), node)));
|
|
34940
|
+
const test = b.unary('!', b.call(requireRuntimeForContext(ctx, 'isRenderCall'), b.id('__s')));
|
|
34941
|
+
return inheritOriginLoc(b.if(test, inheritOriginLoc(b.return(call), node), null), node);
|
|
34942
|
+
}
|
|
34943
|
+
|
|
34944
|
+
function withDirectCallGuard(fn, node, ctx) {
|
|
34945
|
+
if (node._octaneDirectCall === undefined) return fn;
|
|
34946
|
+
// The guard passes the parameters on, so it follows the leading bindings of
|
|
34947
|
+
// those that compiledBodyParams binds from the arguments.
|
|
34948
|
+
const body = fn.body.body;
|
|
34949
|
+
let bound = 0;
|
|
34950
|
+
while (body[bound]?._octaneParamBinding === true) bound++;
|
|
34951
|
+
return {
|
|
34952
|
+
...fn,
|
|
34953
|
+
body: {
|
|
34954
|
+
...fn.body,
|
|
34955
|
+
body: [...body.slice(0, bound), directCallGuard(node, ctx), ...body.slice(bound)],
|
|
34956
|
+
},
|
|
34957
|
+
};
|
|
34958
|
+
}
|
|
34959
|
+
|
|
34960
|
+
/** Visit every node with the functions that enclose it, outermost first. */
|
|
34961
|
+
function walkWithEnclosingFunctions(root, visit) {
|
|
34962
|
+
const functions = [];
|
|
34963
|
+
const walk = (node) => {
|
|
34964
|
+
if (node == null || typeof node !== 'object') return;
|
|
34965
|
+
if (Array.isArray(node)) {
|
|
34966
|
+
for (const child of node) walk(child);
|
|
34967
|
+
return;
|
|
34968
|
+
}
|
|
34969
|
+
visit(node, functions);
|
|
34970
|
+
const isFunction = isFunctionNode(node);
|
|
34971
|
+
if (isFunction) functions.push(node);
|
|
34972
|
+
for (const key in node) {
|
|
34973
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
34974
|
+
const child = node[key];
|
|
34975
|
+
if (child !== null && typeof child === 'object') walk(child);
|
|
34976
|
+
}
|
|
34977
|
+
if (isFunction) functions.pop();
|
|
34978
|
+
};
|
|
34979
|
+
walk(root);
|
|
34980
|
+
}
|
|
34981
|
+
|
|
33378
34982
|
function isJsxNode(node) {
|
|
33379
34983
|
if (!node) return false;
|
|
33380
34984
|
if (isWrappedJsxDirective(node)) return true;
|
|
@@ -33384,12 +34988,16 @@ function isJsxNode(node) {
|
|
|
33384
34988
|
if (node.type === 'JSXElement' || node.type === 'JSXFragment') return true;
|
|
33385
34989
|
// New TSRX directive nodes — always JSX-position. normalizeChildren will
|
|
33386
34990
|
// lower them to IfStatement / ForOfStatement / TryStatement / SwitchStatement
|
|
33387
|
-
// when planJsx runs over them.
|
|
34991
|
+
// when planJsx runs over them. A child `@{ … }` block in a statement list is
|
|
34992
|
+
// a directive arm's output node, never setup: normalizeChildren makes a
|
|
34993
|
+
// render-only block transparent and gives a setup-bearing one its own scope,
|
|
34994
|
+
// exactly as it does for a block among element children.
|
|
33388
34995
|
if (
|
|
33389
34996
|
node.type === 'JSXIfExpression' ||
|
|
33390
34997
|
node.type === 'JSXForExpression' ||
|
|
33391
34998
|
node.type === 'JSXTryExpression' ||
|
|
33392
34999
|
node.type === 'JSXSwitchExpression' ||
|
|
35000
|
+
node.type === 'JSXCodeBlock' ||
|
|
33393
35001
|
node.type === 'JSXExpressionContainer' ||
|
|
33394
35002
|
node.type === 'JSXText' ||
|
|
33395
35003
|
node.type === 'JSXStyleElement'
|
|
@@ -33420,6 +35028,18 @@ function isWrappedJsxDirective(node) {
|
|
|
33420
35028
|
);
|
|
33421
35029
|
}
|
|
33422
35030
|
|
|
35031
|
+
// `@for` item bodies and `@switch` cases wrap a child `@{ … }` block written as
|
|
35032
|
+
// their output in an ExpressionStatement. The parser admits a block only as a
|
|
35033
|
+
// body's final statement, so unwrap just that one. A block inside plain JS
|
|
35034
|
+
// control flow in the body's setup is wrapped the same way and stays a
|
|
35035
|
+
// JavaScript expression, exactly like an element written there.
|
|
35036
|
+
function unwrapOutputCodeBlock(statements) {
|
|
35037
|
+
const last = statements[statements.length - 1];
|
|
35038
|
+
return last?.type === 'ExpressionStatement' && last.expression?.type === 'JSXCodeBlock'
|
|
35039
|
+
? [...statements.slice(0, -1), last.expression]
|
|
35040
|
+
: statements;
|
|
35041
|
+
}
|
|
35042
|
+
|
|
33423
35043
|
function bodyContainsJsx(node) {
|
|
33424
35044
|
if (!node) return false;
|
|
33425
35045
|
if (node.type === 'BlockStatement') return node.body.some(isJsxNode);
|
|
@@ -33487,10 +35107,13 @@ const esrapCommentOptions = {
|
|
|
33487
35107
|
* literal raws are re-derived centrally before the print.
|
|
33488
35108
|
*/
|
|
33489
35109
|
function printNodeWithMap(node, ctx) {
|
|
33490
|
-
|
|
35110
|
+
let printable = stripTsOnlyWrappers(escapeMultilineStringLiterals(node), {
|
|
33491
35111
|
filename: ctx.mapSourceName,
|
|
33492
35112
|
enums: null,
|
|
33493
35113
|
});
|
|
35114
|
+
if (ctx.deferredReads === true) {
|
|
35115
|
+
printable = snapshotDeferredReads(printable, (name) => allocCompilerName(ctx, `${name}$`));
|
|
35116
|
+
}
|
|
33494
35117
|
if (assertPrintedLocs()) assertNodeLocs(printable);
|
|
33495
35118
|
const { code, map } = esrapPrint(printable, withDeferredImports(esrapTsx(esrapCommentOptions)), {
|
|
33496
35119
|
sourceMapSource: ctx.mapSourceName,
|