Skip to content

Commit 6b0bd8b

Browse files
authored
fix: hydrate controlled each blocks correctly (#10259)
Controlled each block didn't handle hydration of fallback correctly fixes #10231
1 parent 434a587 commit 6b0bd8b

File tree

8 files changed

+39
-15
lines changed

8 files changed

+39
-15
lines changed

.changeset/soft-months-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: hydrate controlled each blocks correctly

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,8 +1554,6 @@ function process_children(nodes, parent, { visit, state }) {
15541554
// get hoisted inside clean_nodes?
15551555
visit(node, state);
15561556
} else {
1557-
// Optimization path for each blocks. If the parent isn't a fragment and it only has
1558-
// a single child, then we can classify the block as being "controlled".
15591557
if (
15601558
node.type === 'EachBlock' &&
15611559
nodes.length === 1 &&

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ export interface EachBlock extends BaseNode {
368368
item_name: string;
369369
/** List of bindings that are referenced within the expression */
370370
references: Binding[];
371+
/**
372+
* Optimization path for each blocks: If the parent isn't a fragment and
373+
* it only has a single child, then we can classify the block as being "controlled".
374+
* This saves us from creating an extra comment and insertion being faster.
375+
*/
371376
is_controlled: boolean;
372377
};
373378
}

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const EACH_ITEM_REACTIVE = 1;
22
export const EACH_INDEX_REACTIVE = 1 << 1;
33
export const EACH_KEYED = 1 << 2;
44
export const EACH_PROXIED = 1 << 3;
5+
/** See EachBlock interface metadata.is_controlled for an explanation what this is */
56
export const EACH_IS_CONTROLLED = 1 << 3;
67
export const EACH_IS_ANIMATED = 1 << 4;
78
export const EACH_IS_IMMUTABLE = 1 << 6;

packages/svelte/src/internal/client/each.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,17 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
104104
let anchor = block.a;
105105
const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0;
106106
if (is_controlled) {
107-
anchor = empty();
108-
block.a.appendChild(anchor);
107+
// If the each block is controlled, then the anchor node will be the surrounding
108+
// element in which the each block is rendered, which requires certain handling
109+
// depending on whether we're in hydration mode or not
110+
if (current_hydration_fragment === null) {
111+
// Create a new anchor on the fly because there's none due to the optimization
112+
anchor = empty();
113+
block.a.appendChild(anchor);
114+
} else {
115+
// In case of hydration the anchor will be the first child of the surrounding element
116+
anchor = /** @type {Comment} */ (anchor.firstChild);
117+
}
109118
}
110119
/** @type {(anchor: Node) => void} */ (fallback_fn)(anchor);
111120
fallback.d = block.d;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<!--ssr:0--><h1>Hello, world</h1>
2-
<!--ssr:1--><p>weird</p><!--ssr:1-->
3-
<!--ssr:0-->
2+
<!--ssr:1--><p>foo</p><!--ssr:1-->
3+
<div><!--ssr:2--><p>foo</p><!--ssr:2--></div><!--ssr:0-->
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
<script>
22
export let name = "world";
3-
export let array = [];
3+
export let foo = 'foo';
44
</script>
55

66
<h1>Hello, {name}</h1>
7-
{#each array as elem}
8-
<p>
9-
item
10-
</p>
11-
{:else}
12-
<p>
13-
weird
14-
</p>
7+
{#each [] as _}nope{:else}
8+
<p>{foo}</p>
159
{/each}
10+
11+
<!-- a single each block inside an element is "controlled", which is an optimization which we test here, too -->
12+
<div>
13+
{#each [] as _}nope{:else}
14+
<p>{foo}</p>
15+
{/each}
16+
</div>

packages/svelte/types/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,11 @@ declare module 'svelte/compiler' {
14021402
item_name: string;
14031403
/** List of bindings that are referenced within the expression */
14041404
references: Binding[];
1405+
/**
1406+
* Optimization path for each blocks: If the parent isn't a fragment and
1407+
* it only has a single child, then we can classify the block as being "controlled".
1408+
* This saves us from creating an extra comment and insertion being faster.
1409+
*/
14051410
is_controlled: boolean;
14061411
};
14071412
}

0 commit comments

Comments
 (0)