octane 0.13.3 → 0.13.5

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.
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  version: () => version
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const version = "0.13.3";
24
+ const version = "0.13.5";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  version
@@ -3872,7 +3872,9 @@ const CALCULATION_VALUE_PROOFS = [
3872
3872
  * a pure projection from template evaluation to the end of setup is
3873
3873
  * unobservable. Positions whose evaluation is conditional or deferred keep
3874
3874
  * their expression: directive arms, component children (the child decides
3875
- * whether and when they render), and built-in boundaries. An expression holding
3875
+ * whether and when they render), and built-in boundaries. Already-evaluated
3876
+ * named literals can still be read at eligible props inside component children;
3877
+ * only their identifier reads are collected there. An expression holding
3876
3878
  * JSX keeps its list and descriptor lowering, and event handlers and keys keep
3877
3879
  * their wiring. Ref arrays use the same identity proof as props. A render tree that writes to anything keeps its whole
3878
3880
  * evaluation order.
@@ -3880,34 +3882,34 @@ const CALCULATION_VALUE_PROOFS = [
3880
3882
  * Returns null when nothing moves; otherwise the copy-on-write render nodes,
3881
3883
  * the declarations to append, and their names.
3882
3884
  */
3883
- function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates) {
3885
+ function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates, literalsOnly = false) {
3884
3886
  if ((!ctx.strongMemo && !ctx.strongLiteralMemo) || ctx.nativeReads || !ctx.currentComponentLocals)
3885
3887
  return null;
3886
3888
  if (renderTreeWrites(jsxNodes)) return null;
3887
3889
  const declarations = [];
3888
3890
  const names = [];
3889
3891
  const literalReads = new Map();
3890
- const nodes = mapNodes(jsxNodes, visitNode);
3892
+ const nodes = mapNodes(jsxNodes, (node) => visitNode(node, false));
3891
3893
  return declarations.length === 0 && literalReads.size === 0
3892
3894
  ? null
3893
3895
  : { jsxNodes: nodes, declarations, names, literalReads };
3894
3896
 
3895
- function visitNode(node) {
3897
+ function visitNode(node, deferred) {
3896
3898
  switch (node?.type) {
3897
3899
  case 'JSXElement':
3898
- return visitElement(node);
3900
+ return visitElement(node, deferred);
3899
3901
  case 'JSXFragment': {
3900
- const children = mapNodes(node.children, visitNode);
3902
+ const children = mapNodes(node.children, (child) => visitNode(child, deferred));
3901
3903
  return children === node.children ? node : { ...node, children };
3902
3904
  }
3903
3905
  case 'JSXExpressionContainer':
3904
- return visitContainer(node);
3906
+ return deferred ? node : visitContainer(node);
3905
3907
  default:
3906
3908
  return node;
3907
3909
  }
3908
3910
  }
3909
3911
 
3910
- function visitElement(node) {
3912
+ function visitElement(node, deferred) {
3911
3913
  const opening = node.openingElement;
3912
3914
  const tag = opening?.name;
3913
3915
  const component = isComponentTag(node);
@@ -3942,11 +3944,12 @@ function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates) {
3942
3944
  return attribute;
3943
3945
  if (!component && /^on[A-Z]/.test(name)) return attribute;
3944
3946
  if (attribute.value?.type !== 'JSXExpressionContainer') return attribute;
3945
- const value = visitContainer(attribute.value, true);
3947
+ const value = visitContainer(attribute.value, true, deferred);
3946
3948
  return value === attribute.value ? attribute : { ...attribute, value };
3947
3949
  });
3948
- // Component children render inside the child, if at all.
3949
- const children = component ? node.children : mapNodes(node.children, visitNode);
3950
+ // Component children own evaluation. Discover named reads without moving
3951
+ // any expression from those children into the parent's render.
3952
+ const children = mapNodes(node.children, (child) => visitNode(child, deferred || component));
3950
3953
  if (attributes === opening.attributes && children === node.children) return node;
3951
3954
  return {
3952
3955
  ...node,
@@ -3955,7 +3958,7 @@ function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates) {
3955
3958
  };
3956
3959
  }
3957
3960
 
3958
- function visitContainer(container, prop = false) {
3961
+ function visitContainer(container, prop = false, deferred = false) {
3959
3962
  const expression = container.expression;
3960
3963
  const inner = unwrapTsExpr(expression);
3961
3964
  if (!inner || inner.type === 'JSXEmptyExpression' || containsJsxNode(inner)) return container;
@@ -3964,6 +3967,7 @@ function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates) {
3964
3967
  if (!nodes) literalReads.set(inner.name, (nodes = new Set()));
3965
3968
  nodes.add(inner);
3966
3969
  }
3970
+ if (deferred) return container;
3967
3971
  const calculation = autoCalculationOf(
3968
3972
  inner,
3969
3973
  null,
@@ -3973,7 +3977,7 @@ function hoistStrongRenderCalculations(jsxNodes, ctx, immutableStates) {
3973
3977
  );
3974
3978
  if (
3975
3979
  calculation === null ||
3976
- (!ctx.strongMemo && !calculation.literal) ||
3980
+ ((literalsOnly || !ctx.strongMemo) && !calculation.literal) ||
3977
3981
  (calculation.literal && !prop)
3978
3982
  ) {
3979
3983
  return container;
@@ -17920,9 +17924,11 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
17920
17924
  let warmThunk = null;
17921
17925
  let staticChildOnly = false;
17922
17926
  let hoistedCalculations = null;
17923
- if (options && options.autoCallback) {
17924
- const creations = [];
17925
- const warmChildren = [];
17927
+ const componentBody = options?.autoCallback === true;
17928
+ const literalsOnly = !componentBody && options?.memoizeRenderLiterals === true;
17929
+ const creations = [];
17930
+ const warmChildren = [];
17931
+ if (componentBody) {
17926
17932
  // Pass A′ first: use()-fed const chains memoize at their declarations,
17927
17933
  // so Pass A then sees their trivial identifier arguments as already
17928
17934
  // stable and the warm plan includes the chain heads. Param names feed
@@ -17938,62 +17944,75 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
17938
17944
  creations,
17939
17945
  puParamNames,
17940
17946
  );
17941
- // Auto-calculation runs AFTER Pass A′ so use()-fed chains keep their
17942
- // Symbol-slot treatment (their rewritten inits are hook-shaped and are
17943
- // skipped here), and BEFORE Pass A/B so the numeric-slot useMemo it mints is
17944
- // lowered by the authored inline tier rather than re-wrapped. Client only:
17945
- // a server render evaluates each body once, so a cache is pure overhead.
17946
- if (ctx.currentComponentLocals && ctx.mode !== 'server') {
17947
- // Strong inline render expressions become calculations too (#1610). A
17948
- // returned-output body lowers its JSX into a record whose holes may be
17949
- // evaluated later, so it keeps them inline.
17950
- const hoisted = returnedOutput
17951
- ? null
17952
- : hoistStrongRenderCalculations(jsxNodes, ctx, immutableStateProvenance);
17953
- if (hoisted !== null) {
17954
- jsxNodes = hoisted.jsxNodes;
17955
- hoistedCalculations = hoisted.declarations;
17956
- workingStatements = [...workingStatements, ...hoistedCalculations];
17957
- ctx.currentComponentLocals = new Set([...ctx.currentComponentLocals, ...hoisted.names]);
17958
- }
17959
- const renderReadNames = closeCalculationReads(
17960
- workingStatements,
17961
- collectRenderReadNames(jsxNodes, ctx),
17962
- ctx.currentComponentLocals,
17963
- ctx,
17964
- immutableStateProvenance,
17965
- hoisted?.literalReads,
17966
- jsxNodes,
17967
- );
17968
- if (renderReadNames.size > 0) {
17969
- workingStatements = workingStatements.map((statement) => {
17970
- const rewritten = rewriteAutoCalculation(
17947
+ }
17948
+ // Auto-calculation runs AFTER Pass A′ so use()-fed chains keep their
17949
+ // Symbol-slot treatment (their rewritten inits are hook-shaped and are
17950
+ // skipped here), and BEFORE Pass A/B so the numeric-slot useMemo it mints is
17951
+ // lowered by the authored inline tier rather than re-wrapped. Client only:
17952
+ // a server render evaluates each body once, so a cache is pure overhead.
17953
+ if (
17954
+ (componentBody || (literalsOnly && ctx.strongLiteralMemo)) &&
17955
+ ctx.currentComponentLocals &&
17956
+ ctx.mode !== 'server'
17957
+ ) {
17958
+ // Strong inline render expressions become calculations too (#1610). A
17959
+ // returned-output body lowers its JSX into a record whose holes may be
17960
+ // evaluated later, so it keeps them inline.
17961
+ const hoisted = returnedOutput
17962
+ ? null
17963
+ : hoistStrongRenderCalculations(jsxNodes, ctx, immutableStateProvenance, literalsOnly);
17964
+ if (hoisted !== null) {
17965
+ jsxNodes = hoisted.jsxNodes;
17966
+ hoistedCalculations = hoisted.declarations;
17967
+ workingStatements = [...workingStatements, ...hoistedCalculations];
17968
+ ctx.currentComponentLocals = new Set([...ctx.currentComponentLocals, ...hoisted.names]);
17969
+ }
17970
+ const renderReadNames = closeCalculationReads(
17971
+ workingStatements,
17972
+ collectRenderReadNames(jsxNodes, ctx),
17973
+ ctx.currentComponentLocals,
17974
+ ctx,
17975
+ immutableStateProvenance,
17976
+ hoisted?.literalReads,
17977
+ jsxNodes,
17978
+ );
17979
+ if (renderReadNames.size > 0) {
17980
+ workingStatements = workingStatements.map((statement) => {
17981
+ // A construct helper inherits only the literal cache. Ordinary
17982
+ // calculations and callback stability still require component ownership.
17983
+ if (literalsOnly) {
17984
+ const init = unwrapTsExpr(calculationDeclarator(statement)?.init);
17985
+ if (init?.type !== 'ObjectExpression' && init?.type !== 'ArrayExpression')
17986
+ return statement;
17987
+ }
17988
+ const rewritten = rewriteAutoCalculation(
17989
+ statement,
17990
+ ctx.currentComponentLocals,
17991
+ renderReadNames,
17992
+ ctx,
17993
+ immutableStateProvenance,
17994
+ );
17995
+ if (
17996
+ rewritten !== statement &&
17997
+ rewritten.declarations[0].init.callee._octaneImmutableArrayFilter !== undefined
17998
+ ) {
17999
+ hasImmutableArrayProjection = true;
18000
+ }
18001
+ if (ctx.autoMemo && rewritten !== statement) {
18002
+ (autoCalculatedDeclarations ??= new Map()).set(
18003
+ statement.declarations[0].id.name,
17971
18004
  statement,
17972
- ctx.currentComponentLocals,
17973
- renderReadNames,
17974
- ctx,
17975
- immutableStateProvenance,
17976
- );
17977
- if (
17978
- rewritten !== statement &&
17979
- rewritten.declarations[0].init.callee._octaneImmutableArrayFilter !== undefined
17980
- ) {
17981
- hasImmutableArrayProjection = true;
17982
- }
17983
- if (ctx.autoMemo && rewritten !== statement) {
17984
- (autoCalculatedDeclarations ??= new Map()).set(
17985
- statement.declarations[0].id.name,
17986
- statement,
17987
- );
17988
- }
17989
- return rewritten;
17990
- });
17991
- if (ctx.nativeReads)
17992
- workingStatements = workingStatements.flatMap(
17993
- (statement) => lowerNativeAutoCalculation(statement, ctx, name) ?? [statement],
17994
18005
  );
17995
- }
18006
+ }
18007
+ return rewritten;
18008
+ });
18009
+ if (ctx.nativeReads)
18010
+ workingStatements = workingStatements.flatMap(
18011
+ (statement) => lowerNativeAutoCalculation(statement, ctx, name) ?? [statement],
18012
+ );
17996
18013
  }
18014
+ }
18015
+ if (componentBody) {
17997
18016
  workingStatements = parallelUseMemoizePass(workingStatements, ctx, name, creations, [], null);
17998
18017
  jsxNodes = parallelUseWalkJsx(jsxNodes, ctx, name, creations, warmChildren, [], new Set());
17999
18018
  const warm = buildWarmArtifacts(node, ctx, name, creations, warmChildren);
@@ -33741,6 +33760,9 @@ function hoistBodyHelper(
33741
33760
  if (!free.has('arguments') && !free.has('eval')) inheritedEnvNames = envNames;
33742
33761
  }
33743
33762
  const helperOptions = {
33763
+ // Only construct blocks provide an entered render scope. Component
33764
+ // children have envNames === null and may be evaluated by their caller.
33765
+ memoizeRenderLiterals: envNames !== null && !mappedItem,
33744
33766
  keyedSelection,
33745
33767
  inlineBindingGuards,
33746
33768
  mappedItem,
@@ -1,4 +1,4 @@
1
- const version = "0.13.3";
1
+ const version = "0.13.5";
2
2
  export {
3
3
  version
4
4
  };
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.13.3";
1
+ const version = "0.13.5";
2
2
  export {
3
3
  version
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.13.3",
3
+ "version": "0.13.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": [