diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 55d428a452..1ac7098555 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -41,6 +41,13 @@ function createDebuggingComment(node: Node, generator: DomGenerator) { return `${loc} ${source.slice(c, d)}`.replace(/\n/g, ' '); } +function cannotUseInnerHTML(node: Node) { + while (node && node.canUseInnerHTML) { + node.canUseInnerHTML = false; + node = node.parent; + } +} + // Whitespace inside one of these elements will not result in // a whitespace node being created in any circumstances. (This // list is almost certainly very incomplete) @@ -65,6 +72,7 @@ const preprocessors = { componentStack: Node[], stripWhitespace: boolean ) => { + cannotUseInnerHTML(node); node.var = block.getUniqueName('text'); const dependencies = block.findDependencies(node.expression); @@ -80,6 +88,7 @@ const preprocessors = { componentStack: Node[], stripWhitespace: boolean ) => { + cannotUseInnerHTML(node); node.var = block.getUniqueName('raw'); const dependencies = block.findDependencies(node.expression); @@ -114,6 +123,8 @@ const preprocessors = { stripWhitespace: boolean, nextSibling: Node ) => { + cannotUseInnerHTML(node); + const blocks: Block[] = []; let dynamic = false; let hasIntros = false; @@ -195,6 +206,7 @@ const preprocessors = { stripWhitespace: boolean, nextSibling: Node ) => { + cannotUseInnerHTML(node); node.var = block.getUniqueName(`each`); const dependencies = block.findDependencies(node.expression); @@ -290,10 +302,16 @@ const preprocessors = { stripWhitespace: boolean, nextSibling: Node ) => { + if (node.name === 'slot') { + cannotUseInnerHTML(node); + } + node.attributes.forEach((attribute: Node) => { if (attribute.type === 'Attribute' && attribute.value !== true) { attribute.value.forEach((chunk: Node) => { if (chunk.type !== 'Text') { + if (node.parent) cannotUseInnerHTML(node.parent); + const dependencies = block.findDependencies(chunk.expression); block.addDependencies(dependencies); @@ -311,20 +329,24 @@ const preprocessors = { } } }); - } else if (attribute.type === 'EventHandler' && attribute.expression) { - attribute.expression.arguments.forEach((arg: Node) => { - const dependencies = block.findDependencies(arg); + } else { + if (node.parent) cannotUseInnerHTML(node.parent); + + if (attribute.type === 'EventHandler' && attribute.expression) { + attribute.expression.arguments.forEach((arg: Node) => { + const dependencies = block.findDependencies(arg); + block.addDependencies(dependencies); + }); + } else if (attribute.type === 'Binding') { + const dependencies = block.findDependencies(attribute.value); block.addDependencies(dependencies); - }); - } else if (attribute.type === 'Binding') { - const dependencies = block.findDependencies(attribute.value); - block.addDependencies(dependencies); - } else if (attribute.type === 'Transition') { - if (attribute.intro) - generator.hasIntroTransitions = block.hasIntroMethod = true; - if (attribute.outro) { - generator.hasOutroTransitions = block.hasOutroMethod = true; - block.outros += 1; + } else if (attribute.type === 'Transition') { + if (attribute.intro) + generator.hasIntroTransitions = block.hasIntroMethod = true; + if (attribute.outro) { + generator.hasOutroTransitions = block.hasOutroMethod = true; + block.outros += 1; + } } } }); @@ -340,6 +362,8 @@ const preprocessors = { // so that if `foo.qux` changes, we know that we need to // mark `bar` and `baz` as dirty too if (node.name === 'select') { + cannotUseInnerHTML(node); + const value = node.attributes.find( (attribute: Node) => attribute.name === 'value' ); @@ -359,6 +383,8 @@ const preprocessors = { generator.components.has(node.name) || node.name === ':Self'; if (isComponent) { + cannotUseInnerHTML(node); + node.var = block.getUniqueName( (node.name === ':Self' ? generator.name : node.name).toLowerCase() ); @@ -369,6 +395,7 @@ const preprocessors = { } else { const slot = getStaticAttributeValue(node, 'slot'); if (slot && isChildOfComponent(node, generator)) { + cannotUseInnerHTML(node); node.slotted = true; // TODO validate slots — no nesting, no dynamic names... const component = componentStack[componentStack.length - 1]; @@ -442,6 +469,7 @@ function preprocessChildren( cleaned.forEach((child: Node, i: number) => { child.parent = node; + child.canUseInnerHTML = !generator.hydratable; const preprocessor = preprocessors[child.type]; if (preprocessor) preprocessor(generator, block, state, child, inEachBlock, elementStack, componentStack, stripWhitespace, cleaned[i + 1] || nextSibling); diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 70e8ff280e..8b42d40a18 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -9,6 +9,7 @@ import visitBinding from './Binding'; import visitRef from './Ref'; import * as namespaces from '../../../../utils/namespaces'; import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; +import isVoidElementName from '../../../../utils/isVoidElementName'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; @@ -94,7 +95,6 @@ export default function visitElement( } // add CSS encapsulation attribute - // TODO add a helper for this, rather than repeating it if (node._needsCssAttribute && !generator.customElement) { generator.needsEncapsulateHelper = true; block.builders.hydrate.addLine( @@ -202,9 +202,21 @@ export default function visitElement( node.initialUpdate = node.lateUpdate = statement; } - node.children.forEach((child: Node) => { - visit(generator, block, childState, child, elementStack.concat(node), componentStack); - }); + if (!childState.namespace && node.canUseInnerHTML && node.children.length > 0) { + if (node.children.length === 1 && node.children[0].type === 'Text') { + block.builders.create.addLine( + `${name}.textContent = ${JSON.stringify(node.children[0].data)};` + ); + } else { + block.builders.create.addLine( + `${name}.innerHTML = ${JSON.stringify(node.children.map(toHTML).join(''))};` + ); + } + } else { + node.children.forEach((child: Node) => { + visit(generator, block, childState, child, elementStack.concat(node), componentStack); + }); + } if (node.lateUpdate) { block.builders.update.addLine(node.lateUpdate); @@ -221,6 +233,29 @@ export default function visitElement( block.builders.claim.addLine( `${childState.parentNodes}.forEach(@detachNode);` ); + + function toHTML(node: Node) { + if (node.type === 'Text') return node.data; + + let open = `<${node.name}`; + + if (node._needsCssAttribute) { + open += ` ${generator.stylesheet.id}`; + } + + if (node._cssRefAttribute) { + open += ` svelte-ref-${node._cssRefAttribute}`; + } + + node.attributes.forEach((attr: Node) => { + open += ` ${attr.name}${stringifyAttributeValue(attr.value)}` + }); + + if (isVoidElementName(node.name)) return open + '>'; + if (node.children.length === 0) return open + '/>'; + + return `${open}>${node.children.map(toHTML).join('')}`; + } } function getRenderStatement( @@ -263,3 +298,11 @@ function quoteProp(name: string, legacy: boolean) { if (/[^a-zA-Z_$0-9]/.test(name) || isLegacyPropName) return `"${name}"`; return name; } + +function stringifyAttributeValue(value: Node | true) { + if (value === true) return ''; + if (value.length === 0) return `=""`; + + const data = value[0].data; + return `=${JSON.stringify(data)}`; +} \ No newline at end of file diff --git a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js index 22bcb5f37b..6155fc8ecb 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js @@ -13,10 +13,6 @@ function assign(target) { return target; } -function appendNode(node, target) { - target.appendChild(node); -} - function insertNode(node, target, anchor) { target.insertBefore(node, anchor); } @@ -29,10 +25,6 @@ function createElement(name) { return document.createElement(name); } -function createText(data) { - return document.createTextNode(data); -} - function blankObject() { return Object.create(null); } @@ -187,17 +179,16 @@ var proto = { /* generated by Svelte vX.Y.Z */ function create_main_fragment(state, component) { - var div, text; + var div; return { create: function() { div = createElement("div"); - text = createText("fades in"); + div.textContent = "fades in"; }, mount: function(target, anchor) { insertNode(div, target, anchor); - appendNode(text, div); }, update: noop, diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index 6cea9a7856..06a9f08bf6 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -1,18 +1,17 @@ /* generated by Svelte vX.Y.Z */ -import { appendNode, assign, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; +import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment(state, component) { - var div, text; + var div; return { create: function() { div = createElement("div"); - text = createText("fades in"); + div.textContent = "fades in"; }, mount: function(target, anchor) { insertNode(div, target, anchor); - appendNode(text, div); }, update: noop, diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 4d5e595318..7b896bf8f1 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -13,10 +13,6 @@ function assign(target) { return target; } -function appendNode(node, target) { - target.appendChild(node); -} - function insertNode(node, target, anchor) { target.insertBefore(node, anchor); } @@ -29,10 +25,6 @@ function createElement(name) { return document.createElement(name); } -function createText(data) { - return document.createTextNode(data); -} - function blankObject() { return Object.create(null); } @@ -202,12 +194,12 @@ var template = (function() { }()); function create_main_fragment(state, component) { - var button, foo_handler, text; + var button, foo_handler; return { create: function() { button = createElement("button"); - text = createText("foo"); + button.textContent = "foo"; this.hydrate(); }, @@ -220,7 +212,6 @@ function create_main_fragment(state, component) { mount: function(target, anchor) { insertNode(button, target, anchor); - appendNode(text, button); }, update: noop, diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index bf656c0411..34832b6000 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -1,5 +1,5 @@ /* generated by Svelte vX.Y.Z */ -import { appendNode, assign, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; +import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; var template = (function() { return { @@ -17,12 +17,12 @@ var template = (function() { }()); function create_main_fragment(state, component) { - var button, foo_handler, text; + var button, foo_handler; return { create: function() { button = createElement("button"); - text = createText("foo"); + button.textContent = "foo"; this.hydrate(); }, @@ -35,7 +35,6 @@ function create_main_fragment(state, component) { mount: function(target, anchor) { insertNode(button, target, anchor); - appendNode(text, button); }, update: noop, diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index b413e274e0..6b6a6255b9 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -13,10 +13,6 @@ function assign(target) { return target; } -function appendNode(node, target) { - target.appendChild(node); -} - function insertNode(node, target, anchor) { target.insertBefore(node, anchor); } @@ -29,10 +25,6 @@ function createElement(name) { return document.createElement(name); } -function createText(data) { - return document.createTextNode(data); -} - function createComment() { return document.createComment(''); } @@ -230,17 +222,16 @@ function create_main_fragment(state, component) { // (1:0) {{#if foo}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("foo!"); + p.textContent = "foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -253,17 +244,16 @@ function create_if_block(state, component) { // (3:0) {{else}} function create_if_block_1(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("not foo!"); + p.textContent = "not foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index a513519061..10ecf2297b 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -1,5 +1,5 @@ /* generated by Svelte vX.Y.Z */ -import { appendNode, assign, createComment, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; +import { assign, createComment, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment(state, component) { var if_block_anchor; @@ -41,17 +41,16 @@ function create_main_fragment(state, component) { // (1:0) {{#if foo}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("foo!"); + p.textContent = "foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -64,17 +63,16 @@ function create_if_block(state, component) { // (3:0) {{else}} function create_if_block_1(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("not foo!"); + p.textContent = "not foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 881aafd926..9f3c06d6a0 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -13,10 +13,6 @@ function assign(target) { return target; } -function appendNode(node, target) { - target.appendChild(node); -} - function insertNode(node, target, anchor) { target.insertBefore(node, anchor); } @@ -29,10 +25,6 @@ function createElement(name) { return document.createElement(name); } -function createText(data) { - return document.createTextNode(data); -} - function createComment() { return document.createComment(''); } @@ -233,17 +225,16 @@ function create_main_fragment(state, component) { // (1:0) {{#if foo}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("foo!"); + p.textContent = "foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index aba8b65ee7..67ef669468 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -1,5 +1,5 @@ /* generated by Svelte vX.Y.Z */ -import { appendNode, assign, createComment, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; +import { assign, createComment, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment(state, component) { var if_block_anchor; @@ -44,17 +44,16 @@ function create_main_fragment(state, component) { // (1:0) {{#if foo}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("foo!"); + p.textContent = "foo!"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 977bdfe485..1541e69175 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -191,7 +191,7 @@ var proto = { /* generated by Svelte vX.Y.Z */ function create_main_fragment(state, component) { - var div, text, p, text_1, text_2, text_3, text_4, p_1, text_5, text_6, text_8, if_block_4_anchor; + var div, text, p, text_2, text_3, text_4, p_1, text_6, text_8, if_block_4_anchor; var if_block = (state.a) && create_if_block(state, component); @@ -209,14 +209,14 @@ function create_main_fragment(state, component) { if (if_block) if_block.create(); text = createText("\n\n\t"); p = createElement("p"); - text_1 = createText("this can be used as an anchor"); + p.textContent = "this can be used as an anchor"; text_2 = createText("\n\n\t"); if (if_block_1) if_block_1.create(); text_3 = createText("\n\n\t"); if (if_block_2) if_block_2.create(); text_4 = createText("\n\n\t"); p_1 = createElement("p"); - text_5 = createText("so can this"); + p_1.textContent = "so can this"; text_6 = createText("\n\n\t"); if (if_block_3) if_block_3.create(); text_8 = createText("\n\n"); @@ -229,14 +229,12 @@ function create_main_fragment(state, component) { if (if_block) if_block.mount(div, null); appendNode(text, div); appendNode(p, div); - appendNode(text_1, p); appendNode(text_2, div); if (if_block_1) if_block_1.mount(div, null); appendNode(text_3, div); if (if_block_2) if_block_2.mount(div, null); appendNode(text_4, div); appendNode(p_1, div); - appendNode(text_5, p_1); appendNode(text_6, div); if (if_block_3) if_block_3.mount(div, null); insertNode(text_8, target, anchor); @@ -329,17 +327,16 @@ function create_main_fragment(state, component) { // (2:1) {{#if a}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("a"); + p.textContent = "a"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -352,17 +349,16 @@ function create_if_block(state, component) { // (8:1) {{#if b}} function create_if_block_1(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("b"); + p.textContent = "b"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -375,17 +371,16 @@ function create_if_block_1(state, component) { // (12:1) {{#if c}} function create_if_block_2(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("c"); + p.textContent = "c"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -398,17 +393,16 @@ function create_if_block_2(state, component) { // (18:1) {{#if d}} function create_if_block_3(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("d"); + p.textContent = "d"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -421,17 +415,16 @@ function create_if_block_3(state, component) { // (25:0) {{#if e}} function create_if_block_4(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("e"); + p.textContent = "e"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index e92d7ba6b6..2a1b93eb36 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -2,7 +2,7 @@ import { appendNode, assign, createComment, createElement, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment(state, component) { - var div, text, p, text_1, text_2, text_3, text_4, p_1, text_5, text_6, text_8, if_block_4_anchor; + var div, text, p, text_2, text_3, text_4, p_1, text_6, text_8, if_block_4_anchor; var if_block = (state.a) && create_if_block(state, component); @@ -20,14 +20,14 @@ function create_main_fragment(state, component) { if (if_block) if_block.create(); text = createText("\n\n\t"); p = createElement("p"); - text_1 = createText("this can be used as an anchor"); + p.textContent = "this can be used as an anchor"; text_2 = createText("\n\n\t"); if (if_block_1) if_block_1.create(); text_3 = createText("\n\n\t"); if (if_block_2) if_block_2.create(); text_4 = createText("\n\n\t"); p_1 = createElement("p"); - text_5 = createText("so can this"); + p_1.textContent = "so can this"; text_6 = createText("\n\n\t"); if (if_block_3) if_block_3.create(); text_8 = createText("\n\n"); @@ -40,14 +40,12 @@ function create_main_fragment(state, component) { if (if_block) if_block.mount(div, null); appendNode(text, div); appendNode(p, div); - appendNode(text_1, p); appendNode(text_2, div); if (if_block_1) if_block_1.mount(div, null); appendNode(text_3, div); if (if_block_2) if_block_2.mount(div, null); appendNode(text_4, div); appendNode(p_1, div); - appendNode(text_5, p_1); appendNode(text_6, div); if (if_block_3) if_block_3.mount(div, null); insertNode(text_8, target, anchor); @@ -140,17 +138,16 @@ function create_main_fragment(state, component) { // (2:1) {{#if a}} function create_if_block(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("a"); + p.textContent = "a"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -163,17 +160,16 @@ function create_if_block(state, component) { // (8:1) {{#if b}} function create_if_block_1(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("b"); + p.textContent = "b"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -186,17 +182,16 @@ function create_if_block_1(state, component) { // (12:1) {{#if c}} function create_if_block_2(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("c"); + p.textContent = "c"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -209,17 +204,16 @@ function create_if_block_2(state, component) { // (18:1) {{#if d}} function create_if_block_3(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("d"); + p.textContent = "d"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() { @@ -232,17 +226,16 @@ function create_if_block_3(state, component) { // (25:0) {{#if e}} function create_if_block_4(state, component) { - var p, text; + var p; return { create: function() { p = createElement("p"); - text = createText("e"); + p.textContent = "e"; }, mount: function(target, anchor) { insertNode(p, target, anchor); - appendNode(text, p); }, unmount: function() {