From b91ae93cae0eca256a37f8e1eaff40c4e2b06668 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Thu, 4 May 2017 22:06:40 -0400 Subject: [PATCH 01/12] update keyed each-block outro test to check div order --- .../samples/transition-js-each-block-keyed-outro/_config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js index d40e9a3a068f..567a671d2c44 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js @@ -17,6 +17,11 @@ export default { ] }); + const divs2 = target.querySelectorAll( 'div' ); + assert.strictEqual( divs[0], divs2[0] ); + assert.strictEqual( divs[1], divs2[1] ); + assert.strictEqual( divs[2], divs2[2] ); + raf.tick( 50 ); assert.equal( divs[0].foo, undefined ); assert.equal( divs[1].foo, 0.5 ); From 08f7321d691383d29647ed77c45b57cc909a2960 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Thu, 4 May 2017 23:39:13 -0400 Subject: [PATCH 02/12] create start anchors for each-blocks that need them --- src/generators/dom/Block.js | 9 ++++++++- src/generators/dom/visitors/EachBlock.js | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js index f90575481505..83841b738d6d 100644 --- a/src/generators/dom/Block.js +++ b/src/generators/dom/Block.js @@ -5,10 +5,13 @@ export default class Block { constructor ( options ) { this.generator = options.generator; this.name = options.name; - this.key = options.key; this.expression = options.expression; this.context = options.context; + // for keyed each blocks + this.key = options.key; + this.first = null; + this.contexts = options.contexts; this.indexes = options.indexes; this.contextDependencies = options.contextDependencies; @@ -155,6 +158,10 @@ export default class Block { properties.addBlock( `key: ${localKey},` ); } + if ( this.first ) { + properties.addBlock( `first: ${this.first},` ); + } + if ( this.builders.mount.isEmpty() ) { properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` ); } else { diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 14c4a4297e50..c21ea5b2d073 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -118,6 +118,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea const iteration = block.getUniqueName( `${each_block}_iteration` ); const _iterations = block.getUniqueName( `_${each_block}_iterations` ); + if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw + node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing + } else { + node._block.first = node._block.getUniqueName( 'first' ); + node._block.addElement( node._block.first, `${generator.helper( 'createComment' )}()`, null, true ); + } + block.builders.create.addBlock( deindent` var ${lookup} = Object.create( null ); From 24c4a7c9f033ce6f94397b826c0afe3b6d30e2b7 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 5 May 2017 00:25:33 -0400 Subject: [PATCH 03/12] mostly working list diffing algorithm --- src/generators/dom/visitors/EachBlock.js | 84 +++++++++++++----------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index c21ea5b2d073..9ad173a01aa3 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -110,8 +110,6 @@ export default function visitEachBlock ( generator, block, state, node ) { } function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) { - const fragment = block.getUniqueName( 'fragment' ); - const value = block.getUniqueName( 'value' ); const key = block.getUniqueName( 'key' ); const lookup = block.getUniqueName( `${each_block}_lookup` ); const keys = block.getUniqueName( `${each_block}_keys` ); @@ -135,66 +133,72 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } ` ); - const consequent = node._block.hasUpdateMethod ? - deindent` - ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ]; - ${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); - ` : - `${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ];`; - + const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; - const hasIntros = node._block.hasIntroMethod; - - const destroy = node._block.hasOutroMethod ? - deindent` - function outro ( key ) { + let destroy; + if ( node._block.hasOutroMethod ) { + const outro = block.getUniqueName( `${each_block}_outro` ); + block.builders.create.addBlock( deindent` + function ${outro} ( key ) { ${lookup}[ key ].outro( function () { ${lookup}[ key ].destroy( true ); ${lookup}[ key ] = null; }); } + ` ); - for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - ${key} = ${iterations}[${i}].key; - if ( !${keys}[ ${key} ] ) outro( ${key} ); - } - ` : - deindent` - for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - var ${iteration} = ${iterations}[${i}]; - if ( !${keys}[ ${iteration}.key ] ) ${iteration}.destroy( true ); - } - `; + destroy = `${outro}( ${key} );`; + } else { + destroy = `${iteration}.destroy( true );`; + } block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; - var ${_iterations} = []; + var ${_iterations} = Array( ${each_block_value}.length ); var ${keys} = Object.create( null ); - var ${fragment} = document.createDocumentFragment(); + var index_by_key = Object.create( null ); + var key_by_index = Array( ${each_block_value}.length ); - // create new iterations as necessary - for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { - var ${value} = ${each_block_value}[${i}]; - var ${key} = ${value}.${node.key}; - ${keys}[ ${key} ] = true; + var new_iterations = []; + + for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { + var ${key} = ${each_block_value}[${i}].${node.key}; + index_by_key[${key}] = ${i}; + key_by_index[${i}] = ${key}; if ( ${lookup}[ ${key} ] ) { - ${consequent} - ${hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`} + // TODO this is an empty branch for non-dynamic blocks + ${dynamic && `${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`} } else { - ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - ${hasIntros && `${_iterations}[${i}].intro( ${fragment}, null );`} + ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + new_iterations.push( ${lookup}[ ${key} ] ); } - ${!hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`} + ${_iterations}[${i}] = ${lookup}[ ${key} ]; + } + + // TODO group consecutive runs into fragments? + ${i} = new_iterations.length; + while ( ${i}-- ) { + ${iteration} = new_iterations[${i}]; + var index = index_by_key[${iteration}.key]; + var next_sibling_key = key_by_index[index + 1]; + ${iteration}.${mountOrIntro}( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first ); } - // remove old iterations - ${destroy} + for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + var ${iteration} = ${iterations}[${i}]; + var index = index_by_key[${iteration}.key]; - ${parentNode}.insertBefore( ${fragment}, ${anchor} ); + if ( index === undefined ) { + ${destroy} + } else { + var next_sibling_key = key_by_index[index + 1]; + ${iteration}.mount( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first ); + } + } ${iterations} = ${_iterations}; ` ); From 776b68ff71d00b5520577bd003b6f485c6c7e2e0 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 5 May 2017 12:10:37 -0400 Subject: [PATCH 04/12] fix bug in assert.htmlEqual --- test/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helpers.js b/test/helpers.js index 4318d6d3644e..64b4814e80d8 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -86,6 +86,7 @@ function cleanChildren ( node ) { previous.data = previous.data.replace( /\s{2,}/, '\n' ); node.removeChild( child ); + child = previous; } } From c9dba817fbcc0c42f619ac73bc6b6f070fc842e8 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 5 May 2017 16:50:10 -0400 Subject: [PATCH 05/12] another crack at the algorithm. outros not currently applied --- src/generators/dom/visitors/EachBlock.js | 90 +++++++++++++------ .../_config.js | 48 ++++++++++ .../each-block-keyed-random-permute/main.html | 3 + 3 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 test/runtime/samples/each-block-keyed-random-permute/_config.js create mode 100644 test/runtime/samples/each-block-keyed-random-permute/main.html diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 9ad173a01aa3..94d5886441d7 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -126,10 +126,16 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.create.addBlock( deindent` var ${lookup} = Object.create( null ); + var last; + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { 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}, ${key} ); ${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`} + + if ( last ) last.next = ${lookup}[ ${key} ]; + ${lookup}[ ${key} ].last = last; + last = ${lookup}[${key}]; } ` ); @@ -155,48 +161,78 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; + + if ( ${each_block_value}.length === 0 ) { + // special case + for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].destroy( true ); + } + ${iterations} = []; + return; + } + var ${_iterations} = Array( ${each_block_value}.length ); - var ${keys} = Object.create( null ); - var index_by_key = Object.create( null ); - var key_by_index = Array( ${each_block_value}.length ); + var expected = ${iterations}[0]; + var last; - var new_iterations = []; + var discard_pile = []; for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { var ${key} = ${each_block_value}[${i}].${node.key}; - index_by_key[${key}] = ${i}; - key_by_index[${i}] = ${key}; - if ( ${lookup}[ ${key} ] ) { - // TODO this is an empty branch for non-dynamic blocks - ${dynamic && `${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`} + if ( expected ) { + if ( ${key} === expected.key ) { + expected = expected.next; + } else { + if ( ${key} in ${lookup} ) { + // probably a deletion + do { + expected.discard = true; + discard_pile.push( expected ); + expected = expected.next; + } while ( expected && expected.key !== ${key} ); + + expected = expected && expected.next; + ${lookup}[${key}].discard = false; + ${lookup}[${key}].next = expected; + + ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null ); + } else { + // key is being inserted + ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first ); + } + } } else { - ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - new_iterations.push( ${lookup}[ ${key} ] ); + // we're appending from this point forward + if ( ${lookup}[${key}] ) { + ${lookup}[${key}].discard = false; + ${lookup}[${key}].next = null; + ${lookup}[${key}].mount( ${parentNode}, null ); + } else { + ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${lookup}[${key}].${mountOrIntro}( ${parentNode}, null ); + } } + if ( last ) last.next = ${lookup}[${key}]; + ${lookup}[${key}].last = last; + last = ${lookup}[${key}]; + ${_iterations}[${i}] = ${lookup}[ ${key} ]; } - // TODO group consecutive runs into fragments? - ${i} = new_iterations.length; - while ( ${i}-- ) { - ${iteration} = new_iterations[${i}]; - var index = index_by_key[${iteration}.key]; - var next_sibling_key = key_by_index[index + 1]; - ${iteration}.${mountOrIntro}( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first ); - } + last.next = null; - for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - var ${iteration} = ${iterations}[${i}]; - var index = index_by_key[${iteration}.key]; + while ( expected ) { + expected.destroy( true ); + expected = expected.next; + } - if ( index === undefined ) { - ${destroy} - } else { - var next_sibling_key = key_by_index[index + 1]; - ${iteration}.mount( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first ); + for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { + if ( discard_pile[${i}].discard ) { + discard_pile[${i}].destroy( true ); } } diff --git a/test/runtime/samples/each-block-keyed-random-permute/_config.js b/test/runtime/samples/each-block-keyed-random-permute/_config.js new file mode 100644 index 000000000000..c14c4cd0f7bb --- /dev/null +++ b/test/runtime/samples/each-block-keyed-random-permute/_config.js @@ -0,0 +1,48 @@ +const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' ); + +function toObjects ( array ) { + return array.split( '' ).map( x => ({ id: x }) ); +} + +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 permuted.join( '' ); +} + +export default { + data: { + values: toObjects( 'abc' ) + }, + + html: `(a)(b)(c)`, + + test ( assert, component, target ) { + function test ( sequence ) { + component.set({ values: toObjects( sequence ) }); + assert.htmlEqual( target.innerHTML, sequence.split( '' ).map( x => `(${x})` ).join( '' ) ); + } + + // first, some fixed tests so that we can debug them + test( 'abc' ); + test( 'abcd' ); + test( 'abecd' ); + test( 'fabecd' ); + test( 'fabed' ); + test( 'beadf' ); + test( 'ghbeadf' ); + test( 'gf' ); + test( 'gc' ); + test( 'g' ); + test( '' ); + test( 'abc' ); + + // then, we party + for ( let i = 0; i < 100; i += 1 ) test( permute() ); + } +}; diff --git a/test/runtime/samples/each-block-keyed-random-permute/main.html b/test/runtime/samples/each-block-keyed-random-permute/main.html new file mode 100644 index 000000000000..a6aa4b621db7 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-random-permute/main.html @@ -0,0 +1,3 @@ +{{#each values as value @id}} + ({{value.id}}) +{{/each}} From 5937aef3a652fa15536f4f85ee3eca129d1816c8 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 00:28:30 -0400 Subject: [PATCH 06/12] ok, i think it actually works now --- src/generators/dom/visitors/EachBlock.js | 82 +++++++++++++++--------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 94d5886441d7..3a4f2808ac6b 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -112,7 +112,6 @@ export default function visitEachBlock ( generator, block, state, node ) { function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) { const key = block.getUniqueName( 'key' ); const lookup = block.getUniqueName( `${each_block}_lookup` ); - const keys = block.getUniqueName( `${each_block}_keys` ); const iteration = block.getUniqueName( `${each_block}_iteration` ); const _iterations = block.getUniqueName( `_${each_block}_iterations` ); @@ -139,38 +138,66 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } ` ); - const dynamic = node._block.hasUpdateMethod; + // TODO!!! + // const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; let destroy; if ( node._block.hasOutroMethod ) { - const outro = block.getUniqueName( `${each_block}_outro` ); + const fn = block.getUniqueName( `${each_block}_outro` ); block.builders.create.addBlock( deindent` - function ${outro} ( key ) { - ${lookup}[ key ].outro( function () { - ${lookup}[ key ].destroy( true ); - ${lookup}[ key ] = null; + function ${fn} ( iteration ) { + iteration.outro( function () { + iteration.destroy( true ); + if ( iteration.next ) iteration.next.last = iteration.last; + if ( iteration.last ) iteration.last.next = iteration.next; + ${lookup}[iteration.key] = null; }); } ` ); - destroy = `${outro}( ${key} );`; + destroy = deindent` + while ( expected ) { + ${fn}( expected ); + expected = expected.next; + } + + for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { + if ( discard_pile[${i}].discard ) { + ${fn}( discard_pile[${i}] ); + } + } + `; } else { - destroy = `${iteration}.destroy( true );`; + const fn = block.getUniqueName( `${each_block}_destroy` ); + block.builders.create.addBlock( deindent` + function ${fn} ( iteration ) { + iteration.destroy( true ); + if ( iteration.next && iteration.next.last === iteration ) iteration.next.last = iteration.last; + if ( iteration.last && iteration.last.next === iteration ) iteration.last.next = iteration.next; + ${lookup}[iteration.key] = null; + } + ` ); + + destroy = deindent` + while ( expected ) { + ${fn}( expected ); + expected = expected.next; + } + + for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { + var ${iteration} = discard_pile[${i}]; + if ( ${iteration}.discard ) { + // console.log( 'discarding ' + [ ${iteration}.last && ${iteration}.last.key, ${iteration}.key, ${iteration}.next && ${iteration}.next.key ].join( '-' ) ); + ${fn}( ${iteration} ); + } + } + `; } block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; - if ( ${each_block_value}.length === 0 ) { - // special case - for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - ${iterations}[${i}].destroy( true ); - } - ${iterations} = []; - return; - } - var ${_iterations} = Array( ${each_block_value}.length ); var expected = ${iterations}[0]; @@ -185,7 +212,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea if ( ${key} === expected.key ) { expected = expected.next; } else { - if ( ${key} in ${lookup} ) { + if ( ${lookup}[${key}] ) { // probably a deletion do { expected.discard = true; @@ -195,6 +222,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea expected = expected && expected.next; ${lookup}[${key}].discard = false; + ${lookup}[${key}].last = last; ${lookup}[${key}].next = expected; ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null ); @@ -202,6 +230,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea // key is being inserted ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); ${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first ); + + if ( expected ) expected.last = ${lookup}[${key}]; + ${lookup}[${key}].next = expected; } } } else { @@ -223,18 +254,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ${_iterations}[${i}] = ${lookup}[ ${key} ]; } - last.next = null; - - while ( expected ) { - expected.destroy( true ); - expected = expected.next; - } + if ( last ) last.next = null; - for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { - if ( discard_pile[${i}].discard ) { - discard_pile[${i}].destroy( true ); - } - } + ${destroy} ${iterations} = ${_iterations}; ` ); From e9def64d146b0b89228427f9a2954841cead21dc Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 01:39:26 -0400 Subject: [PATCH 07/12] use anchor when updating --- src/generators/dom/visitors/EachBlock.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 3a4f2808ac6b..da3e30f9e10a 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -225,7 +225,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ${lookup}[${key}].last = last; ${lookup}[${key}].next = expected; - ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null ); + ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : ${anchor} ); } else { // key is being inserted ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); @@ -240,10 +240,10 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea if ( ${lookup}[${key}] ) { ${lookup}[${key}].discard = false; ${lookup}[${key}].next = null; - ${lookup}[${key}].mount( ${parentNode}, null ); + ${lookup}[${key}].mount( ${parentNode}, ${anchor} ); } else { ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - ${lookup}[${key}].${mountOrIntro}( ${parentNode}, null ); + ${lookup}[${key}].${mountOrIntro}( ${parentNode}, ${anchor} ); } } From d829eb94ef4f1178992f9d800b71d6e58b30d856 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 02:06:07 -0400 Subject: [PATCH 08/12] handle bidirectional transitions --- src/generators/dom/visitors/EachBlock.js | 2 +- .../_config.js | 65 +++++++++++++++++++ .../main.html | 18 +++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index da3e30f9e10a..4d5f9622ead2 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -188,7 +188,6 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { var ${iteration} = discard_pile[${i}]; if ( ${iteration}.discard ) { - // console.log( 'discarding ' + [ ${iteration}.last && ${iteration}.last.key, ${iteration}.key, ${iteration}.next && ${iteration}.next.key ].join( '-' ) ); ${fn}( ${iteration} ); } } @@ -249,6 +248,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea if ( last ) last.next = ${lookup}[${key}]; ${lookup}[${key}].last = last; + ${node._block.hasIntroMethod && `${lookup}[${key}].intro( ${parentNode}, ${anchor} );`} last = ${lookup}[${key}]; ${_iterations}[${i}] = ${lookup}[ ${key} ]; diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js new file mode 100644 index 000000000000..0b5c3a1d1a8f --- /dev/null +++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js @@ -0,0 +1,65 @@ +export default { + data: { + things: [ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ] + }, + + test ( assert, component, target, window, raf ) { + const divs = target.querySelectorAll( 'div' ); + divs[0].i = 0; // for debugging + divs[1].i = 1; + divs[2].i = 2; + + assert.equal( divs[0].foo, 0 ); + assert.equal( divs[1].foo, 0 ); + assert.equal( divs[2].foo, 0 ); + + raf.tick( 100 ); + assert.equal( divs[0].foo, 1 ); + assert.equal( divs[1].foo, 1 ); + assert.equal( divs[2].foo, 1 ); + + component.set({ + things: [ + { name: 'a' }, + { name: 'c' } + ] + }); + + const divs2 = target.querySelectorAll( 'div' ); + assert.strictEqual( divs[0], divs2[0] ); + assert.strictEqual( divs[1], divs2[1] ); + assert.strictEqual( divs[2], divs2[2] ); + + raf.tick( 150 ); + assert.equal( divs[0].foo, 1 ); + assert.equal( divs[1].foo, 0.5 ); + assert.equal( divs[2].foo, 1 ); + + component.set({ + things: [ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ] + }); + + raf.tick( 175 ); + assert.equal( divs[0].foo, 1 ); + assert.equal( divs[1].foo, 0.75 ); + assert.equal( divs[2].foo, 1 ); + + raf.tick( 225 ); + const divs3 = target.querySelectorAll( 'div' ); + assert.strictEqual( divs[0], divs3[0] ); + assert.strictEqual( divs[1], divs3[1] ); + assert.strictEqual( divs[2], divs3[2] ); + + assert.equal( divs[0].foo, 1 ); + assert.equal( divs[1].foo, 1 ); + assert.equal( divs[2].foo, 1 ); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html new file mode 100644 index 000000000000..c755bb12e685 --- /dev/null +++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html @@ -0,0 +1,18 @@ +{{#each things as thing @name}} +
{{thing.name}}
+{{/each}} + + \ No newline at end of file From 1f161f7fa82786711afd7412706303f93eb2cd8b Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 02:20:13 -0400 Subject: [PATCH 09/12] update dynamic keyed each blocks --- src/generators/dom/visitors/EachBlock.js | 44 ++++++++++--------- .../each-block-keyed-dynamic/_config.js | 35 +++++++++++++++ .../each-block-keyed-dynamic/main.html | 3 ++ 3 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 test/runtime/samples/each-block-keyed-dynamic/_config.js create mode 100644 test/runtime/samples/each-block-keyed-dynamic/main.html diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 4d5f9622ead2..ae533002d9d0 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -138,8 +138,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } ` ); - // TODO!!! - // const dynamic = node._block.hasUpdateMethod; + const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; let destroy; @@ -206,12 +205,15 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { var ${key} = ${each_block_value}[${i}].${node.key}; + var ${iteration} = ${lookup}[${key}]; + + ${dynamic && `if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`} if ( expected ) { if ( ${key} === expected.key ) { expected = expected.next; } else { - if ( ${lookup}[${key}] ) { + if ( ${iteration} ) { // probably a deletion do { expected.discard = true; @@ -220,36 +222,36 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } while ( expected && expected.key !== ${key} ); expected = expected && expected.next; - ${lookup}[${key}].discard = false; - ${lookup}[${key}].last = last; - ${lookup}[${key}].next = expected; + ${iteration}.discard = false; + ${iteration}.last = last; + ${iteration}.next = expected; - ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : ${anchor} ); + ${iteration}.mount( ${parentNode}, expected ? expected.first : ${anchor} ); } else { // key is being inserted - ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - ${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first ); + ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${iteration}.${mountOrIntro}( ${parentNode}, expected.first ); - if ( expected ) expected.last = ${lookup}[${key}]; - ${lookup}[${key}].next = expected; + if ( expected ) expected.last = ${iteration}; + ${iteration}.next = expected; } } } else { // we're appending from this point forward - if ( ${lookup}[${key}] ) { - ${lookup}[${key}].discard = false; - ${lookup}[${key}].next = null; - ${lookup}[${key}].mount( ${parentNode}, ${anchor} ); + if ( ${iteration} ) { + ${iteration}.discard = false; + ${iteration}.next = null; + ${iteration}.mount( ${parentNode}, ${anchor} ); } else { - ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - ${lookup}[${key}].${mountOrIntro}( ${parentNode}, ${anchor} ); + ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${iteration}.${mountOrIntro}( ${parentNode}, ${anchor} ); } } - if ( last ) last.next = ${lookup}[${key}]; - ${lookup}[${key}].last = last; - ${node._block.hasIntroMethod && `${lookup}[${key}].intro( ${parentNode}, ${anchor} );`} - last = ${lookup}[${key}]; + if ( last ) last.next = ${iteration}; + ${iteration}.last = last; + ${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`} + last = ${iteration}; ${_iterations}[${i}] = ${lookup}[ ${key} ]; } diff --git a/test/runtime/samples/each-block-keyed-dynamic/_config.js b/test/runtime/samples/each-block-keyed-dynamic/_config.js new file mode 100644 index 000000000000..93832a76588f --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dynamic/_config.js @@ -0,0 +1,35 @@ +export default { + data: { + todos: [ + { id: 123, description: 'buy milk' }, + { id: 234, description: 'drink milk' } + ] + }, + + html: ` +

buy milk

+

drink milk

+ `, + + test ( assert, component, target ) { + const [ p1, p2 ] = target.querySelectorAll( 'p' ); + + component.set({ + todos: [ + { id: 123, description: 'buy beer' }, + { id: 234, description: 'drink beer' } + ] + }); + assert.htmlEqual( target.innerHTML, ` +

buy beer

+

drink beer

+ ` ); + + const [ p3, p4 ] = target.querySelectorAll( 'p' ); + + assert.equal( p1, p3 ); + assert.equal( p2, p4 ); + + component.destroy(); + } +}; diff --git a/test/runtime/samples/each-block-keyed-dynamic/main.html b/test/runtime/samples/each-block-keyed-dynamic/main.html new file mode 100644 index 000000000000..7d5b90a9f83f --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dynamic/main.html @@ -0,0 +1,3 @@ +{{#each todos as todo @id}} +

{{todo.description}}

+{{/each}} From 23331e605aeac9e100b7666bf7102c492c184b81 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 11:33:08 -0400 Subject: [PATCH 10/12] dont store keyed block iterations in an array --- src/generators/dom/visitors/EachBlock.js | 69 ++++++++++++------- .../each-block-changed-check/expected.js | 1 + 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index ae533002d9d0..0f110f8b4d6b 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -16,7 +16,6 @@ export default function visitEachBlock ( generator, block, state, node ) { const { snippet } = block.contextualise( node.expression ); block.builders.create.addLine( `var ${each_block_value} = ${snippet};` ); - block.builders.create.addLine( `var ${iterations} = [];` ); if ( node.key ) { keyed( generator, block, state, node, snippet, vars ); @@ -26,23 +25,12 @@ export default function visitEachBlock ( generator, block, state, node ) { const isToplevel = !state.parentNode; - if ( isToplevel ) { - block.builders.mount.addBlock( deindent` - for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { - ${iterations}[${i}].${mountOrIntro}( ${block.target}, null ); - } - ` ); - } - if ( node.needsAnchor ) { block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); } else if ( node.next ) { node.next.usedAsAnchor = true; } - block.builders.destroy.addBlock( - `${generator.helper( 'destroyEach' )}( ${iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` ); - if ( node.else ) { const each_block_else = generator.getUniqueName( `${each_block}_else` ); @@ -109,11 +97,10 @@ export default function visitEachBlock ( generator, block, state, node ) { } } -function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) { +function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, i, params, anchor, mountOrIntro } ) { const key = block.getUniqueName( 'key' ); const lookup = block.getUniqueName( `${each_block}_lookup` ); const iteration = block.getUniqueName( `${each_block}_iteration` ); - const _iterations = block.getUniqueName( `_${each_block}_iterations` ); if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing @@ -125,19 +112,32 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.create.addBlock( deindent` var ${lookup} = Object.create( null ); + var head; var last; for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { 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}, ${key} ); - ${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`} + var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`} + + if ( last ) last.next = ${iteration}; + ${iteration}.last = last; + last = ${iteration}; - if ( last ) last.next = ${lookup}[ ${key} ]; - ${lookup}[ ${key} ].last = last; - last = ${lookup}[${key}]; + if ( ${i} === 0 ) head = ${iteration}; } ` ); + if ( !state.parentNode ) { + block.builders.mount.addBlock( deindent` + var ${iteration} = head; + while ( ${iteration} ) { + ${iteration}.${mountOrIntro}( ${block.target}, null ); + ${iteration} = ${iteration}.next; + } + ` ); + } + const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -196,9 +196,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; - var ${_iterations} = Array( ${each_block_value}.length ); - - var expected = ${iterations}[0]; + var expected = head; var last; var discard_pile = []; @@ -252,32 +250,49 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ${iteration}.last = last; ${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`} last = ${iteration}; - - ${_iterations}[${i}] = ${lookup}[ ${key} ]; } if ( last ) last.next = null; ${destroy} - ${iterations} = ${_iterations}; + head = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}]; + ` ); + + block.builders.destroy.addBlock( deindent` + var ${iteration} = head; + while ( ${iteration} ) { + ${iteration}.destroy( ${state.parentNode ? 'false' : 'detach'} ); + ${iteration} = ${iteration}.next; + } ` ); } function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) { block.builders.create.addBlock( deindent` + var ${iterations} = []; + for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`} } ` ); + if ( !state.parentNode ) { + block.builders.mount.addBlock( deindent` + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].${mountOrIntro}( ${block.target}, null ); + } + ` ); + } + const dependencies = block.findDependencies( node.expression ); const allDependencies = new Set( node._block.dependencies ); dependencies.forEach( dependency => { allDependencies.add( dependency ); }); + // TODO do this for keyed blocks as well const condition = Array.from( allDependencies ) .map( dependency => `'${dependency}' in changed` ) .join( ' || ' ); @@ -331,4 +346,8 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, } ` ); } + + block.builders.destroy.addBlock( + `${generator.helper( 'destroyEach' )}( ${iterations}, ${state.parentNode ? 'false' : 'detach'}, 0 );` + ); } \ No newline at end of file diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index b31be6e9fa11..128d332196f4 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -4,6 +4,7 @@ function create_main_fragment ( state, component ) { var text_1_value; var each_block_value = state.comments; + var each_block_iterations = []; for ( var i = 0; i < each_block_value.length; i += 1 ) { From f8e73c1f36645348548863cb4a023290b775c482 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 11:48:52 -0400 Subject: [PATCH 11/12] get rid of hardcoded variable names --- src/generators/dom/visitors/EachBlock.js | 78 +++++++++++++----------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 0f110f8b4d6b..dcf556ccfd21 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -101,6 +101,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea const key = block.getUniqueName( 'key' ); const lookup = block.getUniqueName( `${each_block}_lookup` ); const iteration = block.getUniqueName( `${each_block}_iteration` ); + const head = block.getUniqueName( `${each_block}_head` ); + const last = block.getUniqueName( `${each_block}_last` ); + const expected = block.getUniqueName( `${each_block}_expected` ); if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing @@ -112,25 +115,25 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.create.addBlock( deindent` var ${lookup} = Object.create( null ); - var head; - var last; + var ${head}; + var ${last}; for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { var ${key} = ${each_block_value}[${i}].${node.key}; var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); ${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`} - if ( last ) last.next = ${iteration}; - ${iteration}.last = last; - last = ${iteration}; + if ( ${last} ) ${last}.next = ${iteration}; + ${iteration}.last = ${last}; + ${last} = ${iteration}; - if ( ${i} === 0 ) head = ${iteration}; + if ( ${i} === 0 ) ${head} = ${iteration}; } ` ); if ( !state.parentNode ) { block.builders.mount.addBlock( deindent` - var ${iteration} = head; + var ${iteration} = ${head}; while ( ${iteration} ) { ${iteration}.${mountOrIntro}( ${block.target}, null ); ${iteration} = ${iteration}.next; @@ -156,9 +159,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ` ); destroy = deindent` - while ( expected ) { - ${fn}( expected ); - expected = expected.next; + while ( ${expected} ) { + ${fn}( ${expected} ); + ${expected} = ${expected}.next; } for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { @@ -179,9 +182,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ` ); destroy = deindent` - while ( expected ) { - ${fn}( expected ); - expected = expected.next; + while ( ${expected} ) { + ${fn}( ${expected} ); + ${expected} = ${expected}.next; } for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) { @@ -196,8 +199,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea block.builders.update.addBlock( deindent` var ${each_block_value} = ${snippet}; - var expected = head; - var last; + var ${expected} = ${head}; + var ${last}; var discard_pile = []; @@ -207,31 +210,31 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea ${dynamic && `if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`} - if ( expected ) { - if ( ${key} === expected.key ) { - expected = expected.next; + if ( ${expected} ) { + if ( ${key} === ${expected}.key ) { + ${expected} = ${expected}.next; } else { if ( ${iteration} ) { // probably a deletion do { - expected.discard = true; - discard_pile.push( expected ); - expected = expected.next; - } while ( expected && expected.key !== ${key} ); + ${expected}.discard = true; + discard_pile.push( ${expected} ); + ${expected} = ${expected}.next; + } while ( ${expected} && ${expected}.key !== ${key} ); - expected = expected && expected.next; + ${expected} = ${expected} && ${expected}.next; ${iteration}.discard = false; - ${iteration}.last = last; - ${iteration}.next = expected; + ${iteration}.last = ${last}; + ${iteration}.next = ${expected}; - ${iteration}.mount( ${parentNode}, expected ? expected.first : ${anchor} ); + ${iteration}.mount( ${parentNode}, ${expected} ? ${expected}.first : ${anchor} ); } else { // key is being inserted ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); - ${iteration}.${mountOrIntro}( ${parentNode}, expected.first ); + ${iteration}.${mountOrIntro}( ${parentNode}, ${expected}.first ); - if ( expected ) expected.last = ${iteration}; - ${iteration}.next = expected; + if ( ${expected} ) ${expected}.last = ${iteration}; + ${iteration}.next = ${expected}; } } } else { @@ -246,21 +249,21 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea } } - if ( last ) last.next = ${iteration}; - ${iteration}.last = last; + if ( ${last} ) ${last}.next = ${iteration}; + ${iteration}.last = ${last}; ${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`} - last = ${iteration}; + ${last} = ${iteration}; } - if ( last ) last.next = null; + if ( ${last} ) ${last}.next = null; ${destroy} - head = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}]; + ${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}]; ` ); block.builders.destroy.addBlock( deindent` - var ${iteration} = head; + var ${iteration} = ${head}; while ( ${iteration} ) { ${iteration}.destroy( ${state.parentNode ? 'false' : 'detach'} ); ${iteration} = ${iteration}.next; @@ -316,9 +319,10 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`; + const outro = block.getUniqueName( 'outro' ); const destroy = node._block.hasOutroMethod ? deindent` - function outro ( i ) { + function ${outro} ( i ) { if ( ${iterations}[i] ) { ${iterations}[i].outro( function () { ${iterations}[i].destroy( true ); @@ -327,7 +331,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, } } - for ( ; ${i} < ${iterations}.length; ${i} += 1 ) outro( ${i} ); + for ( ; ${i} < ${iterations}.length; ${i} += 1 ) ${outro}( ${i} ); ` : deindent` ${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length ); From b0a31dda150db4e03d21f0a557bd12e8b6a86336 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 6 May 2017 13:02:12 -0400 Subject: [PATCH 12/12] reintro unkeyed each block iterations as necessary --- src/generators/dom/visitors/EachBlock.js | 25 ++++++++++++++++-------- src/shared/dom.js | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index dcf556ccfd21..d30406598c87 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -304,14 +304,23 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block, if ( condition !== '' ) { const forLoopBody = node._block.hasUpdateMethod ? - deindent` - if ( ${iterations}[${i}] ) { - ${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}].${mountOrIntro}( ${parentNode}, ${anchor} ); - } - ` : + node._block.hasIntroMethod ? + deindent` + if ( ${iterations}[${i}] ) { + ${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}].intro( ${parentNode}, ${anchor} ); + ` : + deindent` + if ( ${iterations}[${i}] ) { + ${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( ${parentNode}, ${anchor} ); + } + ` : deindent` ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} ); diff --git a/src/shared/dom.js b/src/shared/dom.js index 6280783cb6cf..3c8bb78bc087 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -18,7 +18,7 @@ export function detachBetween ( before, after ) { export function destroyEach ( iterations, detach, start ) { for ( var i = start; i < iterations.length; i += 1 ) { - iterations[i].destroy( detach ); + if ( iterations[i] ) iterations[i].destroy( detach ); } }