Skip to content

Commit 873a561

Browse files
authored
Merge pull request #3329 from sveltejs/gh-3285
More robust handling of `{@html ...}`
2 parents 4e004fd + 03e6338 commit 873a561

File tree

9 files changed

+94
-107
lines changed

9 files changed

+94
-107
lines changed

src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts

Lines changed: 27 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Renderer from '../Renderer';
22
import Block from '../Block';
33
import Tag from './shared/Tag';
44
import Wrapper from './shared/Wrapper';
5-
import deindent from '../../utils/deindent';
65
import MustacheTag from '../../nodes/MustacheTag';
76
import RawMustacheTag from '../../nodes/RawMustacheTag';
87

@@ -19,95 +18,47 @@ export default class RawMustacheTagWrapper extends Tag {
1918
this.cannot_use_innerhtml();
2019
}
2120

22-
render(block: Block, parent_node: string, parent_nodes: string) {
23-
const name = this.var;
24-
21+
render(block: Block, parent_node: string, _parent_nodes: string) {
2522
const in_head = parent_node === '@_document.head';
26-
const needs_anchors = !parent_node || in_head;
27-
28-
// if in head always needs anchors
29-
if (in_head) {
30-
this.prev = null;
31-
this.next = null;
32-
}
3323

34-
// TODO use is_dom_node instead of type === 'Element'?
35-
const needs_anchor_before = this.prev ? this.prev.node.type !== 'Element' : needs_anchors;
36-
const needs_anchor_after = this.next ? this.next.node.type !== 'Element' : needs_anchors;
24+
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
3725

38-
const anchor_before = needs_anchor_before
39-
? block.get_unique_name(`${name}_before`)
40-
: (this.prev && this.prev.var) || 'null';
26+
if (can_use_innerhtml) {
27+
const insert = content => `${parent_node}.innerHTML = ${content};`;
4128

42-
const anchor_after = needs_anchor_after
43-
? block.get_unique_name(`${name}_after`)
44-
: (this.next && this.next.var) || 'null';
45-
46-
let detach: string;
47-
let insert: (content: string) => string;
48-
let use_innerhtml = false;
29+
const { init } = this.rename_this_method(
30+
block,
31+
content => insert(content)
32+
);
4933

50-
if (anchor_before === 'null' && anchor_after === 'null') {
51-
use_innerhtml = true;
52-
detach = `${parent_node}.innerHTML = '';`;
53-
insert = content => `${parent_node}.innerHTML = ${content};`;
54-
} else if (anchor_before === 'null') {
55-
detach = `@detach_before(${anchor_after});`;
56-
insert = content => `${anchor_after}.insertAdjacentHTML("beforebegin", ${content});`;
57-
} else if (anchor_after === 'null') {
58-
detach = `@detach_after(${anchor_before});`;
59-
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
60-
} else {
61-
detach = `@detach_between(${anchor_before}, ${anchor_after});`;
62-
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
34+
block.builders.mount.add_line(insert(init));
6335
}
6436

65-
const { init } = this.rename_this_method(
66-
block,
67-
content => deindent`
68-
${!use_innerhtml && detach}
69-
${insert(content)}
70-
`
71-
);
37+
else {
38+
const needs_anchor = in_head || (this.next && !this.next.is_dom_node());
7239

73-
// we would have used comments here, but the `insertAdjacentHTML` api only
74-
// exists for `Element`s.
75-
if (needs_anchor_before) {
76-
block.add_element(
77-
anchor_before,
78-
`@element('noscript')`,
79-
parent_nodes && `@element('noscript')`,
80-
parent_node,
81-
true
82-
);
83-
}
40+
const html_tag = block.get_unique_name('html_tag');
41+
const html_anchor = needs_anchor && block.get_unique_name('html_anchor');
42+
43+
block.add_variable(html_tag);
8444

85-
function add_anchor_after() {
86-
block.add_element(
87-
anchor_after,
88-
`@element('noscript')`,
89-
parent_nodes && `@element('noscript')`,
90-
parent_node
45+
const { init } = this.rename_this_method(
46+
block,
47+
content => `${html_tag}.p(${content});`
9148
);
92-
}
9349

94-
if (needs_anchor_after && anchor_before === 'null') {
95-
// anchor_after needs to be in the DOM before we
96-
// insert the HTML...
97-
add_anchor_after();
98-
}
50+
const anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
9951

100-
block.builders.mount.add_line(insert(init));
52+
block.builders.hydrate.add_line(`${html_tag} = new @HtmlTag(${init}, ${anchor});`);
53+
block.builders.mount.add_line(`${html_tag}.m(${parent_node || '#target'}, anchor);`);
10154

102-
if (needs_anchors) {
103-
block.builders.destroy.add_conditional('detaching', needs_anchor_before
104-
? `${detach}\n@detach(${anchor_before});`
105-
: detach);
106-
}
55+
if (needs_anchor) {
56+
block.add_element(html_anchor, '@empty()', '@empty()', parent_node);
57+
}
10758

108-
if (needs_anchor_after && anchor_before !== 'null') {
109-
// ...otherwise it should go afterwards
110-
add_anchor_after();
59+
if (!parent_node || in_head) {
60+
block.builders.destroy.add_conditional('detaching', `${html_tag}.d();`);
61+
}
11162
}
11263
}
11364
}

src/compiler/compile/render_dom/wrappers/shared/Tag.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default class Tag extends Wrapper {
3434

3535
const update_cached_value = `${value} !== (${value} = ${snippet})`;
3636

37-
const condition =this.node.should_cache
37+
const condition = this.node.should_cache
3838
? `(${changed_check}) && ${update_cached_value}`
3939
: changed_check;
4040

src/runtime/internal/dom.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,6 @@ export function detach(node: Node) {
1010
node.parentNode.removeChild(node);
1111
}
1212

13-
export function detach_between(before: Node, after: Node) {
14-
while (before.nextSibling && before.nextSibling !== after) {
15-
before.parentNode.removeChild(before.nextSibling);
16-
}
17-
}
18-
19-
export function detach_before(after: Node) {
20-
while (after.previousSibling) {
21-
after.parentNode.removeChild(after.previousSibling);
22-
}
23-
}
24-
25-
export function detach_after(before: Node) {
26-
while (before.nextSibling) {
27-
before.parentNode.removeChild(before.nextSibling);
28-
}
29-
}
30-
3113
export function destroy_each(iterations, detaching) {
3214
for (let i = 0; i < iterations.length; i += 1) {
3315
if (iterations[i]) iterations[i].d(detaching);
@@ -257,3 +239,39 @@ export function custom_event<T=any>(type: string, detail?: T) {
257239
e.initCustomEvent(type, false, false, detail);
258240
return e;
259241
}
242+
243+
export class HtmlTag {
244+
e: HTMLElement;
245+
n: ChildNode[];
246+
t: HTMLElement;
247+
a: HTMLElement;
248+
249+
constructor(html: string, anchor: HTMLElement = null) {
250+
this.e = element('div');
251+
this.a = anchor;
252+
this.u(html);
253+
}
254+
255+
m(target: HTMLElement, anchor: HTMLElement) {
256+
for (let i = 0; i < this.n.length; i += 1) {
257+
insert(target, this.n[i], anchor);
258+
}
259+
260+
this.t = target;
261+
}
262+
263+
u(html: string) {
264+
this.e.innerHTML = html;
265+
this.n = Array.from(this.e.childNodes);
266+
}
267+
268+
p(html: string) {
269+
this.d();
270+
this.u(html);
271+
this.m(this.t, this.a);
272+
}
273+
274+
d() {
275+
this.n.forEach(detach);
276+
}
277+
}

test/js/samples/each-block-changed-check/expected.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/* generated by Svelte vX.Y.Z */
22
import {
3+
HtmlTag,
34
SvelteComponent,
45
append,
56
attr,
67
destroy_each,
78
detach,
8-
detach_after,
99
element,
1010
init,
1111
insert,
@@ -25,7 +25,7 @@ function get_each_context(ctx, list, i) {
2525

2626
// (8:0) {#each comments as comment, i}
2727
function create_each_block(ctx) {
28-
var div, strong, t0, t1, span, t2_value = ctx.comment.author, t2, t3, t4_value = ctx.elapsed(ctx.comment.time, ctx.time), t4, t5, t6, raw_value = ctx.comment.html, raw_before;
28+
var div, strong, t0, t1, span, t2_value = ctx.comment.author, t2, t3, t4_value = ctx.elapsed(ctx.comment.time, ctx.time), t4, t5, t6, html_tag, raw_value = ctx.comment.html;
2929

3030
return {
3131
c() {
@@ -39,8 +39,8 @@ function create_each_block(ctx) {
3939
t4 = text(t4_value);
4040
t5 = text(" ago:");
4141
t6 = space();
42-
raw_before = element('noscript');
4342
attr(span, "class", "meta");
43+
html_tag = new HtmlTag(raw_value, null);
4444
attr(div, "class", "comment");
4545
},
4646

@@ -55,8 +55,7 @@ function create_each_block(ctx) {
5555
append(span, t4);
5656
append(span, t5);
5757
append(div, t6);
58-
append(div, raw_before);
59-
raw_before.insertAdjacentHTML("afterend", raw_value);
58+
html_tag.m(div, anchor);
6059
},
6160

6261
p(changed, ctx) {
@@ -69,8 +68,7 @@ function create_each_block(ctx) {
6968
}
7069

7170
if ((changed.comments) && raw_value !== (raw_value = ctx.comment.html)) {
72-
detach_after(raw_before);
73-
raw_before.insertAdjacentHTML("afterend", raw_value);
71+
html_tag.p(raw_value);
7472
}
7573
},
7674

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default {
2+
html: `
3+
JohnJill
4+
`,
5+
6+
test({ assert, component, target }) {
7+
component.names = component.names.reverse();
8+
assert.htmlEqual(target.innerHTML, `JillJohn`);
9+
}
10+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
export let names = ['John', 'Jill'];
3+
</script>
4+
5+
{#each names as name (name)}
6+
{@html name}
7+
{/each}
8+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
html: `<p>x<span>baz</span></p>`
3+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>{@html 'x'}<span>baz</span></p>

test/runtime/samples/raw-mustaches/_config.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
const ns = '<noscript></noscript>';
2-
31
export default {
42
skip_if_ssr: true,
53

64
props: {
75
raw: '<span><em>raw html!!!\\o/</span></em>'
86
},
97

10-
html: `before${ns}<span><em>raw html!!!\\o/</span></em>${ns}after`,
8+
html: `before<span><em>raw html!!!\\o/</span></em>after`,
119

1210
test({ assert, component, target }) {
1311
component.raw = '';
14-
assert.equal(target.innerHTML, `before${ns}${ns}after`);
12+
assert.equal(target.innerHTML, `beforeafter`);
1513
component.raw = 'how about <strong>unclosed elements?';
16-
assert.equal(target.innerHTML, `before${ns}how about <strong>unclosed elements?</strong>${ns}after`);
14+
assert.equal(target.innerHTML, `beforehow about <strong>unclosed elements?</strong>after`);
1715
component.$destroy();
1816
assert.equal(target.innerHTML, '');
1917
}

0 commit comments

Comments
 (0)