From a80dcda2e02725f829ed198310cf5facf6eb7335 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Sat, 3 Dec 2016 10:11:55 +0100 Subject: [PATCH 1/2] separate create from mount --- compiler/generate/index.js | 44 ++++++++++++++----- compiler/generate/visitors/EachBlock.js | 39 +++++++++------- compiler/generate/visitors/Element.js | 12 +++-- compiler/generate/visitors/IfBlock.js | 22 +++++++--- .../each-block-random-permute/_config.js | 33 ++++++++++++++ .../each-block-random-permute/main.html | 3 ++ 6 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 test/compiler/each-block-random-permute/_config.js create mode 100644 test/compiler/each-block-random-permute/main.html diff --git a/compiler/generate/index.js b/compiler/generate/index.js index f73b4a90b3..3d67f89213 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -15,28 +15,34 @@ export default function generate ( parsed, source, options ) { const generator = { addElement ( name, renderStatement, needsIdentifier = false ) { - const needsTeardown = generator.current.localElementDepth === 0; - if ( needsIdentifier || needsTeardown ) { + const isToplevel = generator.current.localElementDepth === 0; + if ( needsIdentifier || isToplevel ) { generator.current.initStatements.push( deindent` var ${name} = ${renderStatement}; - ${generator.appendToTarget( name )}; ` ); + generator.createMountStatement( name ); } else { generator.current.initStatements.push( deindent` ${generator.current.target}.appendChild( ${renderStatement} ); ` ); } - if ( needsTeardown ) { + if ( isToplevel ) { generator.current.teardownStatements.push( deindent` if ( detach ) ${name}.parentNode.removeChild( ${name} ); ` ); } }, - appendToTarget ( name ) { - if ( generator.current.useAnchor && generator.current.target === 'target' ) { - return `anchor.parentNode.insertBefore( ${name}, anchor )`; + + createMountStatement ( name ) { + if ( generator.current.target === 'target' ) { + generator.current.mountStatements.push( deindent` + target.insertBefore( ${name}, anchor ); + ` ); + } else { + generator.current.initStatements.push( deindent` + ${generator.current.target}.appendChild( ${name} ); + ` ); } - return `${generator.current.target}.appendChild( ${name} )`; }, addRenderer ( fragment ) { @@ -45,10 +51,17 @@ export default function generate ( parsed, source, options ) { } renderers.push( deindent` - function ${fragment.name} ( ${fragment.params}, component, target${fragment.useAnchor ? ', anchor' : ''} ) { + function ${fragment.name} ( ${fragment.params}, component ) { + var target, anchor; ${fragment.initStatements.join( '\n\n' )} return { + mount: function ( _target, _anchor ) { + target = _target; + anchor = _anchor; + ${fragment.mountStatements.join( '\n\n' )} + }, + update: function ( changed, ${fragment.params} ) { ${fragment.updateStatements.join( '\n\n' )} }, @@ -236,6 +249,7 @@ export default function generate ( parsed, source, options ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], @@ -364,13 +378,17 @@ export default function generate ( parsed, source, options ) { if ( generator.hasComplexBindings ) { initStatements.push( deindent` this.__bindings = []; - var mainFragment = renderMainFragment( state, this, options.target ); + var mainFragment = renderMainFragment( state, this ); + if ( options.target ) this.mount( options.target ); while ( this.__bindings.length ) this.__bindings.pop()(); ` ); setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` ); } else { - initStatements.push( `var mainFragment = renderMainFragment( state, this, options.target );` ); + initStatements.push( deindent` + var mainFragment = renderMainFragment( state, this ); + if ( options.target ) this.mount( options.target ); + ` ); } if ( generator.hasComponents ) { @@ -449,6 +467,10 @@ export default function generate ( parsed, source, options ) { ${setStatements.join( '\n\n' )} }; + this.mount = function mount ( target, anchor ) { + mainFragment.mount( target, anchor ); + } + this.observe = function ( key, callback, options ) { var group = ( options && options.defer ) ? observers.deferred : observers.immediate; diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index c20e9b8320..1327c1d120 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -6,10 +6,13 @@ export default { const i = generator.counters.each++; const name = `eachBlock_${i}`; const anchor = `${name}_anchor`; + const iterations = `${name}_iterations`; const renderer = `renderEachBlock_${i}`; const listName = `${name}_value`; + const isToplevel = generator.current.localElementDepth === 0; + generator.addSourcemapLocations( node.expression ); const { dependencies, snippet } = generator.contextualise( node.expression ); @@ -18,39 +21,44 @@ export default { generator.current.initStatements.push( deindent` var ${name}_value = ${snippet}; - var ${name}_fragment = document.createDocumentFragment(); - var ${name}_iterations = []; + var ${iterations} = []; for ( var i = 0; i < ${name}_value.length; i += 1 ) { - ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component ); + ${!isToplevel ? `${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );` : ''} } - - ${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); ` ); + if ( isToplevel ) { + generator.current.mountStatements.push( deindent` + for ( var i = 0; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); + } + ` ); + } + generator.current.updateStatements.push( deindent` var ${name}_value = ${snippet}; for ( var i = 0; i < ${name}_value.length; i += 1 ) { - if ( !${name}_iterations[i] ) { - ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + if ( !${iterations}[i] ) { + ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component ); + ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); } else { - ${name}_iterations[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); + ${iterations}[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); } } - for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { - ${name}_iterations[i].teardown( true ); + for ( var i = ${name}_value.length; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].teardown( true ); } - ${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); - ${name}_iterations.length = ${listName}.length; + ${iterations}.length = ${listName}.length; ` ); - const needsTeardown = generator.current.localElementDepth === 0; generator.current.teardownStatements.push( deindent` - for ( var i = 0; i < ${name}_iterations.length; i += 1 ) { - ${name}_iterations[i].teardown( ${needsTeardown ? 'detach' : 'false'} ); + for ( var i = 0; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].teardown( ${isToplevel ? 'detach' : 'false'} ); } ` ); @@ -88,6 +96,7 @@ export default { params, initStatements: [], + mountStatements: [], updateStatements: [ Object.keys( contexts ).map( contextName => { const listName = listNames[ contextName ]; const indexName = indexNames[ contextName ]; diff --git a/compiler/generate/visitors/Element.js b/compiler/generate/visitors/Element.js index 8f5bf5f861..67b4f6af5f 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -15,6 +15,7 @@ export default { allUsedContexts: new Set(), init: [], + mount: [], update: [], teardown: [] }; @@ -57,7 +58,7 @@ export default { ${statements.join( '\n\n' )} var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, + target: ${!shouldDetach ? generator.current.target: 'null'}, parent: component, data: ${name}_initialData }); @@ -65,11 +66,14 @@ export default { } else { local.init.unshift( deindent` var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, + target: ${!shouldDetach ? generator.current.target: 'null'}, parent: component }); ` ); } + if ( shouldDetach ) { + local.mount.unshift( `${name}.mount( target, anchor );` ); + } if ( local.dynamicAttributes.length ) { const updates = local.dynamicAttributes.map( attribute => { @@ -146,6 +150,7 @@ export default { generator.current.initStatements.push( local.init.join( '\n' ) ); if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) ); + if ( local.mount.length ) generator.current.mountStatements.push( local.mount.join( '\n' ) ); generator.current.teardownStatements.push( local.teardown.join( '\n' ) ); generator.push({ @@ -166,7 +171,6 @@ export default { if ( isComponent ) return; - generator.current.initStatements.push( - generator.appendToTarget( name ) ); + generator.createMountStatement( name ); } }; diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 76ab5255e5..6a3d5d2467 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -11,18 +11,19 @@ function generateBlock ( generator, node, name ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], counter: counter() }); node.children.forEach( generator.visit ); - //generator.visit( node.children ); generator.addRenderer( generator.current ); generator.pop(); // unset the children, to avoid them being visited again node.children = []; } + function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { generator.addSourcemapLocations( node.expression ); const name = `${_name}_${i}`; @@ -54,12 +55,13 @@ export default { enter ( generator, node ) { const i = generator.counters.if++; - const { params, target } = generator.current; + const { params } = generator.current; const name = `ifBlock_${i}`; const anchor = `${name}_anchor`; const getBlock = `getBlock_${i}`; const currentBlock = `currentBlock_${i}`; + const isToplevel = generator.current.localElementDepth === 0; const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); generator.addElement( anchor, `document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} )`, true ); @@ -72,9 +74,16 @@ export default { } var ${currentBlock} = ${getBlock}( ${params} ); - var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); + var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component ); ` ); + const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`; + if ( isToplevel ) { + generator.current.mountStatements.push( mountStatement ); + } else { + generator.current.initStatements.push( mountStatement ); + } + generator.current.updateStatements.push( deindent` var _${currentBlock} = ${currentBlock}; ${currentBlock} = ${getBlock}( ${params} ); @@ -82,10 +91,13 @@ export default { ${name}.update( changed, ${params} ); } else { if ( ${name} ) ${name}.teardown( true ); - ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); + ${name} = ${currentBlock} && ${currentBlock}( ${params}, component ); + if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} ); } ` ); - generator.current.teardownStatements.push( `if ( ${name} ) ${name}.teardown( detach );` ); + generator.current.teardownStatements.push( deindent` + if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} ); + ` ); } }; diff --git a/test/compiler/each-block-random-permute/_config.js b/test/compiler/each-block-random-permute/_config.js new file mode 100644 index 0000000000..14c667091b --- /dev/null +++ b/test/compiler/each-block-random-permute/_config.js @@ -0,0 +1,33 @@ +const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' ); + +function permute () { + const values = VALUES.slice(); + const number = Math.floor(Math.random() * VALUES.length); + const permuted = []; + for (let i = 0; i < number; i++) { + permuted.push( ...values.splice( Math.floor( Math.random() * ( number - i ) ), 1 ) ); + } + + return { + data: permuted, + expected: permuted.length ? `(${permuted.join(')(')})` : '' + }; +} + +let step = permute(); + +export default { + data: { + values: step.data + }, + + html: step.expected, + + test ( assert, component, target ) { + for (let i = 0; i < 100; i++) { + step = permute(); + component.set({ values: step.data }); + assert.htmlEqual( target.innerHTML, step.expected ); + } + } +}; diff --git a/test/compiler/each-block-random-permute/main.html b/test/compiler/each-block-random-permute/main.html new file mode 100644 index 0000000000..8aef076d0e --- /dev/null +++ b/test/compiler/each-block-random-permute/main.html @@ -0,0 +1,3 @@ +{{#each values as value}} + ({{value}}) +{{/each}} From 9463f5fe54b898d97af84f7e685ccb4898a923af Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sun, 4 Dec 2016 09:34:45 -0500 Subject: [PATCH 2/2] keep target and anchor inside mount method --- compiler/generate/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/generate/index.js b/compiler/generate/index.js index 7887b94742..ead297fa2e 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -59,13 +59,10 @@ export default function generate ( parsed, source, options ) { renderers.push( deindent` function ${fragment.name} ( ${fragment.params}, component ) { - var target, anchor; ${fragment.initStatements.join( '\n\n' )} return { - mount: function ( _target, _anchor ) { - target = _target; - anchor = _anchor; + mount: function ( target, anchor ) { ${fragment.mountStatements.join( '\n\n' )} },