octane 0.1.22 → 0.1.24
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-universal.js +6 -2
- package/dist/compiler/compile.js +80 -42
- package/dist/compiler/hook-deps.js +162 -3
- package/dist/compiler/slot-hooks.js +10 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/method-dep.d.ts +1 -0
- package/dist/method-dep.js +11 -0
- package/dist/runtime.d.ts +7 -8
- package/dist/runtime.js +63 -26
- package/dist/runtime.server.d.ts +6 -2
- package/dist/runtime.server.js +21 -10
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2 -0
- package/package.json +1 -1
|
@@ -3182,8 +3182,12 @@ function buildUniversalHmrBlocksAst(state, origin) {
|
|
|
3182
3182
|
return { prelude: [], tail: [] };
|
|
3183
3183
|
}
|
|
3184
3184
|
if (state.hmrDialect === 'webpack') {
|
|
3185
|
-
|
|
3186
|
-
|
|
3185
|
+
// Rspack only guarantees that the `import.meta.webpackHot` root is lowered.
|
|
3186
|
+
// Keep `.data` and the HMR methods on an ordinary local so Rsbuild's React
|
|
3187
|
+
// transform cannot turn a deeper meta-property chain into `undefined`.
|
|
3188
|
+
const hotName = allocName(state, '__octaneWebpackHot');
|
|
3189
|
+
const hot = generatedIdentifier(hotName, origin);
|
|
3190
|
+
const prelude = [generatedConst(hotName, importMetaMember('webpackHot', origin), origin)];
|
|
3187
3191
|
const tail = [];
|
|
3188
3192
|
if (disposals.length === 0) {
|
|
3189
3193
|
const data = generatedIdentifier('data', origin);
|
package/dist/compiler/compile.js
CHANGED
|
@@ -41,7 +41,12 @@ import {
|
|
|
41
41
|
import { print as esrapPrint } from 'esrap';
|
|
42
42
|
import esrapTsx from 'esrap/languages/tsx';
|
|
43
43
|
import { buildFatSegments } from './fat-segments.js';
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
METHOD_DEP_IMPORT,
|
|
46
|
+
analyzeHookDependencies,
|
|
47
|
+
applyHookDependencies,
|
|
48
|
+
isInvariantLiteral,
|
|
49
|
+
} from './hook-deps.js';
|
|
45
50
|
import { compileUniversal, UNIVERSAL_COMPILER_RUNTIME_IMPORTS } from './compile-universal.js';
|
|
46
51
|
import {
|
|
47
52
|
expandDomRendererRegionsAst,
|
|
@@ -4404,6 +4409,31 @@ export function hasOwnValueReturn(node) {
|
|
|
4404
4409
|
return walk(body.body || []);
|
|
4405
4410
|
}
|
|
4406
4411
|
|
|
4412
|
+
/**
|
|
4413
|
+
* Whether a statement list always completes abruptly, so control can never fall
|
|
4414
|
+
* past its end. Lets an outputless `@{ … }` body drop the tail return it would
|
|
4415
|
+
* otherwise synthesize, because the body's own returns already cover every path.
|
|
4416
|
+
*
|
|
4417
|
+
* Deliberately syntactic: `return`, `throw`, a block that ends abruptly, and an
|
|
4418
|
+
* if/else whose arms both do. Anything subtler keeps the tail, which is always
|
|
4419
|
+
* safe — the runtime reads a fallen-through `undefined` as "this body already
|
|
4420
|
+
* emitted its template", so the tail must stay wherever reachability is unproven.
|
|
4421
|
+
*/
|
|
4422
|
+
function alwaysCompletesAbruptly(statements) {
|
|
4423
|
+
const last = statements[statements.length - 1];
|
|
4424
|
+
if (!last) return false;
|
|
4425
|
+
if (last.type === 'ReturnStatement' || last.type === 'ThrowStatement') return true;
|
|
4426
|
+
if (last.type === 'BlockStatement') return alwaysCompletesAbruptly(last.body || []);
|
|
4427
|
+
if (last.type === 'IfStatement') {
|
|
4428
|
+
return (
|
|
4429
|
+
!!last.alternate &&
|
|
4430
|
+
alwaysCompletesAbruptly([last.consequent]) &&
|
|
4431
|
+
alwaysCompletesAbruptly([last.alternate])
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4434
|
+
return false;
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4407
4437
|
/**
|
|
4408
4438
|
* A mixed shorthand's early return must always reach renderReturnedValue. The
|
|
4409
4439
|
* runtime reserves `undefined` for a compiled-void body that already emitted its
|
|
@@ -6039,12 +6069,16 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6039
6069
|
rendererBoundaryPreparation?.universalUnits,
|
|
6040
6070
|
),
|
|
6041
6071
|
});
|
|
6072
|
+
let hookDepHelperNeeded = false;
|
|
6042
6073
|
ast = applyHookDependencies(ast, {
|
|
6043
6074
|
filename,
|
|
6044
6075
|
hookRuntimeModules: hookRuntimeModulesForCompile(
|
|
6045
6076
|
options,
|
|
6046
6077
|
rendererBoundaryPreparation?.universalUnits,
|
|
6047
6078
|
),
|
|
6079
|
+
onRuntimeHelper: () => {
|
|
6080
|
+
hookDepHelperNeeded = true;
|
|
6081
|
+
},
|
|
6048
6082
|
});
|
|
6049
6083
|
const hmrOption = options && options.hmr;
|
|
6050
6084
|
const hmrDialect = hmrOption === true ? 'vite' : hmrOption || false;
|
|
@@ -6177,6 +6211,7 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6177
6211
|
// single module print must carry a loc (OCTANE_COMPILE_ASSERT_LOC).
|
|
6178
6212
|
_moduleOrigin: ast.body.find((n) => n?.loc != null) ?? ast,
|
|
6179
6213
|
};
|
|
6214
|
+
if (hookDepHelperNeeded) ctx.runtimeNeeded.add(METHOD_DEP_IMPORT);
|
|
6180
6215
|
{
|
|
6181
6216
|
const imports = collectOctaneImportBindings(ast.body);
|
|
6182
6217
|
ctx.octaneImportLocals = imports.locals;
|
|
@@ -6797,7 +6832,18 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
6797
6832
|
// it. Persist that canonical identity again for the next update. This keeps
|
|
6798
6833
|
// working across any number of edits; accept callbacks in webpack are error
|
|
6799
6834
|
// handlers, not Vite-style callbacks carrying the new module namespace.
|
|
6800
|
-
|
|
6835
|
+
// Keep every access after the recognized `import.meta.webpackHot` root on a
|
|
6836
|
+
// local. Rspack's React/Rsbuild transform pipeline otherwise treats deeper
|
|
6837
|
+
// expressions such as `import.meta.webpackHot.data` as unsupported even
|
|
6838
|
+
// though it lowers the root itself to `module.hot`.
|
|
6839
|
+
const webpackHotName = allocCompilerName(ctx, '_$webpackHot');
|
|
6840
|
+
const webpackHot = () => b.id(webpackHotName);
|
|
6841
|
+
hmrNodes.push(
|
|
6842
|
+
inheritOriginLoc(
|
|
6843
|
+
b.const(webpackHotName, b.member(importMeta(), 'webpackHot')),
|
|
6844
|
+
moduleOrigin,
|
|
6845
|
+
),
|
|
6846
|
+
);
|
|
6801
6847
|
const previousComponent = (name, optional) =>
|
|
6802
6848
|
b.member(
|
|
6803
6849
|
b.member(
|
|
@@ -7097,9 +7143,13 @@ function compileServer(source, filename, options, analyzedAst = null) {
|
|
|
7097
7143
|
// Mirror the client transform exactly. Effects are server no-ops, but
|
|
7098
7144
|
// useMemo/useCallback execute during SSR and must receive the same inferred
|
|
7099
7145
|
// dependency shape as hydration's client compile.
|
|
7146
|
+
let hookDepHelperNeeded = false;
|
|
7100
7147
|
ast = applyHookDependencies(ast, {
|
|
7101
7148
|
filename,
|
|
7102
7149
|
hookRuntimeModules: hookRuntimeModulesForCompile(options),
|
|
7150
|
+
onRuntimeHelper: () => {
|
|
7151
|
+
hookDepHelperNeeded = true;
|
|
7152
|
+
},
|
|
7103
7153
|
});
|
|
7104
7154
|
const ctx = {
|
|
7105
7155
|
filename,
|
|
@@ -7143,6 +7193,7 @@ function compileServer(source, filename, options, analyzedAst = null) {
|
|
|
7143
7193
|
// Scaffolding without a more precise authored construct maps here.
|
|
7144
7194
|
_moduleOrigin: ast.body.find((n) => n?.loc != null) ?? ast,
|
|
7145
7195
|
};
|
|
7196
|
+
if (hookDepHelperNeeded) ctx.runtimeNeeded.add(METHOD_DEP_IMPORT);
|
|
7146
7197
|
{
|
|
7147
7198
|
const imports = collectOctaneImportBindings(ast.body);
|
|
7148
7199
|
ctx.octaneImportLocals = imports.locals;
|
|
@@ -10562,10 +10613,20 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
10562
10613
|
const shellOrigin = node.loc ? node : node.id?.loc ? node.id : prevFnOrigin;
|
|
10563
10614
|
let plan = null;
|
|
10564
10615
|
let returnedExpression = null;
|
|
10565
|
-
if (returnedOutput) {
|
|
10566
|
-
|
|
10616
|
+
if (returnedOutput && jsxNodes.length === 0) {
|
|
10617
|
+
// A `@{ … }` body can carry value returns with NO trailing output node —
|
|
10618
|
+
// `@{ … return null }` while a component is being written, or a React-shaped
|
|
10619
|
+
// `return <jsx>` inside the block. There is no template to lower: the body's
|
|
10620
|
+
// own returns are the whole output, so the tail is a plain `null` covering
|
|
10621
|
+
// the fall-through path. When the body provably never falls through, that
|
|
10622
|
+
// tail is unreachable and is dropped. Statement returns already normalized
|
|
10623
|
+
// to `?? null`.
|
|
10624
|
+
if (!alwaysCompletesAbruptly(rewrittenStatements)) {
|
|
10625
|
+
returnedExpression = b.literal(null, 'null', node);
|
|
10626
|
+
}
|
|
10627
|
+
} else if (returnedOutput) {
|
|
10567
10628
|
returnedExpression = lowerReturnJsx(
|
|
10568
|
-
rewriteHookCalls(
|
|
10629
|
+
rewriteHookCalls(jsxNodes[0], ctx, name, options?.localHookSlots === true),
|
|
10569
10630
|
ctx,
|
|
10570
10631
|
inlinedSubs,
|
|
10571
10632
|
cssHash,
|
|
@@ -16678,12 +16739,10 @@ function planJsx(
|
|
|
16678
16739
|
ctx.runtimeNeeded.add('queueRefDetach'); // unmount-detach of a spread-supplied ref
|
|
16679
16740
|
}
|
|
16680
16741
|
if (b.kind === 'ref') {
|
|
16681
|
-
ctx.runtimeNeeded.add('attachRef');
|
|
16682
16742
|
ctx.runtimeNeeded.add('queueRefAttach'); // deferred mount attach (commit-phase timing)
|
|
16683
16743
|
ctx.runtimeNeeded.add('queueRefDetach'); // deferred unmount detach (same phasing)
|
|
16684
16744
|
}
|
|
16685
16745
|
if (b.kind === 'fragmentRef') {
|
|
16686
|
-
ctx.runtimeNeeded.add('attachRef');
|
|
16687
16746
|
ctx.runtimeNeeded.add('mountFragmentRef');
|
|
16688
16747
|
ctx.runtimeNeeded.add('queueRefAttach'); // deferred update re-attach
|
|
16689
16748
|
ctx.runtimeNeeded.add('queueRefDetach'); // deferred update/unmount detach
|
|
@@ -18251,18 +18310,18 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
18251
18310
|
// bound element rides along as the cleanup target, so a callback ref
|
|
18252
18311
|
// shared across elements (ref={registerItem} on every @for row)
|
|
18253
18312
|
// releases ITS row's React-19 cleanup, not another row's.
|
|
18254
|
-
// Both deferred
|
|
18255
|
-
//
|
|
18313
|
+
// Both deferred operations retain the bound element. `_ref$` must be a LIVE
|
|
18314
|
+
// cleanup read because updates re-point it. Assigning both bag locals inside
|
|
18315
|
+
// the queue call evaluates each mount value once while avoiding throwaway
|
|
18316
|
+
// temporaries; Suspense still receives the exact ref/target pair.
|
|
18256
18317
|
return st(
|
|
18257
18318
|
b.block([
|
|
18258
|
-
b.const('_r', bind.expr),
|
|
18259
|
-
b.stmt(b.assignment('=', local(`_ref$${bind.id}`), b.id('_r'))),
|
|
18260
|
-
b.stmt(b.assignment('=', local(`_el$${bind.id}`), el())),
|
|
18261
18319
|
b.stmt(
|
|
18262
18320
|
b.call(
|
|
18263
18321
|
'_$queueRefAttach',
|
|
18264
18322
|
b.id('__s'),
|
|
18265
|
-
b.
|
|
18323
|
+
b.assignment('=', local(`_ref$${bind.id}`), bind.expr),
|
|
18324
|
+
b.assignment('=', local(`_el$${bind.id}`), el()),
|
|
18266
18325
|
),
|
|
18267
18326
|
),
|
|
18268
18327
|
cleanupsPush(
|
|
@@ -18286,22 +18345,13 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
18286
18345
|
// the user's ref, and registers a single cleanup that detaches
|
|
18287
18346
|
// the ref + destroys the instance on unmount.
|
|
18288
18347
|
return st(
|
|
18289
|
-
b.
|
|
18290
|
-
b.
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
local(`_fi$${bind.id}`),
|
|
18295
|
-
b.call(
|
|
18296
|
-
'_$mountFragmentRef',
|
|
18297
|
-
b.id('__s'),
|
|
18298
|
-
el(),
|
|
18299
|
-
hostVarNode(bind.endElVar),
|
|
18300
|
-
b.id('_r'),
|
|
18301
|
-
),
|
|
18302
|
-
),
|
|
18348
|
+
b.stmt(
|
|
18349
|
+
b.assignment(
|
|
18350
|
+
'=',
|
|
18351
|
+
local(`_fi$${bind.id}`),
|
|
18352
|
+
b.call('_$mountFragmentRef', b.id('__s'), el(), hostVarNode(bind.endElVar), bind.expr),
|
|
18303
18353
|
),
|
|
18304
|
-
|
|
18354
|
+
),
|
|
18305
18355
|
);
|
|
18306
18356
|
}
|
|
18307
18357
|
}
|
|
@@ -18561,13 +18611,7 @@ function emitBindingUpdate(bind, bag) {
|
|
|
18561
18611
|
),
|
|
18562
18612
|
b.if(
|
|
18563
18613
|
b.binary('!=', b.id('_r'), nullNode()),
|
|
18564
|
-
b.stmt(
|
|
18565
|
-
b.call(
|
|
18566
|
-
'_$queueRefAttach',
|
|
18567
|
-
b.id('__s'),
|
|
18568
|
-
b.arrow([], b.call('_$attachRef', b.id('_r'), F('_el'))),
|
|
18569
|
-
),
|
|
18570
|
-
),
|
|
18614
|
+
b.stmt(b.call('_$queueRefAttach', b.id('__s'), b.id('_r'), F('_el'))),
|
|
18571
18615
|
null,
|
|
18572
18616
|
),
|
|
18573
18617
|
b.stmt(b.assignment('=', F('_ref'), b.id('_r'))),
|
|
@@ -18600,13 +18644,7 @@ function emitBindingUpdate(bind, bag) {
|
|
|
18600
18644
|
),
|
|
18601
18645
|
b.if(
|
|
18602
18646
|
b.binary('!=', b.id('_r'), nullNode()),
|
|
18603
|
-
b.stmt(
|
|
18604
|
-
b.call(
|
|
18605
|
-
'_$queueRefAttach',
|
|
18606
|
-
b.id('__s'),
|
|
18607
|
-
b.arrow([], b.call('_$attachRef', b.id('_r'), fi())),
|
|
18608
|
-
),
|
|
18609
|
-
),
|
|
18647
|
+
b.stmt(b.call('_$queueRefAttach', b.id('__s'), b.id('_r'), fi())),
|
|
18610
18648
|
null,
|
|
18611
18649
|
),
|
|
18612
18650
|
b.stmt(b.assignment('=', cur(), b.id('_r'))),
|
|
@@ -819,10 +819,78 @@ function staticMemberInfo(node) {
|
|
|
819
819
|
return {
|
|
820
820
|
node: dependencyNode,
|
|
821
821
|
root,
|
|
822
|
+
name: current.property.name,
|
|
822
823
|
path: `${current.optional ? '?' : ''}.${current.property.name}`,
|
|
823
824
|
};
|
|
824
825
|
}
|
|
825
826
|
|
|
827
|
+
// Directive prologues that declare "this body executes in another context, not
|
|
828
|
+
// during render" — a nested function carrying one contributes only its ROOT
|
|
829
|
+
// captures to an inferred dependency array, because hoisting its member reads
|
|
830
|
+
// to render time would run getters in a context where they may be illegal
|
|
831
|
+
// (issue #542: TypeGPU's `.$` is only readable inside `'use gpu'` shader code)
|
|
832
|
+
// and at a moment the program never performs them.
|
|
833
|
+
//
|
|
834
|
+
// This is a deliberate ALLOWLIST, not "any directive": directives that mark
|
|
835
|
+
// same-context compiler hints (`'use strict'`, React Compiler's `'use memo'` /
|
|
836
|
+
// `'use no memo'`, `'use signals'`) or reserved module/function markers with
|
|
837
|
+
// their own semantics (`'use server'`, `'use client'`, `'use cache'`,
|
|
838
|
+
// `'use workflow'`/`'use step'`) must NOT truncate. Extend the set as more
|
|
839
|
+
// other-context directives appear in the ecosystem.
|
|
840
|
+
//
|
|
841
|
+
// 'use gpu' — TypeGPU shader functions (transpiled, run on the GPU).
|
|
842
|
+
// 'worklet' — react-native-reanimated / react-native-worklets-core bodies,
|
|
843
|
+
// serialized and executed on a separate UI-thread runtime.
|
|
844
|
+
const OPAQUE_EXECUTION_DIRECTIVES = new Set(['use gpu', 'worklet']);
|
|
845
|
+
|
|
846
|
+
// A directive prologue is the run of leading string-literal expression
|
|
847
|
+
// statements. Parsers implementing ESTree stamp `directive` on those
|
|
848
|
+
// statements; fall back to the literal value where they don't.
|
|
849
|
+
function hasOpaqueExecutionDirective(fn) {
|
|
850
|
+
if (fn.body?.type !== 'BlockStatement') return false;
|
|
851
|
+
for (const statement of fn.body.body || []) {
|
|
852
|
+
if (statement.type !== 'ExpressionStatement') return false;
|
|
853
|
+
const expression = unwrapValue(statement.expression);
|
|
854
|
+
const value =
|
|
855
|
+
statement.directive ??
|
|
856
|
+
(expression?.type === 'Literal' && typeof expression.value === 'string'
|
|
857
|
+
? expression.value
|
|
858
|
+
: null);
|
|
859
|
+
if (value === null) return false;
|
|
860
|
+
if (OPAQUE_EXECUTION_DIRECTIVES.has(value)) return true;
|
|
861
|
+
}
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// The `octane` runtime export inferred method-call dependencies compile to.
|
|
866
|
+
// Both emitters alias it: the full compiler through `ctx.runtimeNeeded` (its
|
|
867
|
+
// `_$`-prefixed rtAlias convention is baked into methodDepNode below) and the
|
|
868
|
+
// surgical pass through its own helper-import allocator.
|
|
869
|
+
export const METHOD_DEP_IMPORT = '__methodDep';
|
|
870
|
+
|
|
871
|
+
// The emitted dependency expression for a one-level method call:
|
|
872
|
+
// `_$__methodDep(root, 'name')` — own property ? member value : receiver (see
|
|
873
|
+
// the runtime helper's contract in src/method-dep.ts). The call node carries
|
|
874
|
+
// the authored member's source range so source maps and the surgical pass's
|
|
875
|
+
// offset expectations stay anchored to the authored expression, while the
|
|
876
|
+
// cloned root identifier keeps its own authored position.
|
|
877
|
+
function methodDepNode(dependency) {
|
|
878
|
+
// Every synthesized node is stamped with the authored member's origin — the
|
|
879
|
+
// bundler print path asserts a loc on each printed node, including the
|
|
880
|
+
// helper's callee identifier.
|
|
881
|
+
const call = b.call(
|
|
882
|
+
b.id(`_$${METHOD_DEP_IMPORT}`, dependency.node),
|
|
883
|
+
{ ...dependency.method.root },
|
|
884
|
+
b.literal(dependency.method.name, JSON.stringify(dependency.method.name), dependency.node),
|
|
885
|
+
);
|
|
886
|
+
return {
|
|
887
|
+
...call,
|
|
888
|
+
start: dependency.node.start,
|
|
889
|
+
end: dependency.node.end,
|
|
890
|
+
loc: dependency.node.loc,
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
826
894
|
function collectDependencies(expression, callbackScope, analysis) {
|
|
827
895
|
const dependencies = [];
|
|
828
896
|
const seen = new Set();
|
|
@@ -863,6 +931,48 @@ function collectDependencies(expression, callbackScope, analysis) {
|
|
|
863
931
|
}
|
|
864
932
|
}
|
|
865
933
|
|
|
934
|
+
// A one-level member CALLED as a method. The member value alone cannot
|
|
935
|
+
// witness a changed receiver when the method is inherited (issue #542:
|
|
936
|
+
// `count.toFixed` is `Number.prototype.toFixed` on every render), and the
|
|
937
|
+
// receiver alone would defeat memoization for own function properties on
|
|
938
|
+
// per-render containers (`props.onChange(...)`). Record the pair and let the
|
|
939
|
+
// emitted `__methodDep(root, 'name')` helper pick the comparable value at
|
|
940
|
+
// runtime. Deeper callees (`a.b.c(...)`) never reach here: their receiver
|
|
941
|
+
// path is recorded by the ordinary member walk, which cannot capture the
|
|
942
|
+
// method itself, so they were never exposed to the stale-method hazard.
|
|
943
|
+
function addMethodCall(info) {
|
|
944
|
+
const scope = analysis.nodeScopes.get(info.root);
|
|
945
|
+
const binding = scope ? resolveBinding(scope, info.root.name) : null;
|
|
946
|
+
if (
|
|
947
|
+
binding === null ||
|
|
948
|
+
binding.imported ||
|
|
949
|
+
binding.dependencyInvariant ||
|
|
950
|
+
(callbackScope !== null && scopeIsWithin(binding.scope, callbackScope))
|
|
951
|
+
) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
// Distinct from the plain-read key: `x.m` read as a value elsewhere in the
|
|
955
|
+
// callback still contributes its own member dependency.
|
|
956
|
+
const key = `b${binding.id}${info.path}()`;
|
|
957
|
+
if (!seen.has(key)) {
|
|
958
|
+
seen.add(key);
|
|
959
|
+
dependencies.push({
|
|
960
|
+
node: info.node,
|
|
961
|
+
key,
|
|
962
|
+
binding,
|
|
963
|
+
method: { root: info.root, name: info.name },
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Depth of enclosing functions whose directive prologue declares another
|
|
969
|
+
// execution context (see OPAQUE_EXECUTION_DIRECTIVES). Inside one, member
|
|
970
|
+
// chains truncate to their root bindings: the body's property reads happen
|
|
971
|
+
// in that other context, so hoisting them into a render-time dependency
|
|
972
|
+
// array would evaluate getters in a context where they may be illegal
|
|
973
|
+
// (TypeGPU's `.$`) and at a time the program never reads them.
|
|
974
|
+
let opaqueDepth = 0;
|
|
975
|
+
|
|
866
976
|
function walk(node) {
|
|
867
977
|
if (!node || typeof node !== 'object') return;
|
|
868
978
|
if (Array.isArray(node)) {
|
|
@@ -878,13 +988,44 @@ function collectDependencies(expression, callbackScope, analysis) {
|
|
|
878
988
|
case 'Identifier':
|
|
879
989
|
addIdentifier(node);
|
|
880
990
|
return;
|
|
991
|
+
case 'CallExpression': {
|
|
992
|
+
if (opaqueDepth > 0) {
|
|
993
|
+
// Roots only: the callee's receiver chain collapses through the
|
|
994
|
+
// MemberExpression case below; no method-pair dependency either,
|
|
995
|
+
// since it would read the member at render.
|
|
996
|
+
walk(node.callee);
|
|
997
|
+
walk(node.arguments);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
// Optional spellings land here too: `a?.b(x)` and `a.b?.(x)` are a
|
|
1001
|
+
// CallExpression under a ChainExpression whose callee is a bare
|
|
1002
|
+
// (possibly optional) MemberExpression, which staticMemberInfo accepts.
|
|
1003
|
+
const callee = unwrapValue(node.callee);
|
|
1004
|
+
const info =
|
|
1005
|
+
callee?.type === 'MemberExpression' || callee?.type === 'ChainExpression'
|
|
1006
|
+
? staticMemberInfo(callee)
|
|
1007
|
+
: null;
|
|
1008
|
+
if (info) addMethodCall(info);
|
|
1009
|
+
else walk(node.callee);
|
|
1010
|
+
walk(node.arguments);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
881
1013
|
case 'ChainExpression': {
|
|
1014
|
+
if (opaqueDepth > 0) {
|
|
1015
|
+
walk(node.expression);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
882
1018
|
const info = staticMemberInfo(node);
|
|
883
1019
|
if (info) addStaticMember(info);
|
|
884
1020
|
else walk(node.expression);
|
|
885
1021
|
return;
|
|
886
1022
|
}
|
|
887
1023
|
case 'MemberExpression': {
|
|
1024
|
+
if (opaqueDepth > 0) {
|
|
1025
|
+
walk(node.object);
|
|
1026
|
+
if (node.computed) walk(node.property);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
888
1029
|
const info = staticMemberInfo(node);
|
|
889
1030
|
if (info) addStaticMember(info);
|
|
890
1031
|
else {
|
|
@@ -913,10 +1054,14 @@ function collectDependencies(expression, callbackScope, analysis) {
|
|
|
913
1054
|
return;
|
|
914
1055
|
case 'FunctionDeclaration':
|
|
915
1056
|
case 'FunctionExpression':
|
|
916
|
-
case 'ArrowFunctionExpression':
|
|
1057
|
+
case 'ArrowFunctionExpression': {
|
|
1058
|
+
const opaque = hasOpaqueExecutionDirective(node);
|
|
1059
|
+
if (opaque) opaqueDepth++;
|
|
917
1060
|
for (const param of node.params || []) walkPatternExpression(param);
|
|
918
1061
|
walk(node.body);
|
|
1062
|
+
if (opaque) opaqueDepth--;
|
|
919
1063
|
return;
|
|
1064
|
+
}
|
|
920
1065
|
case 'ImportDeclaration':
|
|
921
1066
|
case 'ExportAllDeclaration':
|
|
922
1067
|
case 'MetaProperty':
|
|
@@ -1163,7 +1308,7 @@ function rebuildWithHookMetadata(ast, analysis, inferred, insertDeps) {
|
|
|
1163
1308
|
args.splice(result.depsIndex, 0, {
|
|
1164
1309
|
...b.array(
|
|
1165
1310
|
result.dependencies.map((/** @type {any} */ dependency) =>
|
|
1166
|
-
cloneDependency(dependency.node),
|
|
1311
|
+
dependency.method ? methodDepNode(dependency) : cloneDependency(dependency.node),
|
|
1167
1312
|
),
|
|
1168
1313
|
),
|
|
1169
1314
|
start: node.start,
|
|
@@ -1197,8 +1342,22 @@ export function annotateHookCalls(ast, options = {}) {
|
|
|
1197
1342
|
* dependency arrays inserted at each candidate call. Copy-on-write — the input
|
|
1198
1343
|
* AST is never modified; callers must use the returned module.
|
|
1199
1344
|
*/
|
|
1200
|
-
/** @param {any} ast @param {{ onlyImported?: boolean, hookRuntimeModules?: readonly string[], filename?: string }} [options] */
|
|
1345
|
+
/** @param {any} ast @param {{ onlyImported?: boolean, hookRuntimeModules?: readonly string[], filename?: string, onRuntimeHelper?: (name: string) => void }} [options] */
|
|
1201
1346
|
export function applyHookDependencies(ast, options = {}) {
|
|
1202
1347
|
const { analysis, inferred } = analyzeInternal(ast, options);
|
|
1348
|
+
// The inserted `_$__methodDep(...)` calls need their aliased runtime import;
|
|
1349
|
+
// the caller owns import assembly, so report the requirement rather than
|
|
1350
|
+
// splicing an ImportDeclaration into a module whose runtime request
|
|
1351
|
+
// ('octane' vs 'octane/server') this pass cannot know.
|
|
1352
|
+
if (options.onRuntimeHelper !== undefined) {
|
|
1353
|
+
outer: for (const result of inferred.values()) {
|
|
1354
|
+
for (const dependency of result.dependencies) {
|
|
1355
|
+
if (dependency.method) {
|
|
1356
|
+
options.onRuntimeHelper(METHOD_DEP_IMPORT);
|
|
1357
|
+
break outer;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1203
1362
|
return rebuildWithHookMetadata(ast, analysis, inferred, true).ast;
|
|
1204
1363
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { parseModule } from '@tsrx/core';
|
|
18
18
|
import { HOOK_NAMES, hookSlotHash } from './compile.js';
|
|
19
|
-
import { annotateHookCalls } from './hook-deps.js';
|
|
19
|
+
import { METHOD_DEP_IMPORT, annotateHookCalls } from './hook-deps.js';
|
|
20
20
|
import { assertStrongMode } from './strong-mode.js';
|
|
21
21
|
|
|
22
22
|
// Build a cheap import-presence gate. Precise call identity is annotated by the
|
|
@@ -940,9 +940,16 @@ function walk(node, owner, st) {
|
|
|
940
940
|
// The dependency callback is already the final user argument. Insert
|
|
941
941
|
// both the generated array and slot in one edit so equal-position edit
|
|
942
942
|
// ordering cannot reverse them. Dependency nodes retain original source
|
|
943
|
-
// offsets, preserving arbitrary TS syntax byte-for-byte.
|
|
943
|
+
// offsets, preserving arbitrary TS syntax byte-for-byte. Method-call
|
|
944
|
+
// dependencies are the one synthesized form: the helper call's root is
|
|
945
|
+
// a bare identifier and its name a JSON string, so no arbitrary TS
|
|
946
|
+
// syntax needs reprinting there either.
|
|
944
947
|
const deps = inferred.dependencies
|
|
945
|
-
.map((dependency) =>
|
|
948
|
+
.map((dependency) =>
|
|
949
|
+
dependency.method
|
|
950
|
+
? `${requireParallelHelper(st, METHOD_DEP_IMPORT)}(${dependency.method.root.name}, ${JSON.stringify(dependency.method.name)})`
|
|
951
|
+
: st.source.slice(dependency.node.start, dependency.node.end),
|
|
952
|
+
)
|
|
946
953
|
.join(', ');
|
|
947
954
|
st.edits.push({
|
|
948
955
|
pos: node.arguments[node.arguments.length - 1].end,
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { initializeHydrationEventCapture } from './hydration/event-capture.js';
|
|
|
3
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, forBlock, mapSlot, ifBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, 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
|
+
export { __methodDep } from './method-dep.js';
|
package/dist/index.js
CHANGED
|
@@ -180,6 +180,7 @@ import {
|
|
|
180
180
|
getTransitionFallbackTimeout
|
|
181
181
|
} from "./runtime.js";
|
|
182
182
|
import { __serverRpc } from "./server-rpc-client.js";
|
|
183
|
+
import { __methodDep } from "./method-dep.js";
|
|
183
184
|
export {
|
|
184
185
|
Activity,
|
|
185
186
|
Children,
|
|
@@ -195,6 +196,7 @@ export {
|
|
|
195
196
|
ViewTransition,
|
|
196
197
|
ViewTransitionPseudoElement,
|
|
197
198
|
__createVoidRoot,
|
|
199
|
+
__methodDep,
|
|
198
200
|
markSingleRoot2 as __s,
|
|
199
201
|
__serverRpc,
|
|
200
202
|
__useLinkedStateWithGetter,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function __methodDep(receiver: unknown, name: string): unknown;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const hasOwnProp = Object.prototype.hasOwnProperty;
|
|
2
|
+
function __methodDep(receiver, name) {
|
|
3
|
+
if ((typeof receiver !== "object" || receiver === null) && typeof receiver !== "function") {
|
|
4
|
+
return receiver;
|
|
5
|
+
}
|
|
6
|
+
if (hasOwnProp.call(receiver, name)) return receiver[name];
|
|
7
|
+
return name in receiver ? receiver : void 0;
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
__methodDep
|
|
11
|
+
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -385,14 +385,13 @@ export declare function flushSync<T>(fn: () => T): T;
|
|
|
385
385
|
* Compiler-emitted on a host element's ref MOUNT. Defers the attach until commit
|
|
386
386
|
* (drainRefAttaches) so the node is connected when a callback ref fires and
|
|
387
387
|
* ref.current is set before layout effects run. Each entry records its owning
|
|
388
|
-
* `block
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
export declare function queueRefAttach(scope: Scope, fn: () => void): void;
|
|
388
|
+
* `block`; drainRefAttaches preserves enqueue order for disjoint subtrees and
|
|
389
|
+
* only reorders ancestor/descendant pairs for child-before-parent ordering.
|
|
390
|
+
* Ref identity UPDATES queue here too (paired with a queueRefDetach of the old
|
|
391
|
+
* ref), so within one commit every detach drains before every attach — a ref
|
|
392
|
+
* hopping between elements never ends null, whichever binding updates first.
|
|
393
|
+
*/
|
|
394
|
+
export declare function queueRefAttach(scope: Scope, ref: any, el: Element | FragmentInstance): void;
|
|
396
395
|
/**
|
|
397
396
|
* Queue a teardown ref detach for commit (compiled `ref` binding / spread-ref /
|
|
398
397
|
* hostComponent / fragment-ref unmount cleanups, and the de-opt teardown walk).
|
package/dist/runtime.js
CHANGED
|
@@ -1018,6 +1018,9 @@ let passiveScheduled = false;
|
|
|
1018
1018
|
let commitSeq = 0;
|
|
1019
1019
|
const storeSyncQueue = [];
|
|
1020
1020
|
const refAttachQueue = [];
|
|
1021
|
+
function attachLiveFragmentRef(instance) {
|
|
1022
|
+
attachRef(instance._currentRef, instance);
|
|
1023
|
+
}
|
|
1021
1024
|
let WIP_CAPTURE = null;
|
|
1022
1025
|
function createOffscreenCapture() {
|
|
1023
1026
|
return {
|
|
@@ -1474,10 +1477,10 @@ function flushSync(fn) {
|
|
|
1474
1477
|
}
|
|
1475
1478
|
}
|
|
1476
1479
|
const LAYOUT_CASCADE_LIMIT = 50;
|
|
1477
|
-
function queueRefAttach(scope,
|
|
1480
|
+
function queueRefAttach(scope, ref, el) {
|
|
1478
1481
|
(WIP_CAPTURE !== null ? WIP_CAPTURE.refs : refAttachQueue).push({
|
|
1479
|
-
|
|
1480
|
-
|
|
1482
|
+
ref,
|
|
1483
|
+
el,
|
|
1481
1484
|
block: scope.block
|
|
1482
1485
|
});
|
|
1483
1486
|
}
|
|
@@ -1532,13 +1535,13 @@ function drainRefDetaches() {
|
|
|
1532
1535
|
function drainRefAttaches() {
|
|
1533
1536
|
if (refAttachQueue.length === 0) return;
|
|
1534
1537
|
const q = refAttachQueue.splice(0);
|
|
1535
|
-
q.sort((a, b) => comparePostOrder(a.block,
|
|
1538
|
+
q.sort((a, b) => comparePostOrder(a.block, 0, b.block, 0));
|
|
1536
1539
|
for (const r of q) {
|
|
1537
1540
|
if (blockSubtreeDisposed(r.block)) continue;
|
|
1538
1541
|
try {
|
|
1539
1542
|
REF_CALLBACK_DEPTH++;
|
|
1540
1543
|
try {
|
|
1541
|
-
r.
|
|
1544
|
+
attachRef(r.ref, r.el);
|
|
1542
1545
|
} finally {
|
|
1543
1546
|
REF_CALLBACK_DEPTH--;
|
|
1544
1547
|
}
|
|
@@ -1558,6 +1561,30 @@ function blockSubtreeDisposed(block) {
|
|
|
1558
1561
|
}
|
|
1559
1562
|
return false;
|
|
1560
1563
|
}
|
|
1564
|
+
function refAttachWasDiscarded(uncommitted, el, ref) {
|
|
1565
|
+
return uncommitted !== null && uncommitted.get(el) === ref;
|
|
1566
|
+
}
|
|
1567
|
+
function discardSubtreeRefAttachesFrom(queue, root, uncommitted) {
|
|
1568
|
+
let write = 0;
|
|
1569
|
+
for (let read = 0; read < queue.length; read++) {
|
|
1570
|
+
const entry = queue[read];
|
|
1571
|
+
const owner = entry.block;
|
|
1572
|
+
if (owner === root || owner !== null && blockIsAncestorOf(root, owner)) {
|
|
1573
|
+
uncommitted.set(
|
|
1574
|
+
entry.el,
|
|
1575
|
+
entry.ref === attachLiveFragmentRef ? entry.el._currentRef : entry.ref
|
|
1576
|
+
);
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
queue[write++] = entry;
|
|
1580
|
+
}
|
|
1581
|
+
queue.length = write;
|
|
1582
|
+
}
|
|
1583
|
+
function discardSubtreeRefAttaches(root, uncommitted = /* @__PURE__ */ new Map()) {
|
|
1584
|
+
discardSubtreeRefAttachesFrom(refAttachQueue, root, uncommitted);
|
|
1585
|
+
if (WIP_CAPTURE !== null) discardSubtreeRefAttachesFrom(WIP_CAPTURE.refs, root, uncommitted);
|
|
1586
|
+
return uncommitted;
|
|
1587
|
+
}
|
|
1561
1588
|
function commitEffects() {
|
|
1562
1589
|
if (effectEventQueue.length === 0 && effectEventCommitActions.length === 0 && effectQueues[INSERTION].length === 0 && effectQueues[LAYOUT].length === 0 && refDetachQueue.length === 0 && refAttachQueue.length === 0 && storeSyncQueue.length === 0 && activeFragments.size === 0 && !hasControlledSyncs()) {
|
|
1563
1590
|
if ((effectQueues[PASSIVE].length > 0 || pendingPassiveUnmounts.length > 0) && !passiveScheduled) {
|
|
@@ -6017,7 +6044,7 @@ function isFocusable(el) {
|
|
|
6017
6044
|
function mountFragmentRef(scope, startMarker, endMarker, ref) {
|
|
6018
6045
|
const fi = new FragmentInstance(scope.block, startMarker, endMarker);
|
|
6019
6046
|
fi._currentRef = ref;
|
|
6020
|
-
queueRefAttach(scope,
|
|
6047
|
+
queueRefAttach(scope, attachLiveFragmentRef, fi);
|
|
6021
6048
|
(scope.cleanups ??= []).push(() => {
|
|
6022
6049
|
queueRefDetach(fi._currentRef, fi);
|
|
6023
6050
|
fi._destroy();
|
|
@@ -6552,7 +6579,7 @@ function setSpread(el, value, prev, mountScope, skipDangerouslySetInnerHTML = fa
|
|
|
6552
6579
|
const pv = prev ? prev[k] : void 0;
|
|
6553
6580
|
if (k === "ref") {
|
|
6554
6581
|
if (v === pv) continue;
|
|
6555
|
-
if (mountScope) queueRefAttach(mountScope,
|
|
6582
|
+
if (mountScope) queueRefAttach(mountScope, v, el);
|
|
6556
6583
|
else attachRef(v, el);
|
|
6557
6584
|
continue;
|
|
6558
6585
|
}
|
|
@@ -8875,7 +8902,7 @@ function positionalChildren(children) {
|
|
|
8875
8902
|
}
|
|
8876
8903
|
function applyDeoptProp(el, name, v, ownerBlock) {
|
|
8877
8904
|
if (name === "ref") {
|
|
8878
|
-
if (v != null) queueRefAttach(ownerBlock,
|
|
8905
|
+
if (v != null) queueRefAttach(ownerBlock, v, el);
|
|
8879
8906
|
} else if (name === "className" || name === "class") {
|
|
8880
8907
|
setDeoptClass(el, v);
|
|
8881
8908
|
} else if (name === "style") {
|
|
@@ -9000,7 +9027,7 @@ function applyHostProps(el, props, scope, state) {
|
|
|
9000
9027
|
if (name === "ref") {
|
|
9001
9028
|
if (v !== state.ref) {
|
|
9002
9029
|
if (state.ref != null) queueRefDetach(state.ref, el);
|
|
9003
|
-
if (v != null) queueRefAttach(scope,
|
|
9030
|
+
if (v != null) queueRefAttach(scope, v, el);
|
|
9004
9031
|
state.ref = v;
|
|
9005
9032
|
}
|
|
9006
9033
|
} else if (name === "className" || name === "class") {
|
|
@@ -11067,17 +11094,19 @@ function hideTryContentAndMountPending(state, resumeThenable) {
|
|
|
11067
11094
|
}
|
|
11068
11095
|
if (state.tryBlock) {
|
|
11069
11096
|
const persistent = state.tryBlock;
|
|
11097
|
+
const uncommittedRefs = discardSubtreeRefAttaches(persistent);
|
|
11070
11098
|
deactivateScope(persistent, false);
|
|
11071
11099
|
if (state.parentBlock.disposed || persistent.disposed || state.tryBlock !== persistent) {
|
|
11072
11100
|
return false;
|
|
11073
11101
|
}
|
|
11074
11102
|
if (state.detachedRefs === null) {
|
|
11075
11103
|
state.detachedRefs = [];
|
|
11076
|
-
detachSubtreeRefs(persistent, state.detachedRefs, true, false);
|
|
11104
|
+
detachSubtreeRefs(persistent, state.detachedRefs, true, false, uncommittedRefs);
|
|
11077
11105
|
}
|
|
11078
11106
|
if (state.parentBlock.disposed || persistent.disposed || state.tryBlock !== persistent) {
|
|
11079
11107
|
return false;
|
|
11080
11108
|
}
|
|
11109
|
+
discardSubtreeRefAttaches(persistent, uncommittedRefs);
|
|
11081
11110
|
persistent.inactive = true;
|
|
11082
11111
|
}
|
|
11083
11112
|
if (!mountPendingBody(state)) return false;
|
|
@@ -11363,7 +11392,7 @@ function queueCurrentHiddenRefs(state) {
|
|
|
11363
11392
|
collectVisibleSubtreeRefs(state.tryBlock, refs);
|
|
11364
11393
|
for (let i = 0; i < refs.length; i++) {
|
|
11365
11394
|
const entry = refs[i];
|
|
11366
|
-
queueRefAttach(entry.scope,
|
|
11395
|
+
queueRefAttach(entry.scope, entry.ref, entry.el);
|
|
11367
11396
|
}
|
|
11368
11397
|
}
|
|
11369
11398
|
function attemptHiddenReveal(state, scheduledMode) {
|
|
@@ -11488,14 +11517,12 @@ function compareStagedRevealDomOrder(a, b) {
|
|
|
11488
11517
|
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
11489
11518
|
return 0;
|
|
11490
11519
|
}
|
|
11491
|
-
function
|
|
11520
|
+
function rebaseOffscreenEffectSeq(capture) {
|
|
11492
11521
|
const effects = capture.effects[INSERTION].concat(
|
|
11493
11522
|
capture.effects[LAYOUT],
|
|
11494
11523
|
capture.effects[PASSIVE]
|
|
11495
11524
|
).sort((a, b) => a.seq - b.seq);
|
|
11496
11525
|
for (let i = 0; i < effects.length; i++) effects[i].seq = commitSeq++;
|
|
11497
|
-
const refs = capture.refs.slice().sort((a, b) => a.seq - b.seq);
|
|
11498
|
-
for (let i = 0; i < refs.length; i++) refs[i].seq = commitSeq++;
|
|
11499
11526
|
}
|
|
11500
11527
|
function flushStagedReveals() {
|
|
11501
11528
|
if (flushingStagedReveals) return;
|
|
@@ -11521,7 +11548,7 @@ function flushStagedReveals() {
|
|
|
11521
11548
|
const deferEffects = batch.every((state) => state.stagedCapture !== null);
|
|
11522
11549
|
if (deferEffects) {
|
|
11523
11550
|
batch.sort(compareStagedRevealDomOrder);
|
|
11524
|
-
for (const state of batch)
|
|
11551
|
+
for (const state of batch) rebaseOffscreenEffectSeq(state.stagedCapture);
|
|
11525
11552
|
}
|
|
11526
11553
|
const previousDeferral = deferringStagedRevealEffects;
|
|
11527
11554
|
deferringStagedRevealEffects = deferEffects;
|
|
@@ -12515,9 +12542,9 @@ function forEachSubtreeChild(scope, visit, includeHiddenTry = true) {
|
|
|
12515
12542
|
}
|
|
12516
12543
|
}
|
|
12517
12544
|
}
|
|
12518
|
-
function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = true) {
|
|
12545
|
+
function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = true, uncommitted = null) {
|
|
12519
12546
|
const deoptRoot = scope.deoptNode;
|
|
12520
|
-
if (deoptRoot != null) detachDeoptTreeRefs(deoptRoot, out, shouldDetach, scope);
|
|
12547
|
+
if (deoptRoot != null) detachDeoptTreeRefs(deoptRoot, out, shouldDetach, scope, uncommitted);
|
|
12521
12548
|
const rm = scope.refFields;
|
|
12522
12549
|
if (rm !== null) {
|
|
12523
12550
|
const bag = scope.slots[0];
|
|
@@ -12529,19 +12556,25 @@ function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = t
|
|
|
12529
12556
|
if (ref == null) continue;
|
|
12530
12557
|
const el = bag[rm[j + 2]];
|
|
12531
12558
|
out.push({ ref, el, scope });
|
|
12532
|
-
if (shouldDetach
|
|
12559
|
+
if (shouldDetach && !refAttachWasDiscarded(uncommitted, el, ref)) {
|
|
12560
|
+
attachRef(ref, null, el);
|
|
12561
|
+
}
|
|
12533
12562
|
} else if (kind === "s") {
|
|
12534
12563
|
const ref = bag[rm[j + 1]]?.ref;
|
|
12535
12564
|
if (ref == null) continue;
|
|
12536
12565
|
const el = bag[rm[j + 2]];
|
|
12537
12566
|
if (el == null) continue;
|
|
12538
12567
|
out.push({ ref, el, scope });
|
|
12539
|
-
if (shouldDetach
|
|
12568
|
+
if (shouldDetach && !refAttachWasDiscarded(uncommitted, el, ref)) {
|
|
12569
|
+
attachRef(ref, null, el);
|
|
12570
|
+
}
|
|
12540
12571
|
} else {
|
|
12541
12572
|
const fi = bag[rm[j + 1]];
|
|
12542
12573
|
if (fi == null || fi._currentRef == null) continue;
|
|
12543
12574
|
out.push({ ref: fi._currentRef, el: fi, scope });
|
|
12544
|
-
if (shouldDetach
|
|
12575
|
+
if (shouldDetach && !refAttachWasDiscarded(uncommitted, fi, fi._currentRef)) {
|
|
12576
|
+
attachRef(fi._currentRef, null, fi);
|
|
12577
|
+
}
|
|
12545
12578
|
}
|
|
12546
12579
|
}
|
|
12547
12580
|
}
|
|
@@ -12552,27 +12585,31 @@ function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = t
|
|
|
12552
12585
|
if (s === null || typeof s !== "object") continue;
|
|
12553
12586
|
if (s.ref != null && s.anchor !== void 0 && s.el instanceof Element) {
|
|
12554
12587
|
out.push({ ref: s.ref, el: s.el, scope });
|
|
12555
|
-
if (shouldDetach
|
|
12588
|
+
if (shouldDetach && !refAttachWasDiscarded(uncommitted, s.el, s.ref)) {
|
|
12589
|
+
attachRef(s.ref, null, s.el);
|
|
12590
|
+
}
|
|
12556
12591
|
}
|
|
12557
12592
|
if (s.__kind === "childSlot" && s.hostNode != null) {
|
|
12558
|
-
detachDeoptTreeRefs(s.hostNode, out, shouldDetach, scope);
|
|
12593
|
+
detachDeoptTreeRefs(s.hostNode, out, shouldDetach, scope, uncommitted);
|
|
12559
12594
|
}
|
|
12560
12595
|
}
|
|
12561
12596
|
forEachSubtreeChild(
|
|
12562
12597
|
scope,
|
|
12563
|
-
(child2) => detachSubtreeRefs(child2, out, shouldDetach, includeHiddenTry),
|
|
12598
|
+
(child2) => detachSubtreeRefs(child2, out, shouldDetach, includeHiddenTry, uncommitted),
|
|
12564
12599
|
includeHiddenTry
|
|
12565
12600
|
);
|
|
12566
12601
|
}
|
|
12567
12602
|
function collectVisibleSubtreeRefs(scope, out) {
|
|
12568
12603
|
detachSubtreeRefs(scope, out, false, false);
|
|
12569
12604
|
}
|
|
12570
|
-
function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope) {
|
|
12605
|
+
function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope, uncommitted = null) {
|
|
12571
12606
|
const ref = getDeoptDesc(node)?.props?.ref;
|
|
12572
12607
|
if (ref != null) {
|
|
12573
12608
|
if (out !== null) {
|
|
12574
12609
|
out.push({ ref, el: node, scope: ownerScope });
|
|
12575
|
-
if (shouldDetach
|
|
12610
|
+
if (shouldDetach && !refAttachWasDiscarded(uncommitted, node, ref)) {
|
|
12611
|
+
attachRef(ref, null, node);
|
|
12612
|
+
}
|
|
12576
12613
|
} else {
|
|
12577
12614
|
queueRefDetach(ref, node);
|
|
12578
12615
|
}
|
|
@@ -12584,7 +12621,7 @@ function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope) {
|
|
|
12584
12621
|
c = nodeAfterPortalRange(c, rangeEnd);
|
|
12585
12622
|
continue;
|
|
12586
12623
|
}
|
|
12587
|
-
detachDeoptTreeRefs(c, out, shouldDetach, ownerScope);
|
|
12624
|
+
detachDeoptTreeRefs(c, out, shouldDetach, ownerScope, uncommitted);
|
|
12588
12625
|
c = c.nextSibling;
|
|
12589
12626
|
}
|
|
12590
12627
|
}
|
package/dist/runtime.server.d.ts
CHANGED
|
@@ -551,11 +551,15 @@ export interface RenderOptions {
|
|
|
551
551
|
}
|
|
552
552
|
export declare function setSsrSuspenseTimeout(ms: number): void;
|
|
553
553
|
export declare function getSsrSuspenseTimeout(): number;
|
|
554
|
-
type
|
|
554
|
+
type SuspenseResult = {
|
|
555
555
|
value: unknown;
|
|
556
556
|
} | {
|
|
557
557
|
reason: unknown;
|
|
558
558
|
};
|
|
559
|
+
type SuspenseOutcome = SuspenseResult & {
|
|
560
|
+
/** Thenable whose settlement produced this string-keyed cached result. */
|
|
561
|
+
thenable: PromiseLike<unknown>;
|
|
562
|
+
};
|
|
559
563
|
type ResolvedMap = Map<string, SuspenseOutcome> & {
|
|
560
564
|
/** Render-local stable ids for non-primitive control/list keys. */
|
|
561
565
|
asyncIdentities: Map<unknown, number>;
|
|
@@ -571,7 +575,7 @@ type ResolvedMap = Map<string, SuspenseOutcome> & {
|
|
|
571
575
|
site: ServerHookSlot | undefined;
|
|
572
576
|
frame: Frame | null;
|
|
573
577
|
}>;
|
|
574
|
-
resolvedT: Map<PromiseLike<unknown>,
|
|
578
|
+
resolvedT: Map<PromiseLike<unknown>, SuspenseResult>;
|
|
575
579
|
warm: Map<ServerHookSlot, {
|
|
576
580
|
deps: unknown[];
|
|
577
581
|
value: unknown;
|
package/dist/runtime.server.js
CHANGED
|
@@ -2258,6 +2258,13 @@ function use(usable, siteKey) {
|
|
|
2258
2258
|
const resolved = RESOLVED;
|
|
2259
2259
|
if (resolved !== null && resolved.has(key)) {
|
|
2260
2260
|
const entry = resolved.get(key);
|
|
2261
|
+
const thenable = usable;
|
|
2262
|
+
if (entry.thenable !== thenable) {
|
|
2263
|
+
try {
|
|
2264
|
+
thenable.then(NOOP, NOOP);
|
|
2265
|
+
} catch {
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2261
2268
|
if ("reason" in entry) {
|
|
2262
2269
|
recordHydrationRejection(serial, entry.reason);
|
|
2263
2270
|
throw entry.reason;
|
|
@@ -3124,11 +3131,11 @@ async function settleSuspended(suspended, resolved, timeoutMs, signal) {
|
|
|
3124
3131
|
if (resolved.has(key)) return;
|
|
3125
3132
|
const isPu = key.charCodeAt(0) === 124 && key.startsWith("|pu#");
|
|
3126
3133
|
try {
|
|
3127
|
-
const outcome = { value: await promise };
|
|
3134
|
+
const outcome = { value: await promise, thenable: promise };
|
|
3128
3135
|
resolved.set(key, outcome);
|
|
3129
3136
|
if (isPu) pu.resolvedT.set(promise, outcome);
|
|
3130
3137
|
} catch (reason) {
|
|
3131
|
-
const outcome = { reason };
|
|
3138
|
+
const outcome = { reason, thenable: promise };
|
|
3132
3139
|
resolved.set(key, outcome);
|
|
3133
3140
|
if (isPu) pu.resolvedT.set(promise, outcome);
|
|
3134
3141
|
}
|
|
@@ -3147,11 +3154,13 @@ async function settleFirstOfWave(suspended, resolved, timeoutMs, signal) {
|
|
|
3147
3154
|
(async () => {
|
|
3148
3155
|
try {
|
|
3149
3156
|
const value = await promise;
|
|
3150
|
-
|
|
3151
|
-
if (
|
|
3157
|
+
const outcome = { value, thenable: promise };
|
|
3158
|
+
if (!resolved.has(key)) resolved.set(key, outcome);
|
|
3159
|
+
if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
|
|
3152
3160
|
} catch (reason) {
|
|
3153
|
-
|
|
3154
|
-
if (
|
|
3161
|
+
const outcome = { reason, thenable: promise };
|
|
3162
|
+
if (!resolved.has(key)) resolved.set(key, outcome);
|
|
3163
|
+
if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
|
|
3155
3164
|
}
|
|
3156
3165
|
})()
|
|
3157
3166
|
);
|
|
@@ -3296,11 +3305,13 @@ function recordHostedStratum(suspended, resolved) {
|
|
|
3296
3305
|
const isPu = key.startsWith("|pu#");
|
|
3297
3306
|
try {
|
|
3298
3307
|
const value = await promise;
|
|
3299
|
-
|
|
3300
|
-
if (
|
|
3308
|
+
const outcome = { value, thenable: promise };
|
|
3309
|
+
if (!resolved.has(key)) resolved.set(key, outcome);
|
|
3310
|
+
if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
|
|
3301
3311
|
} catch (reason) {
|
|
3302
|
-
|
|
3303
|
-
if (
|
|
3312
|
+
const outcome = { reason, thenable: promise };
|
|
3313
|
+
if (!resolved.has(key)) resolved.set(key, outcome);
|
|
3314
|
+
if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
|
|
3304
3315
|
}
|
|
3305
3316
|
})()
|
|
3306
3317
|
);
|
package/dist/server/index.d.ts
CHANGED
|
@@ -21,4 +21,5 @@
|
|
|
21
21
|
* should call them.
|
|
22
22
|
*/
|
|
23
23
|
export { executeServerFunction } from './rpc.js';
|
|
24
|
+
export { __methodDep } from '../method-dep.js';
|
|
24
25
|
export { renderToString, renderToStaticMarkup, renderToPipeableStream, renderToReadableStream, type RenderResult, type RenderOptions, type StreamOptions, type StreamInjectionSource, setSsrSuspenseTimeout, getSsrSuspenseTimeout, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, useState, useLinkedState, useReducer, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, type LinkedStatePrevious, type LinkedStateOptions, useEffect, useLayoutEffect, useInsertionEffect, useImperativeHandle, useMemo, useCallback, useRef, useId, useEffectEvent, useTransition, useDeferredValue, useSyncExternalStore, useActionState, useFormStatus, useOptimistic, useDebugValue, memo, lazy, hookSlots, withSlot, startTransition, flushSync, isChildrenBlock, isValidElement, cloneElement, Children, createPortal, requestFormReset, preload, preinit, preconnect, prefetchDNS, Suspense, ErrorBoundary, Hydrate, Fragment, Activity, ViewTransition, ViewTransition as unstable_ViewTransition, addTransitionType, addTransitionType as unstable_addTransitionType, createContext, use, useContext, ssrIsSuspense, type Context, type FormStatus, markChildrenBlock, createElement, createScopedValue, createScopedElement, positionalChildren, escapeHtml, escapeAttr, ssrText, ssrTextPre, ssrChild, ssrChildText, ssrAttr, normalizeClass, ssrStyle, ssrClass, ssrAttrs, ssrSnapshotSpread, ssrSpread, ssrInnerHtml, ssrScriptInnerHtml, ssrChildrenSources, ssrVoidContent, ssrValueAttr, ssrCheckedAttr, ssrInputAttrs, ssrTextareaValue, ssrTextareaValueSources, ssrSelectAttrs, ssrSelectScope, ssrSelectScopeSources, ssrOptionValueSources, ssrOption, ssrElement, ssrComponent, ssrComponentNS, ssrInNamespace, ssrBlock, ssrActivity, ssrForBlock, mapSlot, ssrControl, ssrArm, ssrTry, ssrPortal, injectStyle, ssrHeadEl, namespaceHead, namespaceHeadElement, puMemo, puBatch, warmMemo, warmChild, } from '../runtime.server.js';
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { executeServerFunction } from "./rpc.js";
|
|
2
|
+
import { __methodDep } from "../method-dep.js";
|
|
2
3
|
import {
|
|
3
4
|
renderToString,
|
|
4
5
|
renderToStaticMarkup,
|
|
@@ -122,6 +123,7 @@ export {
|
|
|
122
123
|
Hydrate,
|
|
123
124
|
Suspense,
|
|
124
125
|
ViewTransition,
|
|
126
|
+
__methodDep,
|
|
125
127
|
__useLinkedStateWithGetter,
|
|
126
128
|
__useReducerWithGetter,
|
|
127
129
|
__useStateWithGetter,
|