octane 0.1.31 → 0.1.32

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.
@@ -1811,6 +1811,62 @@ function collectComponentNames(ast) {
1811
1811
  return names;
1812
1812
  }
1813
1813
 
1814
+ function collectOwnerFreeThreeHostComponents(ast, state, development) {
1815
+ if (
1816
+ development ||
1817
+ state.hmr ||
1818
+ state.profile ||
1819
+ state.renderer.id !== 'three' ||
1820
+ state.renderer.module !== '@octanejs/three/renderer'
1821
+ ) {
1822
+ return null;
1823
+ }
1824
+
1825
+ const constructors = new Set();
1826
+ for (const statement of ast.body ?? []) {
1827
+ if (
1828
+ statement.type !== 'ImportDeclaration' ||
1829
+ statement.importKind === 'type' ||
1830
+ statement.source?.value !== '@octanejs/three'
1831
+ ) {
1832
+ continue;
1833
+ }
1834
+ for (const specifier of statement.specifiers ?? []) {
1835
+ if (
1836
+ specifier.type === 'ImportSpecifier' &&
1837
+ specifier.importKind !== 'type' &&
1838
+ (specifier.imported?.name ?? specifier.imported?.value) === 'extend' &&
1839
+ typeof specifier.local?.name === 'string'
1840
+ ) {
1841
+ constructors.add(specifier.local.name);
1842
+ }
1843
+ }
1844
+ }
1845
+ if (constructors.size === 0) return null;
1846
+
1847
+ const names = new Set();
1848
+ for (const statement of ast.body ?? []) {
1849
+ const declaration =
1850
+ statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement;
1851
+ if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') continue;
1852
+ for (const binding of declaration.declarations ?? []) {
1853
+ const call = binding.init;
1854
+ if (
1855
+ binding.id?.type === 'Identifier' &&
1856
+ call?.type === 'CallExpression' &&
1857
+ call.optional !== true &&
1858
+ call.callee?.type === 'Identifier' &&
1859
+ constructors.has(call.callee.name) &&
1860
+ call.arguments?.length === 1 &&
1861
+ call.arguments[0]?.type !== 'SpreadElement'
1862
+ ) {
1863
+ names.add(binding.id.name);
1864
+ }
1865
+ }
1866
+ }
1867
+ return names.size === 0 ? null : { names, lexical: createLexicalAnalysis(ast) };
1868
+ }
1869
+
1814
1870
  function addPatternNames(pattern, names) {
1815
1871
  if (!pattern) return;
1816
1872
  if (pattern.type === 'Identifier') {
@@ -1966,32 +2022,52 @@ function isOwnerFreeForAttribute(attribute) {
1966
2022
  );
1967
2023
  }
1968
2024
 
1969
- function ownerFreeForHost(node) {
2025
+ function ownerFreeForLeaf(node) {
1970
2026
  if (node.empty != null) return null;
1971
2027
  if (!isOwnerFreeForExpression(node.right) || !isOwnerFreeForExpression(node.key)) return null;
1972
2028
  const body = (node.body?.body ?? []).filter(
1973
2029
  (statement) => statement.type !== 'JSXText' || normalizeJsxText(statement.value ?? '') !== '',
1974
2030
  );
1975
2031
  if (body.length !== 1) return null;
1976
- const host = body[0];
2032
+ const leaf = body[0];
2033
+ if (leaf.type !== 'JSXElement' && leaf.type !== 'Element') return null;
1977
2034
  if (
1978
- (host.type !== 'JSXElement' && host.type !== 'Element') ||
1979
- isComponentElement(host) ||
1980
- jsxName(host) === 'Activity'
1981
- ) {
1982
- return null;
1983
- }
1984
- const type = jsxName(host);
1985
- if (type === null || !/^[a-z]/.test(type)) return null;
1986
- if (
1987
- (host.children ?? []).some(
2035
+ (leaf.children ?? []).some(
1988
2036
  (child) => child.type !== 'JSXText' || normalizeJsxText(child.value ?? '') !== '',
1989
2037
  )
1990
2038
  ) {
1991
2039
  return null;
1992
2040
  }
1993
- const attributes = host.openingElement?.attributes ?? host.attributes ?? [];
1994
- return attributes.every(isOwnerFreeForAttribute) ? host : null;
2041
+ const attributes = leaf.openingElement?.attributes ?? leaf.attributes ?? [];
2042
+ return attributes.every(isOwnerFreeForAttribute) ? leaf : null;
2043
+ }
2044
+
2045
+ function ownerFreeForHost(node) {
2046
+ const host = ownerFreeForLeaf(node);
2047
+ if (host === null || isComponentElement(host) || jsxName(host) === 'Activity') return null;
2048
+ const type = jsxName(host);
2049
+ return type !== null && /^[a-z]/.test(type) ? host : null;
2050
+ }
2051
+
2052
+ function ownerFreeForThreeHostComponent(node, state) {
2053
+ const trusted = state.ownerFreeThreeHostComponents;
2054
+ if (trusted == null) return null;
2055
+ const component = ownerFreeForLeaf(node);
2056
+ if (component === null || !isComponentElement(component)) return null;
2057
+ const attributes = component.openingElement?.attributes ?? component.attributes ?? [];
2058
+ const attributesSeen = new Set();
2059
+ for (const attribute of attributes) {
2060
+ const attributeKey = attributeName(attribute);
2061
+ if (attributeKey === '__proto__' || attributesSeen.has(attributeKey)) return null;
2062
+ attributesSeen.add(attributeKey);
2063
+ }
2064
+ const name = component.openingElement?.name ?? component.name;
2065
+ if (name?.type !== 'JSXIdentifier' || !trusted.names.has(name.name)) return null;
2066
+ const binding = trusted.lexical.resolveBinding(
2067
+ trusted.lexical.nodeScopes.get(name) ?? trusted.lexical.rootScope,
2068
+ name.name,
2069
+ );
2070
+ return binding?.scope === trusted.lexical.rootScope ? component : null;
1995
2071
  }
1996
2072
 
1997
2073
  function allocPlan(state, root, origin = null) {
@@ -2407,6 +2483,85 @@ function compilePlainPropsObjectAst(attributes, state, origin) {
2407
2483
  return inheritGeneratedOrigin(b.object(entries), origin);
2408
2484
  }
2409
2485
 
2486
+ /**
2487
+ * Lower a `class={[…]}` array literal to its clsx-composed string when every
2488
+ * element is statically a string or falsy.
2489
+ *
2490
+ * The runtime composes class arrays clsx-style, so `['row', on && 'danger']`
2491
+ * is only ever observed as `'row'` or `'row danger'` — but as a slot value the
2492
+ * array is rebuilt on every render, and on a transported root each rebuild is
2493
+ * re-encoded, making the hottest per-row prop a fresh allocation per render.
2494
+ * Emitting the string-building expression instead makes the slot value a
2495
+ * primitive: identity-comparable, allocation-free, and byte-identical in the
2496
+ * background and main-thread programs (so first-screen adoption still sees
2497
+ * the same tree). An all-literal array folds further, into a static plan prop
2498
+ * with no slot at all. Anything not statically string-or-falsy (spreads,
2499
+ * nested arrays, objects, expressions with non-literal truthy arms) keeps the
2500
+ * authored array slot and the runtime's general composition.
2501
+ *
2502
+ * Returns `{ staticValue }` for a fully static class, `{ expression }` for a
2503
+ * string-building lowering, or `null` to keep the authored value.
2504
+ */
2505
+ function loweredHostClassAst(expression, state) {
2506
+ if (expression.type !== 'ArrayExpression') return null;
2507
+ const elements = expression.elements ?? [];
2508
+ if (elements.length === 0) return { staticValue: '' };
2509
+ /** @type {({ static: string } | { test: any, value: string })[]} */
2510
+ const parts = [];
2511
+ for (const element of elements) {
2512
+ if (element == null || element.type === 'SpreadElement') return null;
2513
+ if (element.type === 'Literal' && typeof element.value === 'string') {
2514
+ if (element.value !== '') parts.push({ static: element.value });
2515
+ continue;
2516
+ }
2517
+ if (
2518
+ element.type === 'LogicalExpression' &&
2519
+ element.operator === '&&' &&
2520
+ element.right.type === 'Literal' &&
2521
+ typeof element.right.value === 'string' &&
2522
+ element.right.value !== ''
2523
+ ) {
2524
+ parts.push({ test: element.left, value: element.right.value });
2525
+ continue;
2526
+ }
2527
+ return null;
2528
+ }
2529
+ // The first part must be a literal so every later piece can join with an
2530
+ // unconditional leading space.
2531
+ if (parts.length !== 0 && parts[0].static === undefined) return null;
2532
+ let composed = null;
2533
+ let pendingStatic = '';
2534
+ const flushStatic = () => {
2535
+ if (pendingStatic === '') return;
2536
+ const literal = b.literal(pendingStatic, JSON.stringify(pendingStatic));
2537
+ composed = composed === null ? literal : b.binary('+', composed, literal);
2538
+ pendingStatic = '';
2539
+ };
2540
+ for (const part of parts) {
2541
+ if (part.static !== undefined) {
2542
+ pendingStatic += pendingStatic === '' && composed === null ? part.static : ` ${part.static}`;
2543
+ continue;
2544
+ }
2545
+ flushStatic();
2546
+ const spaced = ` ${part.value}`;
2547
+ composed = b.binary(
2548
+ '+',
2549
+ composed,
2550
+ inheritGeneratedOrigin(
2551
+ b.conditional(
2552
+ dynamicExpressionAst(part.test, state),
2553
+ b.literal(spaced, JSON.stringify(spaced)),
2554
+ b.literal('', '""'),
2555
+ ),
2556
+ part.test,
2557
+ ),
2558
+ );
2559
+ }
2560
+ if (composed === null) return { staticValue: pendingStatic };
2561
+ flushStatic();
2562
+ return { expression: inheritGeneratedOrigin(composed, expression) };
2563
+ }
2564
+
2410
2565
  function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
2411
2566
  if (attribute.type === 'JSXSpreadAttribute' || attribute.type === 'SpreadAttribute') {
2412
2567
  throw universalError(
@@ -2428,6 +2583,18 @@ function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
2428
2583
  if (value.type === 'Literal') return { name, staticValue: value.value };
2429
2584
  if (value.type === 'JSXExpressionContainer') {
2430
2585
  if (!value.expression || value.expression.type === 'JSXEmptyExpression') return null;
2586
+ if (name === 'class') {
2587
+ const lowered = loweredHostClassAst(value.expression, state);
2588
+ if (lowered !== null) {
2589
+ if (lowered.expression === undefined) return { name, staticValue: lowered.staticValue };
2590
+ const slot = context.values.length;
2591
+ context.values.push(lowered.expression);
2592
+ return { name, slot };
2593
+ }
2594
+ }
2595
+ if (value.expression.type === 'Literal' && isStaticPropLiteral(value.expression.value)) {
2596
+ return { name, staticValue: value.expression.value };
2597
+ }
2431
2598
  const slot = context.values.length;
2432
2599
  context.values.push(mainThreadHostValueAst(name, value.expression, state));
2433
2600
  return { name, slot };
@@ -2435,6 +2602,15 @@ function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
2435
2602
  throw universalError(state.filename, attribute, `unsupported value for host attribute ${name}.`);
2436
2603
  }
2437
2604
 
2605
+ /**
2606
+ * Literal values that can ride the frozen plan instead of a per-render slot.
2607
+ * `null` stays a slot: it is a legal "no handler" value for event props, whose
2608
+ * erasure in main-thread first-screen programs happens at the slot site.
2609
+ */
2610
+ function isStaticPropLiteral(value) {
2611
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
2612
+ }
2613
+
2438
2614
  function addDynamicAst(context, expression) {
2439
2615
  const slot = context.values.length;
2440
2616
  context.values.push(expression);
@@ -2710,6 +2886,55 @@ function rewriteSetupStatementAst(statement, state) {
2710
2886
  return rewritten === null ? [] : [rewritten];
2711
2887
  }
2712
2888
 
2889
+ function compileOwnerFreeForHostAst(host, state, itemBinding, indexBinding) {
2890
+ const context = { values: [] };
2891
+ const plan = allocPlan(state, compileHostElementAst(host, context, state), host);
2892
+ return {
2893
+ plan: generatedIdentifier(plan, host),
2894
+ render: generatedArrow(
2895
+ [itemBinding, indexBinding],
2896
+ inheritGeneratedOrigin(b.array(context.values), host),
2897
+ host,
2898
+ ),
2899
+ };
2900
+ }
2901
+
2902
+ function compileOwnerFreeThreeHostComponentAst(component, state, itemBinding, indexBinding) {
2903
+ const attributes = component.openingElement?.attributes ?? component.attributes ?? [];
2904
+ const names = [];
2905
+ const values = [];
2906
+ for (const attribute of attributes) {
2907
+ const name = attributeName(attribute);
2908
+ const value = attribute.value;
2909
+ const expression =
2910
+ value === null
2911
+ ? inheritGeneratedOrigin(b.literal(true), attribute)
2912
+ : value.type === 'Literal'
2913
+ ? inheritGeneratedOrigin(b.literal(value.value), value)
2914
+ : mainThreadHostValueAst(name, value.expression, state);
2915
+ names.push(name);
2916
+ values.push(expression);
2917
+ }
2918
+ const helper = (state.helpers.hostComponentLeafPlan ??= allocName(
2919
+ state,
2920
+ '__octaneUniversalHostComponentLeafPlan',
2921
+ ));
2922
+ const signature = JSON.stringify(names);
2923
+ return {
2924
+ plan: generatedCall(
2925
+ helper,
2926
+ [b.literal(state.renderer.id), jsxNameExpressionAst(component, state), b.literal(signature)],
2927
+ component,
2928
+ ),
2929
+ render: generatedArrow(
2930
+ [itemBinding, indexBinding],
2931
+ inheritGeneratedOrigin(b.array(values), component),
2932
+ component,
2933
+ ),
2934
+ signature: inheritGeneratedOrigin(b.literal(signature), component),
2935
+ };
2936
+ }
2937
+
2713
2938
  function compileForAst(node, context, state) {
2714
2939
  if (node.await) {
2715
2940
  throw universalError(
@@ -2730,18 +2955,43 @@ function compileForAst(node, context, state) {
2730
2955
  node.index ?? generatedIdentifier(allocName(state, '__octaneUniversalIndex'), node);
2731
2956
  assertNoResidualTemplate(node.right, state, '@for source');
2732
2957
  assertNoResidualTemplate(node.key, state, '@for key');
2958
+ const host = !state.hmr ? ownerFreeForHost(node) : null;
2959
+ const component = host === null ? ownerFreeForThreeHostComponent(node, state) : null;
2960
+ const compactHost =
2961
+ host === null ? null : compileOwnerFreeForHostAst(host, state, itemBinding, indexBinding);
2962
+ const compactComponent =
2963
+ component === null
2964
+ ? null
2965
+ : compileOwnerFreeThreeHostComponentAst(component, state, itemBinding, indexBinding);
2733
2966
  const args = [
2734
2967
  rewriteSourceAst(node.right, state),
2735
2968
  generatedArrow([itemBinding, indexBinding], rewriteSourceAst(node.key, state), node.key),
2736
- compileBlockValueAst(
2737
- node.body?.body ?? [],
2738
- state,
2739
- [itemBinding, indexBinding],
2740
- node.body ?? node,
2741
- ),
2969
+ compactHost?.render ??
2970
+ compactComponent?.render ??
2971
+ compileBlockValueAst(
2972
+ node.body?.body ?? [],
2973
+ state,
2974
+ [itemBinding, indexBinding],
2975
+ node.body ?? node,
2976
+ ),
2742
2977
  ];
2743
- if (!state.hmr && ownerFreeForHost(node) !== null) {
2744
- args.push(b.literal(null, 'null'), b.literal(true), b.literal(true));
2978
+ if (host !== null) {
2979
+ args.push(
2980
+ b.literal(null, 'null'),
2981
+ b.literal(true),
2982
+ b.literal(true),
2983
+ inheritGeneratedOrigin(b.unary('void', b.literal(0)), host),
2984
+ compactHost.plan,
2985
+ );
2986
+ } else if (component !== null) {
2987
+ args.push(
2988
+ b.literal(null, 'null'),
2989
+ b.literal(true),
2990
+ b.literal(true),
2991
+ jsxNameExpressionAst(component, state),
2992
+ compactComponent.plan,
2993
+ compactComponent.signature,
2994
+ );
2745
2995
  } else if (node.empty) {
2746
2996
  args.push(compileBlockValueAst(node.empty?.body ?? [], state, [], node.empty));
2747
2997
  }
@@ -2828,6 +3078,16 @@ function compileChildAst(node, context, state) {
2828
3078
  }
2829
3079
  if (node.type === 'JSXExpressionContainer') {
2830
3080
  if (!node.expression || node.expression.type === 'JSXEmptyExpression') return [];
3081
+ // A string-literal child is authored text with braces around it: fold it
3082
+ // into the plan like JSXText, so the constant stops riding every render's
3083
+ // slot array. Renderers without host text keep the renderable-hole slot.
3084
+ if (
3085
+ node.expression.type === 'Literal' &&
3086
+ typeof node.expression.value === 'string' &&
3087
+ state.renderer.text === 'host'
3088
+ ) {
3089
+ return [withPlanOrigin({ kind: 'text', value: node.expression.value }, node)];
3090
+ }
2831
3091
  return [addDynamicAst(context, dynamicExpressionAst(node.expression, state))];
2832
3092
  }
2833
3093
  if (node.type === 'JSXElement' || node.type === 'Element') {
@@ -3048,6 +3308,9 @@ function universalHelperImportAst(state, extraPairs = [], origin = null) {
3048
3308
  ['universalPlan', state.helpers.plan],
3049
3309
  ['universalValue', state.helpers.value],
3050
3310
  ['universalComponent', state.helpers.nestedComponent],
3311
+ ...(state.helpers.hostComponentLeafPlan === undefined
3312
+ ? []
3313
+ : [['universalHostComponentLeafPlan', state.helpers.hostComponentLeafPlan]]),
3051
3314
  ['universalProps', state.helpers.props],
3052
3315
  ['universalIf', state.helpers.if],
3053
3316
  ['universalSwitch', state.helpers.switch],
@@ -3696,6 +3959,11 @@ export function compileUniversal(
3696
3959
  componentNames: collectComponentNames(ast),
3697
3960
  runtimeImports: new Map(),
3698
3961
  };
3962
+ state.ownerFreeThreeHostComponents = collectOwnerFreeThreeHostComponents(
3963
+ ast,
3964
+ state,
3965
+ options.dev === true,
3966
+ );
3699
3967
  state.helpers.component = allocName(state, '__octaneDefineUniversalComponent');
3700
3968
  state.helpers.plan = allocName(state, '__octaneUniversalPlan');
3701
3969
  state.helpers.value = allocName(state, '__octaneUniversalValue');
@@ -1309,10 +1309,10 @@ function annotatePureLazyCalls(ast) {
1309
1309
  }
1310
1310
 
1311
1311
  /**
1312
- * Lower the exact imported `<ErrorBoundary>` builtin to the same tryBlock IR
1313
- * as `@try/@catch` when its fallback is statically compilable. This removes the
1314
- * generic component/children dispatcher while preserving the public JSX API.
1315
- * Dynamic props, spreads, keys, and shadowed imports stay on the runtime path.
1312
+ * Lower the exact imported `<ErrorBoundary>` builtin to catch-only boundary IR
1313
+ * when its fallback is statically compilable. Client output uses errorBlock;
1314
+ * server output keeps the existing ssrTry wire contract. Dynamic props,
1315
+ * spreads, keys, and shadowed imports stay on the generic runtime path.
1316
1316
  */
1317
1317
  function lowerImportedErrorBoundaries(ast) {
1318
1318
  const boundaryLocals = new Set();
@@ -7515,7 +7515,11 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
7515
7515
  // `const el = <App/>`) to createElement(...) before printing — esrap
7516
7516
  // can't print raw JSX, and this is what makes root.render(<App/>) match
7517
7517
  // React's shape.
7518
- const lowered = stampAnonymousDefaultFunctionLoc(rewriteModuleJsxValues(hooked, ctx), ctx);
7518
+ const lowered = markSingleRootMemoInitializers(
7519
+ stampAnonymousDefaultFunctionLoc(rewriteModuleJsxValues(hooked, ctx), ctx),
7520
+ ctx,
7521
+ memoImportNames,
7522
+ );
7519
7523
  // Top-level passthrough (imports, plain consts/functions): already a
7520
7524
  // rewritten statement node — embedded directly in the module AST.
7521
7525
  bodyNodes.push(lowered);
@@ -10942,6 +10946,43 @@ function singleRootInitializer(ctx, component) {
10942
10946
  return markPure(b.call('_$__s', component));
10943
10947
  }
10944
10948
 
10949
+ // An exact public memo wrapper preserves the already-proven host output of its
10950
+ // immutable local component. Stamp only the fresh compiler-owned wrapper:
10951
+ // probing arbitrary component metadata would invoke observable getters, and
10952
+ // dev/HMR, custom comparators, imported components, and renderer units remain
10953
+ // deliberately opaque.
10954
+ function markSingleRootMemoInitializers(node, ctx, memoImportNames) {
10955
+ if (ctx.hmr || ctx.dev || ctx.profile || memoImportNames.size === 0) return node;
10956
+ const exported = node.type === 'ExportNamedDeclaration';
10957
+ const declaration = exported ? node.declaration : node;
10958
+ if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') return node;
10959
+ let changed = false;
10960
+ const declarations = declaration.declarations.map((item) => {
10961
+ const init = item.init;
10962
+ const wrapped = init?.arguments?.[0];
10963
+ if (
10964
+ item.id?.type !== 'Identifier' ||
10965
+ !ctx.defaultMemoBindings.has(item.id.name) ||
10966
+ init?.type !== 'CallExpression' ||
10967
+ init.callee?.type !== 'Identifier' ||
10968
+ !memoImportNames.has(init.callee.name) ||
10969
+ init.arguments.length !== 1 ||
10970
+ wrapped?.type !== 'Identifier' ||
10971
+ !ctx.moduleFunctionDeclarations.has(wrapped.name) ||
10972
+ ctx.componentInfo.get(wrapped.name)?.singleRoot !== true ||
10973
+ ctx._universalRuntimeUnitsByBinding.has(item.id.name) ||
10974
+ ctx._universalRuntimeUnitsByBinding.has(wrapped.name)
10975
+ ) {
10976
+ return item;
10977
+ }
10978
+ changed = true;
10979
+ return { ...item, init: inheritOriginLoc(singleRootInitializer(ctx, init), init) };
10980
+ });
10981
+ if (!changed) return node;
10982
+ const next = { ...declaration, declarations };
10983
+ return exported ? { ...node, declaration: next } : next;
10984
+ }
10985
+
10945
10986
  function finalizeComponentInitializers(ctx, bodyNodes) {
10946
10987
  if (!ctx.componentEffectOwnership || ctx.componentOwners.length === 0) return;
10947
10988
 
@@ -18807,8 +18848,11 @@ function planJsx(
18807
18848
  for (const tc of tryCalls) {
18808
18849
  const slotIndex = tc.slotIndex;
18809
18850
  const org = tc.origin ?? planOrigin;
18810
- ctx.runtimeNeeded.add('tryBlock');
18811
- registerDirectiveOrigin(ctx, org, ['_$tryBlock', tc.tryHelper]);
18851
+ const catchOnly =
18852
+ tc.propagateSuspense && tc.pendingHelper === 'null' && tc.catchHelper !== 'null';
18853
+ const boundaryHelper = catchOnly ? 'errorBlock' : 'tryBlock';
18854
+ ctx.runtimeNeeded.add(boundaryHelper);
18855
+ registerDirectiveOrigin(ctx, org, [rtAlias(boundaryHelper), tc.tryHelper]);
18812
18856
  registerClauseOrigin(ctx, tc.handlerKeyword, [tc.catchHelper]);
18813
18857
  registerClauseOrigin(ctx, tc.pendingKeyword, [tc.pendingHelper]);
18814
18858
  // Anchor selection — see anchorNodeFor (mirrors ifBlock, including the
@@ -18816,24 +18860,25 @@ function planJsx(
18816
18860
  const tryAnchor = anchorNodeFor(tc, 'tryAnchor');
18817
18861
  const tryEnv = envNodeFor(tc);
18818
18862
  const trailing = [];
18819
- if (tryAnchor || tryEnv || tc.propagateSuspense) trailing.push(tryAnchor || undefinedNode());
18820
- if (tryEnv || tc.propagateSuspense) trailing.push(tryEnv || undefinedNode());
18821
- if (tc.propagateSuspense) trailing.push(b.literal(true));
18863
+ if (tryAnchor || tryEnv || (!catchOnly && tc.propagateSuspense)) {
18864
+ trailing.push(tryAnchor || undefinedNode());
18865
+ }
18866
+ if (tryEnv || (!catchOnly && tc.propagateSuspense)) trailing.push(tryEnv || undefinedNode());
18867
+ if (!catchOnly && tc.propagateSuspense) trailing.push(b.literal(true));
18868
+ const boundaryArgs = [
18869
+ b.id('__s'),
18870
+ b.literal(slotIndex),
18871
+ hostNodeFor(`_tryHost$${tc.id}`),
18872
+ helperRefNode(tc.tryHelper),
18873
+ inheritOriginLoc(helperRefNode(tc.catchHelper), tc.handlerKeyword),
18874
+ ];
18875
+ if (!catchOnly) {
18876
+ boundaryArgs.push(inheritOriginLoc(helperRefNode(tc.pendingHelper), tc.pendingKeyword));
18877
+ }
18822
18878
  pushAfterStmt(
18823
18879
  tc.id,
18824
18880
  org,
18825
- b.stmt(
18826
- b.call(
18827
- '_$tryBlock',
18828
- b.id('__s'),
18829
- b.literal(slotIndex),
18830
- hostNodeFor(`_tryHost$${tc.id}`),
18831
- helperRefNode(tc.tryHelper),
18832
- inheritOriginLoc(helperRefNode(tc.catchHelper), tc.handlerKeyword),
18833
- inheritOriginLoc(helperRefNode(tc.pendingHelper), tc.pendingKeyword),
18834
- ...trailing,
18835
- ),
18836
- ),
18881
+ b.stmt(b.call(rtAlias(boundaryHelper), ...boundaryArgs, ...trailing)),
18837
18882
  );
18838
18883
  }
18839
18884
  for (const sc of ctx._switchCalls) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { version } from './version.js';
2
2
  export { initializeHydrationEventCapture } from './hydration/event-capture.js';
3
- export { createRoot, hydrateRoot, flushSync, act, type Root, type RootOptions, useState, useLinkedState, type LinkedStatePrevious, type LinkedStateOptions, useReducer, useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useRef, useId, useImperativeHandle, useEffectEvent, useSyncExternalStore, useDeferredValue, useTransition, useActionState, useFormStatus, useOptimistic, useDebugValue, type FormStatus, startTransition, requestFormReset, memo, lazy, preload, preinit, preconnect, prefetchDNS, createContext, use, useContext, type Context, type ForeignHostContext, Suspense, ErrorBoundary, Hydrate, Activity, ViewTransition, addTransitionType, ViewTransition as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, ViewTransitionPseudoElement, type ViewTransitionProps, type ViewTransitionInstance, Fragment, createPortal, type PortalDescriptor, createElement, cloneElement, isValidElement, isChildrenBlock, Children, type ElementDescriptor, type ComponentBody, type OctaneNode, TsrxErrorBoundary, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, __createVoidRoot, bindRendererRegionOwner, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, createHostContextRequest, __vtSeen, template, clone, drainFrag, bag0, bag1, bag2, bag3, bag4, bag5, bag6, bag7, bag8, bag9, bag10, bag11, bag12, bag13, bag14, bag15, bag16, bagOf, evt0, evt0u, evt1, evt1u, evt2, evt2u, evtN, evtNu, devEventListener, htext, htextSwap, child, sibling, setText, setScriptText, setHTML, setDangerouslySetInnerHTML, setDangerouslySetInnerHTMLSources, markDangerouslySetInnerHTMLChildren, setAttribute, setStringData, setBooleanAttribute, setAriaAttribute, setClassName, setClassAttr, normalizeClass, setStyle, setSpread, snapshotSpread, setHostPropSources, queueNativeChangeDiagnostic, markNativeChangeDiagnosticStatic, setFormAction, setValue, setFormControlSources, setChecked, setCheckedCheckable, setSelectValue, setDefaultValue, setDefaultValueUncontrolled, setDefaultChecked, setAutoFocus, attachRef, queueRefAttach, queueRefDetach, injectStyle, headBlock, namespaceHead, namespaceHeadElement, delegateEvents, delegateCaptureEvents, fastForBlock, fastKeyedForBlock, fastMapSlot, forBlock, keyedForBlock, mapSlot, ifBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, compilerCacheArray, compilerCacheContext, markSingleRoot, markSingleRoot as __s, markChildrenBlock, createScopedValue, createScopedElement, childSlot, positionalChildren, textSlot, textHole, childTextHole, hostComponent, renderBlock, portal, hookSlots, withSlot, useBatch, warmMemo, warmChild, puMiss, puTake0, puTake1, puTake2, puTake3, puTake4, puPub, provideContext, mountFragmentRef, FragmentInstance, hmr, HMR, hasPendingWork, type Scope, type Block, drainPassiveEffects, setIsOctaneActEnvironment, setTransitionFallbackTimeout, getTransitionFallbackTimeout, } from './runtime.js';
3
+ export { createRoot, hydrateRoot, flushSync, act, type Root, type RootOptions, useState, useLinkedState, type LinkedStatePrevious, type LinkedStateOptions, useReducer, useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useRef, useId, useImperativeHandle, useEffectEvent, useSyncExternalStore, useDeferredValue, useTransition, useActionState, useFormStatus, useOptimistic, useDebugValue, type FormStatus, startTransition, requestFormReset, memo, lazy, preload, preinit, preconnect, prefetchDNS, createContext, use, useContext, type Context, type ForeignHostContext, Suspense, ErrorBoundary, Hydrate, Activity, ViewTransition, addTransitionType, ViewTransition as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, ViewTransitionPseudoElement, type ViewTransitionProps, type ViewTransitionInstance, Fragment, createPortal, type PortalDescriptor, createElement, cloneElement, isValidElement, isChildrenBlock, Children, type ElementDescriptor, type ComponentBody, type OctaneNode, TsrxErrorBoundary, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, __createVoidRoot, bindRendererRegionOwner, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, createHostContextRequest, __vtSeen, template, clone, drainFrag, bag0, bag1, bag2, bag3, bag4, bag5, bag6, bag7, bag8, bag9, bag10, bag11, bag12, bag13, bag14, bag15, bag16, bagOf, evt0, evt0u, evt1, evt1u, evt2, evt2u, evtN, evtNu, devEventListener, htext, htextSwap, child, sibling, setText, setScriptText, setHTML, setDangerouslySetInnerHTML, setDangerouslySetInnerHTMLSources, markDangerouslySetInnerHTMLChildren, setAttribute, setStringData, setBooleanAttribute, setAriaAttribute, setClassName, setClassAttr, normalizeClass, setStyle, setSpread, snapshotSpread, setHostPropSources, queueNativeChangeDiagnostic, markNativeChangeDiagnosticStatic, setFormAction, setValue, setFormControlSources, setChecked, setCheckedCheckable, setSelectValue, setDefaultValue, setDefaultValueUncontrolled, setDefaultChecked, setAutoFocus, attachRef, queueRefAttach, queueRefDetach, injectStyle, headBlock, namespaceHead, namespaceHeadElement, delegateEvents, delegateCaptureEvents, fastForBlock, fastKeyedForBlock, fastMapSlot, forBlock, keyedForBlock, mapSlot, ifBlock, errorBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, compilerCacheArray, compilerCacheContext, markSingleRoot, markSingleRoot as __s, markChildrenBlock, createScopedValue, createScopedElement, childSlot, positionalChildren, textSlot, textHole, childTextHole, hostComponent, renderBlock, portal, hookSlots, withSlot, useBatch, warmMemo, warmChild, puMiss, puTake0, puTake1, puTake2, puTake3, puTake4, puPub, provideContext, mountFragmentRef, FragmentInstance, hmr, HMR, hasPendingWork, type Scope, type Block, drainPassiveEffects, setIsOctaneActEnvironment, setTransitionFallbackTimeout, getTransitionFallbackTimeout, } from './runtime.js';
4
4
  export type { HydrateOptions, HydrateProps, HydrateWhen, HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategy, HydrationWhen, } from './hydration/types.js';
5
5
  export { __serverRpc } from './server-rpc-client.js';
6
6
  export { __methodDep } from './method-dep.js';
package/dist/index.js CHANGED
@@ -140,6 +140,7 @@ import {
140
140
  keyedForBlock,
141
141
  mapSlot,
142
142
  ifBlock,
143
+ errorBlock,
143
144
  tryBlock,
144
145
  switchBlock,
145
146
  activityBlock,
@@ -253,6 +254,7 @@ export {
253
254
  devEventListener,
254
255
  drainFrag,
255
256
  drainPassiveEffects,
257
+ errorBlock,
256
258
  evt0,
257
259
  evt0u,
258
260
  evt1,
package/dist/runtime.d.ts CHANGED
@@ -1352,6 +1352,13 @@ export declare const HMR: unique symbol;
1352
1352
  export declare function hmr<P>(fn: ComponentBody<P>): ComponentBody<P>;
1353
1353
  export declare function setTransitionFallbackTimeout(ms: number): void;
1354
1354
  export declare function getTransitionFallbackTimeout(): number;
1355
+ /**
1356
+ * Exact imported JSX ErrorBoundary lowering. Unlike @try, these boundaries
1357
+ * only catch application errors: suspension belongs to an enclosing Suspense.
1358
+ * Keeping their state independent lets catch-only applications discard the
1359
+ * hidden-primary, transition-hold, and off-screen rendering implementations.
1360
+ */
1361
+ export declare function errorBlock(parentScope: Scope, slotKey: number, domParent: Node, tryBody: ComponentBody, catchBody: ComponentBody, anchor?: Node | null, env?: any[]): () => void;
1355
1362
  export declare function tryBlock(parentScope: Scope, slotKey: number, domParent: Node, tryBody: ComponentBody, catchBody: ComponentBody | null, pendingBody: ComponentBody | null, anchor?: Node | null, env?: any[], propagateSuspense?: boolean): () => void;
1356
1363
  export declare function startTransition(fn: () => void | Promise<unknown>): void;
1357
1364
  export declare function useTransition(slot?: symbol): [boolean, (fn: () => void | Promise<unknown>) => void];