Skip to content

Commit

Permalink
more keyed each diffing into a shared helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Mar 18, 2018
1 parent 10600eb commit dab1f6a
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 96 deletions.
98 changes: 3 additions & 95 deletions src/generators/nodes/EachBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}];
`);

Expand Down
2 changes: 1 addition & 1 deletion src/shared/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
1 change: 1 addition & 0 deletions src/shared/index.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
92 changes: 92 additions & 0 deletions src/shared/keyed-each.js
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit dab1f6a

Please sign in to comment.