ripple 0.3.84 → 0.3.85
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 +60 -0
- package/package.json +3 -3
- package/src/constants.js +4 -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 +22 -1
- 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 +16 -2
- 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/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.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 +268 -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.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/html.test.js +50 -0
- package/tests/server/compiler.test.tsrx +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.3.85
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1307](https://github.com/Ripple-TS/ripple/pull/1307)
|
|
8
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2)
|
|
9
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Skip the wrapper anchor for
|
|
10
|
+
single control-flow / code-block / component root scopes. When a scope's entire
|
|
11
|
+
renderable output is a single `@if`, `@switch`, `@for`, `@try`, or static child
|
|
12
|
+
component — i.e. a component body, a control-flow branch, or a `@{}` body whose
|
|
13
|
+
only output after setup is one of these — the compiler now renders it directly
|
|
14
|
+
before the parent-provided `__anchor` instead of synthesizing a `<!>` fragment
|
|
15
|
+
wrapper and an extra append + clone. For deep recursive trees this measurably
|
|
16
|
+
cuts mount time and shrinks generated output; in the recursive-context benchmark
|
|
17
|
+
it brought mount DOM operations to one clone + one append per element (from
|
|
18
|
+
~1.5×) and halved the comment-anchor nodes.
|
|
19
|
+
|
|
20
|
+
Hydration is preserved. The control-flow runtimes
|
|
21
|
+
(`if_block`/`switch_block`/`for_block`/`for_block_keyed`/`try_block`) capture
|
|
22
|
+
the SSR boundary marker and hand it to `append()` afterward, so the existing
|
|
23
|
+
context-aware cursor advance still runs — including for a root scope used as a
|
|
24
|
+
child of a composite/slot with following siblings. Single-component roots need
|
|
25
|
+
no runtime change at all, since a component's own content advances the hydration
|
|
26
|
+
cursor.
|
|
27
|
+
|
|
28
|
+
Also relaxes the compiler's text-expression detection: `string + anything` (e.g.
|
|
29
|
+
`{a + '|' + b}`) is now recognized as text and lowered to the fast `set_text`
|
|
30
|
+
path without requiring an explicit `as string`, since such an expression always
|
|
31
|
+
evaluates to a string in JS.
|
|
32
|
+
|
|
33
|
+
- [#1307](https://github.com/Ripple-TS/ripple/pull/1307)
|
|
34
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2)
|
|
35
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Scope the client flush
|
|
36
|
+
traversal to the updated subtree. Previously every flush walked the whole root
|
|
37
|
+
block tree to find dirty subscribers, so a deeply-scoped update (e.g. mutating
|
|
38
|
+
state read by only a small subtree) paid a cost proportional to the entire tree
|
|
39
|
+
rather than the affected branch. `flush_updates` now descends only along the
|
|
40
|
+
routing path to each directly-scheduled block and fully scans just that block's
|
|
41
|
+
subtree, where its subscribers live. A tracked read from outside its owner's
|
|
42
|
+
subtree (e.g. smuggled across sibling subtrees via a module-level variable) is
|
|
43
|
+
detected in `register_dependency` and transparently falls back to the original
|
|
44
|
+
full-tree scan, so behavior is unchanged.
|
|
45
|
+
|
|
46
|
+
- Updated dependencies
|
|
47
|
+
[[`ba498cd`](https://github.com/Ripple-TS/ripple/commit/ba498cde76e9f83235ce91da825f403a28441bff),
|
|
48
|
+
[`313b351`](https://github.com/Ripple-TS/ripple/commit/313b3513e4a959dd80b546da41c798066c5ccb0f),
|
|
49
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
50
|
+
[`bbe6e74`](https://github.com/Ripple-TS/ripple/commit/bbe6e7422c690558f0dfcb3abe5452d4f4cdde91),
|
|
51
|
+
[`0e9f523`](https://github.com/Ripple-TS/ripple/commit/0e9f52358a615c2fc7759544e96c43dccb533c86),
|
|
52
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
53
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
54
|
+
[`2b65285`](https://github.com/Ripple-TS/ripple/commit/2b65285bfcd4c6a0aa93d7fa0b25082e6ec74e1f),
|
|
55
|
+
[`35ac700`](https://github.com/Ripple-TS/ripple/commit/35ac70052d79efae41bb1df2440fee3f052ca115),
|
|
56
|
+
[`f55466b`](https://github.com/Ripple-TS/ripple/commit/f55466bde65d0cff00c0c4525af9d68ae794ffd2),
|
|
57
|
+
[`b887deb`](https://github.com/Ripple-TS/ripple/commit/b887debf5f47e63d73184ac218ec8b3542a5e21c),
|
|
58
|
+
[`3668c5f`](https://github.com/Ripple-TS/ripple/commit/3668c5fe9cdaca4862707d653d23af94780f42af),
|
|
59
|
+
[`bbc3843`](https://github.com/Ripple-TS/ripple/commit/bbc384387e33c538234be36c07cc4b30ef6ce136)]:
|
|
60
|
+
- @tsrx/ripple@0.1.33
|
|
61
|
+
- @tsrx/core@0.1.33
|
|
62
|
+
|
|
3
63
|
## 0.3.84
|
|
4
64
|
|
|
5
65
|
### 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.85",
|
|
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.33"
|
|
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
|
|
|
@@ -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,11 @@
|
|
|
1
1
|
/** @import { Block, Tracked } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { IS_CONTROLLED, IS_INDEXED } from '../../../constants.js';
|
|
3
|
+
import { IS_CONTROLLED, IS_INDEXED, ROOT_CONTROLLED } from '../../../constants.js';
|
|
4
4
|
import { branch, destroy_block, destroy_block_children, render } from './blocks.js';
|
|
5
5
|
import { FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
|
|
6
6
|
import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
|
|
7
7
|
import { create_text, get_first_child, get_last_child, next_sibling } from './operations.js';
|
|
8
|
+
import { append } from './template.js';
|
|
8
9
|
import { active_block, set, tracked, untrack } from './runtime.js';
|
|
9
10
|
import { array_from, is_array } from '@tsrx/core/runtime/language-helpers';
|
|
10
11
|
|
|
@@ -121,7 +122,10 @@ function collection_to_array(collection) {
|
|
|
121
122
|
export function for_block(node, get_collection, render_fn, flags, render_empty) {
|
|
122
123
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
123
124
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
125
|
+
var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
|
|
124
126
|
var anchor = /** @type {Element | Text} */ (node);
|
|
127
|
+
/** @type {Node | undefined} */
|
|
128
|
+
var boundary;
|
|
125
129
|
|
|
126
130
|
if (is_controlled) {
|
|
127
131
|
if (hydrating) {
|
|
@@ -133,6 +137,9 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
if (hydrating) {
|
|
140
|
+
if (root_controlled) {
|
|
141
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
142
|
+
}
|
|
136
143
|
hydrate_next();
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -153,6 +160,10 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
153
160
|
null,
|
|
154
161
|
FOR_BLOCK,
|
|
155
162
|
);
|
|
163
|
+
|
|
164
|
+
if (hydrating && root_controlled) {
|
|
165
|
+
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
166
|
+
}
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
/**
|
|
@@ -169,7 +180,10 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
169
180
|
export function for_block_keyed(node, get_collection, render_fn, flags, get_key, render_empty) {
|
|
170
181
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
171
182
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
183
|
+
var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
|
|
172
184
|
var anchor = /** @type {Element | Text} */ (node);
|
|
185
|
+
/** @type {Node | undefined} */
|
|
186
|
+
var boundary;
|
|
173
187
|
|
|
174
188
|
if (is_controlled) {
|
|
175
189
|
var parent_node = /** @type {Element} */ (node);
|
|
@@ -183,6 +197,9 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
|
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
if (hydrating) {
|
|
200
|
+
if (root_controlled) {
|
|
201
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
202
|
+
}
|
|
186
203
|
hydrate_next();
|
|
187
204
|
}
|
|
188
205
|
|
|
@@ -208,6 +225,10 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
|
|
|
208
225
|
null,
|
|
209
226
|
FOR_BLOCK,
|
|
210
227
|
);
|
|
228
|
+
|
|
229
|
+
if (hydrating && root_controlled) {
|
|
230
|
+
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
231
|
+
}
|
|
211
232
|
}
|
|
212
233
|
|
|
213
234
|
/**
|
|
@@ -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) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { branch, destroy_block, render } from './blocks.js';
|
|
4
4
|
import { SWITCH_BLOCK } from './constants.js';
|
|
5
|
-
import { hydrate_next, hydrating } from './hydration.js';
|
|
5
|
+
import { hydrate_next, hydrate_node, hydrating } from './hydration.js';
|
|
6
6
|
import { next_sibling } from './operations.js';
|
|
7
7
|
import { append } from './template.js';
|
|
8
8
|
|
|
@@ -32,10 +32,20 @@ function move(block, anchor) {
|
|
|
32
32
|
/**
|
|
33
33
|
* @param {ChildNode} anchor
|
|
34
34
|
* @param {() => ((anchor: ChildNode) => void)[] | null} fn
|
|
35
|
+
* @param {boolean} [root_controlled] When true the block renders before the
|
|
36
|
+
* component's `__anchor` (no `<!>` wrapper); during hydration the SSR boundary
|
|
37
|
+
* marker is handed to `append()` afterwards for the context-aware cursor
|
|
38
|
+
* advance the eliminated wrapper used to perform.
|
|
35
39
|
* @returns {void}
|
|
36
40
|
*/
|
|
37
|
-
export function switch_block(anchor, fn) {
|
|
41
|
+
export function switch_block(anchor, fn, root_controlled) {
|
|
42
|
+
/** @type {Node | undefined} */
|
|
43
|
+
var boundary;
|
|
44
|
+
|
|
38
45
|
if (hydrating) {
|
|
46
|
+
if (root_controlled) {
|
|
47
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
48
|
+
}
|
|
39
49
|
hydrate_next();
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -82,4 +92,8 @@ export function switch_block(anchor, fn) {
|
|
|
82
92
|
null,
|
|
83
93
|
SWITCH_BLOCK,
|
|
84
94
|
);
|
|
95
|
+
|
|
96
|
+
if (hydrating && root_controlled) {
|
|
97
|
+
append(anchor, /** @type {Node} */ (boundary));
|
|
98
|
+
}
|
|
85
99
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Block } from '#client' */
|
|
1
|
+
/** @import { AppendIntoAnchor, Block } from '#client' */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
COMMENT_NODE,
|
|
@@ -137,7 +137,7 @@ export function template(content, flags, count = 1) {
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Appends a DOM node before the anchor node.
|
|
140
|
-
* @param {ChildNode} anchor - The anchor node.
|
|
140
|
+
* @param {ChildNode | AppendIntoAnchor} anchor - The anchor node.
|
|
141
141
|
* @param {Node} dom - The DOM node to append.
|
|
142
142
|
* @param {boolean} [skip_advance] - If true, don't advance hydrate_node (used when next() already positioned it).
|
|
143
143
|
*/
|
|
@@ -191,6 +191,14 @@ export function append(anchor, dom, skip_advance) {
|
|
|
191
191
|
hydrate_advance();
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
|
+
if ('parent' in anchor) {
|
|
195
|
+
// Append-into-parent sentinel: an all-component-children element passes a
|
|
196
|
+
// `{ parent }` object (no `nodeType`) so each component's root appends as
|
|
197
|
+
// the host's last child instead of inserting before a placeholder comment.
|
|
198
|
+
// The block still self-marks its range via assign_nodes, so teardown works.
|
|
199
|
+
anchor.parent.appendChild(dom);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
194
202
|
anchor.before(/** @type {Node} */ (dom));
|
|
195
203
|
}
|
|
196
204
|
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
resume_block,
|
|
10
10
|
} from './blocks.js';
|
|
11
11
|
import { TRY_BLOCK } from './constants.js';
|
|
12
|
-
import { hydrate_next, hydrating } from './hydration.js';
|
|
12
|
+
import { hydrate_next, hydrate_node, hydrating } from './hydration.js';
|
|
13
|
+
import { append } from './template.js';
|
|
13
14
|
import {
|
|
14
15
|
active_block,
|
|
15
16
|
queue_microtask,
|
|
@@ -34,8 +35,10 @@ import {
|
|
|
34
35
|
* @param {PendingFunction | null} [pending_fn=null]
|
|
35
36
|
* @returns {void}
|
|
36
37
|
*/
|
|
37
|
-
export function try_block(node, try_fn, catch_fn, pending_fn = null) {
|
|
38
|
+
export function try_block(node, try_fn, catch_fn, pending_fn = null, root_controlled = false) {
|
|
38
39
|
var anchor = node;
|
|
40
|
+
/** @type {Node | undefined} */
|
|
41
|
+
var boundary;
|
|
39
42
|
var pending_count = 0;
|
|
40
43
|
var request_version = 0;
|
|
41
44
|
/** @type {Set<number>} */
|
|
@@ -316,12 +319,19 @@ export function try_block(node, try_fn, catch_fn, pending_fn = null) {
|
|
|
316
319
|
if (pending_fn !== null) {
|
|
317
320
|
has_resolved = true;
|
|
318
321
|
}
|
|
322
|
+
if (root_controlled) {
|
|
323
|
+
boundary = /** @type {Node} */ (hydrate_node);
|
|
324
|
+
}
|
|
319
325
|
hydrate_next(); // consume <!--[-->
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
try_block = create_try_block(() => {
|
|
323
329
|
resolved_branch = boundary_fn_running_block(() => try_fn(anchor));
|
|
324
330
|
}, state);
|
|
331
|
+
|
|
332
|
+
if (hydrating && root_controlled) {
|
|
333
|
+
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
334
|
+
}
|
|
325
335
|
}
|
|
326
336
|
|
|
327
337
|
/**
|
|
@@ -319,7 +319,6 @@ exports[`for statements > renders a simple dynamic array 2`] = `
|
|
|
319
319
|
|
|
320
320
|
exports[`for statements > renders a simple static array 1`] = `
|
|
321
321
|
<div>
|
|
322
|
-
<!---->
|
|
323
322
|
<div
|
|
324
323
|
class="Item 1"
|
|
325
324
|
>
|
|
@@ -335,7 +334,6 @@ exports[`for statements > renders a simple static array 1`] = `
|
|
|
335
334
|
>
|
|
336
335
|
Item 3
|
|
337
336
|
</div>
|
|
338
|
-
<!---->
|
|
339
337
|
|
|
340
338
|
</div>
|
|
341
339
|
`;
|
|
@@ -579,8 +579,11 @@ function App() @{
|
|
|
579
579
|
`;
|
|
580
580
|
const result = compile_to_volar_mappings(source, 'test.tsrx').code;
|
|
581
581
|
|
|
582
|
-
|
|
583
|
-
|
|
582
|
+
// An authored `<>…</>` is kept verbatim — both as a value (the `nested`
|
|
583
|
+
// initializer) and as the component's render output (`<>{content}</>`) —
|
|
584
|
+
// instead of unwrapping to its single child.
|
|
585
|
+
expect(result).toContain('const nested = <><span class="nested-tsx">');
|
|
586
|
+
expect(result).toContain('return <>{content}</>;');
|
|
584
587
|
expect(result).toContain('const content = <div class="native">');
|
|
585
588
|
});
|
|
586
589
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`composite > render > correct handles passing through component props and children 1`] = `
|
|
4
4
|
<div>
|
|
5
|
-
<!---->
|
|
6
5
|
<div>
|
|
7
6
|
<div>
|
|
8
7
|
I am A
|
|
@@ -17,21 +16,18 @@ exports[`composite > render > correct handles passing through component props an
|
|
|
17
16
|
</div>
|
|
18
17
|
<!---->
|
|
19
18
|
</div>
|
|
20
|
-
<!---->
|
|
21
19
|
|
|
22
20
|
</div>
|
|
23
21
|
`;
|
|
24
22
|
|
|
25
23
|
exports[`composite > render > render simple text as children 1`] = `
|
|
26
24
|
<div>
|
|
27
|
-
<!---->
|
|
28
25
|
<button
|
|
29
26
|
class="my-button"
|
|
30
27
|
>
|
|
31
28
|
Click Me
|
|
32
29
|
<!---->
|
|
33
30
|
</button>
|
|
34
|
-
<!---->
|
|
35
31
|
|
|
36
32
|
</div>
|
|
37
33
|
`;
|