octane 0.1.20 → 0.1.22
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/compiler/compile.js +265 -51
- package/dist/runtime.js +277 -13
- package/package.json +4 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -3936,6 +3936,78 @@ function containsAutoMemoUnsafeStructure(stmts) {
|
|
|
3936
3936
|
return found;
|
|
3937
3937
|
}
|
|
3938
3938
|
|
|
3939
|
+
/**
|
|
3940
|
+
* Bundling MOVES an argument's evaluation out of the arrow body and into the
|
|
3941
|
+
* component body, so it now runs on mount and on every update instead of once
|
|
3942
|
+
* per event. That rewrite is only sound for an expression that is
|
|
3943
|
+
* side-effect-free and O(1)-ish to evaluate: `() => setData(makeData(1000))`
|
|
3944
|
+
* otherwise rebuilds the whole dataset on every unrelated render, and the user
|
|
3945
|
+
* never sees a call they can attribute to the click that did not happen.
|
|
3946
|
+
*
|
|
3947
|
+
* The whitelist is deliberately narrower than "pure", for two reasons.
|
|
3948
|
+
*
|
|
3949
|
+
* A fresh identity per evaluation defeats the runtime arg diff the bundle
|
|
3950
|
+
* exists for — an `ArrayExpression`/`ObjectExpression`/arrow arg can never
|
|
3951
|
+
* compare equal, so it would pay a per-render allocation to skip nothing.
|
|
3952
|
+
* Regex is rejected for the same reason `isInvariantLiteral` rejects it.
|
|
3953
|
+
*
|
|
3954
|
+
* The line is drawn at VALUE STABILITY, not at "provably pure". An accepted
|
|
3955
|
+
* expression must yield at render time what it would have yielded at click
|
|
3956
|
+
* time, and must not do unbounded or author-visible work to get there. A
|
|
3957
|
+
* property read can reach a getter and `a + b` can reach `valueOf` — but that
|
|
3958
|
+
* is the standing premise of the optimization, not a new risk: `select(row.id)`
|
|
3959
|
+
* is the shape it was built for, and refusing property reads would leave it
|
|
3960
|
+
* with nothing to optimize. Refusing arithmetic while accepting `row.id` would
|
|
3961
|
+
* draw the same line in two places, so both stay.
|
|
3962
|
+
*
|
|
3963
|
+
* What cannot stay are the expressions that break value stability outright:
|
|
3964
|
+
* a CALL does unbounded work and can be observed happening (`makeData(1000)`
|
|
3965
|
+
* rebuilt a whole dataset per render), a fresh array/object/regex allocates an
|
|
3966
|
+
* identity that can never compare equal, and an assignment or `++` mutates.
|
|
3967
|
+
*
|
|
3968
|
+
* `.current` is rejected because a ref genuinely returns the WRONG VALUE here,
|
|
3969
|
+
* not merely an early one: `queueRefAttach` runs AFTER the mount that reads it,
|
|
3970
|
+
* so a hoisted `ref.current` hands the first click the `null` it held before
|
|
3971
|
+
* the ref was attached. Computed members fail closed for the same reason —
|
|
3972
|
+
* `ref[key]` can spell `current` without saying so, and
|
|
3973
|
+
* `isAutoMemoCalculationDependency` already refuses every computed key.
|
|
3974
|
+
*/
|
|
3975
|
+
function isDeferralSafeBundleArg(node) {
|
|
3976
|
+
const value = unwrapTsExpr(node);
|
|
3977
|
+
if (!value) return false;
|
|
3978
|
+
switch (value.type) {
|
|
3979
|
+
case 'Literal':
|
|
3980
|
+
return isInvariantLiteral(value);
|
|
3981
|
+
case 'Identifier':
|
|
3982
|
+
return true;
|
|
3983
|
+
case 'ChainExpression':
|
|
3984
|
+
return isDeferralSafeBundleArg(value.expression);
|
|
3985
|
+
case 'MemberExpression':
|
|
3986
|
+
// Computed keys fail closed: the key is only known at runtime, so
|
|
3987
|
+
// `ref[k]` can reach `.current` without naming it.
|
|
3988
|
+
if (value.computed) return false;
|
|
3989
|
+
if (value.property?.name === 'current') return false;
|
|
3990
|
+
return isDeferralSafeBundleArg(value.object);
|
|
3991
|
+
case 'TemplateLiteral':
|
|
3992
|
+
// A fresh string still compares by VALUE, so the arg diff works.
|
|
3993
|
+
return (value.expressions || []).every(isDeferralSafeBundleArg);
|
|
3994
|
+
case 'UnaryExpression':
|
|
3995
|
+
// `delete` mutates; the rest only read their operand.
|
|
3996
|
+
return value.operator !== 'delete' && isDeferralSafeBundleArg(value.argument);
|
|
3997
|
+
case 'BinaryExpression':
|
|
3998
|
+
case 'LogicalExpression':
|
|
3999
|
+
return isDeferralSafeBundleArg(value.left) && isDeferralSafeBundleArg(value.right);
|
|
4000
|
+
case 'ConditionalExpression':
|
|
4001
|
+
return (
|
|
4002
|
+
isDeferralSafeBundleArg(value.test) &&
|
|
4003
|
+
isDeferralSafeBundleArg(value.consequent) &&
|
|
4004
|
+
isDeferralSafeBundleArg(value.alternate)
|
|
4005
|
+
);
|
|
4006
|
+
default:
|
|
4007
|
+
return false;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
|
|
3939
4011
|
/**
|
|
3940
4012
|
* `() => fn(a, b, …)` — a zero-param arrow whose body is a single
|
|
3941
4013
|
* function call. Returns `{ callee, args }` if so, else null. Used to compile
|
|
@@ -3967,8 +4039,9 @@ function detectStableEventBundle(node) {
|
|
|
3967
4039
|
if (!body || body.type !== 'CallExpression') return null;
|
|
3968
4040
|
// Identifier callees only — see the receiver-loss note above.
|
|
3969
4041
|
if (!body.callee || body.callee.type !== 'Identifier') return null;
|
|
3970
|
-
// Bail if any arg is a spread — bundle args are positional only.
|
|
3971
|
-
|
|
4042
|
+
// Bail if any arg is a spread — bundle args are positional only. Everything
|
|
4043
|
+
// else has to survive being hoisted to render time; see the note above.
|
|
4044
|
+
if (!body.arguments.every(isDeferralSafeBundleArg)) return null;
|
|
3972
4045
|
return { callee: body.callee, args: body.arguments };
|
|
3973
4046
|
}
|
|
3974
4047
|
|
|
@@ -3995,11 +4068,22 @@ function isConditionalJsx(node) {
|
|
|
3995
4068
|
);
|
|
3996
4069
|
}
|
|
3997
4070
|
|
|
3998
|
-
/** Wrap
|
|
4071
|
+
/** Wrap a ternary arm as a BlockStatement body, so makeIfCall can consume it. */
|
|
3999
4072
|
function wrapAsBlockStmt(node) {
|
|
4000
4073
|
if (!node) return null;
|
|
4001
4074
|
// null / Literal(null) / Literal(false) → no branch
|
|
4002
4075
|
if (node.type === 'Literal' && (node.value === null || node.value === false)) return null;
|
|
4076
|
+
if (!isJsxLike(node)) {
|
|
4077
|
+
// A non-JSX arm (`{cond ? xs.map(…) : <Jsx/>}`, a string, a variable
|
|
4078
|
+
// holding an element, a nested ternary) is the branch's render VALUE. As
|
|
4079
|
+
// a bare statement the branch body would evaluate-and-discard it, so wrap
|
|
4080
|
+
// it as the authored-equivalent `<>{expr}</>` — the branch renders the
|
|
4081
|
+
// value through the fragment's child hole. The hole sits at the
|
|
4082
|
+
// fragment's ROOT, a value position on both compilers: a nested ternary
|
|
4083
|
+
// stays a value hole here (it is NOT re-claimed into a nested ifBlock),
|
|
4084
|
+
// which is why the server claim requires host-child position too.
|
|
4085
|
+
node = inheritOriginLoc(b.jsx_fragment([b.jsx_expression_container(node, node)]), node);
|
|
4086
|
+
}
|
|
4003
4087
|
return b.block([node]);
|
|
4004
4088
|
}
|
|
4005
4089
|
|
|
@@ -6062,6 +6146,7 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6062
6146
|
nextHookMemoCacheId: 0, // unique non-index slots property per compiled render function
|
|
6063
6147
|
currentInvariantLocals: null, // Set<string> of component-lifetime-stable local values
|
|
6064
6148
|
currentEventInvariantLocals: null, // Set<string> safe to retain in native event slots
|
|
6149
|
+
currentBodyIsComponentScope: false, // planning the component body itself, not a nested arm
|
|
6065
6150
|
currentProfileComponentId: null,
|
|
6066
6151
|
knownStringLocals: null, // Set<string> of provably-string locals (text-hole inference)
|
|
6067
6152
|
nextHookSymId: 0,
|
|
@@ -7482,7 +7567,15 @@ function ssrCompileBodyWithMapTemps(
|
|
|
7482
7567
|
((!!(node.body && node.body.type === 'JSXCodeBlock') && !returnedOutput) ||
|
|
7483
7568
|
returnedFragmentRoot) &&
|
|
7484
7569
|
inheritSoleCompRoot(bodyNodes, ctx);
|
|
7570
|
+
// Body/sub ROOTS (including fragment-root children) are not host-element
|
|
7571
|
+
// children: the client routes rich holes there through the de-opt value
|
|
7572
|
+
// path (a portal has no host to stamp — see the emitNodeHtml root branch),
|
|
7573
|
+
// so the ternary claim must stay off until an element's own children walk
|
|
7574
|
+
// (ssrEmitElement) turns the flag on.
|
|
7575
|
+
const prevHostChildPos = ctx._ssrHostChildPos;
|
|
7576
|
+
ctx._ssrHostChildPos = false;
|
|
7485
7577
|
const htmlExpr = ssrEmitNodes(bodyNodes, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
|
|
7578
|
+
ctx._ssrHostChildPos = prevHostChildPos;
|
|
7486
7579
|
ctx._ssrInheritRoot = prevInheritRoot;
|
|
7487
7580
|
ctx._returnedFragmentTemplate = prevReturnedFragmentTemplate;
|
|
7488
7581
|
ctx._tsxValuePos = prevValuePos;
|
|
@@ -8587,6 +8680,11 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
8587
8680
|
// pre/textarea/listing: the parser eats a '\n' right after the opening tag —
|
|
8588
8681
|
// the first text part must protect a leading newline (see ssrEmitNodes).
|
|
8589
8682
|
const nlGuardFirst = tag === 'pre' || tag === 'textarea' || tag === 'listing';
|
|
8683
|
+
// These children sit directly under a host element — the one position
|
|
8684
|
+
// where the client claims `{cond ? A : B}` holes (emitElementHtml). Root
|
|
8685
|
+
// positions reset this in ssrCompileBody.
|
|
8686
|
+
const prevHostChildPos = ctx._ssrHostChildPos;
|
|
8687
|
+
ctx._ssrHostChildPos = true;
|
|
8590
8688
|
childrenExpr = ssrEmitNodes(
|
|
8591
8689
|
normChildren,
|
|
8592
8690
|
ctx,
|
|
@@ -8597,6 +8695,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
8597
8695
|
childComponentNs,
|
|
8598
8696
|
nlGuardFirst,
|
|
8599
8697
|
);
|
|
8698
|
+
ctx._ssrHostChildPos = prevHostChildPos;
|
|
8600
8699
|
}
|
|
8601
8700
|
// `children=` and spread-held children are content props, not attributes.
|
|
8602
8701
|
// With no nested JSX children, the last present writer renders as the host's
|
|
@@ -8925,18 +9024,35 @@ function ssrCompileSub(
|
|
|
8925
9024
|
) {
|
|
8926
9025
|
const fnName = `${baseName}$${ctx.nextHelperId++}`;
|
|
8927
9026
|
const synth = { params: paramNodes || [], body: bodyStmts };
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
9027
|
+
// Returned-tree mirror subs (`__sfragment`): extractFragment folds EVERY
|
|
9028
|
+
// expression hole in the mirrored tree into a descriptor value hole on the
|
|
9029
|
+
// client (`props.hN` + childTextHole), even holes sitting inside host
|
|
9030
|
+
// elements. Mark them so ssrEmitTsrxExpression keeps `{cond ? A : B}` on
|
|
9031
|
+
// ssrChild throughout the mirror, matching that fold. Directive-arm subs
|
|
9032
|
+
// reset the flag through this same save/restore, so an @if INSIDE a mirror
|
|
9033
|
+
// claims again, exactly like the client. (Component `__schildren` subs are
|
|
9034
|
+
// NOT folded wholesale — only their ROOT holes are the children-as-props
|
|
9035
|
+
// value, which `_ssrHostChildPos` already leaves unclaimed — while a hole
|
|
9036
|
+
// inside a host element within children claims like any template child.)
|
|
9037
|
+
const prevFoldedExprHoles = ctx._ssrFoldedExprHoles;
|
|
9038
|
+
ctx._ssrFoldedExprHoles = baseName === '__sfragment';
|
|
9039
|
+
let fn;
|
|
9040
|
+
try {
|
|
9041
|
+
fn = ssrCompileBody(
|
|
9042
|
+
synth,
|
|
9043
|
+
ctx,
|
|
9044
|
+
fnName,
|
|
9045
|
+
cssHash,
|
|
9046
|
+
[],
|
|
9047
|
+
parentNs || 'html',
|
|
9048
|
+
false,
|
|
9049
|
+
componentNs,
|
|
9050
|
+
returnedFragmentTemplate,
|
|
9051
|
+
returnedFragmentRoot,
|
|
9052
|
+
);
|
|
9053
|
+
} finally {
|
|
9054
|
+
ctx._ssrFoldedExprHoles = prevFoldedExprHoles;
|
|
9055
|
+
}
|
|
8940
9056
|
return { fnName, fn };
|
|
8941
9057
|
}
|
|
8942
9058
|
|
|
@@ -8944,10 +9060,19 @@ function ssrEmitIf(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs)
|
|
|
8944
9060
|
// rewriteHookCalls: key any `use(thenable)` in the @if test (it bypasses the
|
|
8945
9061
|
// setup rewrite, so without a stable key it collides with sibling/body use()).
|
|
8946
9062
|
const testExpr = rewriteHookCalls(node.test, ctx, name);
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
9063
|
+
// A null consequent (`{cond ? null : <Jsx/>}` lowered by wrapAsBlockStmt)
|
|
9064
|
+
// means "no then branch". It must emit like a MISSING @else — plain '' with
|
|
9065
|
+
// no inner arm range — because the hydrating client renders that branch with
|
|
9066
|
+
// a null body and adopts an empty slot range.
|
|
9067
|
+
const thenStmts = node.consequent
|
|
9068
|
+
? node.consequent.type === 'BlockStatement'
|
|
9069
|
+
? node.consequent.body
|
|
9070
|
+
: [node.consequent]
|
|
9071
|
+
: null;
|
|
9072
|
+
const thenSub = thenStmts
|
|
9073
|
+
? ssrCompileSub(thenStmts, ctx, '__sif', [], cssHash, parentNs, componentNs)
|
|
9074
|
+
: null;
|
|
9075
|
+
if (thenSub) inlinedSubs.push(thenSub.fn);
|
|
8951
9076
|
let elseCall = ssrHtmlTemplate([], node, ctx);
|
|
8952
9077
|
let elseFnName = null;
|
|
8953
9078
|
if (node.alternate) {
|
|
@@ -8968,26 +9093,33 @@ function ssrEmitIf(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs)
|
|
|
8968
9093
|
ctx.runtimeNeeded.add('ssrBlock');
|
|
8969
9094
|
ctx.runtimeNeeded.add('ssrControl');
|
|
8970
9095
|
ctx.runtimeNeeded.add('ssrArm');
|
|
8971
|
-
registerDirectiveOrigin(ctx, node, [
|
|
9096
|
+
registerDirectiveOrigin(ctx, node, [
|
|
9097
|
+
'_$ssrControl',
|
|
9098
|
+
'_$ssrArm',
|
|
9099
|
+
thenSub ? thenSub.fnName : null,
|
|
9100
|
+
elseFnName,
|
|
9101
|
+
]);
|
|
8972
9102
|
// Nested ranges: the OUTER ssrBlock is the if-slot; the INNER one wraps the
|
|
8973
9103
|
// taken branch's content. The client adopts BOTH on hydration (slot = outer,
|
|
8974
9104
|
// branch = inner) so no comment markers are inserted — byte-for-byte, exactly
|
|
8975
9105
|
// like @for. The not-taken arm emits no inner range (just `''`).
|
|
8976
|
-
const thenInner =
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
9106
|
+
const thenInner = thenSub
|
|
9107
|
+
? ssrCall(
|
|
9108
|
+
'ssrArm',
|
|
9109
|
+
[
|
|
9110
|
+
b.literal('then', '"then"'),
|
|
9111
|
+
ssrThunk(
|
|
9112
|
+
ssrCall(
|
|
9113
|
+
'ssrBlock',
|
|
9114
|
+
[ssrSubCall(thenSub.fnName, [b.id('undefined')], node.consequent)],
|
|
9115
|
+
node.consequent,
|
|
9116
|
+
),
|
|
9117
|
+
node.consequent,
|
|
9118
|
+
),
|
|
9119
|
+
],
|
|
8986
9120
|
node.consequent,
|
|
8987
|
-
)
|
|
8988
|
-
],
|
|
8989
|
-
node.consequent,
|
|
8990
|
-
);
|
|
9121
|
+
)
|
|
9122
|
+
: ssrHtmlTemplate([], node, ctx);
|
|
8991
9123
|
const elseInner = node.alternate
|
|
8992
9124
|
? ssrCall(
|
|
8993
9125
|
'ssrArm',
|
|
@@ -9441,6 +9573,30 @@ function ssrEmitTsrxExpression(node, ctx, name, inlinedSubs, parentNs, cssHash,
|
|
|
9441
9573
|
ctx.runtimeNeeded.add('ssrPortal');
|
|
9442
9574
|
return ssrCall('ssrPortal', [], node);
|
|
9443
9575
|
}
|
|
9576
|
+
if (
|
|
9577
|
+
node.returnedJsxValue !== true &&
|
|
9578
|
+
ctx._tsxValuePos !== true &&
|
|
9579
|
+
ctx._ssrFoldedExprHoles !== true &&
|
|
9580
|
+
ctx._ssrHostChildPos === true &&
|
|
9581
|
+
isConditionalJsx(expr)
|
|
9582
|
+
) {
|
|
9583
|
+
// Mirror the client's claim EXACTLY: only emitElementHtml's children
|
|
9584
|
+
// walk lowers `{cond ? A : B}` with a JSX arm to an @if, so both sides
|
|
9585
|
+
// compile each arm as an ordinary arm body and the hydration shapes
|
|
9586
|
+
// (control key, arm ranges, a keyed `.map` claimed inside a value arm)
|
|
9587
|
+
// agree by construction. Everywhere else the hole is a VALUE on the
|
|
9588
|
+
// client and must stay on ssrChild here: returned `.tsx` trees
|
|
9589
|
+
// (`_tsxValuePos`), returned-tree mirrors whose holes extractFragment
|
|
9590
|
+
// folds to `props.hN` (`_ssrFoldedExprHoles`), and every non-host-child
|
|
9591
|
+
// position — body/arm/fragment roots and component-children roots,
|
|
9592
|
+
// where a rich hole rides the de-opt value path because a portal there
|
|
9593
|
+
// has no host to stamp (`_ssrHostChildPos`).
|
|
9594
|
+
const asIf = {
|
|
9595
|
+
...b.if(expr.test, wrapAsBlockStmt(expr.consequent), wrapAsBlockStmt(expr.alternate)),
|
|
9596
|
+
loc: expr.loc, // same devLoc/control-key position as the client's claim
|
|
9597
|
+
};
|
|
9598
|
+
return ssrEmitIf(asIf, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
|
|
9599
|
+
}
|
|
9444
9600
|
ctx.runtimeNeeded.add('ssrChild');
|
|
9445
9601
|
// rewriteHookCalls first (key any `use(thenable)` in the hole — it bypasses the
|
|
9446
9602
|
// setup rewrite), then rewriteJsxValues (lower nested JSX to createElement).
|
|
@@ -10381,6 +10537,11 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
10381
10537
|
}
|
|
10382
10538
|
ctx.currentInvariantLocals = invariantLocals;
|
|
10383
10539
|
ctx.currentEventInvariantLocals = eventInvariantLocals;
|
|
10540
|
+
// Same gate `findMountEventCallbackSinks` uses: the lifetime proof below is
|
|
10541
|
+
// defined relative to the COMPONENT's scope, so it is only sound while
|
|
10542
|
+
// planning that scope's own JSX.
|
|
10543
|
+
const prevBodyIsComponentScope = ctx.currentBodyIsComponentScope;
|
|
10544
|
+
ctx.currentBodyIsComponentScope = options?.autoCallback === true;
|
|
10384
10545
|
// M3 inherit-range: only a real `@{ … }` (JSXCodeBlock) component body spans
|
|
10385
10546
|
// its block's whole range — synthetic sub-bodies (@if/@for/@try arms,
|
|
10386
10547
|
// children render-fns) pass statement arrays and stay unflagged. planJsx
|
|
@@ -10414,6 +10575,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
10414
10575
|
}
|
|
10415
10576
|
ctx.currentInvariantLocals = prevInvariantLocals;
|
|
10416
10577
|
ctx.currentEventInvariantLocals = prevEventInvariantLocals;
|
|
10578
|
+
ctx.currentBodyIsComponentScope = prevBodyIsComponentScope;
|
|
10417
10579
|
ctx._inheritBody = prevInheritBody;
|
|
10418
10580
|
ctx._fnOrigin = prevFnOrigin;
|
|
10419
10581
|
ctx._foldedDirectiveCalls = prevFDC;
|
|
@@ -10985,6 +11147,49 @@ function isEventHandlerInvariantExpr(node, ctx) {
|
|
|
10985
11147
|
);
|
|
10986
11148
|
}
|
|
10987
11149
|
|
|
11150
|
+
/**
|
|
11151
|
+
* An inline `onClick={() => …}` arrow is rebuilt and reassigned to its DOM slot
|
|
11152
|
+
* on every render. When nothing the arrow reads can change, that write is dead
|
|
11153
|
+
* work: the handler can be installed once at mount and left alone, which is
|
|
11154
|
+
* already what a NAMED handler gets through `findMountEventCallbackSinks`.
|
|
11155
|
+
* Recognising the inline form closes the gap between the two spellings.
|
|
11156
|
+
*
|
|
11157
|
+
* "Nothing it reads can change" means every free identifier is either proven
|
|
11158
|
+
* event-invariant for this component (a useState setter, a ref object, a
|
|
11159
|
+
* useEffectEvent wrapper, …) or is not a component local at all — module scope,
|
|
11160
|
+
* an import, or a global, each fixed for the module's lifetime. That second
|
|
11161
|
+
* clause is the same inference `isArrowStableOver` makes.
|
|
11162
|
+
*
|
|
11163
|
+
* Sound only while planning the component body's own JSX.
|
|
11164
|
+
* `collectComponentLocals` deliberately ignores nested blocks, so inside a
|
|
11165
|
+
* `@for` item body the loop variable is absent from the set and would read as
|
|
11166
|
+
* module scope — and a keyed survivor can be handed a different item without
|
|
11167
|
+
* remounting, which would freeze the first item's capture in the slot forever.
|
|
11168
|
+
*/
|
|
11169
|
+
function isMountStableInlineHandler(node, ctx) {
|
|
11170
|
+
if (ctx.hmr || ctx.profile || !ctx.currentBodyIsComponentScope) return false;
|
|
11171
|
+
const value = unwrapTsExpr(node);
|
|
11172
|
+
// A FunctionExpression is reachable through its own binding name and carries
|
|
11173
|
+
// its own `this`/`arguments`; only the arrow form is a pure lexical capture.
|
|
11174
|
+
if (value?.type !== 'ArrowFunctionExpression') return false;
|
|
11175
|
+
const locals = ctx.currentComponentLocals;
|
|
11176
|
+
if (!locals) return false;
|
|
11177
|
+
const paramScope = new Set();
|
|
11178
|
+
for (const p of value.params || []) collectBindings(p, paramScope);
|
|
11179
|
+
// Params are walked alongside the body: their names are already bound in
|
|
11180
|
+
// `paramScope`, but a default (`(e, x = n) => …`) is an ordinary expression
|
|
11181
|
+
// that runs per call and can reach a changing local.
|
|
11182
|
+
for (const name of collectFreeIdentifiers([value.body, ...(value.params || [])], paramScope)) {
|
|
11183
|
+
// `arguments` is the render call's own, and a DIRECT `eval` resolves
|
|
11184
|
+
// component locals this walk cannot see — either would tie the installed
|
|
11185
|
+
// closure to whatever the first render happened to hold.
|
|
11186
|
+
if (name === 'arguments' || name === 'eval') return false;
|
|
11187
|
+
if (!locals.has(name)) continue;
|
|
11188
|
+
if (ctx.currentEventInvariantLocals?.has(name) !== true) return false;
|
|
11189
|
+
}
|
|
11190
|
+
return true;
|
|
11191
|
+
}
|
|
11192
|
+
|
|
10988
11193
|
// Object/array/function literals allocate a new identity on every evaluation,
|
|
10989
11194
|
// so an identity diff can never skip their update. This currently feeds the
|
|
10990
11195
|
// class binding path, where dropping the dead previous-value field preserves
|
|
@@ -19408,7 +19613,8 @@ function emitElementHtml(
|
|
|
19408
19613
|
slotKey,
|
|
19409
19614
|
ns: hostNs,
|
|
19410
19615
|
dev: ctx.dev,
|
|
19411
|
-
mountOnly:
|
|
19616
|
+
mountOnly:
|
|
19617
|
+
isEventHandlerInvariantExpr(inner, ctx) || isMountStableInlineHandler(inner, ctx),
|
|
19412
19618
|
});
|
|
19413
19619
|
}
|
|
19414
19620
|
} else if (attrName === 'class') {
|
|
@@ -20169,10 +20375,16 @@ function hoistBodyHelper(
|
|
|
20169
20375
|
// ===========================================================================
|
|
20170
20376
|
|
|
20171
20377
|
function makeIfCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
|
|
20172
|
-
// node.test, node.consequent (BlockStatement | Element), node.alternate (BlockStatement | IfStatement | null)
|
|
20173
|
-
|
|
20174
|
-
|
|
20175
|
-
|
|
20378
|
+
// node.test, node.consequent (BlockStatement | Element | null), node.alternate (BlockStatement | IfStatement | null)
|
|
20379
|
+
// A null consequent (`{cond ? null : <Jsx/>}` lowered by wrapAsBlockStmt)
|
|
20380
|
+
// means "no then branch" — same contract as the null alternate: the helper
|
|
20381
|
+
// slot compiles to `null` and the runtime renders an empty branch.
|
|
20382
|
+
|
|
20383
|
+
const thenStmts = node.consequent
|
|
20384
|
+
? node.consequent.type === 'BlockStatement'
|
|
20385
|
+
? node.consequent.body
|
|
20386
|
+
: [node.consequent]
|
|
20387
|
+
: null;
|
|
20176
20388
|
const elseStmts = node.alternate
|
|
20177
20389
|
? node.alternate.type === 'BlockStatement'
|
|
20178
20390
|
? node.alternate.body
|
|
@@ -20180,20 +20392,22 @@ function makeIfCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
|
|
|
20180
20392
|
: null;
|
|
20181
20393
|
// Phase 2: one shared env tuple for both branches (see unionEnv).
|
|
20182
20394
|
const envNames = unionEnv(ctx, [
|
|
20183
|
-
{ stmts: thenStmts, params: [] },
|
|
20395
|
+
thenStmts && { stmts: thenStmts, params: [] },
|
|
20184
20396
|
elseStmts && { stmts: elseStmts, params: [] },
|
|
20185
20397
|
]);
|
|
20186
|
-
const thenHelperName =
|
|
20187
|
-
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
|
|
20191
|
-
|
|
20192
|
-
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
|
|
20196
|
-
|
|
20398
|
+
const thenHelperName = thenStmts
|
|
20399
|
+
? hoistBodyHelper(
|
|
20400
|
+
ctx,
|
|
20401
|
+
inlinedSubs,
|
|
20402
|
+
'__then',
|
|
20403
|
+
thenStmts,
|
|
20404
|
+
[],
|
|
20405
|
+
parentNs,
|
|
20406
|
+
cssHash,
|
|
20407
|
+
envNames,
|
|
20408
|
+
directiveKeywordOrigin(ctx, node),
|
|
20409
|
+
)
|
|
20410
|
+
: null;
|
|
20197
20411
|
|
|
20198
20412
|
let elseHelperName = null;
|
|
20199
20413
|
if (elseStmts) {
|
package/dist/runtime.js
CHANGED
|
@@ -315,6 +315,13 @@ function flushTransitionActionBatch(batch) {
|
|
|
315
315
|
);
|
|
316
316
|
scheduleRender(block);
|
|
317
317
|
}
|
|
318
|
+
if (batch.updates.size > 0) {
|
|
319
|
+
const retained = [];
|
|
320
|
+
for (const update of batch.updates.values()) {
|
|
321
|
+
if (!update.block.disposed) retained.push(update);
|
|
322
|
+
}
|
|
323
|
+
if (retained.length > 0) FLUSHED_TRANSITION_UPDATES.push(retained);
|
|
324
|
+
}
|
|
318
325
|
batch.updates.clear();
|
|
319
326
|
if (IN_FLIGHT_TRANSITION_ACTION_BATCH === batch) {
|
|
320
327
|
IN_FLIGHT_TRANSITION_ACTION_BATCH = null;
|
|
@@ -331,6 +338,183 @@ const HELD_TRANSITIONS = /* @__PURE__ */ new Set();
|
|
|
331
338
|
const STAGED_REVEALS = /* @__PURE__ */ new Set();
|
|
332
339
|
let flushingStagedReveals = false;
|
|
333
340
|
let deferringStagedRevealEffects = false;
|
|
341
|
+
let ACTIVE_TRANSITION_ATTEMPT = null;
|
|
342
|
+
let FLUSHED_TRANSITION_UPDATES = [];
|
|
343
|
+
let HELD_SYNC_TRANSITION = null;
|
|
344
|
+
let PROMOTED_PU_SWAPS = null;
|
|
345
|
+
let PROMOTED_WARM_HARVEST = null;
|
|
346
|
+
function beginTransitionAttempt(block) {
|
|
347
|
+
if (block.pendingMode !== "transition" || ACTIVE_TRANSITION_ATTEMPT !== null) return null;
|
|
348
|
+
TRANSITION_JOURNAL ??= [];
|
|
349
|
+
TRANSITION_JOURNAL_BAGS ??= /* @__PURE__ */ new Set();
|
|
350
|
+
TRANSITION_JOURNAL_DEPTH++;
|
|
351
|
+
const attempt = {
|
|
352
|
+
origin: block,
|
|
353
|
+
journalCheckpoint: TRANSITION_JOURNAL.length,
|
|
354
|
+
effects: [effectQueues[0].length, effectQueues[1].length, effectQueues[2].length],
|
|
355
|
+
effectEvents: effectEventQueue.length,
|
|
356
|
+
effectEventActions: effectEventCommitActions.length,
|
|
357
|
+
stores: storeSyncQueue.length,
|
|
358
|
+
refAttach: refAttachQueue.length,
|
|
359
|
+
refDetach: refDetachQueue.length,
|
|
360
|
+
effectDeps: snapshotSubtreeEffectDeps(block),
|
|
361
|
+
heldSlots: null,
|
|
362
|
+
puSwaps: null
|
|
363
|
+
};
|
|
364
|
+
ACTIVE_TRANSITION_ATTEMPT = attempt;
|
|
365
|
+
return attempt;
|
|
366
|
+
}
|
|
367
|
+
function endTransitionAttempt(attempt) {
|
|
368
|
+
if (attempt === null) return;
|
|
369
|
+
ACTIVE_TRANSITION_ATTEMPT = null;
|
|
370
|
+
const held = attempt.heldSlots;
|
|
371
|
+
if (held === null || held.size === 0) {
|
|
372
|
+
PROMOTED_PU_SWAPS = null;
|
|
373
|
+
PROMOTED_WARM_HARVEST = null;
|
|
374
|
+
}
|
|
375
|
+
let entries = null;
|
|
376
|
+
if (held !== null && held.size > 0) {
|
|
377
|
+
entries = [];
|
|
378
|
+
for (let i = FLUSHED_TRANSITION_UPDATES.length - 1; i >= 0; i--) {
|
|
379
|
+
const group = FLUSHED_TRANSITION_UPDATES[i];
|
|
380
|
+
let single = true;
|
|
381
|
+
for (let k = 0; k < group.length; k++) {
|
|
382
|
+
if (group[k].block !== attempt.origin) {
|
|
383
|
+
single = false;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!single) continue;
|
|
388
|
+
for (let k = 0; k < group.length; k++) entries.push(group[k]);
|
|
389
|
+
FLUSHED_TRANSITION_UPDATES.splice(i, 1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (held !== null && held.size > 0 && entries !== null && entries.length > 0) {
|
|
393
|
+
rollbackTransitionJournal(attempt.journalCheckpoint);
|
|
394
|
+
for (let phase = 0; phase < 3; phase++) {
|
|
395
|
+
effectQueues[phase].length = attempt.effects[phase];
|
|
396
|
+
}
|
|
397
|
+
effectEventQueue.length = attempt.effectEvents;
|
|
398
|
+
effectEventCommitActions.length = attempt.effectEventActions;
|
|
399
|
+
for (let i = attempt.stores; i < storeSyncQueue.length; i++) {
|
|
400
|
+
storeSyncQueue[i].queued = false;
|
|
401
|
+
}
|
|
402
|
+
storeSyncQueue.length = attempt.stores;
|
|
403
|
+
refAttachQueue.length = attempt.refAttach;
|
|
404
|
+
refDetachQueue.length = attempt.refDetach;
|
|
405
|
+
restoreSubtreeEffectDeps(attempt.origin, attempt.effectDeps);
|
|
406
|
+
const continuing = PROMOTED_PU_SWAPS !== null || PROMOTED_WARM_HARVEST !== null;
|
|
407
|
+
let puSwaps = attempt.puSwaps;
|
|
408
|
+
if (continuing) {
|
|
409
|
+
if (PROMOTED_PU_SWAPS !== null) {
|
|
410
|
+
puSwaps = puSwaps === null ? PROMOTED_PU_SWAPS : PROMOTED_PU_SWAPS.concat(puSwaps);
|
|
411
|
+
PROMOTED_PU_SWAPS = null;
|
|
412
|
+
}
|
|
413
|
+
} else if (puSwaps !== null) {
|
|
414
|
+
for (let i = puSwaps.length - 1; i >= 0; i--) {
|
|
415
|
+
const [hooks, slot, prev] = puSwaps[i];
|
|
416
|
+
if (prev === void 0) hooks.delete(slot);
|
|
417
|
+
else hooks.set(slot, prev);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
for (let i = 0; i < entries.length; i++) {
|
|
421
|
+
entries[i].slot.value = entries[i].baseValue;
|
|
422
|
+
}
|
|
423
|
+
let warmHarvest = PROMOTED_WARM_HARVEST;
|
|
424
|
+
PROMOTED_WARM_HARVEST = null;
|
|
425
|
+
if (warmHarvest !== null) {
|
|
426
|
+
for (let i = 0; i < warmHarvest.length; i++) warmHarvest[i].taken = false;
|
|
427
|
+
}
|
|
428
|
+
const harvest = (scope) => {
|
|
429
|
+
const cache = scope.block.__warmCache;
|
|
430
|
+
if (cache !== void 0) {
|
|
431
|
+
for (const [slot, list] of cache) {
|
|
432
|
+
for (let i = 0; i < list.length; i++) {
|
|
433
|
+
const entry = list[i];
|
|
434
|
+
if (entry.available) {
|
|
435
|
+
(warmHarvest ??= []).push({
|
|
436
|
+
slot,
|
|
437
|
+
deps: entry.deps,
|
|
438
|
+
value: entry.value,
|
|
439
|
+
taken: false
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
forEachSubtreeChild(scope, harvest);
|
|
446
|
+
};
|
|
447
|
+
harvest(attempt.origin);
|
|
448
|
+
HELD_SYNC_TRANSITION = {
|
|
449
|
+
origin: attempt.origin,
|
|
450
|
+
entries,
|
|
451
|
+
puSwaps,
|
|
452
|
+
warmHarvest,
|
|
453
|
+
holders: new Set(held)
|
|
454
|
+
};
|
|
455
|
+
if (!continuing) scheduleRender(attempt.origin);
|
|
456
|
+
}
|
|
457
|
+
if (--TRANSITION_JOURNAL_DEPTH === 0) {
|
|
458
|
+
TRANSITION_JOURNAL = null;
|
|
459
|
+
TRANSITION_JOURNAL_BAGS = null;
|
|
460
|
+
flushParkedItems();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function journalPuEntry(scope, slot, next) {
|
|
464
|
+
const attempt = ACTIVE_TRANSITION_ATTEMPT;
|
|
465
|
+
if (attempt === null) return;
|
|
466
|
+
const hooks = ensureHooks(scope);
|
|
467
|
+
(attempt.puSwaps ??= []).push([hooks, slot, hooks.get(slot), next]);
|
|
468
|
+
}
|
|
469
|
+
function heldSyncCellsIntact(state) {
|
|
470
|
+
const held = HELD_SYNC_TRANSITION;
|
|
471
|
+
if (held === null || !held.holders.has(state)) return false;
|
|
472
|
+
for (let i = 0; i < held.entries.length; i++) {
|
|
473
|
+
const entry = held.entries[i];
|
|
474
|
+
if (!Object.is(entry.slot.value, entry.baseValue)) return false;
|
|
475
|
+
}
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
function promoteHeldSyncTransition() {
|
|
479
|
+
const held = HELD_SYNC_TRANSITION;
|
|
480
|
+
if (held === null) return false;
|
|
481
|
+
HELD_SYNC_TRANSITION = null;
|
|
482
|
+
const puSwaps = held.puSwaps;
|
|
483
|
+
if (puSwaps !== null) {
|
|
484
|
+
for (let i = 0; i < puSwaps.length; i++) {
|
|
485
|
+
const [hooks, slot, , next] = puSwaps[i];
|
|
486
|
+
hooks.set(slot, next);
|
|
487
|
+
}
|
|
488
|
+
PROMOTED_PU_SWAPS = PROMOTED_PU_SWAPS === null ? puSwaps : PROMOTED_PU_SWAPS.concat(puSwaps);
|
|
489
|
+
}
|
|
490
|
+
PROMOTED_WARM_HARVEST = held.warmHarvest;
|
|
491
|
+
const promoted = [];
|
|
492
|
+
TRANSITION_DEPTH++;
|
|
493
|
+
try {
|
|
494
|
+
for (let i = 0; i < held.entries.length; i++) {
|
|
495
|
+
const entry = held.entries[i];
|
|
496
|
+
if (!Object.is(entry.slot.value, entry.baseValue)) continue;
|
|
497
|
+
entry.slot.value = entry.value;
|
|
498
|
+
if (!entry.block.disposed) {
|
|
499
|
+
promoted.push(entry);
|
|
500
|
+
scheduleRender(entry.block);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} finally {
|
|
504
|
+
TRANSITION_DEPTH--;
|
|
505
|
+
}
|
|
506
|
+
if (promoted.length > 0) FLUSHED_TRANSITION_UPDATES.push(promoted);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
function discardHeldSyncTransition(state) {
|
|
510
|
+
const held = HELD_SYNC_TRANSITION;
|
|
511
|
+
if (held === null || !held.holders.delete(state)) return;
|
|
512
|
+
if (held.holders.size === 0) {
|
|
513
|
+
HELD_SYNC_TRANSITION = null;
|
|
514
|
+
PROMOTED_PU_SWAPS = null;
|
|
515
|
+
PROMOTED_WARM_HARVEST = null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
334
518
|
const JOURNAL_TEXT = 0;
|
|
335
519
|
const JOURNAL_ATTR = 1;
|
|
336
520
|
const JOURNAL_BAG = 2;
|
|
@@ -1007,7 +1191,12 @@ function drainQueue() {
|
|
|
1007
1191
|
block.drainStamp = drainId;
|
|
1008
1192
|
block.drainRenders = 1;
|
|
1009
1193
|
}
|
|
1010
|
-
|
|
1194
|
+
const attempt = beginTransitionAttempt(block);
|
|
1195
|
+
try {
|
|
1196
|
+
renderBlock(block);
|
|
1197
|
+
} finally {
|
|
1198
|
+
endTransitionAttempt(attempt);
|
|
1199
|
+
}
|
|
1011
1200
|
} catch (err) {
|
|
1012
1201
|
try {
|
|
1013
1202
|
handleRenderError(block, err);
|
|
@@ -1040,6 +1229,7 @@ function flush() {
|
|
|
1040
1229
|
}
|
|
1041
1230
|
function flushWork() {
|
|
1042
1231
|
inFlush = true;
|
|
1232
|
+
const clearRetainedTransitionUpdates = FLUSHED_TRANSITION_UPDATES.length > 0;
|
|
1043
1233
|
const viewTransitionDriver = VIEW_TRANSITION_DRIVER;
|
|
1044
1234
|
const clearViewTransitionTypes = viewTransitionDriver?.shouldClearTypesAfterFlush() === true;
|
|
1045
1235
|
try {
|
|
@@ -1049,6 +1239,8 @@ function flushWork() {
|
|
|
1049
1239
|
if (pendingError !== null) throw pendingError.err;
|
|
1050
1240
|
} finally {
|
|
1051
1241
|
inFlush = false;
|
|
1242
|
+
if (clearRetainedTransitionUpdates || FLUSHED_TRANSITION_UPDATES.length > 0)
|
|
1243
|
+
FLUSHED_TRANSITION_UPDATES.length = 0;
|
|
1052
1244
|
if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__)
|
|
1053
1245
|
__devtoolsNotifyFlush();
|
|
1054
1246
|
if (clearViewTransitionTypes) viewTransitionDriver.clearTypes();
|
|
@@ -2766,16 +2958,19 @@ function useMemo(compute, deps, slot) {
|
|
|
2766
2958
|
if (WARM_EVER && d !== void 0) {
|
|
2767
2959
|
const adopted = adoptWarmValue(s, d);
|
|
2768
2960
|
if (adopted !== WARM_MISS) {
|
|
2769
|
-
|
|
2961
|
+
const adoptedEntry = {
|
|
2770
2962
|
deps: d,
|
|
2771
2963
|
value: adopted,
|
|
2772
2964
|
warmEpisode: CURRENT_WARM_EPISODE
|
|
2773
|
-
}
|
|
2965
|
+
};
|
|
2966
|
+
if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(scope, s, adoptedEntry);
|
|
2967
|
+
ensureHooks(scope).set(s, adoptedEntry);
|
|
2774
2968
|
return adopted;
|
|
2775
2969
|
}
|
|
2776
2970
|
}
|
|
2777
2971
|
const value = compute.apply(null, d ?? []);
|
|
2778
2972
|
const entry = { deps: d, value };
|
|
2973
|
+
if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(scope, s, entry);
|
|
2779
2974
|
ensureHooks(scope).set(s, entry);
|
|
2780
2975
|
if (d !== void 0 && recordRealWarmMemo(s, d, entry)) {
|
|
2781
2976
|
entry.warmEpisode = CURRENT_WARM_EPISODE;
|
|
@@ -4332,6 +4527,16 @@ function adoptWarmValue(slot, deps) {
|
|
|
4332
4527
|
}
|
|
4333
4528
|
b = b.parentBlock;
|
|
4334
4529
|
}
|
|
4530
|
+
const harvestList = HELD_SYNC_TRANSITION?.warmHarvest ?? PROMOTED_WARM_HARVEST;
|
|
4531
|
+
if (harvestList !== null && harvestList !== void 0) {
|
|
4532
|
+
for (let i = 0; i < harvestList.length; i++) {
|
|
4533
|
+
const entry = harvestList[i];
|
|
4534
|
+
if (!entry.taken && entry.slot === slot && !depsChanged(entry.deps, deps)) {
|
|
4535
|
+
entry.taken = true;
|
|
4536
|
+
return entry.value;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4335
4540
|
return WARM_MISS;
|
|
4336
4541
|
}
|
|
4337
4542
|
const puMiss = /* @__PURE__ */ Symbol("octane.pu.miss");
|
|
@@ -4344,11 +4549,13 @@ function puHit(slot, entry) {
|
|
|
4344
4549
|
function puAdopt(slot, deps) {
|
|
4345
4550
|
const adopted = adoptWarmValue(slot, deps);
|
|
4346
4551
|
if (adopted === WARM_MISS) return puMiss;
|
|
4347
|
-
|
|
4552
|
+
const adoptedEntry = {
|
|
4348
4553
|
deps,
|
|
4349
4554
|
value: adopted,
|
|
4350
4555
|
warmEpisode: CURRENT_WARM_EPISODE
|
|
4351
|
-
}
|
|
4556
|
+
};
|
|
4557
|
+
if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(CURRENT_SCOPE, slot, adoptedEntry);
|
|
4558
|
+
ensureHooks(CURRENT_SCOPE).set(slot, adoptedEntry);
|
|
4352
4559
|
return adopted;
|
|
4353
4560
|
}
|
|
4354
4561
|
function puTake0(slot) {
|
|
@@ -4403,6 +4610,7 @@ function puTake4(slot, d0, d1, d2, d3) {
|
|
|
4403
4610
|
}
|
|
4404
4611
|
function puPub(slot, value, ...deps) {
|
|
4405
4612
|
const entry = { deps, value };
|
|
4613
|
+
if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(CURRENT_SCOPE, slot, entry);
|
|
4406
4614
|
ensureHooks(CURRENT_SCOPE).set(slot, entry);
|
|
4407
4615
|
if (recordRealWarmMemo(slot, deps, entry)) entry.warmEpisode = CURRENT_WARM_EPISODE;
|
|
4408
4616
|
return value;
|
|
@@ -7848,7 +8056,8 @@ function createScopedValue(readElement) {
|
|
|
7848
8056
|
const resolve = () => {
|
|
7849
8057
|
const scope = CURRENT_SCOPE;
|
|
7850
8058
|
const epoch = COMPILER_CACHE_CONTEXT_EPOCH;
|
|
7851
|
-
|
|
8059
|
+
const sameScope = resolvedScope === scope || resolvedScope !== null && scope !== null && scope.block.parentBlock === resolvedScope.block && scope.$$ctxValues === null;
|
|
8060
|
+
if (resolved === void 0 || !sameScope || resolvedEpoch !== epoch) {
|
|
7852
8061
|
const next = readElement();
|
|
7853
8062
|
if (next.key === null && KEYED_ELEMENT_DESCRIPTORS.has(next)) {
|
|
7854
8063
|
KEYED_ELEMENT_DESCRIPTORS.add(descriptor);
|
|
@@ -7856,6 +8065,8 @@ function createScopedValue(readElement) {
|
|
|
7856
8065
|
resolvedScope = scope;
|
|
7857
8066
|
resolvedEpoch = epoch;
|
|
7858
8067
|
resolved = next;
|
|
8068
|
+
} else if (resolvedScope !== scope) {
|
|
8069
|
+
resolvedScope = scope;
|
|
7859
8070
|
}
|
|
7860
8071
|
return resolved;
|
|
7861
8072
|
};
|
|
@@ -7894,7 +8105,7 @@ function createScopedElement(type, props, readChildren) {
|
|
|
7894
8105
|
const children = () => {
|
|
7895
8106
|
const scope = CURRENT_SCOPE;
|
|
7896
8107
|
const epoch = COMPILER_CACHE_CONTEXT_EPOCH;
|
|
7897
|
-
const sameScope = resolvedScope === scope || resolvedScope !== null && scope !== null && scope.block.parentBlock === resolvedScope.block;
|
|
8108
|
+
const sameScope = resolvedScope === scope || resolvedScope !== null && scope !== null && scope.block.parentBlock === resolvedScope.block && scope.$$ctxValues === null;
|
|
7898
8109
|
if (!resolved || !sameScope || resolvedEpoch !== epoch) {
|
|
7899
8110
|
const nextChildren = readChildren();
|
|
7900
8111
|
resolvedScope = scope;
|
|
@@ -9721,6 +9932,10 @@ function childSlot(parentScope, slotKey, domParent, value, anchor, ownEnd, ownsH
|
|
|
9721
9932
|
if (preparedList !== null) {
|
|
9722
9933
|
if (state.forSlot === null) {
|
|
9723
9934
|
if (hydration === null && !upgradeArmed) clearChildContent(state);
|
|
9935
|
+
if (state.end !== null && state.end.parentNode === null) {
|
|
9936
|
+
state.end = null;
|
|
9937
|
+
state.start = null;
|
|
9938
|
+
}
|
|
9724
9939
|
if (state.end === null) {
|
|
9725
9940
|
state.end = document.createComment("");
|
|
9726
9941
|
domParent.insertBefore(state.end, null);
|
|
@@ -10563,8 +10778,10 @@ function tryBlock(parentScope, slotKey, domParent, tryBody, catchBody, pendingBo
|
|
|
10563
10778
|
const journalCheckpoint = armTransitionJournal(s);
|
|
10564
10779
|
try {
|
|
10565
10780
|
renderBlock(s.tryBlock);
|
|
10566
|
-
|
|
10567
|
-
|
|
10781
|
+
if (!heldSyncCellsIntact(s)) {
|
|
10782
|
+
releaseHeldTransition(s);
|
|
10783
|
+
s.pendingThenable = null;
|
|
10784
|
+
}
|
|
10568
10785
|
} catch (err) {
|
|
10569
10786
|
if (isHostContextRequest(err)) throw err;
|
|
10570
10787
|
if (isSuspenseException(err)) {
|
|
@@ -10790,6 +11007,7 @@ function showTryBlock(state) {
|
|
|
10790
11007
|
}
|
|
10791
11008
|
}
|
|
10792
11009
|
function releaseHeldTransition(state) {
|
|
11010
|
+
discardHeldSyncTransition(state);
|
|
10793
11011
|
if (state.transitionHeld) {
|
|
10794
11012
|
state.transitionHeld = false;
|
|
10795
11013
|
tickTransitionCount(-1);
|
|
@@ -10804,6 +11022,9 @@ function handleSuspense(state, thenable, sourceBlock, journalCheckpoint = -1) {
|
|
|
10804
11022
|
const isTransition = sourceBlock.currentRenderMode === "transition";
|
|
10805
11023
|
if ((isTransition || state.transitionHeld) && state.hasResolved && state.branch === 1 && state.hiddenDom === null) {
|
|
10806
11024
|
rollbackTransitionJournal(journalCheckpoint);
|
|
11025
|
+
if (ACTIVE_TRANSITION_ATTEMPT !== null) {
|
|
11026
|
+
(ACTIVE_TRANSITION_ATTEMPT.heldSlots ??= /* @__PURE__ */ new Set()).add(state);
|
|
11027
|
+
}
|
|
10807
11028
|
if (!state.transitionHeld) {
|
|
10808
11029
|
state.transitionHeld = true;
|
|
10809
11030
|
tickTransitionCount(1);
|
|
@@ -10920,6 +11141,9 @@ function refreshPendingBody(state) {
|
|
|
10920
11141
|
}
|
|
10921
11142
|
function swapToPendingFallback(state) {
|
|
10922
11143
|
if (!state.pendingBody || state.branch !== 1 || !state.tryBlock) return;
|
|
11144
|
+
if (HELD_SYNC_TRANSITION !== null && HELD_SYNC_TRANSITION.holders.has(state)) {
|
|
11145
|
+
promoteHeldSyncTransition();
|
|
11146
|
+
}
|
|
10923
11147
|
hideTryContentAndMountPending(state);
|
|
10924
11148
|
}
|
|
10925
11149
|
function commitResume(state) {
|
|
@@ -11275,6 +11499,20 @@ function rebaseOffscreenCaptureSeq(capture) {
|
|
|
11275
11499
|
}
|
|
11276
11500
|
function flushStagedReveals() {
|
|
11277
11501
|
if (flushingStagedReveals) return;
|
|
11502
|
+
if (HELD_SYNC_TRANSITION !== null) {
|
|
11503
|
+
let allVisible = true;
|
|
11504
|
+
for (const holder of HELD_SYNC_TRANSITION.holders) {
|
|
11505
|
+
if (holder.hiddenDom !== null) {
|
|
11506
|
+
allVisible = false;
|
|
11507
|
+
break;
|
|
11508
|
+
}
|
|
11509
|
+
}
|
|
11510
|
+
if (allVisible && promoteHeldSyncTransition()) {
|
|
11511
|
+
STAGED_REVEALS.clear();
|
|
11512
|
+
flush();
|
|
11513
|
+
return;
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
11278
11516
|
flushingStagedReveals = true;
|
|
11279
11517
|
try {
|
|
11280
11518
|
const run = () => {
|
|
@@ -11970,10 +12208,31 @@ function renderBranchSlot(parentScope, slotKey, state, domParent, next, body, ma
|
|
|
11970
12208
|
let bStart;
|
|
11971
12209
|
let bEnd;
|
|
11972
12210
|
let borrowed = false;
|
|
12211
|
+
let rebuild = false;
|
|
12212
|
+
let inner = null;
|
|
12213
|
+
let innerEnd = null;
|
|
11973
12214
|
if (hydration !== null && hydration.isOpen(state.start.nextSibling)) {
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
12215
|
+
inner = state.start.nextSibling;
|
|
12216
|
+
innerEnd = hydration.close(inner);
|
|
12217
|
+
if (innerEnd !== state.end && innerEnd.nextSibling !== state.end) {
|
|
12218
|
+
if (process.env.NODE_ENV !== "production") {
|
|
12219
|
+
const mmLoc = siteLoc(parentScope, slotKey);
|
|
12220
|
+
if (mmLoc)
|
|
12221
|
+
hydration.warnStructural(
|
|
12222
|
+
mmLoc,
|
|
12223
|
+
"a single branch range",
|
|
12224
|
+
hydration.describe(innerEnd.nextSibling)
|
|
12225
|
+
);
|
|
12226
|
+
}
|
|
12227
|
+
removeRange(state.start.nextSibling, state.end);
|
|
12228
|
+
inner = null;
|
|
12229
|
+
rebuild = true;
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
if (inner !== null) {
|
|
12233
|
+
bStart = inner;
|
|
12234
|
+
bEnd = innerEnd;
|
|
12235
|
+
hydration.node = inner.nextSibling;
|
|
11977
12236
|
} else {
|
|
11978
12237
|
bStart = state.start;
|
|
11979
12238
|
bEnd = state.end;
|
|
@@ -11992,7 +12251,12 @@ function renderBranchSlot(parentScope, slotKey, state, domParent, next, body, ma
|
|
|
11992
12251
|
);
|
|
11993
12252
|
if (borrowed) b.exclusiveMarkers = true;
|
|
11994
12253
|
state.block = b;
|
|
11995
|
-
|
|
12254
|
+
if (rebuild) {
|
|
12255
|
+
hydration.suspend(() => renderBlock(b));
|
|
12256
|
+
hydration.node = state.end.nextSibling;
|
|
12257
|
+
} else {
|
|
12258
|
+
renderBlock(b);
|
|
12259
|
+
}
|
|
11996
12260
|
} else if (hydration !== null && state.start.nextSibling !== state.end) {
|
|
11997
12261
|
if (process.env.NODE_ENV !== "production") {
|
|
11998
12262
|
const mmLoc = siteLoc(parentScope, slotKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octane",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -119,11 +119,14 @@
|
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@tsrx/react": "^0.2.56",
|
|
122
|
+
"@wagmi/connectors": "8.0.25",
|
|
123
|
+
"@wagmi/core": "3.6.4",
|
|
122
124
|
"esbuild": "^0.28.1",
|
|
123
125
|
"happy-dom": "^20.11.0",
|
|
124
126
|
"playwright": "^1.61.1",
|
|
125
127
|
"react": "^19.2.7",
|
|
126
128
|
"react-dom": "^19.2.7",
|
|
129
|
+
"viem": "2.55.10",
|
|
127
130
|
"vite": "^8.1.5",
|
|
128
131
|
"vitest": "^4.1.10"
|
|
129
132
|
},
|