From b574c405eba0f34631cda3e422f460302e2194ab Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Mon, 17 Apr 2017 10:43:44 -0400 Subject: [PATCH] conditionalise each-block updates --- src/generators/dom/Block.js | 53 ++++----- src/generators/dom/preprocess.js | 16 ++- src/generators/dom/visitors/EachBlock.js | 109 ++++++++---------- src/generators/dom/visitors/IfBlock.js | 21 ++-- .../each-block-changed-check/expected.js | 6 +- 5 files changed, 92 insertions(+), 113 deletions(-) diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js index 0b2b1760fd..652262c512 100644 --- a/src/generators/dom/Block.js +++ b/src/generators/dom/Block.js @@ -2,34 +2,23 @@ import CodeBuilder from '../../utils/CodeBuilder.js'; import deindent from '../../utils/deindent.js'; export default class Block { - constructor ({ - generator, - name, - key, - expression, - context, - contextDependencies, - dependencies, - contexts, - indexes, - params, - indexNames, - listNames - }) { - this.generator = generator; - this.name = name; - this.key = key; - this.expression = expression; - this.context = context; - - this.contexts = contexts; - this.indexes = indexes; - this.contextDependencies = contextDependencies; - this.dependencies = dependencies; - - this.params = params; - this.indexNames = indexNames; - this.listNames = listNames; + constructor ( options ) { + this.generator = options.generator; + this.name = options.name; + this.key = options.key; + this.expression = options.expression; + this.context = options.context; + + this.contexts = options.contexts; + this.indexes = options.indexes; + this.contextDependencies = options.contextDependencies; + this.dependencies = new Set(); + + this.params = options.params; + this.indexNames = options.indexNames; + this.listNames = options.listNames; + + this.listName = options.listName; this.builders = { create: new CodeBuilder(), @@ -40,13 +29,19 @@ export default class Block { destroy: new CodeBuilder() }; - this.getUniqueName = generator.getUniqueNameMaker( params ); + this.getUniqueName = this.generator.getUniqueNameMaker( options.params ); // unique names this.component = this.getUniqueName( 'component' ); this.target = this.getUniqueName( 'target' ); } + addDependencies ( dependencies ) { + dependencies.forEach( dependency => { + this.dependencies.add( dependency ); + }); + } + addElement ( name, renderStatement, parentNode, needsIdentifier = false ) { const isToplevel = !parentNode; if ( needsIdentifier || isToplevel ) { diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js index 6c20b844b8..62ee632a62 100644 --- a/src/generators/dom/preprocess.js +++ b/src/generators/dom/preprocess.js @@ -7,37 +7,39 @@ function isElseIf ( node ) { const preprocessors = { MustacheTag: ( generator, block, node ) => { const { dependencies } = block.contextualise( node.expression ); - dependencies.forEach( dependency => { - block.dependencies.add( dependency ); - }); + block.addDependencies( dependencies ); }, IfBlock: ( generator, block, node ) => { function attachBlocks ( node ) { const { dependencies } = block.contextualise( node.expression ); + block.addDependencies( dependencies ); node._block = block.child({ name: generator.getUniqueName( `create_if_block` ) }); preprocessChildren( generator, node._block, node.children ); + block.addDependencies( node._block.dependencies ); if ( isElseIf( node.else ) ) { - attachBlocks( node.else ); + attachBlocks( node.else.children[0] ); } else if ( node.else ) { node.else._block = block.child({ name: generator.getUniqueName( `create_if_block` ) }); preprocessChildren( generator, node.else._block, node.else.children ); + block.addDependencies( node.else._block.dependencies ); } } - attachBlocks ( node ); + attachBlocks( node ); }, EachBlock: ( generator, block, node ) => { const { dependencies } = block.contextualise( node.expression ); + block.addDependencies( dependencies ); const indexNames = new Map( block.indexNames ); const indexName = node.index || block.getUniqueName( `${node.context}_index` ); @@ -67,12 +69,16 @@ const preprocessors = { contexts, indexes, + listName, + indexName, + indexNames, listNames, params: block.params.concat( listName, context, indexName ) }); preprocessChildren( generator, node._block, node.children ); + block.addDependencies( node._block.dependencies ); }, Element: ( generator, block, node ) => { diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index dce874c7fd..8ce11b11de 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -7,18 +7,18 @@ export default function visitEachBlock ( generator, block, state, node ) { const each_block_else = generator.getUniqueName( `${each_block}_else` ); const create_each_block = node._block.name; const create_each_block_else = generator.getUniqueName( `${create_each_block}_else` ); - const listName = block.getUniqueName( `${each_block}_value` ); + const each_block_value = node._block.listName; const iterations = block.getUniqueName( `${each_block}_iterations` ); const i = block.getUniqueName( `i` ); const params = block.params.join( ', ' ); const anchor = block.getUniqueName( `${each_block}_anchor` ); - const vars = { each_block, create_each_block, listName, iterations, i, params, anchor }; + const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor }; - const { dependencies, snippet } = block.contextualise( node.expression ); + const { snippet } = block.contextualise( node.expression ); block.createAnchor( anchor, state.parentNode ); - block.builders.create.addLine( `var ${listName} = ${snippet};` ); + block.builders.create.addLine( `var ${each_block_value} = ${snippet};` ); block.builders.create.addLine( `var ${iterations} = [];` ); if ( node.key ) { @@ -45,7 +45,7 @@ export default function visitEachBlock ( generator, block, state, node ) { // TODO neaten this up... will end up with an empty line in the block block.builders.create.addBlock( deindent` - if ( !${listName}.length ) { + if ( !${each_block_value}.length ) { ${each_block_else} = ${create_each_block_else}( ${params}, ${block.component} ); ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, ${anchor} );` : ''} } @@ -58,9 +58,9 @@ export default function visitEachBlock ( generator, block, state, node ) { ` ); block.builders.update.addBlock( deindent` - if ( !${listName}.length && ${each_block_else} ) { + if ( !${each_block_value}.length && ${each_block_else} ) { ${each_block_else}.update( changed, ${params} ); - } else if ( !${listName}.length ) { + } else if ( !${each_block_value}.length ) { ${each_block_else} = ${create_each_block_else}( ${params}, ${block.component} ); ${each_block_else}.mount( ${anchor}.parentNode, ${anchor} ); } else if ( ${each_block_else} ) { @@ -75,37 +75,6 @@ export default function visitEachBlock ( generator, block, state, node ) { ` ); } - // const indexNames = new Map( block.indexNames ); - // const indexName = node.index || block.getUniqueName( `${node.context}_index` ); - // indexNames.set( node.context, indexName ); - - // const listNames = new Map( block.listNames ); - // listNames.set( node.context, listName ); - - // const context = generator.getUniqueName( node.context ); - // const contexts = new Map( block.contexts ); - // contexts.set( node.context, context ); - - // const indexes = new Map( block.indexes ); - // if ( node.index ) indexes.set( indexName, node.context ); - - // const contextDependencies = new Map( block.contextDependencies ); - // contextDependencies.set( node.context, dependencies ); - - // const childBlock = block.child({ - // name: vars.create_each_block, - // expression: node.expression, - // context: node.context, - // key: node.key, - - // contextDependencies, - // contexts, - // indexes, - - // indexNames, - // listNames, - // params: block.params.concat( listName, context, indexName ) - // }); const childBlock = node._block; const childState = Object.assign( {}, state, { @@ -132,7 +101,7 @@ export default function visitEachBlock ( generator, block, state, node ) { } } -function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, listName, iterations, i, params, anchor } ) { +function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor } ) { const fragment = block.getUniqueName( 'fragment' ); const value = block.getUniqueName( 'value' ); const key = block.getUniqueName( 'key' ); @@ -146,8 +115,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea const create = new CodeBuilder(); create.addBlock( deindent` - var ${key} = ${listName}[${i}].${node.key}; - ${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } ); + var ${key} = ${each_block_value}[${i}].${node.key}; + ${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } ); ` ); if ( state.parentNode ) { @@ -157,28 +126,28 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } block.builders.create.addBlock( deindent` - for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { ${create} } ` ); block.builders.update.addBlock( deindent` - var ${listName} = ${snippet}; + var ${each_block_value} = ${snippet}; var ${_iterations} = []; var ${_lookup} = Object.create( null ); var ${fragment} = document.createDocumentFragment(); // create new iterations as necessary - for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { - var ${value} = ${listName}[${i}]; + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { + var ${value} = ${each_block_value}[${i}]; var ${key} = ${value}.${node.key}; if ( ${lookup}[ ${key} ] ) { ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ]; - ${_lookup}[ ${key} ].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} ); + ${_lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); } else { - ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } ); + ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } ); } ${_iterations}[${i}].mount( ${fragment}, null ); @@ -199,11 +168,11 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ` ); } -function unkeyed ( generator, block, state, node, snippet, { create_each_block, listName, iterations, i, params, anchor } ) { +function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor } ) { const create = new CodeBuilder(); create.addLine( - `${iterations}[${i}] = ${create_each_block}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} );` + `${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );` ); if ( state.parentNode ) { @@ -213,25 +182,39 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, } block.builders.create.addBlock( deindent` - for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { ${create} } ` ); - block.builders.update.addBlock( deindent` - var ${listName} = ${snippet}; + const { dependencies } = block.contextualise( node.expression ); + const allDependencies = new Set( block.dependencies ); + dependencies.forEach( dependency => { + allDependencies.add( dependency ); + }); - for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { - if ( !${iterations}[${i}] ) { - ${iterations}[${i}] = ${create_each_block}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} ); - ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); - } else { - ${iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} ); - } - } + const condition = Array.from( allDependencies ) + .map( dependency => `'${dependency}' in changed` ) + .join( ' || ' ); - ${generator.helper( 'destroyEach' )}( ${iterations}, true, ${listName}.length ); + if ( condition !== '' ) { + block.builders.update.addBlock( deindent` + var ${each_block_value} = ${snippet}; - ${iterations}.length = ${listName}.length; - ` ); + if ( ${condition} ) { + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { + if ( !${iterations}[${i}] ) { + ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); + ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); + } else { + ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); + } + } + + ${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length ); + + ${iterations}.length = ${each_block_value}.length; + } + ` ); + } } \ No newline at end of file diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index b3f6e2214c..5ff5f0565d 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -5,39 +5,34 @@ function isElseIf ( node ) { return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; } -function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) { - const name = generator.getUniqueName( `${_name}_${i}` ); - +function getConditionsAndBlocks ( generator, block, state, node ) { const conditionsAndBlocks = [{ condition: block.contextualise( node.expression ).snippet, - block: name + block: node._block.name }]; - generateBlock( generator, block, state, node, name ); + generateBlock( generator, block, state, node ); if ( isElseIf( node.else ) ) { conditionsAndBlocks.push( - ...getConditionsAndBlocks( generator, block, state, node.else.children[0], _name, i + 1 ) + ...getConditionsAndBlocks( generator, block, state, node.else.children[0] ) ); } else { - const name = generator.getUniqueName( `${_name}_${i + 1}` ); conditionsAndBlocks.push({ condition: null, - block: node.else ? name : null, + block: node.else ? node.else._block.name : null, }); if ( node.else ) { - generateBlock( generator, block, state, node.else, name ); + generateBlock( generator, block, state, node.else ); } } return conditionsAndBlocks; } -function generateBlock ( generator, block, state, node, name ) { - const childBlock = block.child({ - name - }); +function generateBlock ( generator, block, state, node ) { + const childBlock = node._block; const childState = Object.assign( {}, state, { parentNode: null diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index d2140d8338..8b3edeb87e 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -14,14 +14,14 @@ function create_main_fragment ( root, component ) { insertNode( each_block_anchor, target, anchor ); for ( var i = 0; i < each_block_iterations.length; i += 1 ) { - each_block_iterations[i].mount( each_block_anchor.parentNode, each_block_anchor ); + each_block_iterations[i].mount( target, each_block_anchor ); } }, update: function ( changed, root ) { - if ( 'comments' in changed || 'time' in changed ) { - var each_block_value = root.comments; + var each_block_value = root.comments; + if ( 'comments' in changed || 'elapsed' in changed || 'time' in changed ) { for ( var i = 0; i < each_block_value.length; i += 1 ) { if ( !each_block_iterations[i] ) { each_block_iterations[i] = create_each_block( root, each_block_value, each_block_value[i], i, component );