fix: hydrate controlled each blocks correctly (#10259)

Controlled each block didn't handle hydration of fallback correctly
fixes #10231
pull/10266/head
Simon H 12 months ago committed by GitHub
parent 434a58711f
commit 6b0bd8b23a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: hydrate controlled each blocks correctly

@ -1554,8 +1554,6 @@ function process_children(nodes, parent, { visit, state }) {
// get hoisted inside clean_nodes?
visit(node, state);
} else {
// Optimization path for each blocks. If the parent isn't a fragment and it only has
// a single child, then we can classify the block as being "controlled".
if (
node.type === 'EachBlock' &&
nodes.length === 1 &&

@ -368,6 +368,11 @@ export interface EachBlock extends BaseNode {
item_name: string;
/** List of bindings that are referenced within the expression */
references: Binding[];
/**
* Optimization path for each blocks: If the parent isn't a fragment and
* it only has a single child, then we can classify the block as being "controlled".
* This saves us from creating an extra comment and insertion being faster.
*/
is_controlled: boolean;
};
}

@ -2,6 +2,7 @@ export const EACH_ITEM_REACTIVE = 1;
export const EACH_INDEX_REACTIVE = 1 << 1;
export const EACH_KEYED = 1 << 2;
export const EACH_PROXIED = 1 << 3;
/** See EachBlock interface metadata.is_controlled for an explanation what this is */
export const EACH_IS_CONTROLLED = 1 << 3;
export const EACH_IS_ANIMATED = 1 << 4;
export const EACH_IS_IMMUTABLE = 1 << 6;

@ -104,8 +104,17 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
let anchor = block.a;
const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0;
if (is_controlled) {
anchor = empty();
block.a.appendChild(anchor);
// If the each block is controlled, then the anchor node will be the surrounding
// element in which the each block is rendered, which requires certain handling
// depending on whether we're in hydration mode or not
if (current_hydration_fragment === null) {
// Create a new anchor on the fly because there's none due to the optimization
anchor = empty();
block.a.appendChild(anchor);
} else {
// In case of hydration the anchor will be the first child of the surrounding element
anchor = /** @type {Comment} */ (anchor.firstChild);
}
}
/** @type {(anchor: Node) => void} */ (fallback_fn)(anchor);
fallback.d = block.d;

@ -1,3 +1,3 @@
<!--ssr:0--><h1>Hello, world</h1>
<!--ssr:1--><p>weird</p><!--ssr:1-->
<!--ssr:0-->
<!--ssr:1--><p>foo</p><!--ssr:1-->
<div><!--ssr:2--><p>foo</p><!--ssr:2--></div><!--ssr:0-->

@ -1,15 +1,16 @@
<script>
export let name = "world";
export let array = [];
export let foo = 'foo';
</script>
<h1>Hello, {name}</h1>
{#each array as elem}
<p>
item
</p>
{:else}
<p>
weird
</p>
{#each [] as _}nope{:else}
<p>{foo}</p>
{/each}
<!-- a single each block inside an element is "controlled", which is an optimization which we test here, too -->
<div>
{#each [] as _}nope{:else}
<p>{foo}</p>
{/each}
</div>

@ -1402,6 +1402,11 @@ declare module 'svelte/compiler' {
item_name: string;
/** List of bindings that are referenced within the expression */
references: Binding[];
/**
* Optimization path for each blocks: If the parent isn't a fragment and
* it only has a single child, then we can classify the block as being "controlled".
* This saves us from creating an extra comment and insertion being faster.
*/
is_controlled: boolean;
};
}

Loading…
Cancel
Save