octane 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/compatibility.cjs +35 -0
- package/dist/cjs/component-flags.cjs +8 -1
- package/dist/cjs/dom-tables.cjs +2 -0
- package/dist/cjs/error-codes.client.generated.cjs +15 -2
- package/dist/cjs/error-codes.server.generated.cjs +5 -0
- package/dist/cjs/event-names.cjs +39 -0
- package/dist/cjs/index.cjs +13 -0
- package/dist/cjs/runtime.cjs +1223 -332
- package/dist/cjs/runtime.server.cjs +362 -120
- package/dist/cjs/server/index.cjs +22 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/compatibility.d.ts +7 -0
- package/dist/compatibility.js +10 -0
- package/dist/compiler/bundler.js +13 -30
- package/dist/compiler/compile-renderer-boundaries.js +1 -0
- package/dist/compiler/compile-universal.js +3 -0
- package/dist/compiler/compile.js +619 -99
- package/dist/compiler/hook-deps.js +100 -3
- package/dist/compiler/hook-names.js +1 -0
- package/dist/compiler/manual-hooks.js +171 -0
- package/dist/compiler/plain-hook-memo.js +54 -5
- package/dist/compiler/slot-hooks.d.ts +43 -0
- package/dist/compiler/slot-hooks.js +154 -35
- package/dist/component-flags.d.ts +1 -1
- package/dist/component-flags.js +8 -1
- package/dist/dom-tables.js +2 -0
- package/dist/error-codes.client.generated.d.ts +3 -1
- package/dist/error-codes.client.generated.js +15 -2
- package/dist/error-codes.server.generated.d.ts +1 -0
- package/dist/error-codes.server.generated.js +5 -0
- package/dist/event-names.d.ts +1 -0
- package/dist/event-names.js +15 -0
- package/dist/html-tree-validation.d.ts +17 -34
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -1
- package/dist/jsx-runtime.d.ts +6 -1
- package/dist/public-types.d.ts +188 -0
- package/dist/public-types.js +0 -0
- package/dist/runtime.d.ts +109 -66
- package/dist/runtime.js +1220 -332
- package/dist/runtime.server.d.ts +39 -14
- package/dist/runtime.server.js +356 -120
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +19 -0
- package/dist/static/index.d.ts +2 -1
- package/dist/static/index.js +3 -1
- package/dist/universal-core.d.ts +8 -1
- package/dist/universal-core.js +95 -9
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
strongHash,
|
|
40
40
|
} from '@tsrx/core';
|
|
41
41
|
import { parseModule } from '#octane/compiler-parser';
|
|
42
|
+
import { decodeHTMLStrict } from 'entities';
|
|
42
43
|
import { print as esrapPrint } from 'esrap';
|
|
43
44
|
import esrapTsx from 'esrap/languages/tsx';
|
|
44
45
|
import { buildFatSegments } from './fat-segments.js';
|
|
@@ -106,7 +107,13 @@ import {
|
|
|
106
107
|
cssStyleValue,
|
|
107
108
|
hyphenateStyleName,
|
|
108
109
|
} from '../dom-tables.js';
|
|
110
|
+
import { isDelegatedEventProp } from '../event-names.js';
|
|
109
111
|
import { sanitizeURLAttribute } from '../sanitize-url.js';
|
|
112
|
+
import {
|
|
113
|
+
invalidHtmlNestingWithAncestor,
|
|
114
|
+
invalidHtmlNestingWithParent,
|
|
115
|
+
invalidHtmlTextNesting,
|
|
116
|
+
} from '../html-tree-validation.js';
|
|
110
117
|
|
|
111
118
|
// --- Parser-AST immutability enforcement ------------------------------------
|
|
112
119
|
//
|
|
@@ -622,7 +629,7 @@ const CONTROLLED_KIND_HELPERS = {
|
|
|
622
629
|
// recorder (recordBakedAttrOrigins) derives the name/value offsets from that
|
|
623
630
|
// shape arithmetically — keep the invariant when adding forms.
|
|
624
631
|
function bakeStaticAttr(attrName, lv, tag, namespace = 'html') {
|
|
625
|
-
if (lv == null) return '';
|
|
632
|
+
if (lv == null || attrName === 'innerText' || attrName === 'textContent') return '';
|
|
626
633
|
const isCustom =
|
|
627
634
|
(namespace === 'html' || namespace === 'opaque') && tag !== undefined && tag.includes('-');
|
|
628
635
|
const lower = attrName.toLowerCase();
|
|
@@ -663,6 +670,8 @@ function bakeStaticAttr(attrName, lv, tag, namespace = 'html') {
|
|
|
663
670
|
return lv ? ` ${lower}=""` : '';
|
|
664
671
|
}
|
|
665
672
|
if (typeof lv === 'string') {
|
|
673
|
+
if (!isCustom && (lower === 'rowspan' || lower === 'start') && Number.isNaN(Number(lv)))
|
|
674
|
+
return '';
|
|
666
675
|
if (!isCustom && POSITIVE_NUMERIC_ATTR_PROPS.has(lower) && !(Number(lv) >= 1)) return '';
|
|
667
676
|
if (
|
|
668
677
|
!isCustom &&
|
|
@@ -1565,12 +1574,13 @@ function inheritSoleCompRoot(bodyNodes, ctx) {
|
|
|
1565
1574
|
// Collect the LOCAL names bound to the octane boundary builtins (see
|
|
1566
1575
|
// inheritSoleCompRoot) from a module's import declarations. Aliased imports
|
|
1567
1576
|
// (`import { Suspense as S }`) are matched by their local binding.
|
|
1568
|
-
function collectOctaneBoundaryNames(astBody) {
|
|
1577
|
+
function collectOctaneBoundaryNames(astBody, onlySuspense = false) {
|
|
1569
1578
|
const names = new Set();
|
|
1570
1579
|
for (const node of astBody) {
|
|
1571
1580
|
if (node.type !== 'ImportDeclaration' || node.source.value !== 'octane') continue;
|
|
1572
1581
|
for (const sp of node.specifiers || []) {
|
|
1573
1582
|
const imported = sp.imported?.name;
|
|
1583
|
+
if (onlySuspense && imported !== 'Suspense') continue;
|
|
1574
1584
|
if (
|
|
1575
1585
|
(imported === 'Suspense' ||
|
|
1576
1586
|
imported === 'ErrorBoundary' ||
|
|
@@ -4713,6 +4723,225 @@ function containsRenderCall(stmts, memoCtx = null) {
|
|
|
4713
4723
|
return found;
|
|
4714
4724
|
}
|
|
4715
4725
|
|
|
4726
|
+
// Built-in hooks whose render-time work cannot schedule an update for the
|
|
4727
|
+
// calling component. Their function arguments run later (commit, event, or
|
|
4728
|
+
// never), so those bodies are deferred like any other closure.
|
|
4729
|
+
const SETUP_PASSIVE_HOOKS = new Set([
|
|
4730
|
+
'useCallback',
|
|
4731
|
+
'useRef',
|
|
4732
|
+
'useEffect',
|
|
4733
|
+
'useLayoutEffect',
|
|
4734
|
+
'useInsertionEffect',
|
|
4735
|
+
'useImperativeHandle',
|
|
4736
|
+
'useEffectEvent',
|
|
4737
|
+
'useId',
|
|
4738
|
+
'useDeferredValue',
|
|
4739
|
+
'useTransition',
|
|
4740
|
+
'useFormStatus',
|
|
4741
|
+
'useActionState',
|
|
4742
|
+
'useFormState',
|
|
4743
|
+
'useContext',
|
|
4744
|
+
'use',
|
|
4745
|
+
]);
|
|
4746
|
+
// Built-in hooks that invoke a function argument synchronously during render
|
|
4747
|
+
// (a memo factory, a lazy initializer, a snapshot reader, an optimistic
|
|
4748
|
+
// reducer, a linked-state reconciler). Those bodies belong to setup.
|
|
4749
|
+
const SETUP_SYNC_FACTORY_HOOKS = new Set([
|
|
4750
|
+
'useMemo',
|
|
4751
|
+
'useState',
|
|
4752
|
+
'useLinkedState',
|
|
4753
|
+
'useReducer',
|
|
4754
|
+
'useSyncExternalStore',
|
|
4755
|
+
'useOptimistic',
|
|
4756
|
+
]);
|
|
4757
|
+
// Standard globals that only project their arguments. A user-defined
|
|
4758
|
+
// `toString`/`valueOf`/`toJSON` that schedules a render violates the hook rules
|
|
4759
|
+
// in React and Octane alike, so these count as call-free setup.
|
|
4760
|
+
const SETUP_PURE_GLOBAL_CALLEES = new Set([
|
|
4761
|
+
'String',
|
|
4762
|
+
'Number',
|
|
4763
|
+
'Boolean',
|
|
4764
|
+
'BigInt',
|
|
4765
|
+
'Symbol',
|
|
4766
|
+
'parseInt',
|
|
4767
|
+
'parseFloat',
|
|
4768
|
+
'isNaN',
|
|
4769
|
+
'isFinite',
|
|
4770
|
+
'encodeURI',
|
|
4771
|
+
'encodeURIComponent',
|
|
4772
|
+
'decodeURI',
|
|
4773
|
+
'decodeURIComponent',
|
|
4774
|
+
]);
|
|
4775
|
+
// Namespace members that never invoke an argument. `Array.from`,
|
|
4776
|
+
// `Object.groupBy`, `Object.fromEntries`, and friends iterate or call user
|
|
4777
|
+
// code, so they stay unclassified. JSON's replacer and reviver positions are
|
|
4778
|
+
// checked like a hook factory (see walkInvokedArgument).
|
|
4779
|
+
const SETUP_PURE_NAMESPACE_MEMBERS = new Map([
|
|
4780
|
+
['Math', null],
|
|
4781
|
+
[
|
|
4782
|
+
'Number',
|
|
4783
|
+
new Set(['isFinite', 'isInteger', 'isNaN', 'isSafeInteger', 'parseFloat', 'parseInt']),
|
|
4784
|
+
],
|
|
4785
|
+
['Object', new Set(['entries', 'freeze', 'hasOwn', 'is', 'isFrozen', 'keys', 'values'])],
|
|
4786
|
+
['Array', new Set(['isArray', 'of'])],
|
|
4787
|
+
['Date', new Set(['now', 'parse', 'UTC'])],
|
|
4788
|
+
['JSON', new Set(['parse', 'stringify'])],
|
|
4789
|
+
]);
|
|
4790
|
+
|
|
4791
|
+
// Every binding name the module declares anywhere. A global namespace shadowed
|
|
4792
|
+
// by any local, parameter, or import is treated as an unknown callee.
|
|
4793
|
+
function collectModuleBoundNames(ast) {
|
|
4794
|
+
const names = new Set();
|
|
4795
|
+
const seen = new WeakSet();
|
|
4796
|
+
function walk(n) {
|
|
4797
|
+
if (!n || typeof n !== 'object') return;
|
|
4798
|
+
if (Array.isArray(n)) {
|
|
4799
|
+
for (const x of n) walk(x);
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
if (seen.has(n)) return;
|
|
4803
|
+
seen.add(n);
|
|
4804
|
+
switch (n.type) {
|
|
4805
|
+
case 'VariableDeclarator':
|
|
4806
|
+
collectBindings(n.id, names);
|
|
4807
|
+
break;
|
|
4808
|
+
case 'FunctionDeclaration':
|
|
4809
|
+
case 'FunctionExpression':
|
|
4810
|
+
case 'ArrowFunctionExpression':
|
|
4811
|
+
if (n.id) names.add(n.id.name);
|
|
4812
|
+
for (const param of n.params || []) collectBindings(param, names);
|
|
4813
|
+
break;
|
|
4814
|
+
case 'ClassDeclaration':
|
|
4815
|
+
case 'ClassExpression':
|
|
4816
|
+
if (n.id) names.add(n.id.name);
|
|
4817
|
+
break;
|
|
4818
|
+
case 'CatchClause':
|
|
4819
|
+
collectBindings(n.param, names);
|
|
4820
|
+
break;
|
|
4821
|
+
case 'ImportSpecifier':
|
|
4822
|
+
case 'ImportDefaultSpecifier':
|
|
4823
|
+
case 'ImportNamespaceSpecifier':
|
|
4824
|
+
if (n.local) names.add(n.local.name);
|
|
4825
|
+
break;
|
|
4826
|
+
}
|
|
4827
|
+
for (const key in n) {
|
|
4828
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
4829
|
+
walk(n[key]);
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
walk(ast);
|
|
4833
|
+
return names;
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4836
|
+
/**
|
|
4837
|
+
* Can this setup schedule a render-phase update for its own component? A
|
|
4838
|
+
* setter, dispatcher, transition start, custom hook, or any other call the
|
|
4839
|
+
* compiler cannot classify may; the built-in hooks above and standard pure
|
|
4840
|
+
* globals cannot. Closures are deferred unless a built-in hook runs them
|
|
4841
|
+
* during render. This admits the setup checkpoint, an optimization over the
|
|
4842
|
+
* runtime's own child-initialization guards, so an unclassified callee fails
|
|
4843
|
+
* closed to emitting the checkpoint.
|
|
4844
|
+
*/
|
|
4845
|
+
function setupCanScheduleSelfUpdate(stmts, ctx) {
|
|
4846
|
+
let found = false;
|
|
4847
|
+
const seen = new WeakSet();
|
|
4848
|
+
const bound = ctx._moduleBoundNames;
|
|
4849
|
+
const isGlobal = (name) => bound !== undefined && !bound.has(name);
|
|
4850
|
+
function walk(n, deep) {
|
|
4851
|
+
if (found || !n) return;
|
|
4852
|
+
if (Array.isArray(n)) {
|
|
4853
|
+
for (const x of n) walk(x, deep);
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
4856
|
+
if (typeof n !== 'object') return;
|
|
4857
|
+
const t = n.type;
|
|
4858
|
+
if (!t) return;
|
|
4859
|
+
if (seen.has(n)) return;
|
|
4860
|
+
seen.add(n);
|
|
4861
|
+
if (
|
|
4862
|
+
!deep &&
|
|
4863
|
+
(t === 'ArrowFunctionExpression' || t === 'FunctionExpression' || t === 'FunctionDeclaration')
|
|
4864
|
+
) {
|
|
4865
|
+
return;
|
|
4866
|
+
}
|
|
4867
|
+
if (t === 'CallExpression' || t === 'OptionalCallExpression') {
|
|
4868
|
+
const hook = n._octaneImportedHook;
|
|
4869
|
+
if (hook !== undefined && SETUP_PASSIVE_HOOKS.has(hook)) {
|
|
4870
|
+
walk(n.arguments, deep);
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4873
|
+
if (hook !== undefined && SETUP_SYNC_FACTORY_HOOKS.has(hook)) {
|
|
4874
|
+
for (const argument of n.arguments) walkInvokedArgument(argument);
|
|
4875
|
+
return;
|
|
4876
|
+
}
|
|
4877
|
+
const callee = n.callee;
|
|
4878
|
+
if (
|
|
4879
|
+
callee?.type === 'Identifier' &&
|
|
4880
|
+
n._octaneUnboundCallee === true &&
|
|
4881
|
+
SETUP_PURE_GLOBAL_CALLEES.has(callee.name)
|
|
4882
|
+
) {
|
|
4883
|
+
walk(n.arguments, true);
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
if (
|
|
4887
|
+
callee?.type === 'MemberExpression' &&
|
|
4888
|
+
!callee.computed &&
|
|
4889
|
+
callee.object?.type === 'Identifier' &&
|
|
4890
|
+
callee.property?.type === 'Identifier' &&
|
|
4891
|
+
SETUP_PURE_NAMESPACE_MEMBERS.has(callee.object.name) &&
|
|
4892
|
+
isGlobal(callee.object.name)
|
|
4893
|
+
) {
|
|
4894
|
+
const members = SETUP_PURE_NAMESPACE_MEMBERS.get(callee.object.name);
|
|
4895
|
+
if (members === null || members.has(callee.property.name)) {
|
|
4896
|
+
if (callee.object.name === 'JSON') {
|
|
4897
|
+
walk(n.arguments[0], true);
|
|
4898
|
+
for (let index = 1; index < n.arguments.length; index++)
|
|
4899
|
+
walkInvokedArgument(n.arguments[index]);
|
|
4900
|
+
} else walk(n.arguments, true);
|
|
4901
|
+
return;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
found = true;
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
if (t === 'NewExpression' || t === 'TaggedTemplateExpression') {
|
|
4908
|
+
found = true;
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
for (const key in n) {
|
|
4912
|
+
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
4913
|
+
walk(n[key], deep);
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
// A sync-factory hook may invoke any argument whose value can be a function.
|
|
4917
|
+
// Only shapes that visibly cannot be callable, or whose bodies are visible
|
|
4918
|
+
// here, keep the analysis open; a named factory, member, call result, or
|
|
4919
|
+
// conditional fails closed exactly like an unclassified call.
|
|
4920
|
+
function walkInvokedArgument(argument) {
|
|
4921
|
+
if (found || !argument) return;
|
|
4922
|
+
switch (argument.type) {
|
|
4923
|
+
case 'ArrowFunctionExpression':
|
|
4924
|
+
case 'FunctionExpression':
|
|
4925
|
+
case 'Literal':
|
|
4926
|
+
case 'TemplateLiteral':
|
|
4927
|
+
case 'BinaryExpression':
|
|
4928
|
+
case 'UnaryExpression':
|
|
4929
|
+
case 'UpdateExpression':
|
|
4930
|
+
walk(argument, true);
|
|
4931
|
+
return;
|
|
4932
|
+
case 'ArrayExpression':
|
|
4933
|
+
// Dependency and initial-value arrays are never invoked; their
|
|
4934
|
+
// elements are ordinary setup expressions.
|
|
4935
|
+
walk(argument.elements, true);
|
|
4936
|
+
return;
|
|
4937
|
+
default:
|
|
4938
|
+
found = true;
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
for (const s of stmts) walk(s, false);
|
|
4942
|
+
return found;
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4716
4945
|
// A hook call is identified by naming convention — the same signal React and
|
|
4717
4946
|
// React Compiler key on — plus the exact `unstable_` and `UNSTABLE_` staging
|
|
4718
4947
|
// prefixes bindings expose (`unstable_useRouterState` in @octanejs/remix-router
|
|
@@ -8333,7 +8562,9 @@ function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
|
8333
8562
|
throw new Error(`Unknown compile mode "${mode}" — expected 'client' or 'server'.`);
|
|
8334
8563
|
}
|
|
8335
8564
|
const cleanFilename = cleanCompileFilename(filename);
|
|
8336
|
-
const analyzedAst =
|
|
8565
|
+
const analyzedAst = markParserSensitiveHosts(
|
|
8566
|
+
normalizeAuthoredJsxLiterals(parseModule(source, cleanFilename)),
|
|
8567
|
+
);
|
|
8337
8568
|
analyzeTsrx(analyzedAst, cleanFilename);
|
|
8338
8569
|
adoptParserAst(analyzedAst);
|
|
8339
8570
|
assertNativeReadDiagnostics(analyzedAst, source, cleanFilename, options);
|
|
@@ -9023,8 +9254,12 @@ function compileInternal(
|
|
|
9023
9254
|
// that are ever assigned (`foo = bar`) are excluded: their identity is then
|
|
9024
9255
|
// as mutable as a module `let`, which fails closed.
|
|
9025
9256
|
ctx.moduleFunctionDeclarations = collectImmutableModuleFunctions(ast.body);
|
|
9257
|
+
// Global namespaces are only trusted by the setup checkpoint analysis when
|
|
9258
|
+
// no module binding shadows them (see setupCanScheduleSelfUpdate).
|
|
9259
|
+
ctx._moduleBoundNames = collectModuleBoundNames(ast);
|
|
9026
9260
|
// M3 inherit-range exclusion set (see inheritSoleCompRoot).
|
|
9027
9261
|
ctx._octaneBoundaryNames = collectOctaneBoundaryNames(ast.body);
|
|
9262
|
+
ctx._octaneSuspenseNames = collectOctaneBoundaryNames(ast.body, true);
|
|
9028
9263
|
const productionEffects = !ctx.hmr && !ctx.dev && !ctx.profile;
|
|
9029
9264
|
const exportedComponents = ast.body.filter(
|
|
9030
9265
|
(statement) =>
|
|
@@ -10143,6 +10378,7 @@ function compileServer(
|
|
|
10143
10378
|
// M3 inherit-range exclusion set — must match the client compile's
|
|
10144
10379
|
// (see inheritSoleCompRoot; both modes read the same import declarations).
|
|
10145
10380
|
ctx._octaneBoundaryNames = collectOctaneBoundaryNames(ast.body);
|
|
10381
|
+
ctx._octaneSuspenseNames = collectOctaneBoundaryNames(ast.body, true);
|
|
10146
10382
|
|
|
10147
10383
|
// Style maps are module values and can be referenced by a component declared
|
|
10148
10384
|
// before the map itself. Lower every top-level map before compiling any
|
|
@@ -10240,7 +10476,8 @@ function compileServer(
|
|
|
10240
10476
|
},
|
|
10241
10477
|
});
|
|
10242
10478
|
} else {
|
|
10243
|
-
|
|
10479
|
+
const fnName = node.id?.name || node.declaration?.id?.name || 'module';
|
|
10480
|
+
bodyNodes.push(rewriteModuleJsxValues(rewriteHookCalls(node, ctx, fnName), ctx));
|
|
10244
10481
|
}
|
|
10245
10482
|
}
|
|
10246
10483
|
|
|
@@ -10366,7 +10603,10 @@ function compileServerComponent(node, ctx) {
|
|
|
10366
10603
|
const warmStatement =
|
|
10367
10604
|
warmNode === null
|
|
10368
10605
|
? null
|
|
10369
|
-
: inheritOriginLoc(
|
|
10606
|
+
: inheritOriginLoc(
|
|
10607
|
+
b.stmt(b.call(requireRuntimeForContext(ctx, 'markWarm'), b.id(name), warmNode)),
|
|
10608
|
+
node,
|
|
10609
|
+
);
|
|
10370
10610
|
|
|
10371
10611
|
// Mirror the client emission's hoisting rule (see compileComponent): a
|
|
10372
10612
|
// component referenced ABOVE its declaration keeps real function-declaration
|
|
@@ -10382,7 +10622,7 @@ function compileServerComponent(node, ctx) {
|
|
|
10382
10622
|
b.logical(
|
|
10383
10623
|
'&&',
|
|
10384
10624
|
b.binary('===', b.unary('typeof', b.id(name)), b.literal('function', "'function'")),
|
|
10385
|
-
b.
|
|
10625
|
+
b.call(requireRuntimeForContext(ctx, 'markWarm'), b.id(name), warmNode),
|
|
10386
10626
|
),
|
|
10387
10627
|
),
|
|
10388
10628
|
node,
|
|
@@ -10432,6 +10672,7 @@ function ssrCompileBody(
|
|
|
10432
10672
|
componentNs = null,
|
|
10433
10673
|
returnedFragmentTemplate = false,
|
|
10434
10674
|
returnedFragmentRoot = false,
|
|
10675
|
+
unboxedHtml = false,
|
|
10435
10676
|
) {
|
|
10436
10677
|
const prevMapTemps = ctx.currentMapTemps;
|
|
10437
10678
|
ctx.currentMapTemps = [];
|
|
@@ -10447,6 +10688,7 @@ function ssrCompileBody(
|
|
|
10447
10688
|
componentNs,
|
|
10448
10689
|
returnedFragmentTemplate,
|
|
10449
10690
|
returnedFragmentRoot,
|
|
10691
|
+
unboxedHtml,
|
|
10450
10692
|
);
|
|
10451
10693
|
} finally {
|
|
10452
10694
|
ctx.currentMapTemps = prevMapTemps;
|
|
@@ -10464,6 +10706,7 @@ function ssrCompileBodyWithMapTemps(
|
|
|
10464
10706
|
componentNs,
|
|
10465
10707
|
returnedFragmentTemplate,
|
|
10466
10708
|
returnedFragmentRoot,
|
|
10709
|
+
unboxedHtml,
|
|
10467
10710
|
) {
|
|
10468
10711
|
const returnedOutput = node.body?.type === 'JSXCodeBlock' && hasOwnValueReturn(node);
|
|
10469
10712
|
const mapTemps = ctx.currentMapTemps;
|
|
@@ -10685,7 +10928,12 @@ function ssrCompileBodyWithMapTemps(
|
|
|
10685
10928
|
node.params.length > 0
|
|
10686
10929
|
? [...node.params, b.id('__s'), b.id('__extra')]
|
|
10687
10930
|
: [b.id('__props'), b.id('__s'), b.id('__extra')];
|
|
10688
|
-
|
|
10931
|
+
// Private loop items feed only HTML concatenation, so their serialized tail
|
|
10932
|
+
// needs no carrier. Other bodies can cross a component/value boundary, where
|
|
10933
|
+
// the carrier distinguishes compiled HTML from authored text returns. This
|
|
10934
|
+
// flag belongs to this body only; nested callbacks keep their own contract.
|
|
10935
|
+
if (!unboxedHtml) ctx.runtimeNeeded.add('ssrHtml');
|
|
10936
|
+
body.push(b.return(unboxedHtml ? htmlExpr : ssrCall('ssrHtml', [htmlExpr], node)));
|
|
10689
10937
|
const origin =
|
|
10690
10938
|
node.loc != null
|
|
10691
10939
|
? node
|
|
@@ -11022,14 +11270,15 @@ function ssrEmitNode(
|
|
|
11022
11270
|
ctx.runtimeNeeded.add(fn);
|
|
11023
11271
|
return ssrCall(fn, [resolveStyleExpr(rewriteHookCalls(expr, ctx, name), cssHash)], node);
|
|
11024
11272
|
}
|
|
11025
|
-
ctx.runtimeNeeded.add('ssrChild');
|
|
11026
11273
|
// rewriteJsxValues lowers any JSX embedded in the expression (e.g.
|
|
11027
11274
|
// `{cond && <div/>}`, a ternary, a `.map(x => <Row/>)`) to printable
|
|
11028
11275
|
// createElement(...) descriptors — exactly like ssrEmitTsrxExpression and
|
|
11029
11276
|
// the client makeChildCall. Without it the raw JSX leaks into the emitted
|
|
11030
11277
|
// ssrChild(...) call as unparseable source.
|
|
11278
|
+
const childHelper = nlGuard ? 'ssrChildPre' : 'ssrChild';
|
|
11279
|
+
ctx.runtimeNeeded.add(childHelper);
|
|
11031
11280
|
const childExpr = ssrCall(
|
|
11032
|
-
|
|
11281
|
+
childHelper,
|
|
11033
11282
|
[
|
|
11034
11283
|
resolveStyleExpr(rewriteJsxValues(rewriteHookCalls(expr, ctx, name), ctx), cssHash),
|
|
11035
11284
|
b.id('__s'),
|
|
@@ -11901,9 +12150,11 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
11901
12150
|
staticTextLiteral(onlyChild0.expression) === null &&
|
|
11902
12151
|
!isKnownStringChildExpression(onlyChild0.expression, ctx.knownStringChildLocals)
|
|
11903
12152
|
) {
|
|
11904
|
-
|
|
12153
|
+
const childHelper =
|
|
12154
|
+
tag === 'pre' || tag === 'textarea' || tag === 'listing' ? 'ssrChildTextPre' : 'ssrChildText';
|
|
12155
|
+
ctx.runtimeNeeded.add(childHelper);
|
|
11905
12156
|
childrenExpr = ssrCall(
|
|
11906
|
-
|
|
12157
|
+
childHelper,
|
|
11907
12158
|
[
|
|
11908
12159
|
resolveStyleExpr(
|
|
11909
12160
|
rewriteJsxValues(rewriteHookCalls(onlyChild0.expression, ctx, name), ctx),
|
|
@@ -12394,6 +12645,7 @@ function ssrCompileSub(
|
|
|
12394
12645
|
componentNs,
|
|
12395
12646
|
returnedFragmentTemplate = false,
|
|
12396
12647
|
returnedFragmentRoot = false,
|
|
12648
|
+
unboxedHtml = false,
|
|
12397
12649
|
) {
|
|
12398
12650
|
const fnName = `${baseName}$${ctx.nextHelperId++}`;
|
|
12399
12651
|
const synth = { params: paramNodes || [], body: bodyStmts };
|
|
@@ -12422,6 +12674,7 @@ function ssrCompileSub(
|
|
|
12422
12674
|
componentNs,
|
|
12423
12675
|
returnedFragmentTemplate,
|
|
12424
12676
|
returnedFragmentRoot,
|
|
12677
|
+
unboxedHtml,
|
|
12425
12678
|
);
|
|
12426
12679
|
} finally {
|
|
12427
12680
|
ctx._ssrFoldedExprHoles = prevFoldedExprHoles;
|
|
@@ -12584,6 +12837,9 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
|
|
|
12584
12837
|
cssHash,
|
|
12585
12838
|
parentNs,
|
|
12586
12839
|
componentNs,
|
|
12840
|
+
false,
|
|
12841
|
+
false,
|
|
12842
|
+
true,
|
|
12587
12843
|
);
|
|
12588
12844
|
inlinedSubs.push(itemSub.fn);
|
|
12589
12845
|
let emptyCall = ssrHtmlTemplate([], node, ctx);
|
|
@@ -13855,7 +14111,9 @@ function compileComponent(node, ctx, options) {
|
|
|
13855
14111
|
: fnNode,
|
|
13856
14112
|
];
|
|
13857
14113
|
if (pendingWarm) {
|
|
13858
|
-
statements.push(
|
|
14114
|
+
statements.push(
|
|
14115
|
+
guarded(b.call(requireRuntimeForContext(ctx, 'markWarm'), b.id(name), pendingWarm)),
|
|
14116
|
+
);
|
|
13859
14117
|
}
|
|
13860
14118
|
if (hmrWrap && returnedOutput) {
|
|
13861
14119
|
statements.push(
|
|
@@ -13890,7 +14148,12 @@ function compileComponent(node, ctx, options) {
|
|
|
13890
14148
|
// identity by naming the inner FunctionExpression.
|
|
13891
14149
|
const fnExpr = functionExpressionFromDeclaration(fnNode);
|
|
13892
14150
|
const warmedFn = pendingWarm
|
|
13893
|
-
? markPure(
|
|
14151
|
+
? markPure(
|
|
14152
|
+
inheritOriginLoc(
|
|
14153
|
+
b.call(requireRuntimeForContext(ctx, 'markWarm'), fnExpr, pendingWarm),
|
|
14154
|
+
node,
|
|
14155
|
+
),
|
|
14156
|
+
)
|
|
13894
14157
|
: fnExpr;
|
|
13895
14158
|
const abiFn =
|
|
13896
14159
|
hmrWrap && returnedOutput
|
|
@@ -14370,6 +14633,32 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
14370
14633
|
bodyStatements.push(...mapTemps.map((temp) => inheritOriginLoc(b.let(temp), node)));
|
|
14371
14634
|
}
|
|
14372
14635
|
bodyStatements.push(...rewrittenStatements);
|
|
14636
|
+
// A self-update scheduled by setup discards this output. The owning Block
|
|
14637
|
+
// replays setup before returning to its parent, so children initialize from
|
|
14638
|
+
// the settled state and no template/resource work escapes the first pass.
|
|
14639
|
+
// Setup that only reads built-in hooks and pure globals cannot schedule an
|
|
14640
|
+
// update; keep those bodies unchanged (see setupCanScheduleSelfUpdate).
|
|
14641
|
+
if (
|
|
14642
|
+
ctx.mode !== 'server' &&
|
|
14643
|
+
ctx._universalRuntimeUnit == null &&
|
|
14644
|
+
!returnedOutput &&
|
|
14645
|
+
setupCanScheduleSelfUpdate(statements, ctx)
|
|
14646
|
+
) {
|
|
14647
|
+
bodyStatements.push(
|
|
14648
|
+
inheritOriginLoc(
|
|
14649
|
+
b.if(
|
|
14650
|
+
b.logical(
|
|
14651
|
+
'&&',
|
|
14652
|
+
b.member(b.member(b.id('__s'), 'block'), 'pending'),
|
|
14653
|
+
b.unary('!', b.member(b.member(b.id('__s'), 'block'), 'crossRenderUpdate')),
|
|
14654
|
+
),
|
|
14655
|
+
b.return(null),
|
|
14656
|
+
null,
|
|
14657
|
+
),
|
|
14658
|
+
node,
|
|
14659
|
+
),
|
|
14660
|
+
);
|
|
14661
|
+
}
|
|
14373
14662
|
// GLOBAL resource registrations (stylesheet precedence links, style
|
|
14374
14663
|
// resources, async scripts) run here: after setup, so an href computed from
|
|
14375
14664
|
// a body local is initialized, and BEFORE the constructs below mount any
|
|
@@ -14564,6 +14853,11 @@ function transformUniversalParallelUse(ast, ctx, metadata) {
|
|
|
14564
14853
|
node?.type === 'FunctionExpression' || node?.type === 'ArrowFunctionExpression';
|
|
14565
14854
|
const unwrapAssignedFunction = (node) => {
|
|
14566
14855
|
if (isFunction(node)) return node;
|
|
14856
|
+
if (
|
|
14857
|
+
calleeName(node) === runtimeAliasForContext(ctx, 'markWarm') &&
|
|
14858
|
+
isFunction(node.arguments?.[0])
|
|
14859
|
+
)
|
|
14860
|
+
return node.arguments[0];
|
|
14567
14861
|
if (
|
|
14568
14862
|
node?.type === 'CallExpression' &&
|
|
14569
14863
|
node.callee?.type === 'MemberExpression' &&
|
|
@@ -14696,14 +14990,7 @@ function transformUniversalParallelUse(ast, ctx, metadata) {
|
|
|
14696
14990
|
};
|
|
14697
14991
|
// The warm-plan attach maps to the component function it decorates.
|
|
14698
14992
|
const assignWarmPlan = (fn, warmNode) =>
|
|
14699
|
-
inheritOriginLoc(
|
|
14700
|
-
b.call(
|
|
14701
|
-
b.member(b.id('Object'), 'assign'),
|
|
14702
|
-
fn,
|
|
14703
|
-
b.object([b.prop('init', b.id('__warm'), warmNode)]),
|
|
14704
|
-
),
|
|
14705
|
-
fn,
|
|
14706
|
-
);
|
|
14993
|
+
inheritOriginLoc(b.call(requireRuntimeForContext(ctx, 'markWarm'), fn, warmNode), fn);
|
|
14707
14994
|
|
|
14708
14995
|
const transformFunction = (fn, name, component) => {
|
|
14709
14996
|
if (!isFunction(fn) || transformed.has(fn)) return fn;
|
|
@@ -15764,7 +16051,11 @@ function parallelUseWalkJsx(nodes, ctx, componentName, creations, warmChildren,
|
|
|
15764
16051
|
const nameNode = node.openingElement && node.openingElement.name;
|
|
15765
16052
|
const isComponent =
|
|
15766
16053
|
nameNode && nameNode.type === 'JSXIdentifier' && /^[A-Z]/.test(nameNode.name);
|
|
15767
|
-
|
|
16054
|
+
const transparentBoundary =
|
|
16055
|
+
isComponent &&
|
|
16056
|
+
ctx._octaneSuspenseNames?.has(nameNode.name) &&
|
|
16057
|
+
!ctx.currentComponentLocals?.has(nameNode.name);
|
|
16058
|
+
if (isComponent && !transparentBoundary) {
|
|
15768
16059
|
// Server: creations flowing INTO child props (`<Kid p={make(x)}/>`)
|
|
15769
16060
|
// need the same cross-pass identity as use()-site creations — every
|
|
15770
16061
|
// streaming wave re-runs this body, and a recreated per-request
|
|
@@ -16424,7 +16715,7 @@ function buildWarmArtifacts(node, ctx, componentName, creations, warmChildren) {
|
|
|
16424
16715
|
// The hoisted fetch plan: creations + child calls, params destructured
|
|
16425
16716
|
// from the incoming props object. Single-param components only (the norm).
|
|
16426
16717
|
// Returned as a bare arrow — compileComponent attaches it to the INNER
|
|
16427
|
-
// function object via
|
|
16718
|
+
// function object via markWarm, so the component's own body (where
|
|
16428
16719
|
// the function-expression name shadows the module const) sees it too;
|
|
16429
16720
|
// hmr() forwards it from the wrapped fn onto the HMR wrapper.
|
|
16430
16721
|
let warmNode = null;
|
|
@@ -16644,13 +16935,17 @@ const NUMERIC_HOOK_SLOT_POSITION = {
|
|
|
16644
16935
|
useEffectEvent: 1,
|
|
16645
16936
|
useImperativeHandle: 3,
|
|
16646
16937
|
useActionState: 3,
|
|
16938
|
+
useFormState: 3,
|
|
16647
16939
|
useOptimistic: 2,
|
|
16648
16940
|
useSignal$: 1,
|
|
16649
16941
|
};
|
|
16650
16942
|
|
|
16651
16943
|
function appendHookSlotArgument(name, args, slot, numeric, origin) {
|
|
16652
16944
|
const out = [...args];
|
|
16653
|
-
const position =
|
|
16945
|
+
const position =
|
|
16946
|
+
numeric || name === 'useState' || name === 'useRef'
|
|
16947
|
+
? NUMERIC_HOOK_SLOT_POSITION[name]
|
|
16948
|
+
: undefined;
|
|
16654
16949
|
if (position !== undefined) {
|
|
16655
16950
|
while (out.length < position) out.push(b.id('undefined', origin));
|
|
16656
16951
|
}
|
|
@@ -16658,6 +16953,18 @@ function appendHookSlotArgument(name, args, slot, numeric, origin) {
|
|
|
16658
16953
|
return out;
|
|
16659
16954
|
}
|
|
16660
16955
|
|
|
16956
|
+
function wrapHookCallWithSlot(call, ctx, slot, callee, args) {
|
|
16957
|
+
const fn = call.typeArguments
|
|
16958
|
+
? { type: 'TSInstantiationExpression', expression: callee, typeArguments: call.typeArguments }
|
|
16959
|
+
: callee;
|
|
16960
|
+
return {
|
|
16961
|
+
...call,
|
|
16962
|
+
callee: b.id(requireRuntimeForContext(ctx, 'withSlot')),
|
|
16963
|
+
typeArguments: null,
|
|
16964
|
+
arguments: [typeof slot === 'string' ? b.id(slot) : slot, fn, ...args],
|
|
16965
|
+
};
|
|
16966
|
+
}
|
|
16967
|
+
|
|
16661
16968
|
function arrayPatternObservesStateGetter(pattern) {
|
|
16662
16969
|
const elements = pattern.elements || [];
|
|
16663
16970
|
if (elements[2] != null) return true;
|
|
@@ -17308,20 +17615,20 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17308
17615
|
const importedName = n._octaneImportedHook;
|
|
17309
17616
|
const hookRuntimeImportedName = n._octaneHookRuntimeImportedHook;
|
|
17310
17617
|
const name = importedName ?? hookRuntimeImportedName ?? localName;
|
|
17311
|
-
// Three kinds of call
|
|
17618
|
+
// Three kinds of call receive a per-call-site hook identity:
|
|
17312
17619
|
// - a built-in base hook (HOOK_NAMES) — also needs its runtime import;
|
|
17313
17620
|
// - a custom / library hook by React's `use[A-Z]` convention — e.g. a
|
|
17314
17621
|
// `useStore` binding from @octanejs/zustand that WRAPS a base hook and
|
|
17315
|
-
//
|
|
17622
|
+
// resolves the enclosing call path. `useContext` is keyed by context identity (no
|
|
17316
17623
|
// slot) and `use` has no uppercase suffix, so both are excluded here. We
|
|
17317
17624
|
// do NOT import custom hooks — they're user/library imports;
|
|
17318
17625
|
// - a server-mode `use(thenable)` — a stable suspense-cache key.
|
|
17319
17626
|
// Distinct call sites get distinct slots, so `useStore(a)`/`useStore(b)`
|
|
17320
17627
|
// (or the same hook twice) stay independent.
|
|
17321
17628
|
//
|
|
17322
|
-
//
|
|
17323
|
-
//
|
|
17324
|
-
//
|
|
17629
|
+
// A `use*` name is reserved for hooks. The custom wrapper preserves its
|
|
17630
|
+
// authored argument list; inside a plain JS loop the convention is
|
|
17631
|
+
// enforced by rejectHookInJsLoop.
|
|
17325
17632
|
const isBuiltin =
|
|
17326
17633
|
(HOOK_NAMES.has(name) ||
|
|
17327
17634
|
(ctx.nativeReads &&
|
|
@@ -17338,10 +17645,9 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17338
17645
|
const inline = lowerLocalMemoExpression(n, name);
|
|
17339
17646
|
if (inline !== null) return inline;
|
|
17340
17647
|
}
|
|
17341
|
-
// Keep a Symbol
|
|
17342
|
-
//
|
|
17343
|
-
//
|
|
17344
|
-
// production ABI; arbitrary callable helpers cannot.
|
|
17648
|
+
// Keep a Symbol on custom-hook call paths so their internal base-hook
|
|
17649
|
+
// identities compose across modules. Proven render-scope base hooks
|
|
17650
|
+
// can use the smaller numeric production ABI.
|
|
17345
17651
|
const hasSpread = n.arguments.some((arg) => arg.type === 'SpreadElement');
|
|
17346
17652
|
const memoRoute = isBuiltin && canLowerClientMemo(n, name);
|
|
17347
17653
|
const memoCandidate = memoRoute ? slotMemoCandidate(n, name) : null;
|
|
@@ -17406,18 +17712,20 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17406
17712
|
// separately must not re-promote its nested hooks to render-local.
|
|
17407
17713
|
const argsLocalRoot = localSlotMarks.get(n) === true;
|
|
17408
17714
|
const args = n.arguments.map((a) => rewriteHookCalls(a, ctx, componentName, argsLocalRoot));
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17715
|
+
const callee = nativeMemo
|
|
17716
|
+
? b.id(runtimeAliasForContext(ctx, 'nativePuMemo'))
|
|
17717
|
+
: getterHelper !== null
|
|
17718
|
+
? b.id(runtimeAliasForContext(ctx, getterHelper))
|
|
17719
|
+
: n.callee._octaneGenerated
|
|
17720
|
+
? b.id(runtimeAliasForContext(ctx, name))
|
|
17721
|
+
: n.callee;
|
|
17722
|
+
// The call path supplies identity without adding an argument that
|
|
17723
|
+
// changes a custom hook's defaults, rest values or arguments.length.
|
|
17724
|
+
// State/ref spreads also need this path: an empty spread has no
|
|
17725
|
+
// initializer position into which a trailing slot may safely fall.
|
|
17726
|
+
if (isCustom || (hasSpread && (name === 'useState' || name === 'useRef')))
|
|
17727
|
+
return wrapHookCallWithSlot(n, ctx, slot, callee, args);
|
|
17728
|
+
|
|
17421
17729
|
const hookArgs = explicitMemoSlot
|
|
17422
17730
|
? args
|
|
17423
17731
|
: appendHookSlotArgument(name, args, slot, numericSlot, n);
|
|
@@ -17432,13 +17740,7 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17432
17740
|
return {
|
|
17433
17741
|
...n,
|
|
17434
17742
|
...(memoRoute ? { _octaneInlineMemoHook: memoCandidate } : null),
|
|
17435
|
-
callee
|
|
17436
|
-
? b.id(runtimeAliasForContext(ctx, 'nativePuMemo'))
|
|
17437
|
-
: getterHelper !== null
|
|
17438
|
-
? b.id(runtimeAliasForContext(ctx, getterHelper))
|
|
17439
|
-
: n.callee._octaneGenerated
|
|
17440
|
-
? b.id(runtimeAliasForContext(ctx, name))
|
|
17441
|
-
: n.callee,
|
|
17743
|
+
callee,
|
|
17442
17744
|
arguments: hookArgs,
|
|
17443
17745
|
};
|
|
17444
17746
|
}
|
|
@@ -17505,14 +17807,20 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17505
17807
|
const args = n.arguments.map((a) =>
|
|
17506
17808
|
rewriteHookCalls(a, ctx, componentName, localSlotMarks.get(n) === true),
|
|
17507
17809
|
);
|
|
17810
|
+
const callee = nativeMemo
|
|
17811
|
+
? b.id(runtimeAliasForContext(ctx, 'nativePuMemo'))
|
|
17812
|
+
: getterHelper !== null
|
|
17813
|
+
? b.id(runtimeAliasForContext(ctx, getterHelper))
|
|
17814
|
+
: n.callee;
|
|
17815
|
+
if (
|
|
17816
|
+
(name === 'useState' || name === 'useRef') &&
|
|
17817
|
+
args.some((arg) => arg.type === 'SpreadElement')
|
|
17818
|
+
)
|
|
17819
|
+
return wrapHookCallWithSlot(n, ctx, slot, callee, args);
|
|
17508
17820
|
return {
|
|
17509
17821
|
...n,
|
|
17510
17822
|
...(memoRoute ? { _octaneInlineMemoHook: memoCandidate } : null),
|
|
17511
|
-
callee
|
|
17512
|
-
? b.id(runtimeAliasForContext(ctx, 'nativePuMemo'))
|
|
17513
|
-
: getterHelper !== null
|
|
17514
|
-
? b.id(runtimeAliasForContext(ctx, getterHelper))
|
|
17515
|
-
: n.callee,
|
|
17823
|
+
callee,
|
|
17516
17824
|
arguments: explicitMemoSlot
|
|
17517
17825
|
? args
|
|
17518
17826
|
: appendHookSlotArgument(name, args, slot, numericSlot, n),
|
|
@@ -17524,9 +17832,8 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17524
17832
|
// and the like). Same `use[A-Z]` convention, applied to the PROPERTY name.
|
|
17525
17833
|
// The callee is a member access, so the plain withSlot form would sever
|
|
17526
17834
|
// `this`; instead the WHOLE call is wrapped in a thunk —
|
|
17527
|
-
// `_$withSlot(sym, () => obj.useX(...args
|
|
17528
|
-
//
|
|
17529
|
-
// `sym` still reaches slot-forwarding bindings (splitSlot convention).
|
|
17835
|
+
// `_$withSlot(sym, () => obj.useX(...args))` preserves the receiver and
|
|
17836
|
+
// authored arguments while supplying the inner base hooks' call path.
|
|
17530
17837
|
if (
|
|
17531
17838
|
n.type === 'CallExpression' &&
|
|
17532
17839
|
!n.optional &&
|
|
@@ -17558,7 +17865,7 @@ function rewriteHookCalls(node, ctx, componentName, localRoot = false) {
|
|
|
17558
17865
|
return b.call(
|
|
17559
17866
|
withSlotAlias,
|
|
17560
17867
|
b.id(symVar),
|
|
17561
|
-
b.arrow([],
|
|
17868
|
+
b.arrow([], { ...n, callee: { ...n.callee, object }, arguments: args }),
|
|
17562
17869
|
);
|
|
17563
17870
|
}
|
|
17564
17871
|
return null;
|
|
@@ -19217,6 +19524,20 @@ function rewriteJsxValues(node, ctx, eagerMapCallbackRoots = false, eagerMapCall
|
|
|
19217
19524
|
if (lower != null) node = lowerSetupValueDirectives(node, lower);
|
|
19218
19525
|
return mapAst(node, (n) => {
|
|
19219
19526
|
const t = n && n.type;
|
|
19527
|
+
if (isFunctionNode(n) && n.body?.type === 'JSXCodeBlock') {
|
|
19528
|
+
const name = n.id?.name ?? allocCompilerName(ctx, '__template');
|
|
19529
|
+
const previousLocals = ctx.currentComponentLocals;
|
|
19530
|
+
ctx.currentComponentLocals = collectComponentLocals(n);
|
|
19531
|
+
try {
|
|
19532
|
+
const compiled =
|
|
19533
|
+
ctx.mode === 'server'
|
|
19534
|
+
? ssrCompileBody(n, ctx, name, null, [], 'opaque')
|
|
19535
|
+
: compileFunctionBody(n, ctx, name, 'opaque');
|
|
19536
|
+
return functionExpressionFromDeclaration({ ...compiled, id: n.id ?? null }, n);
|
|
19537
|
+
} finally {
|
|
19538
|
+
ctx.currentComponentLocals = previousLocals;
|
|
19539
|
+
}
|
|
19540
|
+
}
|
|
19220
19541
|
if (
|
|
19221
19542
|
ctx.nativeReads &&
|
|
19222
19543
|
lower == null &&
|
|
@@ -20067,11 +20388,6 @@ function jsxTagName(n) {
|
|
|
20067
20388
|
: null;
|
|
20068
20389
|
}
|
|
20069
20390
|
|
|
20070
|
-
/** @param {any} n @returns {boolean} */
|
|
20071
|
-
function isHeadElementNode(n) {
|
|
20072
|
-
return Boolean(n && n.type === 'JSXElement' && jsxTagName(n) === 'head');
|
|
20073
|
-
}
|
|
20074
|
-
|
|
20075
20391
|
/** @param {any} n @returns {boolean} */
|
|
20076
20392
|
function isHoistableHeadElementNode(n) {
|
|
20077
20393
|
if (n == null || (n.type !== 'JSXElement' && n.type !== 'Element')) return false;
|
|
@@ -20111,7 +20427,7 @@ function isHoistableHeadElementNode(n) {
|
|
|
20111
20427
|
inner.value === 'stylesheet';
|
|
20112
20428
|
}
|
|
20113
20429
|
}
|
|
20114
|
-
if (tag === 'link' && relStylesheet && hasPrecedence
|
|
20430
|
+
if (tag === 'link' && relStylesheet && (!hasPrecedence || !hasHref || emptyHref)) return false;
|
|
20115
20431
|
return true;
|
|
20116
20432
|
}
|
|
20117
20433
|
|
|
@@ -20329,7 +20645,7 @@ function deferredHeadAttrs(el) {
|
|
|
20329
20645
|
// props.children under HTML, SVG, or MathML. namespaceHead resolves that choice
|
|
20330
20646
|
// from its actual parent at render time while retaining ordinary component-block
|
|
20331
20647
|
// hydration and keyed ownership.
|
|
20332
|
-
function deferredTitleElement(el, ctx) {
|
|
20648
|
+
function deferredTitleElement(el, ctx, tag = 'title') {
|
|
20333
20649
|
ctx.runtimeNeeded.add('namespaceHeadElement');
|
|
20334
20650
|
let authoredKey = null;
|
|
20335
20651
|
for (const attr of el.openingElement.attributes || []) {
|
|
@@ -20345,7 +20661,7 @@ function deferredTitleElement(el, ctx) {
|
|
|
20345
20661
|
}
|
|
20346
20662
|
const args = [
|
|
20347
20663
|
b.literal(headKey(el, 0, ctx)),
|
|
20348
|
-
b.literal(
|
|
20664
|
+
b.literal(tag),
|
|
20349
20665
|
deferredHeadAttrs(el),
|
|
20350
20666
|
headTextExpression(el),
|
|
20351
20667
|
];
|
|
@@ -20658,9 +20974,137 @@ function emitHeadServer(headNodes, ctx) {
|
|
|
20658
20974
|
});
|
|
20659
20975
|
}
|
|
20660
20976
|
|
|
20977
|
+
// JSX indentation is syntax, while same-line spaces are rendered content. Cook
|
|
20978
|
+
// authored literals once, before either emitter sees them, so entities cannot be
|
|
20979
|
+
// decoded twice as a tree passes through fragment and value lowering.
|
|
20980
|
+
function normalizeAuthoredJsxLiterals(ast) {
|
|
20981
|
+
return mapAst(ast, (node) => {
|
|
20982
|
+
if (node.type === 'JSXText') {
|
|
20983
|
+
const lines = node.value.split(/\r\n|\n|\r/);
|
|
20984
|
+
let last = 0;
|
|
20985
|
+
for (let i = 0; i < lines.length; i++) if (/[^ \t]/.test(lines[i])) last = i;
|
|
20986
|
+
let value = '';
|
|
20987
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20988
|
+
let line = lines[i].replace(/\t/g, ' ');
|
|
20989
|
+
if (i !== 0) line = line.replace(/^ +/, '');
|
|
20990
|
+
if (i !== lines.length - 1) line = line.replace(/ +$/, '');
|
|
20991
|
+
if (line !== '') value += line + (i !== last ? ' ' : '');
|
|
20992
|
+
}
|
|
20993
|
+
if (value.includes('&')) value = decodeHTMLStrict(value);
|
|
20994
|
+
return value === node.value ? node : { ...node, value, raw: value };
|
|
20995
|
+
}
|
|
20996
|
+
if (
|
|
20997
|
+
node.type === 'JSXAttribute' &&
|
|
20998
|
+
node.value?.type === 'Literal' &&
|
|
20999
|
+
typeof node.value.value === 'string' &&
|
|
21000
|
+
node.value.value.includes('&')
|
|
21001
|
+
) {
|
|
21002
|
+
const value = decodeHTMLStrict(node.value.value);
|
|
21003
|
+
return { ...node, value: { ...node.value, value, raw: JSON.stringify(value) } };
|
|
21004
|
+
}
|
|
21005
|
+
if (
|
|
21006
|
+
node.type === 'JSXStyleElement' &&
|
|
21007
|
+
(hasJsxAttribute(node, 'dangerouslySetInnerHTML') || hasJsxAttribute(node, 'children'))
|
|
21008
|
+
) {
|
|
21009
|
+
return normalizeAuthoredJsxLiterals(
|
|
21010
|
+
b.jsx_element(node, node.openingElement.attributes || [], []),
|
|
21011
|
+
);
|
|
21012
|
+
}
|
|
21013
|
+
return null;
|
|
21014
|
+
});
|
|
21015
|
+
}
|
|
21016
|
+
|
|
21017
|
+
function requiresImperativeHostTree(root, ctx) {
|
|
21018
|
+
const tag = jsxTagName(root) || elementTagName(root);
|
|
21019
|
+
return (
|
|
21020
|
+
(ctx.mode !== 'server' && root._octaneImperativeHost === true) ||
|
|
21021
|
+
hasJsxAttribute(root, 'key') ||
|
|
21022
|
+
tag === 'noscript' ||
|
|
21023
|
+
tag === 'html' ||
|
|
21024
|
+
tag === 'head' ||
|
|
21025
|
+
tag === 'body'
|
|
21026
|
+
);
|
|
21027
|
+
}
|
|
21028
|
+
|
|
21029
|
+
// Analyze parser repair once per authored tree. Mark only the affected ancestor,
|
|
21030
|
+
// preserving template compilation for unrelated hosts around that subtree.
|
|
21031
|
+
function markParserSensitiveHosts(ast) {
|
|
21032
|
+
const unsafe = new WeakSet();
|
|
21033
|
+
let found = false;
|
|
21034
|
+
const mark = (node) => {
|
|
21035
|
+
unsafe.add(node);
|
|
21036
|
+
found = true;
|
|
21037
|
+
};
|
|
21038
|
+
function visit(node, ancestors = [], namespace = 'html') {
|
|
21039
|
+
if (node === null || typeof node !== 'object') return;
|
|
21040
|
+
if (Array.isArray(node)) {
|
|
21041
|
+
for (const child of node) visit(child, ancestors, namespace);
|
|
21042
|
+
return;
|
|
21043
|
+
}
|
|
21044
|
+
if (node.type === 'JSXText') {
|
|
21045
|
+
if (ancestors.length && invalidHtmlTextNesting(node.value, ancestors[0].tag) !== null)
|
|
21046
|
+
mark(ancestors[0].node);
|
|
21047
|
+
return;
|
|
21048
|
+
}
|
|
21049
|
+
if (node.type === 'JSXFragment') {
|
|
21050
|
+
visit(node.children || [], ancestors, namespace);
|
|
21051
|
+
return;
|
|
21052
|
+
}
|
|
21053
|
+
if (node.type === 'JSXElement') {
|
|
21054
|
+
const tag = jsxTagName(node);
|
|
21055
|
+
if (isComponentTag(node)) {
|
|
21056
|
+
visit(node.children || []);
|
|
21057
|
+
return;
|
|
21058
|
+
}
|
|
21059
|
+
const self = nsForSelf(tag, namespace);
|
|
21060
|
+
if (self === 'html' && ancestors.length) {
|
|
21061
|
+
if (invalidHtmlNestingWithParent(tag, ancestors[0].tag) !== null) mark(ancestors[0].node);
|
|
21062
|
+
const tags = ancestors.map((entry) => entry.tag);
|
|
21063
|
+
for (let i = 1; i < ancestors.length; i++) {
|
|
21064
|
+
if (invalidHtmlNestingWithAncestor(tag, tags.slice(0, i + 1)) !== null)
|
|
21065
|
+
mark(ancestors[i].node);
|
|
21066
|
+
}
|
|
21067
|
+
}
|
|
21068
|
+
const childNamespace = nsForChildren(tag, namespace);
|
|
21069
|
+
visit(
|
|
21070
|
+
node.children || [],
|
|
21071
|
+
childNamespace === 'html' ? [{ node, tag }, ...ancestors] : [],
|
|
21072
|
+
childNamespace,
|
|
21073
|
+
);
|
|
21074
|
+
visit(node.openingElement?.attributes || []);
|
|
21075
|
+
return;
|
|
21076
|
+
}
|
|
21077
|
+
// A value expression or directive owns its insertion range. Its elements
|
|
21078
|
+
// are parsed separately, so lexical host ancestry does not cross it.
|
|
21079
|
+
for (const key in node) {
|
|
21080
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
21081
|
+
visit(node[key]);
|
|
21082
|
+
}
|
|
21083
|
+
}
|
|
21084
|
+
visit(ast);
|
|
21085
|
+
return found
|
|
21086
|
+
? mapAst(ast, (node) => (unsafe.has(node) ? { ...node, _octaneImperativeHost: true } : null))
|
|
21087
|
+
: ast;
|
|
21088
|
+
}
|
|
21089
|
+
|
|
21090
|
+
function rewriteImperativeHeadElements(node, ctx, namespace = 'html', inNoscript = false) {
|
|
21091
|
+
if (node?.type !== 'JSXElement' && node?.type !== 'JSXFragment') return node;
|
|
21092
|
+
const tag = jsxTagName(node);
|
|
21093
|
+
const nextNamespace = tag ? nsForChildren(tag, namespace) : namespace;
|
|
21094
|
+
if (!inNoscript && namespace === 'html' && isHoistableHeadElementNode(node)) {
|
|
21095
|
+
return deferredTitleElement(node, ctx, tag);
|
|
21096
|
+
}
|
|
21097
|
+
return {
|
|
21098
|
+
...node,
|
|
21099
|
+
children: (node.children || []).map((child) =>
|
|
21100
|
+
rewriteImperativeHeadElements(child, ctx, nextNamespace, inNoscript || tag === 'noscript'),
|
|
21101
|
+
),
|
|
21102
|
+
};
|
|
21103
|
+
}
|
|
21104
|
+
|
|
20661
21105
|
/**
|
|
20662
21106
|
* Normalize a list of JSX child nodes into the shapes the emitters consume:
|
|
20663
|
-
* -
|
|
21107
|
+
* - Indentation-only JSXText / JSX comments (`{…}` empty containers) → dropped
|
|
20664
21108
|
* - JSXText with content → `Text` node wrapping a string Literal
|
|
20665
21109
|
* - `{expr}` container → `Text` hole, or `TSRXExpression` when the
|
|
20666
21110
|
* expression needs the rich dispatcher (portals, JSX ternaries,
|
|
@@ -20670,7 +21114,7 @@ function emitHeadServer(headNodes, ctx) {
|
|
|
20670
21114
|
* - JSXElement → `Element` (with `<Fragment ref>` expanded to a
|
|
20671
21115
|
* FragmentStart/…/FragmentEnd sequence, `<Activity>` lowered to an
|
|
20672
21116
|
* ActivityStatement, eligible `<title>/<meta>/<link>` hoisted as `HeadHoist`,
|
|
20673
|
-
* and
|
|
21117
|
+
* and parser-sensitive host trees lowered as descriptors)
|
|
20674
21118
|
* - Fragments (`<>…</>`, Tsx/Tsrx) → flattened (children inlined)
|
|
20675
21119
|
* - JSXStyleElement → dropped (its CSS is handled by the scoping pipeline)
|
|
20676
21120
|
* - Directive expressions (@if/@for/@try/@switch, `@{…}` child blocks) →
|
|
@@ -20681,13 +21125,19 @@ function emitHeadServer(headNodes, ctx) {
|
|
|
20681
21125
|
// `inSvg`: the children being normalized sit inside an SVG-namespace subtree.
|
|
20682
21126
|
// SVG has its own `<title>` (the accessibility tooltip element) — it must stay
|
|
20683
21127
|
// where it is, NOT hoist to document.head (React 19 makes the same exception).
|
|
20684
|
-
function normalizeChildren(
|
|
21128
|
+
function normalizeChildren(
|
|
21129
|
+
nodes,
|
|
21130
|
+
inSvg = false,
|
|
21131
|
+
ctx = null,
|
|
21132
|
+
inNoscript = false,
|
|
21133
|
+
allowImperative = true,
|
|
21134
|
+
) {
|
|
20685
21135
|
const out = [];
|
|
20686
21136
|
if (!nodes) return out;
|
|
20687
21137
|
for (const n of nodes) {
|
|
20688
21138
|
if (!n) continue;
|
|
20689
21139
|
if (n.type === 'JSXText') {
|
|
20690
|
-
if (/^\s*$/.test(n.value)) continue;
|
|
21140
|
+
if (n.value === '' || (/^\s*$/.test(n.value) && /[\n\r]/.test(n.value))) continue;
|
|
20691
21141
|
// The Text node and its literal map to the authored JSX text.
|
|
20692
21142
|
out.push(
|
|
20693
21143
|
inheritOriginLoc(
|
|
@@ -20788,14 +21238,29 @@ function normalizeChildren(nodes, inSvg = false, ctx = null, inNoscript = false)
|
|
|
20788
21238
|
out.push({ type: 'ActivityStatement', mode, children: n.children || [] });
|
|
20789
21239
|
continue;
|
|
20790
21240
|
}
|
|
20791
|
-
//
|
|
20792
|
-
//
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
21241
|
+
// Keys need a reconciliation boundary. Parser-sensitive host trees need
|
|
21242
|
+
// imperative construction so HTML repair cannot change binding paths.
|
|
21243
|
+
// The shared descriptor path provides both without taxing ordinary templates.
|
|
21244
|
+
if (
|
|
21245
|
+
ctx &&
|
|
21246
|
+
allowImperative &&
|
|
21247
|
+
!isComponentTag(n) &&
|
|
21248
|
+
(hasJsxAttribute(n, 'key') || (!inSvg && requiresImperativeHostTree(n, ctx)))
|
|
21249
|
+
) {
|
|
21250
|
+
out.push(
|
|
21251
|
+
inheritOriginLoc(
|
|
21252
|
+
{
|
|
21253
|
+
type: 'Text',
|
|
21254
|
+
expression: jsxElementToCreateElement(
|
|
21255
|
+
rewriteImperativeHeadElements(n, ctx, inSvg ? 'svg' : 'html'),
|
|
21256
|
+
ctx,
|
|
21257
|
+
),
|
|
21258
|
+
imperativeRoot: n,
|
|
21259
|
+
},
|
|
21260
|
+
n,
|
|
21261
|
+
),
|
|
20798
21262
|
);
|
|
21263
|
+
continue;
|
|
20799
21264
|
}
|
|
20800
21265
|
// `<title>`/`<meta>`/eligible `<link>` → hoist to the document-head channel (NOT
|
|
20801
21266
|
// body DOM). Kept in `out` as a synthetic node so planJsx / ssrCompileBody
|
|
@@ -20843,6 +21308,18 @@ function normalizeChildren(nodes, inSvg = false, ctx = null, inNoscript = false)
|
|
|
20843
21308
|
} else if (n.type === 'Tsx' || n.type === 'Tsrx' || n.type === 'JSXFragment') {
|
|
20844
21309
|
out.push(...normalizeChildren(n.children || [], inSvg, ctx, inNoscript));
|
|
20845
21310
|
} else if (n.type === 'JSXStyleElement') {
|
|
21311
|
+
if (hasJsxAttribute(n, 'dangerouslySetInnerHTML') || hasJsxAttribute(n, 'children')) {
|
|
21312
|
+
out.push(
|
|
21313
|
+
...normalizeChildren(
|
|
21314
|
+
[b.jsx_element(n, n.openingElement.attributes || [], [])],
|
|
21315
|
+
inSvg,
|
|
21316
|
+
ctx,
|
|
21317
|
+
inNoscript,
|
|
21318
|
+
allowImperative,
|
|
21319
|
+
),
|
|
21320
|
+
);
|
|
21321
|
+
continue;
|
|
21322
|
+
}
|
|
20846
21323
|
// `<style href precedence>` is a Float style resource, not scoped CSS —
|
|
20847
21324
|
// partition it to the head channel like precedence links.
|
|
20848
21325
|
if (!inSvg && !inNoscript && headResourceKind(n) === 'style') {
|
|
@@ -21852,9 +22329,9 @@ function planJsx(
|
|
|
21852
22329
|
for (let rootI = 0; rootI < jsxNodes.length; rootI++) {
|
|
21853
22330
|
const node = jsxNodes[rootI];
|
|
21854
22331
|
const nodeIsComp = node.type === 'Element' && isComponentTag(node);
|
|
21855
|
-
if (ctx.dev && node.type === 'Element' && !nodeIsComp) {
|
|
22332
|
+
if (ctx.dev && ((node.type === 'Element' && !nodeIsComp) || node.imperativeRoot)) {
|
|
21856
22333
|
collectClientHtmlNestingWarnings(
|
|
21857
|
-
node,
|
|
22334
|
+
node.imperativeRoot ?? node,
|
|
21858
22335
|
ctx,
|
|
21859
22336
|
parentNs === 'opaque' ? 'html' : parentNs,
|
|
21860
22337
|
nestingWarningIdentities,
|
|
@@ -22692,6 +23169,7 @@ function planJsx(
|
|
|
22692
23169
|
(fc.indexIndependent ? 8 : 0) |
|
|
22693
23170
|
(fc.ssrMarkerless ? 16 : 0) |
|
|
22694
23171
|
(fc.requiresScope ? 32 : 0) |
|
|
23172
|
+
(ctx.dev && fc.missingMapKey ? 64 : 0) |
|
|
22695
23173
|
(fc.keyedSelectionIndex >= 0 ? fc.keyedSelectionIndex << 6 : 0);
|
|
22696
23174
|
// Arg layout: forBlock(__s, slot, host, items, keyFn, body, flags?, deps?,
|
|
22697
23175
|
// emptyBody?, anchor?, ownEnd?, selectionBody?). The final argument is
|
|
@@ -23710,7 +24188,15 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
23710
24188
|
// `suppressHydrationWarning`: stamp a JS flag (NOT a DOM attribute) the runtime reads to
|
|
23711
24189
|
// keep the server value + skip the warning on a hydration mismatch for this element.
|
|
23712
24190
|
if (bind.kind === 'suppress') {
|
|
23713
|
-
return st(
|
|
24191
|
+
return st(
|
|
24192
|
+
b.stmt(
|
|
24193
|
+
b.assignment(
|
|
24194
|
+
'=',
|
|
24195
|
+
b.member(el(), '__oct_suppress'),
|
|
24196
|
+
b.binary('===', bind.expr, b.literal(true)),
|
|
24197
|
+
),
|
|
24198
|
+
),
|
|
24199
|
+
);
|
|
23714
24200
|
}
|
|
23715
24201
|
if (bind.kind === 'dangerChild') {
|
|
23716
24202
|
return st(b.stmt(b.call('_$markDangerouslySetInnerHTMLChildren', el())));
|
|
@@ -24850,7 +25336,7 @@ function collectClientHtmlNestingWarnings(root, ctx, inheritedNamespace, identit
|
|
|
24850
25336
|
const visit = (node, namespace, ancestors) => {
|
|
24851
25337
|
if (node?.type !== 'Element' && node?.type !== 'JSXElement') return;
|
|
24852
25338
|
if (isComponentTag(node)) return;
|
|
24853
|
-
const tag = elementTagName(node);
|
|
25339
|
+
const tag = jsxTagName(node) || elementTagName(node);
|
|
24854
25340
|
if (tag === null) return;
|
|
24855
25341
|
const semanticTag = tag.toLowerCase();
|
|
24856
25342
|
const ownNamespace = nsForSelf(tag, namespace);
|
|
@@ -24884,7 +25370,13 @@ function collectClientHtmlNestingWarnings(root, ctx, inheritedNamespace, identit
|
|
|
24884
25370
|
? 'html'
|
|
24885
25371
|
: nsForChildren(tag, namespace);
|
|
24886
25372
|
const nextAncestors = [...ancestors, { tag: semanticTag, namespace: ownNamespace, location }];
|
|
24887
|
-
const children = normalizeChildren(
|
|
25373
|
+
const children = normalizeChildren(
|
|
25374
|
+
node.children || [],
|
|
25375
|
+
childNamespace === 'svg',
|
|
25376
|
+
ctx,
|
|
25377
|
+
false,
|
|
25378
|
+
false,
|
|
25379
|
+
);
|
|
24888
25380
|
for (const child of children) {
|
|
24889
25381
|
if (child.type === 'Text' && ownNamespace === 'html' && restrictedTextParent(semanticTag)) {
|
|
24890
25382
|
const text = staticTextLiteral(child.expression);
|
|
@@ -25156,12 +25648,22 @@ function emitElementHtml(
|
|
|
25156
25648
|
// `suppressHydrationWarning` (React shallow semantics): NEVER a DOM attribute (not
|
|
25157
25649
|
// serialized client or server) — stamp a JS flag the runtime's hydration-mismatch
|
|
25158
25650
|
// paths read, so a server/client divergence on this element keeps the server value
|
|
25159
|
-
// and skips the warning.
|
|
25651
|
+
// and skips the warning. Only a bare prop or a value equal to true opts in.
|
|
25160
25652
|
if (rawAttrName === 'suppressHydrationWarning') {
|
|
25161
25653
|
const v = attr.value;
|
|
25162
25654
|
const inner = v && v.type === 'JSXExpressionContainer' ? v.expression : v;
|
|
25163
25655
|
const isFalse = inner && inner.type === 'Literal' && inner.value === false;
|
|
25164
|
-
if (!isFalse)
|
|
25656
|
+
if (!isFalse)
|
|
25657
|
+
bindings.push({
|
|
25658
|
+
id: bindings.length,
|
|
25659
|
+
kind: 'suppress',
|
|
25660
|
+
path,
|
|
25661
|
+
origin: attr,
|
|
25662
|
+
expr:
|
|
25663
|
+
inner == null
|
|
25664
|
+
? b.literal(true, 'true', attr)
|
|
25665
|
+
: tsrxExprNode(inner, ctx, componentName, inlinedSubs),
|
|
25666
|
+
});
|
|
25165
25667
|
continue;
|
|
25166
25668
|
}
|
|
25167
25669
|
const attrName = normalizeJsxAttrName(rawAttrName, tag, hostNs);
|
|
@@ -25383,6 +25885,7 @@ function emitElementHtml(
|
|
|
25383
25885
|
// path; valid static attrs still bake with their real boolean semantics.
|
|
25384
25886
|
if (
|
|
25385
25887
|
isEventAttrName(attrName) ||
|
|
25888
|
+
MUST_USE_PROPERTY_PROPS.has(attrName.toLowerCase()) ||
|
|
25386
25889
|
isAfterSpread ||
|
|
25387
25890
|
classBeforeSpread ||
|
|
25388
25891
|
(ctx.dev && needsDevStaticAttrValidation(attrName, true, tag, hostNs))
|
|
@@ -25473,6 +25976,7 @@ function emitElementHtml(
|
|
|
25473
25976
|
if (
|
|
25474
25977
|
inner.type === 'Literal' &&
|
|
25475
25978
|
!isEventAttrName(attrName) &&
|
|
25979
|
+
!MUST_USE_PROPERTY_PROPS.has(attrName.toLowerCase()) &&
|
|
25476
25980
|
!isAfterSpread &&
|
|
25477
25981
|
!classBeforeSpread &&
|
|
25478
25982
|
!(ctx.dev && needsDevStaticAttrValidation(attrName, inner.value, tag, hostNs))
|
|
@@ -25485,7 +25989,7 @@ function emitElementHtml(
|
|
|
25485
25989
|
// Dynamic value — record a binding. (Also reached for literal values that
|
|
25486
25990
|
// come after a spread, since those need to win over the spread at runtime.)
|
|
25487
25991
|
const expr = tsrxExprNode(inner, ctx, componentName, inlinedSubs);
|
|
25488
|
-
if (isEventAttrName(attrName)) {
|
|
25992
|
+
if (isEventAttrName(attrName) && !(tag.includes('-') && !isDelegatedEventProp(attrName))) {
|
|
25489
25993
|
// React-shape: a trailing `Capture` selects the capture phase (fired
|
|
25490
25994
|
// root→target before bubble handlers), stamped under `$$capture:<type>`.
|
|
25491
25995
|
// The real events gotpointercapture / lostpointercapture literally end in
|
|
@@ -25501,7 +26005,14 @@ function emitElementHtml(
|
|
|
25501
26005
|
capture = true;
|
|
25502
26006
|
rest = rest.slice(0, -7);
|
|
25503
26007
|
}
|
|
25504
|
-
const eventName =
|
|
26008
|
+
const eventName =
|
|
26009
|
+
rest === 'DoubleClick'
|
|
26010
|
+
? 'dblclick'
|
|
26011
|
+
: rest === 'Focus'
|
|
26012
|
+
? 'focusin'
|
|
26013
|
+
: rest === 'Blur'
|
|
26014
|
+
? 'focusout'
|
|
26015
|
+
: rest.toLowerCase();
|
|
25505
26016
|
const slotKey = capture ? `$$capture:${eventName}` : `$$${eventName}`;
|
|
25506
26017
|
// `onClick` → the `'$$click'` slot key. Without this the attribute name
|
|
25507
26018
|
// is unreachable from the output: the key's map position resolves to
|
|
@@ -26182,7 +26693,10 @@ function emitElementHtml(
|
|
|
26182
26693
|
};
|
|
26183
26694
|
const ic = makeIfCall(asIf, ctx, inlinedSubs, childNs, cssHash);
|
|
26184
26695
|
ic.hostPath = path;
|
|
26696
|
+
ic.anchorPath = [...path, childIdx];
|
|
26185
26697
|
ifCalls.push(ic);
|
|
26698
|
+
appendTemplatePart(html, '<!>', 'anchor');
|
|
26699
|
+
childIdx++;
|
|
26186
26700
|
} else if (isKnownStringChildExpression(expr, ctx.knownStringChildLocals)) {
|
|
26187
26701
|
bindings.push({
|
|
26188
26702
|
id: bindings.length,
|
|
@@ -26277,7 +26791,7 @@ function makePortalCall(callNode, ctx, componentName, inlinedSubs, parentNs, css
|
|
|
26277
26791
|
bodyArg,
|
|
26278
26792
|
);
|
|
26279
26793
|
} else {
|
|
26280
|
-
bodyExpr = tsrxExprNode(bodyArg, ctx, componentName, inlinedSubs);
|
|
26794
|
+
bodyExpr = tsrxExprNode(rewriteJsxValues(bodyArg, ctx), ctx, componentName, inlinedSubs);
|
|
26281
26795
|
}
|
|
26282
26796
|
return {
|
|
26283
26797
|
id: ctx.nextHelperId++,
|
|
@@ -27707,16 +28221,20 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
27707
28221
|
const keyHelper = `_key$${ctx.nextHelperId++}`;
|
|
27708
28222
|
const keyOrigin = node.loc ? node : keyFn.loc ? keyFn : ctx._moduleOrigin;
|
|
27709
28223
|
const keyBody = keyFn.body;
|
|
28224
|
+
const keyCapturesParent = [...collectFreeIdentifiers(keyFn, new Set())].some((name) =>
|
|
28225
|
+
ctx.currentComponentLocals?.has(name),
|
|
28226
|
+
);
|
|
27710
28227
|
const inlineKey =
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
27719
|
-
|
|
28228
|
+
keyCapturesParent ||
|
|
28229
|
+
(node.nativeArrayMap !== undefined &&
|
|
28230
|
+
mapCallbackHasEventClosure(node.nativeArrayMap.callback) &&
|
|
28231
|
+
!isDestructured &&
|
|
28232
|
+
!node.index &&
|
|
28233
|
+
keyBody?.type === 'MemberExpression' &&
|
|
28234
|
+
keyBody.computed !== true &&
|
|
28235
|
+
keyBody.object?.type === 'Identifier' &&
|
|
28236
|
+
keyBody.object.name === itemName &&
|
|
28237
|
+
keyBody.property?.name === 'id');
|
|
27720
28238
|
if (!inlineKey) {
|
|
27721
28239
|
ctx.hoistedHelpers.push(inheritOriginLoc(b.const(keyHelper, keyFn), keyOrigin));
|
|
27722
28240
|
}
|
|
@@ -28129,6 +28647,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
28129
28647
|
);
|
|
28130
28648
|
|
|
28131
28649
|
const mapCall = node.nativeArrayMap || null;
|
|
28650
|
+
if (keyCapturesParent) autoMemoDeps = null;
|
|
28132
28651
|
return {
|
|
28133
28652
|
id: ctx.nextHelperId++,
|
|
28134
28653
|
loc: devLoc(ctx, node),
|
|
@@ -28148,6 +28667,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
28148
28667
|
b.id(mapCall.receiverName),
|
|
28149
28668
|
])
|
|
28150
28669
|
: node.right, // the fold replaces this with a `props.hN` member read
|
|
28670
|
+
missingMapKey: mapCall !== null && node.key == null,
|
|
28151
28671
|
mapMethodExpr: mapCall
|
|
28152
28672
|
? mapCall.methodName === null
|
|
28153
28673
|
? b.member(b.id(mapCall.receiverName), 'map')
|