Repository: sveltejs/svelte (JavaScript)
Date: 2020-05-19 02:14:48+00:00
Task type: Bug Fix

Issue:
{@html  html-string} replacing tr,td tags
If a table's tbody has an existing tr element and we place another tr before it using @html tag, 
the tags tr, td are stripped.
**To Reproduce**
Please see here:
https://svelte.dev/repl/dee930a465e14a31baa1846003463e12?version=3.22.3

Thanks



Issue discussion:
It looks like this happened between 3.6.11 and 3.7.0 as a result of #3329. I'm not sure what can reasonably be done about this. The newer way of handling `{@html}` blocks involves creating a temporary `<div>` and setting its `innerHTML`, which presents a problem here, as certain types of elements are not allowed as children of `<div>`s.
What if instead of creating a `<div>`, the compiler creates a `<template>` element instead?
It seems like it was meant for element fragments.
In the meantime, here's a workaround which uses `<template>`:
https://svelte.dev/repl/bf25404232fe427899450cc397f461a6?version=3.22.3
As an alternative to using `<template>` (which has the IE compatibility issue @Conduitry identifies in #4857), what if we used the same element as the parent, or maybe only in cases where these issues arise?

e.g.

```diff
-html_tag = new HtmlTag(/*html_data*/ ctx[0], null);
+html_tag = new HtmlTag(/*html_data*/ ctx[0], null, 'tbody');
```

That *would* require the compiler to know the parent element type at runtime, which precludes top-level `{@html ...}` tags and any future `<svelte:element>` additions. Though perhaps it could be done at runtime instead, if we rejiggered things in `HtmlTag` slightly.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
 * Add `muted` binding for media elements ([#2998](https://github.com/sveltejs/svelte/issues/2998))
 * Fix let-less `<slot>` with context overflow ([#4624](https://github.com/sveltejs/svelte/issues/4624))
 * Fix `use:` actions being recreated when a keyed `{#each}` is reordered ([#4693](https://github.com/sveltejs/svelte/issues/4693))
+* Fix `{@html}` when using tags that can only appear inside certain tags ([#4852](https://github.com/sveltejs/svelte/issues/4852))
 * Fix reactivity when binding directly to `{#each}` context ([#4879](https://github.com/sveltejs/svelte/issues/4879))
 
 ## 3.22.3
diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts
--- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts
+++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts
@@ -53,8 +53,8 @@ export default class RawMustacheTagWrapper extends Tag {
 
 			const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
 
-			block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`);
-			block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);
+			block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${update_anchor});`);
+			block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);
 
 			if (needs_anchor) {
 				block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);
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
@@ -294,7 +294,7 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
 		} else if (unsubscribe && iframe.contentWindow) {
 			unsubscribe();
 		}
-		
+
 		detach(iframe);
 	};
 }
@@ -319,29 +319,36 @@ export class HtmlTag {
 	t: HTMLElement;
 	a: HTMLElement;
 
-	constructor(html: string, anchor: HTMLElement = null) {
-		this.e = element('div');
+	constructor(anchor: HTMLElement = null) {
 		this.a = anchor;
-		this.u(html);
+		this.e = this.n = null;
 	}
 
-	m(target: HTMLElement, anchor: HTMLElement = null) {
-		for (let i = 0; i < this.n.length; i += 1) {
-			insert(target, this.n[i], anchor);
+	m(html: string, target: HTMLElement, anchor: HTMLElement = null) {
+		if (!this.e) {
+			this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
+			this.t = target;
+			this.h(html);
 		}
 
-		this.t = target;
+		this.i(anchor);
 	}
 
-	u(html: string) {
+	h(html) {
 		this.e.innerHTML = html;
 		this.n = Array.from(this.e.childNodes);
 	}
 
+	i(anchor) {
+		for (let i = 0; i < this.n.length; i += 1) {
+			insert(this.t, this.n[i], anchor);
+		}
+	}
+
 	p(html: string) {
 		this.d();
-		this.u(html);
-		this.m(this.t, this.a);
+		this.h(html);
+		this.i(this.a);
 	}
 
 	d() {


Test patch:
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -53,7 +53,7 @@ function create_each_block(ctx) {
 			t5 = text(" ago:");
 			t6 = space();
 			attr(span, "class", "meta");
-			html_tag = new HtmlTag(raw_value, null);
+			html_tag = new HtmlTag(null);
 			attr(div, "class", "comment");
 		},
 		m(target, anchor) {
@@ -67,7 +67,7 @@ function create_each_block(ctx) {
 			append(span, t4);
 			append(span, t5);
 			append(div, t6);
-			html_tag.m(div);
+			html_tag.m(raw_value, div);
 		},
 		p(ctx, dirty) {
 			if (dirty & /*comments*/ 1 && t2_value !== (t2_value = /*comment*/ ctx[4].author + "")) set_data(t2, t2_value);
diff --git a/test/runtime/samples/raw-mustaches-td-tr/_config.js b/test/runtime/samples/raw-mustaches-td-tr/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/raw-mustaches-td-tr/_config.js
@@ -0,0 +1,18 @@
+export default {
+	props: {
+		raw: "<tr><td>1</td><td>2</td></tr>",
+	},
+
+	html: `
+		<table>
+			<tbody>
+				<tr>
+					<td>5</td><td>7</td>
+				</tr>
+				<tr>
+					<td>1</td><td>2</td>
+				</tr>
+			</tbody>
+		</table>
+	`,
+};
diff --git a/test/runtime/samples/raw-mustaches-td-tr/main.svelte b/test/runtime/samples/raw-mustaches-td-tr/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/raw-mustaches-td-tr/main.svelte
@@ -0,0 +1,12 @@
+<script>
+	export let raw;
+</script>
+
+<table>
+	<tbody>
+		<tr>
+			<td>5</td><td>7</td>
+		</tr>
+		{@html raw}
+	</tbody>
+</table>
\ No newline at end of file