From dab1f6a0fa359c77a3a8f31156a6d0d1695c6234 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 18 Mar 2018 14:34:19 -0400 Subject: [PATCH] more keyed each diffing into a shared helper --- src/generators/nodes/EachBlock.ts | 98 +------------------------------ src/shared/_build.js | 2 +- src/shared/index.js | 1 + src/shared/keyed-each.js | 92 +++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 96 deletions(-) create mode 100644 src/shared/keyed-each.js diff --git a/src/generators/nodes/EachBlock.ts b/src/generators/nodes/EachBlock.ts index 6aa9ab673eeb..ffc95eb485cd 100644 --- a/src/generators/nodes/EachBlock.ts +++ b/src/generators/nodes/EachBlock.ts @@ -315,108 +315,16 @@ export default class EachBlock extends Node { `); const dynamic = this.block.hasUpdateMethod; - let fn_destroy; - if (this.block.hasOutroMethod) { - fn_destroy = block.getUniqueName(`${each}_outro`); - block.builders.init.addBlock(deindent` - function ${fn_destroy}(iteration) { - iteration.o(function() { - iteration.u(); - iteration.d(); - ${lookup}[iteration.key] = null; - }); - } - `); - } else { - fn_destroy = block.getUniqueName(`${each}_destroy`); - block.builders.init.addBlock(deindent` - function ${fn_destroy}(iteration) { - var first = iteration.first - if (first && first.parentNode) { - iteration.u(); - } - iteration.d(); - ${lookup}[iteration.key] = null; - } - `); - } - const destroy = deindent` - ${iteration} = ${head}; - while(${iteration}) { - if (!${keep}[${iteration}.key]) { - ${fn_destroy}(${iteration}); - } - ${iteration} = ${iteration}.next; - } - `; block.builders.update.addBlock(deindent` var ${each_block_value} = ${snippet}; - var ${expected} = ${head}; - var ${last} = null; - - var ${keep} = {}; - var ${mounts} = {}; - var ${next_iteration} = null; - for (#i = 0; #i < ${each_block_value}.${length}; #i += 1) { - var ${key} = ${each_block_value}[#i].${this.key}; - var ${iteration} = ${lookup}[${key}]; - var next_data = ${each_block_value}[#i+1]; - var next = next_data && ${lookup}[next_data.${this.key}]; - - var ${this.each_context} = @assign({}, state, { + @updateKeyedEach(#component, ${key}, changed, "${this.key}", ${dynamic}, ${each_block_value}, ${head}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, function(#i) { + return @assign({}, state, { ${this.contextProps.join(',\n')} }); + }); - ${dynamic && - `if (${iteration}) ${iteration}.p(changed, ${this.each_context});`} - if (${expected} && (${key} === ${expected}.key)) { - var first = ${iteration} && ${iteration}.first; - var parentNode = first && first.parentNode - if (!parentNode || (${iteration} && ${iteration}.next) != next) ${mounts}[${key}] = ${iteration}; - ${expected} = ${iteration}.next; - } else if (${iteration}) { - ${mounts}[${key}] = ${iteration}; - ${expected} = ${iteration}.next; - } else { - // key is being inserted - ${iteration} = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, ${this.each_context}); - ${iteration}.c(); - ${mounts}[${key}] = ${iteration}; - } - ${lookup}[${key}] = ${iteration}; - ${keep}[${iteration}.key] = ${iteration}; - ${last} = ${iteration}; - } - ${destroy} - - // Work backwards due to DOM api having insertBefore - for (#i = ${each_block_value}.${length} - 1; #i >= 0; #i -= 1) { - var data = ${each_block_value}[#i]; - var ${key} = data.${this.key}; - ${iteration} = ${lookup}[${key}]; - if (${mounts}[${key}]) { - var anchor; - ${this.block.hasOutroMethod - ? deindent` - var key_next_iteration = ${next_iteration} && ${next_iteration}.key; - var iteration_anchor = ${iteration}.next; - var key_anchor; - do { - anchor = iteration_anchor && iteration_anchor.first; - iteration_anchor = iteration_anchor && iteration_anchor.next; - key_anchor = iteration_anchor && iteration_anchor.key; - } while(iteration_anchor && key_anchor != key_next_iteration && !${keep}[key_anchor])` - : deindent` - anchor = ${next_iteration} && ${next_iteration}.first; - ` } - ${mounts}[${key}].${mountOrIntro}(${updateMountNode}, anchor); - } - ${iteration}.next = ${next_iteration}; - if (${next_iteration}) ${next_iteration}.last = ${iteration}; - ${next_iteration} = ${iteration}; - } ${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${this.key}]; `); diff --git a/src/shared/_build.js b/src/shared/_build.js index 0339ba558c16..3cc523e7cd1e 100644 --- a/src/shared/_build.js +++ b/src/shared/_build.js @@ -5,7 +5,7 @@ const acorn = require('acorn'); const declarations = {}; fs.readdirSync(__dirname).forEach(file => { - if (!/^[a-z]+\.js$/.test(file)) return; + if (!/^[a-z\-]+\.js$/.test(file)) return; const source = fs.readFileSync(path.join(__dirname, file), 'utf-8'); const ast = acorn.parse(source, { diff --git a/src/shared/index.js b/src/shared/index.js index ab8a053151c9..a410e7d9dfa2 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -1,6 +1,7 @@ import { assign } from './utils.js'; import { noop } from './utils.js'; export * from './dom.js'; +export * from './keyed-each.js'; export * from './transitions.js'; export * from './utils.js'; diff --git a/src/shared/keyed-each.js b/src/shared/keyed-each.js new file mode 100644 index 000000000000..fd9ad18c5925 --- /dev/null +++ b/src/shared/keyed-each.js @@ -0,0 +1,92 @@ +import { assign } from './utils.js'; + +export function updateKeyedEach(component, key, changed, block_key, dynamic, each_block_value, head, lookup, updateMountNode, hasOutroMethod, create_each_block, get_context) { + var last = null; + var expected = head; + + var keep = {}; + var mounts = {}; + var next_iteration = null; + + for (i = 0; i < each_block_value.length; i += 1) { + var key = each_block_value[i][block_key]; + var iteration = lookup[key]; + var next_data = each_block_value[i+1]; + var next = next_data && lookup[next_data[block_key]]; + + if (dynamic && iteration) iteration.p(changed, get_context(i)); // TODO should this be deferred? could it be redundant? + + if (expected && (key === expected.key)) { + var first = iteration && iteration.first; + var parentNode = first && first.parentNode + if (!parentNode || (iteration && iteration.next) != next) mounts[key] = iteration; + expected = iteration.next; + } else if (iteration) { + mounts[key] = iteration; + expected = iteration.next; + } else { + // key is being inserted + iteration = lookup[key] = create_each_block(component, key, get_context(i)); + iteration.c(); + mounts[key] = iteration; + } + lookup[key] = iteration; + keep[iteration.key] = iteration; + last = iteration; + } + + var destroy = hasOutroMethod + ? function(iteration) { + iteration.o(function() { + iteration.u(); + iteration.d(); + lookup[iteration.key] = null; + }); + } + : function(iteration) { + var first = iteration.first + if (first && first.parentNode) { + iteration.u(); + } + iteration.d(); + lookup[iteration.key] = null; + } + + iteration = head; + while(iteration) { + if (!keep[iteration.key]) { + destroy(iteration); + } + iteration = iteration.next; + } + + // Work backwards due to DOM api having insertBefore + for (i = each_block_value.length - 1; i >= 0; i -= 1) { + var data = each_block_value[i]; + var key = data[block_key]; + iteration = lookup[key]; + + var block = mounts[key]; + if (block) { + var anchor; + + if (hasOutroMethod) { + var key_next_iteration = next_iteration && next_iteration.key; + var iteration_anchor = iteration.next; + var key_anchor; + do { + anchor = iteration_anchor && iteration_anchor.first; + iteration_anchor = iteration_anchor && iteration_anchor.next; + key_anchor = iteration_anchor && iteration_anchor.key; + } while(iteration_anchor && key_anchor != key_next_iteration && !keep[key_anchor]) + } else { + anchor = next_iteration && next_iteration.first; + } + + block[block.i ? 'i' : 'm'](updateMountNode, anchor); + } + iteration.next = next_iteration; + if (next_iteration) next_iteration.last = iteration; + next_iteration = iteration; + } +} \ No newline at end of file