Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 27 additions & 76 deletions src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';

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

render(block: Block, parent_node: string, parent_nodes: string) {
const name = this.var;

render(block: Block, parent_node: string, _parent_nodes: string) {
const in_head = parent_node === '@_document.head';
const needs_anchors = !parent_node || in_head;

// if in head always needs anchors
if (in_head) {
this.prev = null;
this.next = null;
}

// TODO use is_dom_node instead of type === 'Element'?
const needs_anchor_before = this.prev ? this.prev.node.type !== 'Element' : needs_anchors;
const needs_anchor_after = this.next ? this.next.node.type !== 'Element' : needs_anchors;
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;

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

const anchor_after = needs_anchor_after
? block.get_unique_name(`${name}_after`)
: (this.next && this.next.var) || 'null';

let detach: string;
let insert: (content: string) => string;
let use_innerhtml = false;
const { init } = this.rename_this_method(
block,
content => insert(content)
);

if (anchor_before === 'null' && anchor_after === 'null') {
use_innerhtml = true;
detach = `${parent_node}.innerHTML = '';`;
insert = content => `${parent_node}.innerHTML = ${content};`;
} else if (anchor_before === 'null') {
detach = `@detach_before(${anchor_after});`;
insert = content => `${anchor_after}.insertAdjacentHTML("beforebegin", ${content});`;
} else if (anchor_after === 'null') {
detach = `@detach_after(${anchor_before});`;
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
} else {
detach = `@detach_between(${anchor_before}, ${anchor_after});`;
insert = content => `${anchor_before}.insertAdjacentHTML("afterend", ${content});`;
block.builders.mount.add_line(insert(init));
}

const { init } = this.rename_this_method(
block,
content => deindent`
${!use_innerhtml && detach}
${insert(content)}
`
);
else {
const needs_anchor = in_head || (this.next && !this.next.is_dom_node());

// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
if (needs_anchor_before) {
block.add_element(
anchor_before,
`@element('noscript')`,
parent_nodes && `@element('noscript')`,
parent_node,
true
);
}
const html_tag = block.get_unique_name('html_tag');
const html_anchor = needs_anchor && block.get_unique_name('html_anchor');

block.add_variable(html_tag);

function add_anchor_after() {
block.add_element(
anchor_after,
`@element('noscript')`,
parent_nodes && `@element('noscript')`,
parent_node
const { init } = this.rename_this_method(
block,
content => `${html_tag}.p(${content});`
);
}

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

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

if (needs_anchors) {
block.builders.destroy.add_conditional('detaching', needs_anchor_before
? `${detach}\n@detach(${anchor_before});`
: detach);
}
if (needs_anchor) {
block.add_element(html_anchor, '@empty()', '@empty()', parent_node);
}

if (needs_anchor_after && anchor_before !== 'null') {
// ...otherwise it should go afterwards
add_anchor_after();
if (!parent_node || in_head) {
block.builders.destroy.add_conditional('detaching', `${html_tag}.d();`);
}
}
}
}
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/wrappers/shared/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class Tag extends Wrapper {

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

const condition =this.node.should_cache
const condition = this.node.should_cache
? `(${changed_check}) && ${update_cached_value}`
: changed_check;

Expand Down
54 changes: 36 additions & 18 deletions src/runtime/internal/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@ export function detach(node: Node) {
node.parentNode.removeChild(node);
}

export function detach_between(before: Node, after: Node) {
while (before.nextSibling && before.nextSibling !== after) {
before.parentNode.removeChild(before.nextSibling);
}
}

export function detach_before(after: Node) {
while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling);
}
}

export function detach_after(before: Node) {
while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling);
}
}

export function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
if (iterations[i]) iterations[i].d(detaching);
Expand Down Expand Up @@ -257,3 +239,39 @@ export function custom_event<T=any>(type: string, detail?: T) {
e.initCustomEvent(type, false, false, detail);
return e;
}

export class HtmlTag {
e: HTMLElement;
n: ChildNode[];
t: HTMLElement;
a: HTMLElement;

constructor(html: string, anchor: HTMLElement = null) {
this.e = element('div');
this.a = anchor;
this.u(html);
}

m(target: HTMLElement, anchor: HTMLElement) {
for (let i = 0; i < this.n.length; i += 1) {
insert(target, this.n[i], anchor);
}

this.t = target;
}

u(html: string) {
this.e.innerHTML = html;
this.n = Array.from(this.e.childNodes);
}

p(html: string) {
this.d();
this.u(html);
this.m(this.t, this.a);
}

d() {
this.n.forEach(detach);
}
}
12 changes: 5 additions & 7 deletions test/js/samples/each-block-changed-check/expected.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* generated by Svelte vX.Y.Z */
import {
HtmlTag,
SvelteComponent,
append,
attr,
destroy_each,
detach,
detach_after,
element,
init,
insert,
Expand All @@ -25,7 +25,7 @@ function get_each_context(ctx, list, i) {

// (8:0) {#each comments as comment, i}
function create_each_block(ctx) {
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;
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;

return {
c() {
Expand All @@ -39,8 +39,8 @@ function create_each_block(ctx) {
t4 = text(t4_value);
t5 = text(" ago:");
t6 = space();
raw_before = element('noscript');
attr(span, "class", "meta");
html_tag = new HtmlTag(raw_value, null);
attr(div, "class", "comment");
},

Expand All @@ -55,8 +55,7 @@ function create_each_block(ctx) {
append(span, t4);
append(span, t5);
append(div, t6);
append(div, raw_before);
raw_before.insertAdjacentHTML("afterend", raw_value);
html_tag.m(div, anchor);
},

p(changed, ctx) {
Expand All @@ -69,8 +68,7 @@ function create_each_block(ctx) {
}

if ((changed.comments) && raw_value !== (raw_value = ctx.comment.html)) {
detach_after(raw_before);
raw_before.insertAdjacentHTML("afterend", raw_value);
html_tag.p(raw_value);
}
},

Expand Down
10 changes: 10 additions & 0 deletions test/runtime/samples/each-block-keyed-html/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default {
html: `
JohnJill
`,

test({ assert, component, target }) {
component.names = component.names.reverse();
assert.htmlEqual(target.innerHTML, `JillJohn`);
}
};
8 changes: 8 additions & 0 deletions test/runtime/samples/each-block-keyed-html/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
export let names = ['John', 'Jill'];
</script>

{#each names as name (name)}
{@html name}
{/each}

3 changes: 3 additions & 0 deletions test/runtime/samples/raw-mustache-before-element/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: `<p>x<span>baz</span></p>`
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{@html 'x'}<span>baz</span></p>
8 changes: 3 additions & 5 deletions test/runtime/samples/raw-mustaches/_config.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
const ns = '<noscript></noscript>';

export default {
skip_if_ssr: true,

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

html: `before${ns}<span><em>raw html!!!\\o/</span></em>${ns}after`,
html: `before<span><em>raw html!!!\\o/</span></em>after`,

test({ assert, component, target }) {
component.raw = '';
assert.equal(target.innerHTML, `before${ns}${ns}after`);
assert.equal(target.innerHTML, `beforeafter`);
component.raw = 'how about <strong>unclosed elements?';
assert.equal(target.innerHTML, `before${ns}how about <strong>unclosed elements?</strong>${ns}after`);
assert.equal(target.innerHTML, `beforehow about <strong>unclosed elements?</strong>after`);
component.$destroy();
assert.equal(target.innerHTML, '');
}
Expand Down