From 162281ef4f2d280d25c849ac21f4d09059e167fd Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 19 Apr 2017 12:39:01 -0400 Subject: [PATCH] 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 1309c4ddb2..173b987aa1 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 f8ab06ddc3..320724c332 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 c54945338d..6430c4f07c 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 dbb1d2c085..7919cf1f3d 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 7f55d84ec9..001c8a52e2 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 1f1800b536..593a7ca986 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 301345e6cc..568f745da5 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 2eed5bfdb6..7a39643150 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 923175e5ab..052c4e58a7 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 0000000000..e72585e606 --- /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 0000000000..c55e7bd4de --- /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