From 162281ef4f2d280d25c849ac21f4d09059e167fd Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 12:39:01 -0400 Subject: [PATCH 1/6] populate state in preprocess step, including naming DOM nodes --- src/generators/dom/index.js | 4 +- src/generators/dom/preprocess.js | 105 ++++++-- .../dom/visitors/Component/Component.js | 4 +- src/generators/dom/visitors/EachBlock.js | 13 +- .../dom/visitors/Element/Element.js | 11 +- src/generators/dom/visitors/IfBlock.js | 10 +- src/generators/dom/visitors/MustacheTag.js | 2 +- src/generators/dom/visitors/RawMustacheTag.js | 4 +- src/generators/dom/visitors/Text.js | 23 +- .../use-elements-as-anchors/expected.js | 244 ++++++++++++++++++ .../use-elements-as-anchors/input.html | 27 ++ 11 files changed, 374 insertions(+), 73 deletions(-) create mode 100644 test/js/samples/use-elements-as-anchors/expected.js create mode 100644 test/js/samples/use-elements-as-anchors/input.html diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 1309c4ddb26d..173b987aa1b4 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -43,14 +43,14 @@ export default function dom ( parsed, source, options ) { const { computations, hasJs, templateProperties, namespace } = generator.parseJs(); - const block = preprocess( generator, parsed.html ); - const state = { namespace, parentNode: null, isTopLevel: true }; + const block = preprocess( generator, state, parsed.html ); + parsed.html.children.forEach( node => { visit( generator, block, state, node ); }); diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js index f8ab06ddc37a..320724c332c6 100644 --- a/src/generators/dom/preprocess.js +++ b/src/generators/dom/preprocess.js @@ -1,17 +1,62 @@ import Block from './Block.js'; import { trimStart, trimEnd } from '../../utils/trim.js'; +import { assign } from '../../shared/index.js'; function isElseIf ( node ) { return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; } +function getChildState ( parent, child ) { + return assign( {}, parent, { name: null, parentNode: null }, child || {} ); +} + +// 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) +const elementsWithoutText = new Set([ + 'audio', + 'datalist', + 'dl', + 'ol', + 'optgroup', + 'select', + 'ul', + 'video' +]); + const preprocessors = { - MustacheTag: ( generator, block, node ) => { + MustacheTag: ( generator, block, state, node ) => { const dependencies = block.findDependencies( node.expression ); block.addDependencies( dependencies ); + + node._state = getChildState( state, { + name: block.getUniqueName( 'text' ) + }); + }, + + RawMustacheTag: ( generator, block, state, node ) => { + const dependencies = block.findDependencies( node.expression ); + block.addDependencies( dependencies ); + + const basename = block.getUniqueName( 'raw' ); + const name = block.getUniqueName( `${basename}_before` ); + + node._state = getChildState( state, { basename, name }); + }, + + Text: ( generator, block, state, node ) => { + node._state = getChildState( state ); + + if ( !/\S/.test( node.data ) ) { + if ( state.namespace ) return; + if ( elementsWithoutText.has( state.parentNodeName ) ) return; + } + + node._state.shouldCreate = true; + node._state.name = block.getUniqueName( `text` ); }, - IfBlock: ( generator, block, node ) => { + IfBlock: ( generator, block, state, node ) => { const blocks = []; let dynamic = false; @@ -23,8 +68,10 @@ const preprocessors = { name: generator.getUniqueName( `create_if_block` ) }); + node._state = getChildState( state ); + blocks.push( node._block ); - preprocessChildren( generator, node._block, node ); + preprocessChildren( generator, node._block, node._state, node ); if ( node._block.dependencies.size > 0 ) { dynamic = true; @@ -38,8 +85,10 @@ const preprocessors = { name: generator.getUniqueName( `create_if_block` ) }); + node.else._state = getChildState( state ); + blocks.push( node.else._block ); - preprocessChildren( generator, node.else._block, node.else ); + preprocessChildren( generator, node.else._block, node.else._state, node.else ); if ( node.else._block.dependencies.size > 0 ) { dynamic = true; @@ -57,7 +106,7 @@ const preprocessors = { generator.blocks.push( ...blocks ); }, - EachBlock: ( generator, block, node ) => { + EachBlock: ( generator, block, state, node ) => { const dependencies = block.findDependencies( node.expression ); block.addDependencies( dependencies ); @@ -97,8 +146,12 @@ const preprocessors = { params: block.params.concat( listName, context, indexName ) }); + node._state = getChildState( state, { + inEachBlock: true + }); + generator.blocks.push( node._block ); - preprocessChildren( generator, node._block, node ); + preprocessChildren( generator, node._block, node._state, node ); block.addDependencies( node._block.dependencies ); node._block.hasUpdateMethod = node._block.dependencies.size > 0; @@ -107,13 +160,32 @@ const preprocessors = { name: generator.getUniqueName( `${node._block.name}_else` ) }); + node.else._state = getChildState( state ); + generator.blocks.push( node.else._block ); - preprocessChildren( generator, node.else._block, node.else ); + preprocessChildren( generator, node.else._block, node.else._state, node.else ); node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0; } }, - Element: ( generator, block, node ) => { + Element: ( generator, block, state, node ) => { + const isComponent = generator.components.has( node.name ) || node.name === ':Self'; + + if ( isComponent ) { + node._state = getChildState( state ); + } else { + const name = block.getUniqueName( node.name ); + + node._state = getChildState( state, { + isTopLevel: false, + name, + parentNode: name, + parentNodeName: node.name, + namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace, + allUsedContexts: [] + }); + } + node.attributes.forEach( attribute => { if ( attribute.type === 'Attribute' && attribute.value !== true ) { attribute.value.forEach( chunk => { @@ -130,8 +202,6 @@ const preprocessors = { } }); - const isComponent = generator.components.has( node.name ) || node.name === ':Self'; - if ( node.children.length ) { if ( isComponent ) { const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() ); @@ -141,21 +211,19 @@ const preprocessors = { }); generator.blocks.push( node._block ); - preprocessChildren( generator, node._block, node ); + preprocessChildren( generator, node._block, node._state, node ); block.addDependencies( node._block.dependencies ); node._block.hasUpdateMethod = node._block.dependencies.size > 0; } else { - preprocessChildren( generator, block, node ); + preprocessChildren( generator, block, node._state, node ); } } } }; -preprocessors.RawMustacheTag = preprocessors.MustacheTag; - -function preprocessChildren ( generator, block, node ) { +function preprocessChildren ( generator, block, state, node ) { // glue text nodes together const cleaned = []; let lastChild; @@ -170,6 +238,7 @@ function preprocessChildren ( generator, block, node ) { cleaned.push( child ); } + if ( lastChild ) lastChild.next = child; lastChild = child; }); @@ -177,11 +246,11 @@ function preprocessChildren ( generator, block, node ) { cleaned.forEach( child => { const preprocess = preprocessors[ child.type ]; - if ( preprocess ) preprocess( generator, block, child ); + if ( preprocess ) preprocess( generator, block, state, child ); }); } -export default function preprocess ( generator, node ) { +export default function preprocess ( generator, state, node ) { const block = new Block({ generator, name: generator.alias( 'create_main_fragment' ), @@ -199,7 +268,7 @@ export default function preprocess ( generator, node ) { }); generator.blocks.push( block ); - preprocessChildren( generator, block, node ); + preprocessChildren( generator, block, state, node ); block.hasUpdateMethod = block.dependencies.size > 0; // trim leading and trailing whitespace from the top level diff --git a/src/generators/dom/visitors/Component/Component.js b/src/generators/dom/visitors/Component/Component.js index c54945338def..6430c4f07c18 100644 --- a/src/generators/dom/visitors/Component/Component.js +++ b/src/generators/dom/visitors/Component/Component.js @@ -36,9 +36,7 @@ export default function visitComponent ( generator, block, state, node ) { const hasChildren = node.children.length > 0; const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() ); - const childState = Object.assign( {}, state, { - parentNode: null - }); + const childState = node._state; const local = { name, diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index dbb1d2c085f1..7919cf1f3d76 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -87,22 +87,13 @@ export default function visitEachBlock ( generator, block, state, node ) { ` ); } - const childState = Object.assign( {}, state, { - parentNode: null, - inEachBlock: true - }); - node.children.forEach( child => { - visit( generator, node._block, childState, child ); + visit( generator, node._block, node._state, child ); }); if ( node.else ) { - const childState = Object.assign( {}, state, { - parentNode: null - }); - node.else.children.forEach( child => { - visit( generator, node.else._block, childState, child ); + visit( generator, node.else._block, node.else._state, child ); }); } } diff --git a/src/generators/dom/visitors/Element/Element.js b/src/generators/dom/visitors/Element/Element.js index 7f55d84ec987..001c8a52e202 100644 --- a/src/generators/dom/visitors/Element/Element.js +++ b/src/generators/dom/visitors/Element/Element.js @@ -34,15 +34,8 @@ export default function visitElement ( generator, block, state, node ) { return visitComponent( generator, block, state, node ); } - const name = block.getUniqueName( node.name ); - - const childState = Object.assign( {}, state, { - isTopLevel: false, - parentNode: name, - parentNodeName: node.name, - namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace, - allUsedContexts: [] - }); + const childState = node._state; + const name = childState.parentNode; block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` ); block.mount( name, state.parentNode ); diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 1f1800b536b1..593a7ca98671 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -34,12 +34,8 @@ function getBranches ( generator, block, state, node ) { } function visitChildren ( generator, block, state, node ) { - const childState = Object.assign( {}, state, { - parentNode: null - }); - node.children.forEach( child => { - visit( generator, node._block, childState, child ); + visit( generator, node._block, node._state, child ); }); } @@ -76,7 +72,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor if ( isToplevel ) { block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` ); } else { - block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, ${anchor} );` ); + block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` ); } if ( dynamic ) { @@ -128,7 +124,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an if ( isToplevel ) { block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` ); } else { - block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, ${anchor} );` ); + block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` ); } if ( dynamic ) { diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index 301345e6cc0a..568f745da56b 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -1,7 +1,7 @@ import deindent from '../../../utils/deindent.js'; export default function visitMustacheTag ( generator, block, state, node ) { - const name = block.getUniqueName( 'text' ); + const name = node._state.name; const value = block.getUniqueName( `${name}_value` ); const { snippet } = block.contextualise( node.expression ); diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js index 2eed5bfdb6cf..7a3964315083 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -1,9 +1,9 @@ import deindent from '../../../utils/deindent.js'; export default function visitRawMustacheTag ( generator, block, state, node ) { - const name = block.getUniqueName( 'raw' ); + const name = node._state.basename; + const before = node._state.name; const value = block.getUniqueName( `${name}_value` ); - const before = block.getUniqueName( `${name}_before` ); const after = block.getUniqueName( `${name}_after` ); const { snippet } = block.contextualise( node.expression ); diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js index 923175e5ab67..052c4e58a7d5 100644 --- a/src/generators/dom/visitors/Text.js +++ b/src/generators/dom/visitors/Text.js @@ -1,23 +1,6 @@ -// 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) -const elementsWithoutText = new Set([ - 'audio', - 'datalist', - 'dl', - 'ol', - 'optgroup', - 'select', - 'ul', - 'video' -]); -export default function visitText ( generator, block, state, node ) { - if ( !/\S/.test( node.data ) ) { - if ( state.namespace ) return; - if ( elementsWithoutText.has( state.parentNodeName) ) return; - } - const name = block.getUniqueName( `text` ); - block.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false ); +export default function visitText ( generator, block, state, node ) { + if ( !node._state.shouldCreate ) return; + block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false ); } \ No newline at end of file diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js new file mode 100644 index 000000000000..e72585e6063d --- /dev/null +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -0,0 +1,244 @@ +import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div = createElement( 'div' ); + var if_block_anchor = createComment(); + appendNode( if_block_anchor, div ); + + var if_block = state.a && create_if_block( state, component ); + + if ( if_block ) if_block.mount( div, null ); + appendNode( createText( "\n\n\t" ), div ); + var p = createElement( 'p' ); + appendNode( p, div ); + appendNode( createText( "this can be used as an anchor" ), p ); + appendNode( createText( "\n\n\t" ), div ); + + var if_block_1 = state.b && create_if_block_1( state, component ); + + if ( if_block_1 ) if_block_1.mount( div, null ); + var if_block_1_anchor = createComment(); + appendNode( if_block_1_anchor, div ); + + appendNode( createText( "\n\n\t" ), div ); + + var if_block_2 = state.c && create_if_block_2( state, component ); + + if ( if_block_2 ) if_block_2.mount( div, null ); + appendNode( createText( "\n\n\t" ), div ); + var p_1 = createElement( 'p' ); + appendNode( p_1, div ); + appendNode( createText( "so can this" ), p_1 ); + appendNode( createText( "\n\n\t" ), div ); + + var if_block_3 = state.d && create_if_block_3( state, component ); + + if ( if_block_3 ) if_block_3.mount( div, null ); + appendNode( createText( "\n\n\t" ), div ); + var text_8 = createText( "\n\n" ); + var if_block_4_anchor = createComment(); + + var if_block_4 = state.e && create_if_block_4( state, component ); + + return { + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + insertNode( text_8, target, anchor ); + insertNode( if_block_4_anchor, target, anchor ); + if ( if_block_4 ) if_block_4.mount( target, if_block_4_anchor ); + }, + + update: function ( changed, state ) { + if ( state.a ) { + if ( !if_block ) { + if_block = create_if_block( state, component ); + if_block.mount( if_block_anchor.parentNode, p ); + } + } else if ( if_block ) { + if_block.destroy( true ); + if_block = null; + } + + if ( state.b ) { + if ( !if_block_1 ) { + if_block_1 = create_if_block_1( state, component ); + if_block_1.mount( if_block_1_anchor.parentNode, if_block_1_anchor ); + } + } else if ( if_block_1 ) { + if_block_1.destroy( true ); + if_block_1 = null; + } + + if ( state.c ) { + if ( !if_block_2 ) { + if_block_2 = create_if_block_2( state, component ); + if_block_2.mount( if_block_2_anchor.parentNode, p_2 ); + } + } else if ( if_block_2 ) { + if_block_2.destroy( true ); + if_block_2 = null; + } + + if ( state.d ) { + if ( !if_block_3 ) { + if_block_3 = create_if_block_3( state, component ); + if_block_3.mount( if_block_3_anchor.parentNode, null ); + } + } else if ( if_block_3 ) { + if_block_3.destroy( true ); + if_block_3 = null; + } + + if ( state.e ) { + if ( !if_block_4 ) { + if_block_4 = create_if_block_4( state, component ); + if_block_4.mount( if_block_4_anchor.parentNode, if_block_4_anchor ); + } + } else if ( if_block_4 ) { + if_block_4.destroy( true ); + if_block_4 = null; + } + }, + + destroy: function ( detach ) { + if ( if_block ) if_block.destroy( false ); + if ( if_block_1 ) if_block_1.destroy( false ); + if ( if_block_2 ) if_block_2.destroy( false ); + if ( if_block_3 ) if_block_3.destroy( false ); + if ( if_block_4 ) if_block_4.destroy( detach ); + + if ( detach ) { + detachNode( div ); + detachNode( text_8 ); + detachNode( if_block_4_anchor ); + } + } + }; +} + +function create_if_block ( state, component ) { + var p = createElement( 'p' ); + appendNode( createText( "a" ), p ); + + return { + mount: function ( target, anchor ) { + insertNode( p, target, anchor ); + }, + + destroy: function ( detach ) { + if ( detach ) { + detachNode( p ); + } + } + }; +} + +function create_if_block_1 ( state, component ) { + var p = createElement( 'p' ); + appendNode( createText( "b" ), p ); + + return { + mount: function ( target, anchor ) { + insertNode( p, target, anchor ); + }, + + destroy: function ( detach ) { + if ( detach ) { + detachNode( p ); + } + } + }; +} + +function create_if_block_2 ( state, component ) { + var p = createElement( 'p' ); + appendNode( createText( "c" ), p ); + + return { + mount: function ( target, anchor ) { + insertNode( p, target, anchor ); + }, + + destroy: function ( detach ) { + if ( detach ) { + detachNode( p ); + } + } + }; +} + +function create_if_block_3 ( state, component ) { + var p = createElement( 'p' ); + appendNode( createText( "d" ), p ); + + return { + mount: function ( target, anchor ) { + insertNode( p, target, anchor ); + }, + + destroy: function ( detach ) { + if ( detach ) { + detachNode( p ); + } + } + }; +} + +function create_if_block_4 ( state, component ) { + var p = createElement( 'p' ); + appendNode( createText( "e" ), p ); + + return { + mount: function ( target, anchor ) { + insertNode( p, target, anchor ); + }, + + destroy: function ( detach ) { + if ( detach ) { + detachNode( p ); + } + } + }; +} + +function SvelteComponent ( options ) { + options = options || {}; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root; + this._yield = options._yield; + + this._torndown = false; + + this._fragment = create_main_fragment( this._state, this ); + if ( options.target ) this._fragment.mount( options.target, null ); +} + +assign( SvelteComponent.prototype, proto ); + +SvelteComponent.prototype._set = function _set ( newState ) { + var oldState = this._state; + this._state = assign( {}, oldState, newState ); + dispatchObservers( this, this._observers.pre, newState, oldState ); + if ( this._fragment ) this._fragment.update( newState, this._state ); + dispatchObservers( this, this._observers.post, newState, oldState ); +}; + +SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) { + this.fire( 'destroy' ); + + this._fragment.destroy( detach !== false ); + this._fragment = null; + + this._state = {}; + this._torndown = true; +}; + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/use-elements-as-anchors/input.html b/test/js/samples/use-elements-as-anchors/input.html new file mode 100644 index 000000000000..c55e7bd4de7b --- /dev/null +++ b/test/js/samples/use-elements-as-anchors/input.html @@ -0,0 +1,27 @@ +
+ {{#if a}} +

a

+ {{/if}} + +

this can be used as an anchor

+ + {{#if b}} +

b

+ {{/if}} + + {{#if c}} +

c

+ {{/if}} + +

so can this

+ + {{#if d}} +

d

+ {{/if}} + + +
+ +{{#if e}} +

e

+{{/if}} \ No newline at end of file From d274d08734b2cdbe1eda51cf06d60d7df710a569 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 13:52:36 -0400 Subject: [PATCH 2/6] only create anchors for if blocks when necessary --- src/generators/dom/preprocess.js | 48 ++++++++++++------- src/generators/dom/visitors/IfBlock.js | 24 ++++++---- src/generators/dom/visitors/Text.js | 2 +- .../js/samples/if-block-no-update/expected.js | 2 +- test/js/samples/if-block-simple/expected.js | 2 +- .../use-elements-as-anchors/expected.js | 27 +++++------ .../samples/if-block-widget/_config.js | 21 ++++++-- 7 files changed, 81 insertions(+), 45 deletions(-) diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js index 320724c332c6..6009f4dec827 100644 --- a/src/generators/dom/preprocess.js +++ b/src/generators/dom/preprocess.js @@ -223,7 +223,7 @@ const preprocessors = { } }; -function preprocessChildren ( generator, block, state, node ) { +function preprocessChildren ( generator, block, state, node, isTopLevel ) { // glue text nodes together const cleaned = []; let lastChild; @@ -238,16 +238,43 @@ function preprocessChildren ( generator, block, state, node ) { cleaned.push( child ); } - if ( lastChild ) lastChild.next = child; lastChild = child; }); - node.children = cleaned; + if ( isTopLevel ) { + // trim leading and trailing whitespace from the top level + const firstChild = cleaned[0]; + if ( firstChild && firstChild.type === 'Text' ) { + firstChild.data = trimStart( firstChild.data ); + if ( !firstChild.data ) cleaned.shift(); + } + + const lastChild = cleaned[ cleaned.length - 1 ]; + if ( lastChild && lastChild.type === 'Text' ) { + lastChild.data = trimEnd( lastChild.data ); + if ( !lastChild.data ) cleaned.pop(); + } + } + + lastChild = null; cleaned.forEach( child => { const preprocess = preprocessors[ child.type ]; if ( preprocess ) preprocess( generator, block, state, child ); + + if ( lastChild ) { + lastChild.next = child; + lastChild.needsAnchor = !child._state.name; + } + + lastChild = child; }); + + if ( lastChild ) { + lastChild.needsAnchor = !state.parentNode; + } + + node.children = cleaned; } export default function preprocess ( generator, state, node ) { @@ -268,21 +295,8 @@ export default function preprocess ( generator, state, node ) { }); generator.blocks.push( block ); - preprocessChildren( generator, block, state, node ); + preprocessChildren( generator, block, state, node, true ); block.hasUpdateMethod = block.dependencies.size > 0; - // trim leading and trailing whitespace from the top level - const firstChild = node.children[0]; - if ( firstChild && firstChild.type === 'Text' ) { - firstChild.data = trimStart( firstChild.data ); - if ( !firstChild.data ) node.children.shift(); - } - - const lastChild = node.children[ node.children.length - 1 ]; - if ( lastChild && lastChild.type === 'Text' ) { - lastChild.data = trimEnd( lastChild.data ); - if ( !lastChild.data ) node.children.pop(); - } - return block; } \ No newline at end of file diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 593a7ca98671..d9fd1b81c74a 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -41,12 +41,16 @@ function visitChildren ( generator, block, state, node ) { export default function visitIfBlock ( generator, block, state, node ) { const name = generator.getUniqueName( `if_block` ); - const anchor = generator.getUniqueName( `${name}_anchor` ); + const anchor = node.needsAnchor ? block.getUniqueName( `${name}_anchor` ) : ( node.next && node.next._state.name ) || 'null'; const params = block.params.join( ', ' ); const vars = { name, anchor, params }; - block.createAnchor( anchor, state.parentNode ); + if ( node.needsAnchor ) { + block.createAnchor( anchor, state.parentNode ); + } else if ( node.next ) { + node.next.usedAsAnchor = true; + } const branches = getBranches( generator, block, state, node, generator.getUniqueName( `create_if_block` ) ); const dynamic = branches.some( branch => branch.dynamic ); @@ -70,11 +74,13 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor const isToplevel = !state.parentNode; if ( isToplevel ) { - block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` ); + block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, null );` ); } else { block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` ); } + const parentNode = state.parentNode || `${anchor}.parentNode`; + if ( dynamic ) { block.builders.update.addBlock( deindent` if ( ${branch.condition} ) { @@ -82,7 +88,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor ${name}.update( changed, ${params} ); } else { ${name} = ${branch.block}( ${params}, ${block.component} ); - ${name}.mount( ${anchor}.parentNode, ${anchor} ); + ${name}.mount( ${parentNode}, ${anchor} ); } } else if ( ${name} ) { ${name}.destroy( true ); @@ -94,7 +100,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor if ( ${branch.condition} ) { if ( !${name} ) { ${name} = ${branch.block}( ${params}, ${block.component} ); - ${name}.mount( ${anchor}.parentNode, ${anchor} ); + ${name}.mount( ${parentNode}, ${anchor} ); } } else if ( ${name} ) { ${name}.destroy( true ); @@ -122,11 +128,13 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an const isToplevel = !state.parentNode; if ( isToplevel ) { - block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` ); + block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, null );` ); } else { block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` ); } + const parentNode = state.parentNode || `${anchor}.parentNode`; + if ( dynamic ) { block.builders.update.addBlock( deindent` if ( ${current_block} === ( ${current_block} = ${getBlock}( ${params} ) ) && ${name} ) { @@ -134,7 +142,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an } else { if ( ${name} ) ${name}.destroy( true ); ${name} = ${current_block} && ${current_block}( ${params}, ${block.component} ); - if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} ); + if ( ${name} ) ${name}.mount( ${parentNode}, ${anchor} ); } ` ); } else { @@ -142,7 +150,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an if ( ${current_block} !== ( ${current_block} = ${getBlock}( ${params} ) ) ) { if ( ${name} ) ${name}.destroy( true ); ${name} = ${current_block} && ${current_block}( ${params}, ${block.component} ); - if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} ); + if ( ${name} ) ${name}.mount( ${parentNode}, ${anchor} ); } ` ); } diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js index 052c4e58a7d5..9abb0fa19ec0 100644 --- a/src/generators/dom/visitors/Text.js +++ b/src/generators/dom/visitors/Text.js @@ -2,5 +2,5 @@ export default function visitText ( generator, block, state, node ) { if ( !node._state.shouldCreate ) return; - block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false ); + block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor ); } \ No newline at end of file diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index 1ed5eb2e7fe6..ef158af47d47 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -14,7 +14,7 @@ function create_main_fragment ( state, component ) { return { mount: function ( target, anchor ) { insertNode( if_block_anchor, target, anchor ); - if ( if_block ) if_block.mount( target, if_block_anchor ); + if ( if_block ) if_block.mount( target, null ); }, update: function ( changed, state ) { diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index fcdd72f55d02..c31e4ae91442 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -8,7 +8,7 @@ function create_main_fragment ( state, component ) { return { mount: function ( target, anchor ) { insertNode( if_block_anchor, target, anchor ); - if ( if_block ) if_block.mount( target, if_block_anchor ); + if ( if_block ) if_block.mount( target, null ); }, update: function ( changed, state ) { diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index e72585e6063d..792dac264e80 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -2,13 +2,12 @@ import { appendNode, assign, createComment, createElement, createText, detachNod function create_main_fragment ( state, component ) { var div = createElement( 'div' ); - var if_block_anchor = createComment(); - appendNode( if_block_anchor, div ); var if_block = state.a && create_if_block( state, component ); if ( if_block ) if_block.mount( div, null ); - appendNode( createText( "\n\n\t" ), div ); + var text = createText( "\n\n\t" ); + appendNode( text, div ); var p = createElement( 'p' ); appendNode( p, div ); appendNode( createText( "this can be used as an anchor" ), p ); @@ -17,15 +16,14 @@ function create_main_fragment ( state, component ) { var if_block_1 = state.b && create_if_block_1( state, component ); if ( if_block_1 ) if_block_1.mount( div, null ); - var if_block_1_anchor = createComment(); - appendNode( if_block_1_anchor, div ); - - appendNode( createText( "\n\n\t" ), div ); + var text_3 = createText( "\n\n\t" ); + appendNode( text_3, div ); var if_block_2 = state.c && create_if_block_2( state, component ); if ( if_block_2 ) if_block_2.mount( div, null ); - appendNode( createText( "\n\n\t" ), div ); + var text_4 = createText( "\n\n\t" ); + appendNode( text_4, div ); var p_1 = createElement( 'p' ); appendNode( p_1, div ); appendNode( createText( "so can this" ), p_1 ); @@ -34,7 +32,8 @@ function create_main_fragment ( state, component ) { var if_block_3 = state.d && create_if_block_3( state, component ); if ( if_block_3 ) if_block_3.mount( div, null ); - appendNode( createText( "\n\n\t" ), div ); + var text_7 = createText( "\n\n\t" ); + appendNode( text_7, div ); var text_8 = createText( "\n\n" ); var if_block_4_anchor = createComment(); @@ -45,14 +44,14 @@ function create_main_fragment ( state, component ) { insertNode( div, target, anchor ); insertNode( text_8, target, anchor ); insertNode( if_block_4_anchor, target, anchor ); - if ( if_block_4 ) if_block_4.mount( target, if_block_4_anchor ); + if ( if_block_4 ) if_block_4.mount( target, null ); }, update: function ( changed, state ) { if ( state.a ) { if ( !if_block ) { if_block = create_if_block( state, component ); - if_block.mount( if_block_anchor.parentNode, p ); + if_block.mount( div, text ); } } else if ( if_block ) { if_block.destroy( true ); @@ -62,7 +61,7 @@ function create_main_fragment ( state, component ) { if ( state.b ) { if ( !if_block_1 ) { if_block_1 = create_if_block_1( state, component ); - if_block_1.mount( if_block_1_anchor.parentNode, if_block_1_anchor ); + if_block_1.mount( div, text_3 ); } } else if ( if_block_1 ) { if_block_1.destroy( true ); @@ -72,7 +71,7 @@ function create_main_fragment ( state, component ) { if ( state.c ) { if ( !if_block_2 ) { if_block_2 = create_if_block_2( state, component ); - if_block_2.mount( if_block_2_anchor.parentNode, p_2 ); + if_block_2.mount( div, text_4 ); } } else if ( if_block_2 ) { if_block_2.destroy( true ); @@ -82,7 +81,7 @@ function create_main_fragment ( state, component ) { if ( state.d ) { if ( !if_block_3 ) { if_block_3 = create_if_block_3( state, component ); - if_block_3.mount( if_block_3_anchor.parentNode, null ); + if_block_3.mount( div, text_7 ); } } else if ( if_block_3 ) { if_block_3.destroy( true ); diff --git a/test/runtime/samples/if-block-widget/_config.js b/test/runtime/samples/if-block-widget/_config.js index 40d9d050e979..b82b4dfeb946 100644 --- a/test/runtime/samples/if-block-widget/_config.js +++ b/test/runtime/samples/if-block-widget/_config.js @@ -2,11 +2,26 @@ export default { data: { visible: true }, - html: 'before\n

Widget

\nafter', + + html: ` + before +

Widget

+ after + `, + test ( assert, component, target ) { component.set({ visible: false }); - assert.equal( target.innerHTML, 'before\n\nafter' ); + assert.htmlEqual( target.innerHTML, ` + before + + after + ` ); + component.set({ visible: true }); - assert.equal( target.innerHTML, 'before\n

Widget

\nafter' ); + assert.htmlEqual( target.innerHTML, ` + before +

Widget

+ after + ` ); } }; From 9480f349ec49c2411e74cedfb743068d50d860a8 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 14:05:34 -0400 Subject: [PATCH 3/6] anchor-less each blocks --- src/generators/dom/visitors/EachBlock.js | 35 ++++++++++++------- .../each-block-changed-check/expected.js | 10 ++---- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 7919cf1f3d76..bf7e63b4d065 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -9,13 +9,12 @@ export default function visitEachBlock ( generator, block, state, node ) { const iterations = block.getUniqueName( `${each_block}_iterations` ); const i = block.alias( `i` ); const params = block.params.join( ', ' ); - const anchor = block.getUniqueName( `${each_block}_anchor` ); + const anchor = node.needsAnchor ? block.getUniqueName( `${each_block}_anchor` ) : ( node.next && node.next._state.name ) || 'null'; const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor }; const { snippet } = block.contextualise( node.expression ); - block.createAnchor( anchor, state.parentNode ); block.builders.create.addLine( `var ${each_block_value} = ${snippet};` ); block.builders.create.addLine( `var ${iterations} = [];` ); @@ -30,11 +29,17 @@ export default function visitEachBlock ( generator, block, state, node ) { if ( isToplevel ) { block.builders.mount.addBlock( deindent` for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - ${iterations}[${i}].mount( ${block.target}, ${anchor} ); + ${iterations}[${i}].mount( ${block.target}, null ); } ` ); } + if ( node.needsAnchor ) { + block.createAnchor( anchor, state.parentNode ); + } else if ( node.next ) { + node.next.usedAsAnchor = true; + } + block.builders.destroy.addBlock( `${generator.helper( 'destroyEach' )}( ${iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` ); @@ -47,23 +52,25 @@ export default function visitEachBlock ( generator, block, state, node ) { block.builders.create.addBlock( deindent` if ( !${each_block_value}.length ) { ${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); - ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, ${anchor} );` : ''} + ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, null );` : ''} } ` ); block.builders.mount.addBlock( deindent` if ( ${each_block_else} ) { - ${each_block_else}.mount( ${state.parentNode || block.target}, ${anchor} ); + ${each_block_else}.mount( ${state.parentNode || block.target}, null ); } ` ); + const parentNode = state.parentNode || `${anchor}.parentNode`; + if ( node.else._block.hasUpdateMethod ) { block.builders.update.addBlock( deindent` if ( !${each_block_value}.length && ${each_block_else} ) { ${each_block_else}.update( changed, ${params} ); } else if ( !${each_block_value}.length ) { ${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); - ${each_block_else}.mount( ${anchor}.parentNode, ${anchor} ); + ${each_block_else}.mount( ${parentNode}, ${anchor} ); } else if ( ${each_block_else} ) { ${each_block_else}.destroy( true ); } @@ -74,7 +81,7 @@ export default function visitEachBlock ( generator, block, state, node ) { if ( ${each_block_else} ) ${each_block_else}.destroy( true ); } else if ( !${each_block_else} ) { ${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); - ${each_block_else}.mount( ${anchor}.parentNode, ${anchor} ); + ${each_block_else}.mount( ${parentNode}, ${anchor} ); } ` ); } @@ -118,7 +125,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea if ( state.parentNode ) { create.addLine( - `${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );` + `${iterations}[${i}].mount( ${state.parentNode}, null );` ); } @@ -135,6 +142,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ` : `${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];`; + const parentNode = state.parentNode || `${anchor}.parentNode`; + block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; var ${_iterations} = []; @@ -164,7 +173,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } } - ${anchor}.parentNode.insertBefore( ${fragment}, ${anchor} ); + ${parentNode}.insertBefore( ${fragment}, ${anchor} ); ${iterations} = ${_iterations}; ${lookup} = ${_lookup}; @@ -180,7 +189,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, if ( state.parentNode ) { create.addLine( - `${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );` + `${iterations}[${i}].mount( ${state.parentNode}, null );` ); } @@ -200,6 +209,8 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, .map( dependency => `'${dependency}' in changed` ) .join( ' || ' ); + const parentNode = state.parentNode || `${anchor}.parentNode`; + if ( condition !== '' ) { const forLoopBody = node._block.hasUpdateMethod ? deindent` @@ -207,12 +218,12 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); } else { ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); - ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); + ${iterations}[${i}].mount( ${parentNode}, ${anchor} ); } ` : deindent` ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); - ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); + ${iterations}[${i}].mount( ${parentNode}, ${anchor} ); `; const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`; diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 7b0f01698f12..40d2487c11c8 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -1,7 +1,6 @@ -import { appendNode, assign, createComment, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js"; +import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var each_block_anchor = createComment(); var each_block_value = state.comments; var each_block_iterations = []; @@ -17,10 +16,8 @@ function create_main_fragment ( state, component ) { return { mount: function ( target, anchor ) { - insertNode( each_block_anchor, target, anchor ); - for ( var i = 0; i < each_block_iterations.length; i += 1 ) { - each_block_iterations[i].mount( target, each_block_anchor ); + each_block_iterations[i].mount( target, null ); } insertNode( text, target, anchor ); @@ -36,7 +33,7 @@ function create_main_fragment ( state, component ) { each_block_iterations[i].update( changed, state, each_block_value, each_block_value[i], i ); } else { each_block_iterations[i] = create_each_block( state, each_block_value, each_block_value[i], i, component ); - each_block_iterations[i].mount( each_block_anchor.parentNode, each_block_anchor ); + each_block_iterations[i].mount( text.parentNode, text ); } } @@ -54,7 +51,6 @@ function create_main_fragment ( state, component ) { destroyEach( each_block_iterations, detach, 0 ); if ( detach ) { - detachNode( each_block_anchor ); detachNode( text ); detachNode( p ); } From 6587cbdbac8a63fc123d5b4768e1e77ccc135da0 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 14:07:55 -0400 Subject: [PATCH 4/6] yield blocks never need an anchor --- src/generators/dom/visitors/YieldTag.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/generators/dom/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js index 2ee310566919..aa95cd284e07 100644 --- a/src/generators/dom/visitors/YieldTag.js +++ b/src/generators/dom/visitors/YieldTag.js @@ -1,9 +1,6 @@ export default function visitYieldTag ( generator, block, state ) { - const anchor = `yield_anchor`; - block.createAnchor( anchor, state.parentNode ); - block.builders.mount.addLine( - `${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || block.target}, ${anchor} );` + `${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || block.target}, null );` ); block.builders.destroy.addLine( From 4cb1578147c83d8a5266dd1ba9609418733b466f Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 14:10:00 -0400 Subject: [PATCH 5/6] reduce indirection --- src/generators/dom/Block.js | 5 ----- src/generators/dom/visitors/EachBlock.js | 2 +- src/generators/dom/visitors/IfBlock.js | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js index e14d8591f4d1..aa023dfaaf94 100644 --- a/src/generators/dom/Block.js +++ b/src/generators/dom/Block.js @@ -78,11 +78,6 @@ export default class Block { return this.generator.contextualise( this, expression, context, isEventHandler ); } - createAnchor ( name, parentNode ) { - const renderStatement = `${this.generator.helper( 'createComment' )}()`; - this.addElement( name, renderStatement, parentNode, true ); - } - findDependencies ( expression ) { return this.generator.findDependencies( this.contextDependencies, this.indexes, expression ); } diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index bf7e63b4d065..049769132fb5 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -35,7 +35,7 @@ export default function visitEachBlock ( generator, block, state, node ) { } if ( node.needsAnchor ) { - block.createAnchor( anchor, state.parentNode ); + block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); } else if ( node.next ) { node.next.usedAsAnchor = true; } diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index d9fd1b81c74a..e6c3d9952a7e 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -47,7 +47,7 @@ export default function visitIfBlock ( generator, block, state, node ) { const vars = { name, anchor, params }; if ( node.needsAnchor ) { - block.createAnchor( anchor, state.parentNode ); + block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); } else if ( node.next ) { node.next.usedAsAnchor = true; } From 4fe20fb383d4265502b33757b0247d518fb730b9 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 25 Apr 2017 15:01:26 -0400 Subject: [PATCH 6/6] sanitize element names in preprocess step --- src/generators/dom/preprocess.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js index 6009f4dec827..8661dbb0fb85 100644 --- a/src/generators/dom/preprocess.js +++ b/src/generators/dom/preprocess.js @@ -174,7 +174,7 @@ const preprocessors = { if ( isComponent ) { node._state = getChildState( state ); } else { - const name = block.getUniqueName( node.name ); + const name = block.getUniqueName( node.name.replace( /[^a-zA-Z0-9_$]/g, '_' ) ); node._state = getChildState( state, { isTopLevel: false,