Skip to content

Commit 35a7fc6

Browse files
authored
Merge pull request #1256 from sveltejs/each-keyed-helper
[WIP] move keyed each diffing into a shared helper
2 parents 5b086df + ffa45dd commit 35a7fc6

File tree

5 files changed

+121
-122
lines changed

5 files changed

+121
-122
lines changed

src/generators/nodes/EachBlock.ts

Lines changed: 3 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -315,108 +315,16 @@ export default class EachBlock extends Node {
315315
`);
316316

317317
const dynamic = this.block.hasUpdateMethod;
318-
let fn_destroy;
319-
if (this.block.hasOutroMethod) {
320-
fn_destroy = block.getUniqueName(`${each}_outro`);
321-
block.builders.init.addBlock(deindent`
322-
function ${fn_destroy}(iteration) {
323-
iteration.o(function() {
324-
iteration.u();
325-
iteration.d();
326-
${lookup}[iteration.key] = null;
327-
});
328-
}
329-
`);
330-
} else {
331-
fn_destroy = block.getUniqueName(`${each}_destroy`);
332-
block.builders.init.addBlock(deindent`
333-
function ${fn_destroy}(iteration) {
334-
var first = iteration.first
335-
if (first && first.parentNode) {
336-
iteration.u();
337-
}
338-
iteration.d();
339-
${lookup}[iteration.key] = null;
340-
}
341-
`);
342-
}
343-
const destroy = deindent`
344-
${iteration} = ${head};
345-
while(${iteration}) {
346-
if (!${keep}[${iteration}.key]) {
347-
${fn_destroy}(${iteration});
348-
}
349-
${iteration} = ${iteration}.next;
350-
}
351-
`;
352318

353319
block.builders.update.addBlock(deindent`
354320
var ${each_block_value} = ${snippet};
355-
var ${expected} = ${head};
356-
var ${last} = null;
357-
358-
var ${keep} = {};
359-
var ${mounts} = {};
360-
var ${next_iteration} = null;
361321
362-
for (#i = 0; #i < ${each_block_value}.${length}; #i += 1) {
363-
var ${key} = ${each_block_value}[#i].${this.key};
364-
var ${iteration} = ${lookup}[${key}];
365-
var next_data = ${each_block_value}[#i+1];
366-
var next = next_data && ${lookup}[next_data.${this.key}];
367-
368-
var ${this.each_context} = @assign({}, state, {
322+
@updateKeyedEach(#component, ${key}, changed, "${this.key}", ${dynamic}, ${each_block_value}, ${head}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", function(#i) {
323+
return @assign({}, state, {
369324
${this.contextProps.join(',\n')}
370325
});
326+
});
371327
372-
${dynamic &&
373-
`if (${iteration}) ${iteration}.p(changed, ${this.each_context});`}
374-
if (${expected} && (${key} === ${expected}.key)) {
375-
var first = ${iteration} && ${iteration}.first;
376-
var parentNode = first && first.parentNode
377-
if (!parentNode || (${iteration} && ${iteration}.next) != next) ${mounts}[${key}] = ${iteration};
378-
${expected} = ${iteration}.next;
379-
} else if (${iteration}) {
380-
${mounts}[${key}] = ${iteration};
381-
${expected} = ${iteration}.next;
382-
} else {
383-
// key is being inserted
384-
${iteration} = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, ${this.each_context});
385-
${iteration}.c();
386-
${mounts}[${key}] = ${iteration};
387-
}
388-
${lookup}[${key}] = ${iteration};
389-
${keep}[${iteration}.key] = ${iteration};
390-
${last} = ${iteration};
391-
}
392-
${destroy}
393-
394-
// Work backwards due to DOM api having insertBefore
395-
for (#i = ${each_block_value}.${length} - 1; #i >= 0; #i -= 1) {
396-
var data = ${each_block_value}[#i];
397-
var ${key} = data.${this.key};
398-
${iteration} = ${lookup}[${key}];
399-
if (${mounts}[${key}]) {
400-
var anchor;
401-
${this.block.hasOutroMethod
402-
? deindent`
403-
var key_next_iteration = ${next_iteration} && ${next_iteration}.key;
404-
var iteration_anchor = ${iteration}.next;
405-
var key_anchor;
406-
do {
407-
anchor = iteration_anchor && iteration_anchor.first;
408-
iteration_anchor = iteration_anchor && iteration_anchor.next;
409-
key_anchor = iteration_anchor && iteration_anchor.key;
410-
} while(iteration_anchor && key_anchor != key_next_iteration && !${keep}[key_anchor])`
411-
: deindent`
412-
anchor = ${next_iteration} && ${next_iteration}.first;
413-
` }
414-
${mounts}[${key}].${mountOrIntro}(${updateMountNode}, anchor);
415-
}
416-
${iteration}.next = ${next_iteration};
417-
if (${next_iteration}) ${next_iteration}.last = ${iteration};
418-
${next_iteration} = ${iteration};
419-
}
420328
${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${this.key}];
421329
`);
422330

src/shared/_build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const acorn = require('acorn');
55
const declarations = {};
66

77
fs.readdirSync(__dirname).forEach(file => {
8-
if (!/^[a-z]+\.js$/.test(file)) return;
8+
if (!/^[a-z\-]+\.js$/.test(file)) return;
99

1010
const source = fs.readFileSync(path.join(__dirname, file), 'utf-8');
1111
const ast = acorn.parse(source, {

src/shared/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { assign } from './utils.js';
22
import { noop } from './utils.js';
33
export * from './dom.js';
4+
export * from './keyed-each.js';
45
export * from './transitions.js';
56
export * from './utils.js';
67

src/shared/keyed-each.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { assign } from './utils.js';
2+
3+
export function destroyIteration(iteration, lookup) {
4+
var first = iteration.first
5+
if (first && first.parentNode) {
6+
iteration.u();
7+
}
8+
iteration.d();
9+
lookup[iteration.key] = null;
10+
}
11+
12+
export function outroAndDestroyIteration(iteration, lookup) {
13+
iteration.o(function() {
14+
iteration.u();
15+
iteration.d();
16+
lookup[iteration.key] = null;
17+
});
18+
}
19+
20+
// TODO is it possible to avoid mounting iterations that are
21+
// already in the right place?
22+
export function updateKeyedEach(component, key, changed, key_prop, dynamic, list, head, lookup, node, has_outro, create_each_block, intro_method, get_context) {
23+
var keep = {};
24+
25+
var i = list.length;
26+
while (i--) {
27+
var key = list[i][key_prop];
28+
var iteration = lookup[key];
29+
30+
if (iteration) {
31+
if (dynamic) iteration.p(changed, get_context(i));
32+
} else {
33+
iteration = lookup[key] = create_each_block(component, key, get_context(i));
34+
iteration.c();
35+
}
36+
37+
lookup[key] = iteration;
38+
keep[key] = 1;
39+
}
40+
41+
var destroy = has_outro
42+
? outroAndDestroyIteration
43+
: destroyIteration;
44+
45+
iteration = head;
46+
while (iteration) {
47+
if (!keep[iteration.key]) destroy(iteration, lookup);
48+
iteration = iteration.next;
49+
}
50+
51+
var next = null;
52+
53+
i = list.length;
54+
while (i--) {
55+
key = list[i][key_prop];
56+
iteration = lookup[key];
57+
58+
var anchor;
59+
60+
if (has_outro) {
61+
var next_key = next && next.key;
62+
var neighbour = iteration.next;
63+
var anchor_key;
64+
65+
while (neighbour && anchor_key != next_key && !keep[anchor_key]) {
66+
anchor = neighbour && neighbour.first;
67+
neighbour = neighbour.next;
68+
anchor_key = neighbour && neighbour.key;
69+
}
70+
} else {
71+
anchor = next && next.first;
72+
}
73+
74+
iteration[intro_method](node, anchor);
75+
76+
iteration.next = next;
77+
if (next) next.last = iteration;
78+
next = iteration;
79+
}
80+
}
Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,60 @@
1-
const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' );
1+
const VALUES = Array.from('abcdefghijklmnopqrstuvwxyz');
22

3-
function toObjects ( array ) {
4-
return array.split( '' ).map( x => ({ id: x }) );
3+
function toObjects(array) {
4+
return array.split('').map(x => ({ id: x }));
55
}
66

7-
function permute () {
7+
function permute() {
88
const values = VALUES.slice();
99
const number = Math.floor(Math.random() * VALUES.length);
1010
const permuted = [];
1111
for (let i = 0; i < number; i++) {
12-
permuted.push( ...values.splice( Math.floor( Math.random() * ( number - i ) ), 1 ) );
12+
permuted.push(
13+
...values.splice(Math.floor(Math.random() * (number - i)), 1)
14+
);
1315
}
1416

15-
return permuted.join( '' );
17+
return permuted.join('');
1618
}
1719

1820
export default {
1921
data: {
20-
values: toObjects( 'abc' )
22+
values: toObjects('abc'),
2123
},
2224

2325
html: `(a)(b)(c)`,
2426

25-
test ( assert, component, target ) {
26-
function test ( sequence ) {
27-
component.set({ values: toObjects( sequence ) });
28-
assert.htmlEqual( target.innerHTML, sequence.split( '' ).map( x => `(${x})` ).join( '' ) );
27+
test(assert, component, target) {
28+
function test(sequence) {
29+
const previous = target.textContent;
30+
const expected = sequence.split('').map(x => `(${x})`).join('');
31+
component.set({ values: toObjects(sequence) });
32+
assert.htmlEqual(
33+
target.innerHTML,
34+
expected,
35+
`\n${previous} -> ${expected}\n${target.textContent}`
36+
);
2937
}
3038

3139
// first, some fixed tests so that we can debug them
32-
test( 'abc' );
33-
test( 'abcd' );
34-
test( 'abecd' );
35-
test( 'fabecd' );
36-
test( 'fabed' );
37-
test( 'beadf' );
38-
test( 'ghbeadf' );
39-
test( 'gf' );
40-
test( 'gc' );
41-
test( 'g' );
42-
test( '' );
43-
test( 'abc' );
44-
test( 'duqbmineapjhtlofrskcg' );
45-
test( 'hdnkjougmrvftewsqpailcb' );
40+
test('abc');
41+
test('abcd');
42+
test('abecd');
43+
test('fabecd');
44+
test('fabed');
45+
test('beadf');
46+
test('ghbeadf');
47+
test('gf');
48+
test('gc');
49+
test('g');
50+
test('');
51+
test('abc');
52+
test('duqbmineapjhtlofrskcg');
53+
test('hdnkjougmrvftewsqpailcb');
54+
test('bidhfacge');
55+
test('kgjnempcboaflidh');
4656

4757
// then, we party
48-
for ( let i = 0; i < 100; i += 1 ) test( permute() );
58+
for (let i = 0; i < 100; i += 1) test(permute());
4959
}
5060
};

0 commit comments

Comments
 (0)