Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Starting with v3.39.0 sorting a keyed each block messes up the DOM
### Describe the bug

I have a keyed each block displaying items. These items can sorted by two different criterias, whenever I resort the items, the DOM of the individual items is changed in a strange way. Div elements within the items are displayed out of order.

This works fine up to version 3.38.3, and starts failing with 3.39.0. It also fails with versions 3.40.0 and 3.40.1.

### Reproduction

I'm attaching some screenshots. Here you can see the code as it's seen on the IDE, and then how it's shown on the browser dev tools. Notice how in the browser `metrics__title` is shown last, while in the IDE it's the first element within an item.
<img width="752" alt="Screen Shot 2021-07-23 at 00 31 22" src="https://user-images.githubusercontent.com/2463995/126734737-bb78d439-0812-45a9-bb09-4be467512ae2.png">
<img width="568" alt="Screen Shot 2021-07-23 at 00 32 15" src="https://user-images.githubusercontent.com/2463995/126734745-1099c7e1-a285-4ed9-ba7e-c4c14ce920af.png">

I haven't been been able to reproduce this on the REPL, but here's one that is as close as I could with a simple example: https://svelte.dev/repl/6ef13760c89f4fd7af060d0312ca1a71?version=3.40.1

I know this is not ideal, but maybe since I can pin point this to a specific version, it can help narrow down the problem. Removing the key to the each block also fixes the problem, but I need that for animate:flip to work.

### Logs

_No response_

### System Info

```shell
➜  web git:(develop) ✗ npx envinfo --system --npmPackages svelte,rollup,webpack --binaries --browsers
Need to install the following packages:
  envinfo
Ok to proceed? (y)

  System:
    OS: macOS 11.4
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 39.97 MB / 16.00 GB
    Shell: 5.8 - /usr/local/bin/zsh
  Binaries:
    Node: 16.2.0 - /usr/local/bin/node
    Yarn: 1.13.0 - /usr/local/bin/yarn
    npm: 7.19.1 - /usr/local/bin/npm
    Watchman: 2021.06.07.00 - /usr/local/bin/watchman
  Browsers:
    Chrome: 92.0.4515.107
    Firefox Developer Edition: 91.0
    Safari: 14.1.1
  npmPackages:
    svelte: ^3.38.3 => 3.40.1
```


### Severity

blocking an upgrade


Discussion:
Some more info. I can confirm that the elements wrongly placed after sorting are conditionally rendered. If I comment out the if around the title, the problem is fixed, for the title at least. There's still problems with other elements though.

<img width="544" alt="Screen Shot 2021-07-23 at 09 18 08" src="https://user-images.githubusercontent.com/2463995/126780515-68be4387-2453-4003-9a7f-997a38cfcfb7.png">

I'm still trying  to repro this on a simplified REPL btw, with no success.
@Conduitry I've been able to reproduce this with a simple example, thing is, apparently this only happens when using Vite with the `@sveltejs/vite-plugin-svelte` plugin. Would this still be the right place to report this or should it be reported somewhere else?

This is the repro code (though it works on the REPL): https://svelte.dev/repl/e25e9d00d43e4d6eaadf5f3362c47f92?version=3.40.1

This is how it looks when using Vite:
![Screen-Recording-2021-07-24-at-01 04 49](https://user-images.githubusercontent.com/2463995/126856960-f5b77943-de3a-47a8-8cfb-8416d909a2b3.gif)

If you're able to repro with the latest version of vite-plugin-svelte, I'd say we should move this to https://github.com/sveltejs/vite-plugin-svelte/issues
Reproduction with plain svelte template: https://github.com/dominikg/svelte-hydration-reorder-bug

The issue is caused by `hydratable: true` compiler option which vite-plugin-svelte sets by default. 


Also note that if you remove the wrapping `{#if item.name}` in Item.svelte , it won't reorder
@hbirler @daybrush can help take a look on this issue?

Resolving patch:
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -185,7 +185,7 @@ export function append_hydration(target: NodeEx, node: NodeEx) {
 		} else {
 			target.actual_end_child = node.nextSibling;
 		}
-	} else if (node.parentNode !== target) {
+	} else if (node.parentNode !== target || node.nextSibling !== null) {
 		target.appendChild(node);
 	}
 }


Test patch:
diff --git a/test/runtime/samples/key-block-post-hydrate/_config.js b/test/runtime/samples/key-block-post-hydrate/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/key-block-post-hydrate/_config.js
@@ -0,0 +1,19 @@
+export default {
+	html: `
+	<div>
+	<div><span class="name">item 1</span><span>something</span></div>
+	<div><span class="name">item 2</span><span>something</span></div>
+	<div><span class="name">item 3</span><span>something</span></div>
+	</div>
+	`,
+	test({ assert, component, target }) {
+		component.sortById = false;
+		assert.htmlEqual( target.innerHTML, `
+		<div>
+		<div><span class="name">item 3</span><span>something</span></div>
+		<div><span class="name">item 2</span><span>something</span></div>
+		<div><span class="name">item 1</span><span>something</span></div>
+		</div>
+		`);
+	}
+};
diff --git a/test/runtime/samples/key-block-post-hydrate/main.svelte b/test/runtime/samples/key-block-post-hydrate/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/key-block-post-hydrate/main.svelte
@@ -0,0 +1,23 @@
+<script>
+  export let sortById = true;
+  let items = [
+    { id: 1, name: "item 1", value: 3 },
+    { id: 2, name: "item 2", value: 2 },
+    { id: 3, name: "item 3", value: 1 },
+  ];
+
+  $: items = items.sort((a, b) => { return sortById ? a.id - b.id : a.value - b.value; });
+</script>
+
+<div>
+  {#each items as item (item.id)}
+  <div>
+    {#if item.name}
+      <span class="name">
+        {item.name}
+      </span>
+    {/if}
+    <span>something</span>
+  </div>
+  {/each}
+</div>