Repository: sveltejs/svelte (JavaScript)
Date: 2020-01-20 15:37:21+00:00
Task type: Bug Fix

Issue:
Hydrating element removes every other attribute
I'm new to Svelte so it's entirely possible i'm missing something basic.  I'm seeing some weird behavior around the hydration feature. Attributes on the element being hydrated are removed and I'm not sure why. 

For example, given this markup:
```html
<span id="rehydrateContainer">
  <button data-track-id="123" class="button button--small" id="button" role="button" disabled>content</button>
</span>
```
and this component:
```html
<button on:click="set({ count: count + 1 })">
  {text} {count}
</button>

<script>
  export default {
    oncreate() {
      this.set({ count: 0 });
    }
  };
</script>
```
the hydrated dom ends up being this:
```html
<span id="rehydrateContainer">
  <button class="button button--small" role="button">rehydrated 0</button>
</span>
```

At first glance it seems that it maybe only works with certain attributes like `class` or `role` but that's not the case. When I change the order it seems like the odd numbered attributes are being removed.

given this:
```html
<button class="button button--small" data-track-id="123" role="button" id="button" disabled>content</button>
```

we end up with this:
```html
<button data-track-id="123" id="button">rehydrated 0</button>
```

here's a small reproduction to play around with: https://github.com/sammynave/rehydrate-attrs


Issue discussion:
Thought I would have a good chunk of open source time to look into this today. So far I've only had time to add a failing test. Maybe helpful to someone?
https://github.com/sveltejs/svelte/compare/master...sammynave:multiple-attr-hydration?expand=1
I do believe the hydration feature is meant to be used with SSR, not an unknown element with unknown attributes. While the odd numbered attributes thing is odd (unintended), I think your situation is a case of UD.
This still exists in Svelte 3. Here's a purer example that can be used in Sapper:

```svelte
{#if process.browser}
	<div>
		Foo
	</div>
{/if}

<div attr-a attr-b attr-c attr-d attr-e>
	Bar
</div>
```

(where `process.browser` is replaced with `false` in the server bundle and `true` in the browser bundle). When hydrated, the first `<div>` is left with `attr-b` and `attr-d` still present, when it should have had all of its attributes removed.
I'm guessing the bug is that in [this loop](https://github.com/sveltejs/svelte/blob/b3582c7fb24572a6dfd58e2009925158a9a37233/src/runtime/internal/dom.ts#L155) we don't want to be incrementing `j` after removing the attribute, and that's what causes alternate attributes to be skipped.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+* Fix removing attributes during hydration ([#1733](https://github.com/sveltejs/svelte/issues/1733))
 * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012))
 * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173))
 * Fix `~=` and class selector matching against values separated by any whitespace characters ([#4242](https://github.com/sveltejs/svelte/issues/4242))
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
@@ -152,11 +152,16 @@ export function claim_element(nodes, name, attributes, svg) {
 	for (let i = 0; i < nodes.length; i += 1) {
 		const node = nodes[i];
 		if (node.nodeName === name) {
-			for (let j = 0; j < node.attributes.length; j += 1) {
+			let j = 0;
+			while (j < node.attributes.length) {
 				const attribute = node.attributes[j];
-				if (!attributes[attribute.name]) node.removeAttribute(attribute.name);
+				if (attributes[attribute.name]) {
+					j++;
+				} else {
+					node.removeAttribute(attribute.name);
+				}
 			}
-			return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes
+			return nodes.splice(i, 1)[0];
 		}
 	}
 


Test patch:
diff --git a/test/hydration/samples/element-attribute-removed/_before.html b/test/hydration/samples/element-attribute-removed/_before.html
--- a/test/hydration/samples/element-attribute-removed/_before.html
+++ b/test/hydration/samples/element-attribute-removed/_before.html
@@ -1 +1 @@
-<div class='foo'></div>
\ No newline at end of file
+<div class='foo' title='bar'></div>
\ No newline at end of file