From 64f8e77999fce32aafee6506b68556c1751da759 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 28 Nov 2016 10:40:54 +0100 Subject: [PATCH] optimize generated code for if statements Instead of walking elseif statements recursively, collect all the conditions and blocks into a single if block --- compiler/generate/visitors/ElseBlock.js | 23 ---- compiler/generate/visitors/IfBlock.js | 166 ++++++++++-------------- compiler/generate/visitors/index.js | 2 - 3 files changed, 70 insertions(+), 121 deletions(-) delete mode 100644 compiler/generate/visitors/ElseBlock.js diff --git a/compiler/generate/visitors/ElseBlock.js b/compiler/generate/visitors/ElseBlock.js deleted file mode 100644 index 55f3d833d5..0000000000 --- a/compiler/generate/visitors/ElseBlock.js +++ /dev/null @@ -1,23 +0,0 @@ -import counter from '../utils/counter.js'; - -export default { - enter ( generator ) { - const name = generator.current.name.replace( 'If', 'Else' ); - - generator.push({ - name, - localElementDepth: 0, - - initStatements: [], - updateStatements: [], - teardownStatements: [], - - counter: counter() - }); - }, - - leave ( generator ) { - generator.addRenderer( generator.current ); - generator.pop(); - } -}; diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 298c2e3457..db6de77729 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -1,125 +1,99 @@ import deindent from '../utils/deindent.js'; import counter from '../utils/counter.js'; +// collect all the conditions and blocks in the if/elseif/else chain +function generateBlock ( generator, node, name ) { + // walk the children here + generator.push({ + useAnchor: true, + name, + target: 'target', + localElementDepth: 0, + + initStatements: [], + 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}`; + + const conditionsAndBlocks = [{ + condition: generator.contextualise( node.expression ).snippet, + block: name + }]; + generateBlock( generator, node, name ); + + if ( node.else && node.else.children.length === 1 && + node.else.children[0].type === 'IfBlock' ) { + conditionsAndBlocks.push( + ...getConditionsAndBlocks( generator, node.else.children[0], _name, i + 1 ) ); + } else { + const name = `${_name}_${i + 1}`; + conditionsAndBlocks.push({ + condition: null, + block: node.else ? name : null, + }); + if (node.else) { + generateBlock( generator, node.else, name ); + } + } + return conditionsAndBlocks; +} + export default { enter ( generator, node ) { const i = generator.counters.if++; - const name = `ifBlock_${i}`; - const renderer = `renderIfBlock_${i}`; - const elseName = `elseBlock_${i}`; - const elseRenderer = `renderElseBlock_${i}`; + const { params, target } = generator.current; + const name = `ifBlock_${i}`; + const getBlock = `getBlock_${i}`; + const currentBlock = `currentBlock_${i}`; - generator.addSourcemapLocations( node.expression ); - const { snippet } = generator.contextualise( node.expression ); + const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); generator.current.initStatements.push( deindent` var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} ); ${generator.appendToTarget( `${name}_anchor` )}; - ` ); - if ( node.else ) { - generator.current.initStatements.push( deindent` - var ${name} = null; - var ${elseName} = null; - - if ( ${snippet} ) { - ${name} = ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); - } else { - ${elseName} = ${elseRenderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); - } - ` ); - } else { - generator.current.initStatements.push( deindent` - var ${name} = ${snippet} ? ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ) : null; - ` ); - } - - const ifTrue = [ deindent` - if ( !${name } ) { - ${name} = ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); - } else { - ${name}.update( changed, ${generator.current.params} ); - } - ` ]; - - if ( node.else ) { - ifTrue.push( deindent` - if ( ${elseName } ) { - ${elseName}.teardown( true ); - ${elseName} = null; - } - ` ); - } - - const ifFalse = [ deindent` - if ( ${name} ) { - ${name}.teardown( true ); - ${name} = null; + function ${getBlock} ( ${params} ) { + ${conditionsAndBlocks.map( ({ condition, block }) => { + return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`; + } ).join( '\n' )} } - ` ]; - - if ( node.else ) { - ifFalse.push( deindent` - if ( !${elseName } ) { - ${elseName} = ${elseRenderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); - } else { - ${elseName}.update( changed, ${generator.current.params} ); - } - ` ); - } - let update = deindent` - if ( ${snippet} ) { - ${ifTrue.join( '\n\n' )} - } + var ${currentBlock} = ${getBlock}( ${params} ); + var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${name}_anchor ); + ` ); - else { - ${ifFalse.join( '\n\n' )} + generator.current.updateStatements.push( deindent` + var _${currentBlock} = ${currentBlock}; + ${currentBlock} = ${getBlock}( ${params} ); + if ( _${currentBlock} === ${currentBlock} && ${name}) { + ${name}.update( changed, ${params} ); + } else { + if ( ${name} ) ${name}.teardown( true ); + ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${name}_anchor ); } - `; - - if ( node.else ) { - update += `\nif ( ${elseName} ) ${elseName}.update( changed, ${generator.current.params} );`; - } - - generator.current.updateStatements.push( update ); + ` ); const teardownStatements = [ `if ( ${name} ) ${name}.teardown( detach );` ]; - if ( node.else ) { - teardownStatements.push( `if ( ${elseName} ) ${elseName}.teardown( detach );` ); - } - if ( generator.current.localElementDepth === 0 ) { teardownStatements.push( `if ( detach ) ${name}_anchor.parentNode.removeChild( ${name}_anchor );` ); } generator.current.teardownStatements.push( teardownStatements.join( '\n' ) ); - - generator.push({ - useAnchor: true, - name: renderer, - target: 'target', - localElementDepth: 0, - - initStatements: [], - updateStatements: [], - teardownStatements: [], - - counter: counter() - }); - }, - - leave ( generator, node ) { - generator.addRenderer( generator.current ); - - if ( node.else ) { - generator.visit( node.else ); - } - - generator.pop(); } }; diff --git a/compiler/generate/visitors/index.js b/compiler/generate/visitors/index.js index 0d3005366a..064669762f 100644 --- a/compiler/generate/visitors/index.js +++ b/compiler/generate/visitors/index.js @@ -1,7 +1,6 @@ import Comment from './Comment.js'; import EachBlock from './EachBlock.js'; import Element from './Element.js'; -import ElseBlock from './ElseBlock.js'; import IfBlock from './IfBlock.js'; import MustacheTag from './MustacheTag.js'; import Text from './Text.js'; @@ -10,7 +9,6 @@ export default { Comment, EachBlock, Element, - ElseBlock, IfBlock, MustacheTag, Text