ripple 0.3.84 → 0.3.86
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/CHANGELOG.md +81 -0
- package/package.json +3 -3
- package/src/constants.js +4 -0
- package/src/runtime/internal/client/blocks.js +53 -0
- package/src/runtime/internal/client/component.js +3 -3
- package/src/runtime/internal/client/constants.js +4 -0
- package/src/runtime/internal/client/for.js +87 -14
- package/src/runtime/internal/client/if.js +18 -2
- package/src/runtime/internal/client/index.js +1 -0
- package/src/runtime/internal/client/operations.js +25 -0
- package/src/runtime/internal/client/runtime.js +62 -10
- package/src/runtime/internal/client/switch.js +33 -5
- package/src/runtime/internal/client/template.js +10 -2
- package/src/runtime/internal/client/try.js +12 -2
- package/src/runtime/internal/client/types.d.ts +4 -0
- package/tests/client/__snapshots__/for.test.tsrx.snap +0 -2
- package/tests/client/compiler/compiler.basic.test.tsrx +5 -2
- package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -4
- package/tests/client/for-single-root-items.test.tsrx +130 -0
- package/tests/client/scoped-flush.test.tsrx +105 -0
- package/tests/hydration/compiled/client/basic.js +198 -214
- package/tests/hydration/compiled/client/composite.js +23 -72
- package/tests/hydration/compiled/client/for.js +66 -72
- package/tests/hydration/compiled/client/hmr.js +2 -13
- package/tests/hydration/compiled/client/html.js +619 -479
- package/tests/hydration/compiled/client/if-children.js +72 -84
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +463 -0
- package/tests/hydration/compiled/client/if.js +87 -92
- package/tests/hydration/compiled/client/mixed-control-flow.js +120 -160
- package/tests/hydration/compiled/client/nested-control-flow.js +288 -360
- package/tests/hydration/compiled/client/portal.js +15 -21
- package/tests/hydration/compiled/client/reactivity.js +7 -12
- package/tests/hydration/compiled/client/switch.js +113 -115
- package/tests/hydration/compiled/client/track-async-serialization.js +71 -122
- package/tests/hydration/compiled/client/try.js +26 -41
- package/tests/hydration/compiled/server/basic.js +295 -556
- package/tests/hydration/compiled/server/composite.js +31 -50
- package/tests/hydration/compiled/server/events.js +37 -173
- package/tests/hydration/compiled/server/for.js +236 -847
- package/tests/hydration/compiled/server/head.js +110 -241
- package/tests/hydration/compiled/server/hmr.js +14 -41
- package/tests/hydration/compiled/server/html-in-template.js +14 -38
- package/tests/hydration/compiled/server/html.js +951 -1176
- package/tests/hydration/compiled/server/if-children.js +59 -493
- package/tests/hydration/compiled/server/if-fragment-controlflow.js +227 -0
- package/tests/hydration/compiled/server/if.js +57 -209
- package/tests/hydration/compiled/server/mixed-control-flow.js +144 -250
- package/tests/hydration/compiled/server/nested-control-flow.js +309 -559
- package/tests/hydration/compiled/server/portal.js +94 -156
- package/tests/hydration/compiled/server/reactivity.js +23 -65
- package/tests/hydration/compiled/server/return.js +6 -16
- package/tests/hydration/compiled/server/switch.js +91 -219
- package/tests/hydration/compiled/server/track-async-serialization.js +211 -256
- package/tests/hydration/compiled/server/try.js +67 -126
- package/tests/hydration/components/html.tsrx +75 -0
- package/tests/hydration/components/if-fragment-controlflow.tsrx +145 -0
- package/tests/hydration/html.test.js +50 -0
- package/tests/hydration/if-fragment-controlflow.test.js +95 -0
- package/tests/server/compiler.test.tsrx +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.3.86
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1310](https://github.com/Ripple-TS/ripple/pull/1310)
|
|
8
|
+
[`4ebd58d`](https://github.com/Ripple-TS/ripple/commit/4ebd58dfe853c1ed945072822eaba8a7a9e19a6c)
|
|
9
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Fix
|
|
10
|
+
`Cannot use 'in' operator to search for 'parent' in null` thrown from `append()`
|
|
11
|
+
when reordering or inserting into a keyed `@for` whose item body is a single
|
|
12
|
+
control-flow / component root (e.g. `@for (...; key ...) { <Card {item} /> }`
|
|
13
|
+
where `Card`'s own body is a single `@if`/`@for`/component). The 0.3.85
|
|
14
|
+
wrapper-anchor optimization leaves such an item block's `s.start` null because
|
|
15
|
+
its DOM is rendered through a descendant block, and keyed reconciliation read
|
|
16
|
+
`s.start` directly as the insertion anchor. Reconciliation now resolves the real
|
|
17
|
+
first/last DOM node by descending child blocks, so no `<!>` comment marker is
|
|
18
|
+
reintroduced and the optimization's reduced DOM-mutation cost is preserved. The
|
|
19
|
+
same resolution is applied to `@switch` case reordering.
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
[[`e4e6d7b`](https://github.com/Ripple-TS/ripple/commit/e4e6d7b854786ad19a2c86276ea7e0ffb062e61a)]:
|
|
22
|
+
- @tsrx/ripple@0.1.34
|
|
23
|
+
|
|
24
|
+
## 0.3.85
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- [#1307](https://github.com/Ripple-TS/ripple/pull/1307)
|
|
29
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2)
|
|
30
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Skip the wrapper anchor for
|
|
31
|
+
single control-flow / code-block / component root scopes. When a scope's entire
|
|
32
|
+
renderable output is a single `@if`, `@switch`, `@for`, `@try`, or static child
|
|
33
|
+
component — i.e. a component body, a control-flow branch, or a `@{}` body whose
|
|
34
|
+
only output after setup is one of these — the compiler now renders it directly
|
|
35
|
+
before the parent-provided `__anchor` instead of synthesizing a `<!>` fragment
|
|
36
|
+
wrapper and an extra append + clone. For deep recursive trees this measurably
|
|
37
|
+
cuts mount time and shrinks generated output; in the recursive-context benchmark
|
|
38
|
+
it brought mount DOM operations to one clone + one append per element (from
|
|
39
|
+
~1.5×) and halved the comment-anchor nodes.
|
|
40
|
+
|
|
41
|
+
Hydration is preserved. The control-flow runtimes
|
|
42
|
+
(`if_block`/`switch_block`/`for_block`/`for_block_keyed`/`try_block`) capture
|
|
43
|
+
the SSR boundary marker and hand it to `append()` afterward, so the existing
|
|
44
|
+
context-aware cursor advance still runs — including for a root scope used as a
|
|
45
|
+
child of a composite/slot with following siblings. Single-component roots need
|
|
46
|
+
no runtime change at all, since a component's own content advances the hydration
|
|
47
|
+
cursor.
|
|
48
|
+
|
|
49
|
+
Also relaxes the compiler's text-expression detection: `string + anything` (e.g.
|
|
50
|
+
`{a + '|' + b}`) is now recognized as text and lowered to the fast `set_text`
|
|
51
|
+
path without requiring an explicit `as string`, since such an expression always
|
|
52
|
+
evaluates to a string in JS.
|
|
53
|
+
|
|
54
|
+
- [#1307](https://github.com/Ripple-TS/ripple/pull/1307)
|
|
55
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2)
|
|
56
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Scope the client flush
|
|
57
|
+
traversal to the updated subtree. Previously every flush walked the whole root
|
|
58
|
+
block tree to find dirty subscribers, so a deeply-scoped update (e.g. mutating
|
|
59
|
+
state read by only a small subtree) paid a cost proportional to the entire tree
|
|
60
|
+
rather than the affected branch. `flush_updates` now descends only along the
|
|
61
|
+
routing path to each directly-scheduled block and fully scans just that block's
|
|
62
|
+
subtree, where its subscribers live. A tracked read from outside its owner's
|
|
63
|
+
subtree (e.g. smuggled across sibling subtrees via a module-level variable) is
|
|
64
|
+
detected in `register_dependency` and transparently falls back to the original
|
|
65
|
+
full-tree scan, so behavior is unchanged.
|
|
66
|
+
|
|
67
|
+
- Updated dependencies
|
|
68
|
+
[[`ba498cd`](https://github.com/Ripple-TS/ripple/commit/ba498cde76e9f83235ce91da825f403a28441bff),
|
|
69
|
+
[`313b351`](https://github.com/Ripple-TS/ripple/commit/313b3513e4a959dd80b546da41c798066c5ccb0f),
|
|
70
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
71
|
+
[`bbe6e74`](https://github.com/Ripple-TS/ripple/commit/bbe6e7422c690558f0dfcb3abe5452d4f4cdde91),
|
|
72
|
+
[`0e9f523`](https://github.com/Ripple-TS/ripple/commit/0e9f52358a615c2fc7759544e96c43dccb533c86),
|
|
73
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
74
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
75
|
+
[`2b65285`](https://github.com/Ripple-TS/ripple/commit/2b65285bfcd4c6a0aa93d7fa0b25082e6ec74e1f),
|
|
76
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
77
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2),
|
|
78
|
+
[`b887deb`](https://github.com/Ripple-TS/ripple/commit/b887debf5f47e63d73184ac218ec8b3542a5e21c),
|
|
79
|
+
[`3668c5f`](https://github.com/Ripple-TS/ripple/commit/3668c5fe9cdaca4862707d653d23af94780f42af),
|
|
80
|
+
[`bbc3843`](https://github.com/Ripple-TS/ripple/commit/bbc384387e33c538234be36c07cc4b30ef6ce136)]:
|
|
81
|
+
- @tsrx/ripple@0.1.33
|
|
82
|
+
- @tsrx/core@0.1.33
|
|
83
|
+
|
|
3
84
|
## 0.3.84
|
|
4
85
|
|
|
5
86
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.86",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"clsx": "^2.1.1",
|
|
74
74
|
"devalue": "^5.8.1",
|
|
75
75
|
"esm-env": "^1.2.2",
|
|
76
|
-
"@tsrx/core": "0.1.
|
|
77
|
-
"@tsrx/ripple": "0.1.
|
|
76
|
+
"@tsrx/core": "0.1.33",
|
|
77
|
+
"@tsrx/ripple": "0.1.34"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/estree": "^1.0.8",
|
package/src/constants.js
CHANGED
|
@@ -2,6 +2,10 @@ export const TEMPLATE_FRAGMENT = 1;
|
|
|
2
2
|
export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
|
|
3
3
|
export const IS_CONTROLLED = 1 << 2;
|
|
4
4
|
export const IS_INDEXED = 1 << 3;
|
|
5
|
+
// A @for whose component body is its sole root: renders before the parent
|
|
6
|
+
// `__anchor` (sibling semantics, no `<!>` wrapper). Distinct from IS_CONTROLLED,
|
|
7
|
+
// which renders inside a parent element.
|
|
8
|
+
export const ROOT_CONTROLLED = 1 << 4;
|
|
5
9
|
export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
|
|
6
10
|
export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
|
|
7
11
|
|
|
@@ -429,6 +429,59 @@ export function move_block(block, target) {
|
|
|
429
429
|
return moved;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Resolve the first DOM node owned by a block. A block normally records its
|
|
434
|
+
* range in `s.start`/`s.end`, but an optimized single control-flow / component
|
|
435
|
+
* root scope renders its content through a descendant block instead of a
|
|
436
|
+
* synthesized `<!>` wrapper, so its own `s.start` is null. In that case we
|
|
437
|
+
* descend into child blocks to find the real first node. Returns null when the
|
|
438
|
+
* block currently renders no DOM.
|
|
439
|
+
* @param {Block} block
|
|
440
|
+
* @returns {Node | null}
|
|
441
|
+
*/
|
|
442
|
+
export function get_first_node(block) {
|
|
443
|
+
var f = block.f;
|
|
444
|
+
if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
|
|
445
|
+
var s = block.s;
|
|
446
|
+
if (s !== null && s.start !== null) {
|
|
447
|
+
return s.start;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
var child = block.first;
|
|
451
|
+
while (child !== null) {
|
|
452
|
+
var node = get_first_node(child);
|
|
453
|
+
if (node !== null) {
|
|
454
|
+
return node;
|
|
455
|
+
}
|
|
456
|
+
child = child.next;
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Mirror of {@link get_first_node} for the last DOM node owned by a block.
|
|
463
|
+
* @param {Block} block
|
|
464
|
+
* @returns {Node | null}
|
|
465
|
+
*/
|
|
466
|
+
export function get_last_node(block) {
|
|
467
|
+
var f = block.f;
|
|
468
|
+
if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
|
|
469
|
+
var s = block.s;
|
|
470
|
+
if (s !== null && s.start !== null) {
|
|
471
|
+
return s.end;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
var child = block.last;
|
|
475
|
+
while (child !== null) {
|
|
476
|
+
var node = get_last_node(child);
|
|
477
|
+
if (node !== null) {
|
|
478
|
+
return node;
|
|
479
|
+
}
|
|
480
|
+
child = child.prev;
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
432
485
|
/**
|
|
433
486
|
* @param {Block} block
|
|
434
487
|
* @param {boolean} [remove_dom]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Block } from '#client' */
|
|
1
|
+
/** @import { AppendIntoAnchor, Block } from '#client' */
|
|
2
2
|
|
|
3
3
|
import { is_tsrx_element } from '../../element.js';
|
|
4
4
|
import { render_value } from './expression.js';
|
|
@@ -6,7 +6,7 @@ import { active_block, pop_component, push_component } from './runtime.js';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {Function} fn
|
|
9
|
-
* @param {Node} anchor
|
|
9
|
+
* @param {Node | AppendIntoAnchor} anchor
|
|
10
10
|
* @param {Record<string, any>} props
|
|
11
11
|
* @param {Block | null} [block=active_block]
|
|
12
12
|
* @returns {void}
|
|
@@ -21,7 +21,7 @@ export function render_component(fn, anchor, props, block = active_block) {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @param {Function} fn
|
|
24
|
-
* @param {Node} anchor
|
|
24
|
+
* @param {Node | AppendIntoAnchor} anchor
|
|
25
25
|
* @param {Record<string, any>} props
|
|
26
26
|
* @param {Block | null} [block=active_block]
|
|
27
27
|
* @returns {void}
|
|
@@ -19,6 +19,10 @@ export var DERIVED = 1 << 18;
|
|
|
19
19
|
export var DEFERRED = 1 << 19;
|
|
20
20
|
export var PAUSED = 1 << 20;
|
|
21
21
|
export var DESTROYED = 1 << 21;
|
|
22
|
+
// Marks a block that was directly scheduled (the owner of a mutated tracked).
|
|
23
|
+
// Its whole subtree is scanned for dirty subscribers during flush; blocks above
|
|
24
|
+
// it are only traversed along the CONTAINS_UPDATE routing path.
|
|
25
|
+
export var UPDATE_SOURCE = 1 << 22;
|
|
22
26
|
|
|
23
27
|
export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | SWITCH_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
|
|
24
28
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/** @import { Block, Tracked } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { IS_CONTROLLED, IS_INDEXED } from '../../../constants.js';
|
|
4
|
-
import {
|
|
3
|
+
import { IS_CONTROLLED, IS_INDEXED, ROOT_CONTROLLED } from '../../../constants.js';
|
|
4
|
+
import {
|
|
5
|
+
branch,
|
|
6
|
+
destroy_block,
|
|
7
|
+
destroy_block_children,
|
|
8
|
+
get_first_node,
|
|
9
|
+
get_last_node,
|
|
10
|
+
render,
|
|
11
|
+
} from './blocks.js';
|
|
5
12
|
import { FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
|
|
6
13
|
import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
|
|
7
14
|
import { create_text, get_first_child, get_last_child, next_sibling } from './operations.js';
|
|
15
|
+
import { append } from './template.js';
|
|
8
16
|
import { active_block, set, tracked, untrack } from './runtime.js';
|
|
9
17
|
import { array_from, is_array } from '@tsrx/core/runtime/language-helpers';
|
|
10
18
|
|
|
@@ -70,12 +78,26 @@ function create_empty(anchor, render_empty) {
|
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
80
|
* @param {Block} block
|
|
73
|
-
* @param {
|
|
81
|
+
* @param {ChildNode} anchor
|
|
74
82
|
* @returns {void}
|
|
75
83
|
*/
|
|
76
84
|
function move(block, anchor) {
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// Fast path: a normal item records its own range. Only an optimized single
|
|
86
|
+
// control-flow / component root item (DOM rendered through a descendant
|
|
87
|
+
// block, `s.start` null) needs the descent via `get_first_node`/`get_last_node`.
|
|
88
|
+
var s = block.s;
|
|
89
|
+
var node = s.start;
|
|
90
|
+
var end;
|
|
91
|
+
|
|
92
|
+
if (node === null) {
|
|
93
|
+
node = get_first_node(block);
|
|
94
|
+
if (node === null) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
end = get_last_node(block);
|
|
98
|
+
} else {
|
|
99
|
+
end = s.end;
|
|
100
|
+
}
|
|
79
101
|
|
|
80
102
|
if (node === end) {
|
|
81
103
|
anchor.before(node);
|
|
@@ -86,12 +108,43 @@ function move(block, anchor) {
|
|
|
86
108
|
anchor.before(node);
|
|
87
109
|
node = next_node;
|
|
88
110
|
if (node === end) {
|
|
89
|
-
anchor.before(end);
|
|
111
|
+
anchor.before(/** @type {Node} */ (end));
|
|
90
112
|
break;
|
|
91
113
|
}
|
|
92
114
|
}
|
|
93
115
|
}
|
|
94
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Resolve the insertion anchor for a block at `index`: the first real DOM node
|
|
119
|
+
* at or after `index`, or `fallback` when every remaining block renders nothing.
|
|
120
|
+
* Scanning forward keeps insertions correct even when an optimized item renders
|
|
121
|
+
* no DOM (e.g. a single `@if` that is currently false), which previously relied
|
|
122
|
+
* on a synthesized `<!>` wrapper as a stable position marker.
|
|
123
|
+
* @param {Block[]} blocks
|
|
124
|
+
* @param {number} index
|
|
125
|
+
* @param {number} length
|
|
126
|
+
* @param {Element | Text} fallback
|
|
127
|
+
* @returns {ChildNode}
|
|
128
|
+
*/
|
|
129
|
+
function block_start(blocks, index, length, fallback) {
|
|
130
|
+
if (index >= length) {
|
|
131
|
+
return fallback;
|
|
132
|
+
}
|
|
133
|
+
// Fast path: a normal item records its own boundary, so this is the same
|
|
134
|
+
// single property read the pre-#1307 code did — no descent, no scan.
|
|
135
|
+
var first = blocks[index].s.start;
|
|
136
|
+
if (first !== null) {
|
|
137
|
+
return first;
|
|
138
|
+
}
|
|
139
|
+
for (var k = index; k < length; k++) {
|
|
140
|
+
var node = get_first_node(blocks[k]);
|
|
141
|
+
if (node !== null) {
|
|
142
|
+
return /** @type {ChildNode} */ (node);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
/**
|
|
96
149
|
* @template V
|
|
97
150
|
* @param {V[] | Iterable<V>} collection
|
|
@@ -121,7 +174,10 @@ function collection_to_array(collection) {
|
|
|
121
174
|
export function for_block(node, get_collection, render_fn, flags, render_empty) {
|
|
122
175
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
123
176
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
177
|
+
var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
|
|
124
178
|
var anchor = /** @type {Element | Text} */ (node);
|
|
179
|
+
/** @type {Node | undefined} */
|
|
180
|
+
var boundary;
|
|
125
181
|
|
|
126
182
|
if (is_controlled) {
|
|
127
183
|
if (hydrating) {
|
|
@@ -133,6 +189,9 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
133
189
|
}
|
|
134
190
|
|
|
135
191
|
if (hydrating) {
|
|
192
|
+
if (root_controlled) {
|
|
193
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
194
|
+
}
|
|
136
195
|
hydrate_next();
|
|
137
196
|
}
|
|
138
197
|
|
|
@@ -153,6 +212,10 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
153
212
|
null,
|
|
154
213
|
FOR_BLOCK,
|
|
155
214
|
);
|
|
215
|
+
|
|
216
|
+
if (hydrating && root_controlled) {
|
|
217
|
+
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
218
|
+
}
|
|
156
219
|
}
|
|
157
220
|
|
|
158
221
|
/**
|
|
@@ -169,7 +232,10 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
169
232
|
export function for_block_keyed(node, get_collection, render_fn, flags, get_key, render_empty) {
|
|
170
233
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
171
234
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
235
|
+
var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
|
|
172
236
|
var anchor = /** @type {Element | Text} */ (node);
|
|
237
|
+
/** @type {Node | undefined} */
|
|
238
|
+
var boundary;
|
|
173
239
|
|
|
174
240
|
if (is_controlled) {
|
|
175
241
|
var parent_node = /** @type {Element} */ (node);
|
|
@@ -183,6 +249,9 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
|
|
|
183
249
|
}
|
|
184
250
|
|
|
185
251
|
if (hydrating) {
|
|
252
|
+
if (root_controlled) {
|
|
253
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
254
|
+
}
|
|
186
255
|
hydrate_next();
|
|
187
256
|
}
|
|
188
257
|
|
|
@@ -208,6 +277,10 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
|
|
|
208
277
|
null,
|
|
209
278
|
FOR_BLOCK,
|
|
210
279
|
);
|
|
280
|
+
|
|
281
|
+
if (hydrating && root_controlled) {
|
|
282
|
+
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
283
|
+
}
|
|
211
284
|
}
|
|
212
285
|
|
|
213
286
|
/**
|
|
@@ -408,7 +481,7 @@ function reconcile_by_key(
|
|
|
408
481
|
if (j <= b_end) {
|
|
409
482
|
while (j <= b_end) {
|
|
410
483
|
b_val = b[j];
|
|
411
|
-
var target = j
|
|
484
|
+
var target = block_start(a_blocks, j, a_length, anchor);
|
|
412
485
|
b_blocks[j] = create_item(target, b_val, j, render_fn, is_indexed, true);
|
|
413
486
|
j++;
|
|
414
487
|
}
|
|
@@ -527,14 +600,14 @@ function reconcile_by_key(
|
|
|
527
600
|
b_val = b[pos];
|
|
528
601
|
next_pos = pos + 1;
|
|
529
602
|
|
|
530
|
-
var target = next_pos
|
|
603
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
531
604
|
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true);
|
|
532
605
|
} else if (j < 0 || i !== seq[j]) {
|
|
533
606
|
pos = i + b_start;
|
|
534
607
|
b_val = b[pos];
|
|
535
608
|
next_pos = pos + 1;
|
|
536
609
|
|
|
537
|
-
var target = next_pos
|
|
610
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
538
611
|
move(b_blocks[pos], target);
|
|
539
612
|
} else {
|
|
540
613
|
j--;
|
|
@@ -547,7 +620,7 @@ function reconcile_by_key(
|
|
|
547
620
|
b_val = b[pos];
|
|
548
621
|
next_pos = pos + 1;
|
|
549
622
|
|
|
550
|
-
var target = next_pos
|
|
623
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
551
624
|
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true);
|
|
552
625
|
}
|
|
553
626
|
}
|
|
@@ -695,7 +768,7 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
|
|
|
695
768
|
if (j <= b_end) {
|
|
696
769
|
while (j <= b_end) {
|
|
697
770
|
b_val = b[j];
|
|
698
|
-
var target = j
|
|
771
|
+
var target = block_start(a_blocks, j, a_length, anchor);
|
|
699
772
|
b_blocks[j] = create_item(target, b_val, j, render_fn, is_indexed, false);
|
|
700
773
|
j++;
|
|
701
774
|
}
|
|
@@ -808,14 +881,14 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
|
|
|
808
881
|
b_val = b[pos];
|
|
809
882
|
next_pos = pos + 1;
|
|
810
883
|
|
|
811
|
-
var target = next_pos
|
|
884
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
812
885
|
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, false);
|
|
813
886
|
} else if (j < 0 || i !== seq[j]) {
|
|
814
887
|
pos = i + b_start;
|
|
815
888
|
b_val = b[pos];
|
|
816
889
|
next_pos = pos + 1;
|
|
817
890
|
|
|
818
|
-
var target = next_pos
|
|
891
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
819
892
|
move(b_blocks[pos], target);
|
|
820
893
|
} else {
|
|
821
894
|
j--;
|
|
@@ -828,7 +901,7 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
|
|
|
828
901
|
b_val = b[pos];
|
|
829
902
|
next_pos = pos + 1;
|
|
830
903
|
|
|
831
|
-
var target = next_pos
|
|
904
|
+
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
832
905
|
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, false);
|
|
833
906
|
}
|
|
834
907
|
}
|
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
import { branch, destroy_block, render } from './blocks.js';
|
|
4
4
|
import { IF_BLOCK, UNINITIALIZED } from './constants.js';
|
|
5
|
-
import { hydrate_next, hydrating } from './hydration.js';
|
|
5
|
+
import { hydrate_next, hydrate_node, hydrating } from './hydration.js';
|
|
6
|
+
import { append } from './template.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {Node} node
|
|
9
10
|
* @param {(set_branch: (fn: (anchor: Node) => void, flag?: boolean) => void) => void} fn
|
|
11
|
+
* @param {boolean} [root_controlled] When true the block renders directly before
|
|
12
|
+
* the component's `__anchor` (no synthesized `<!>` wrapper). During hydration
|
|
13
|
+
* the SSR boundary start marker sits at the cursor; we hand it to `append()`
|
|
14
|
+
* afterwards so it performs the same context-aware boundary advance the
|
|
15
|
+
* eliminated wrapper's `append()` used to do.
|
|
10
16
|
* @returns {void}
|
|
11
17
|
*/
|
|
12
|
-
export function if_block(node, fn) {
|
|
18
|
+
export function if_block(node, fn, root_controlled) {
|
|
19
|
+
/** @type {Node | undefined} */
|
|
20
|
+
var boundary;
|
|
21
|
+
|
|
13
22
|
if (hydrating) {
|
|
23
|
+
if (root_controlled) {
|
|
24
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
25
|
+
}
|
|
14
26
|
hydrate_next();
|
|
15
27
|
}
|
|
16
28
|
|
|
@@ -52,4 +64,8 @@ export function if_block(node, fn) {
|
|
|
52
64
|
null,
|
|
53
65
|
IF_BLOCK,
|
|
54
66
|
);
|
|
67
|
+
|
|
68
|
+
if (hydrating && root_controlled) {
|
|
69
|
+
append(/** @type {ChildNode} */ (anchor), /** @type {Node} */ (boundary));
|
|
70
|
+
}
|
|
55
71
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import { AppendIntoAnchor } from '#client' */
|
|
2
|
+
|
|
1
3
|
import { TEXT_NODE } from '../../../constants.js';
|
|
2
4
|
import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
|
|
3
5
|
import { get_descriptor } from '@tsrx/core/runtime/language-helpers';
|
|
@@ -83,6 +85,29 @@ export function first_child(node, is_text) {
|
|
|
83
85
|
return child;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Anchor sentinel for all-component children: components render directly into
|
|
90
|
+
* `parent` instead of each inserting before a placeholder comment. `append()`
|
|
91
|
+
* detects the sentinel (no `nodeType`) and appendChild()s into `parent`. During
|
|
92
|
+
* hydration we descend the cursor into `parent` (mirroring first_child) so the
|
|
93
|
+
* first appended component adopts the server's first child of `parent`.
|
|
94
|
+
* @param {Node} parent
|
|
95
|
+
* @returns {AppendIntoAnchor}
|
|
96
|
+
*/
|
|
97
|
+
export function append_into(parent) {
|
|
98
|
+
if (hydrating) {
|
|
99
|
+
var child = get_first_child(/** @type {Node} */ (hydrate_node));
|
|
100
|
+
|
|
101
|
+
if (child === null) {
|
|
102
|
+
child = /** @type {Node} */ (hydrate_node).appendChild(create_text());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set_hydrate_node(child);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { parent };
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
/**
|
|
87
112
|
* @template {Node} N
|
|
88
113
|
* @param {N} node
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
SUSPENSE_REJECTED,
|
|
35
35
|
TRY_BLOCK,
|
|
36
36
|
DIRECT_CHILD_BLOCK,
|
|
37
|
+
UPDATE_SOURCE,
|
|
37
38
|
} from './constants.js';
|
|
38
39
|
import {
|
|
39
40
|
begin_boundary_request,
|
|
@@ -93,6 +94,12 @@ let is_micro_task_queued = false;
|
|
|
93
94
|
let clock = 0;
|
|
94
95
|
/** @type {Block[]} */
|
|
95
96
|
let queued_root_blocks = [];
|
|
97
|
+
// Flush normally only scans the subtree of each directly-scheduled block, since a
|
|
98
|
+
// tracked's subscribers live inside the subtree of the block that owns it. If a
|
|
99
|
+
// tracked is ever read from outside its owner's subtree (e.g. smuggled across
|
|
100
|
+
// sibling subtrees via a module-level variable), that invariant breaks, so we
|
|
101
|
+
// permanently fall back to scanning the whole root tree to stay correct.
|
|
102
|
+
let disable_scoped_flush = false;
|
|
96
103
|
/** @type {(() => void)[]} */
|
|
97
104
|
let queued_microtasks = [];
|
|
98
105
|
/** @type {number} */
|
|
@@ -939,22 +946,33 @@ function trigger_track_get(fn, v) {
|
|
|
939
946
|
function flush_updates(root_block) {
|
|
940
947
|
/** @type {Block | null} */
|
|
941
948
|
var current = root_block;
|
|
942
|
-
var containing_update = null;
|
|
943
949
|
var pre_effects = [];
|
|
944
950
|
var other_blocks = [];
|
|
945
951
|
var effects = [];
|
|
946
|
-
|
|
952
|
+
// The nearest enclosing directly-scheduled block ("update source"). While it is
|
|
953
|
+
// non-null we are inside a source's subtree and scan every descendant. Above
|
|
954
|
+
// sources we only follow the CONTAINS_UPDATE routing path, so sibling subtrees
|
|
955
|
+
// that contain no update are skipped. When scoping is disabled we treat the
|
|
956
|
+
// whole root tree as one source — the original full-tree scan.
|
|
957
|
+
/** @type {Block | null} */
|
|
958
|
+
var scope_root = disable_scoped_flush ? root_block : null;
|
|
947
959
|
|
|
948
960
|
while (current !== null) {
|
|
949
961
|
var flags = current.f;
|
|
962
|
+
var on_path = (flags & CONTAINS_UPDATE) !== 0;
|
|
950
963
|
|
|
951
|
-
if (
|
|
964
|
+
if (on_path) {
|
|
952
965
|
current.f ^= CONTAINS_UPDATE;
|
|
953
|
-
containing_update_head = { v: containing_update, n: containing_update_head };
|
|
954
|
-
containing_update = current;
|
|
955
966
|
}
|
|
956
967
|
|
|
957
|
-
if ((flags &
|
|
968
|
+
if ((flags & UPDATE_SOURCE) !== 0) {
|
|
969
|
+
current.f ^= UPDATE_SOURCE;
|
|
970
|
+
if (scope_root === null) {
|
|
971
|
+
scope_root = current;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if ((flags & PAUSED) === 0 && (on_path || scope_root !== null)) {
|
|
958
976
|
if ((flags & PRE_EFFECT_BLOCK) !== 0) {
|
|
959
977
|
pre_effects.push(current);
|
|
960
978
|
} else if ((flags & EFFECT_BLOCK) !== 0) {
|
|
@@ -976,10 +994,8 @@ function flush_updates(root_block) {
|
|
|
976
994
|
current = current.next;
|
|
977
995
|
|
|
978
996
|
while (current === null && parent !== null) {
|
|
979
|
-
if (parent ===
|
|
980
|
-
|
|
981
|
-
containing_update = head.v;
|
|
982
|
-
containing_update_head = head.n;
|
|
997
|
+
if (parent === scope_root) {
|
|
998
|
+
scope_root = null;
|
|
983
999
|
}
|
|
984
1000
|
current = parent.next;
|
|
985
1001
|
parent = parent.p;
|
|
@@ -1115,6 +1131,10 @@ export function schedule_update(block) {
|
|
|
1115
1131
|
if (scheduler_mode === FLUSH_MICROTASK) {
|
|
1116
1132
|
queue_microtask();
|
|
1117
1133
|
}
|
|
1134
|
+
// The scheduled block roots the subtree that flush_updates scans for dirty
|
|
1135
|
+
// subscribers. Mark it even if it (or an ancestor) is already on a routing
|
|
1136
|
+
// path, so the early return below still records it as a scan root.
|
|
1137
|
+
block.f |= UPDATE_SOURCE;
|
|
1118
1138
|
let current = block;
|
|
1119
1139
|
|
|
1120
1140
|
while (current !== null) {
|
|
@@ -1134,6 +1154,38 @@ export function schedule_update(block) {
|
|
|
1134
1154
|
* @param {Tracked | Derived} tracked
|
|
1135
1155
|
*/
|
|
1136
1156
|
function register_dependency(tracked) {
|
|
1157
|
+
if (!disable_scoped_flush && active_block !== null && active_block !== tracked.b) {
|
|
1158
|
+
// Scoped flush only scans the owner's subtree for dirty subscribers, valid
|
|
1159
|
+
// only while every subscriber lives inside it. The subscriber↔owner
|
|
1160
|
+
// ancestry is structural and stable for a block's lifetime (its `.p` chain
|
|
1161
|
+
// never changes — DOM moves don't reparent blocks), so we only need to
|
|
1162
|
+
// verify *new* dependency edges: if this reaction already depended on
|
|
1163
|
+
// `tracked` on its previous run (it's in the prior chain), the ancestry
|
|
1164
|
+
// walk was already done — skip it. This keeps the (possibly deep) walk off
|
|
1165
|
+
// the hot path for the common case of stable subscriptions.
|
|
1166
|
+
var already_seen = false;
|
|
1167
|
+
var prev_dep = active_reaction === null ? null : active_reaction.d;
|
|
1168
|
+
while (prev_dep !== null) {
|
|
1169
|
+
if (prev_dep.t === tracked) {
|
|
1170
|
+
already_seen = true;
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
prev_dep = prev_dep.n;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (!already_seen) {
|
|
1177
|
+
var owner = tracked.b;
|
|
1178
|
+
/** @type {Block | null} */
|
|
1179
|
+
var node = active_block;
|
|
1180
|
+
while (node !== null && node !== owner) {
|
|
1181
|
+
node = node.p;
|
|
1182
|
+
}
|
|
1183
|
+
if (node === null) {
|
|
1184
|
+
disable_scoped_flush = true;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1137
1189
|
var dependency = active_dependency;
|
|
1138
1190
|
|
|
1139
1191
|
if (dependency === null) {
|