Skip to content

Allow #each block to handle arbitrary non array like iterables #7425

@joeally

Description

@joeally

Describe the problem

Consider the following REPL:

<script>
	const myMap = new Map();
	myMap.set('key1', 'value1');
	myMap.set('key2', 'value2');
	myMap.set('key3', 'value3');
	for(const [k, v] of myMap.entries()){
		// Javascript lets us iterate through a set so I don't see why svelte shouldn't
		console.log(k, v);
	}
</script>
<!-- This is not allowed because mySet does not have `.length` -->
{#each myMap.entries() as [k, v]}
	<h1>Key: {k}, value: {v}</h1>
{/each}

Obviously one could trivially achieve this by wrapping myMap in Array.from or spreading into an array but it would be nice if svelte could handle iterables in addition to ArrayLike objects.

Describe the proposed solution

The problem is caused by the fact that svelte generates the code which loops over the each value using its .length property. This can be seen below:

// from top of create_fragment
let each_1_anchor;
let each_value = /*myMap*/ ctx[0].entries;
let each_blocks = [];

for (let i = 0; i < each_value.length; i += 1) {
	each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
}

and

// from the p function returned by create_fragment
for (i = 0; i < each_value.length; i += 1) {
	const child_ctx = get_each_context(ctx, each_value, i);
	if (each_blocks[i]) {
		each_blocks[i].p(child_ctx, dirty);
	} else {
		each_blocks[i] = create_each_block(child_ctx);
		each_blocks[i].c();
		each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);	
	}
}

As you can see it assumes that each_value (in this case myMap) has a length property. It seems to me that we could easily generate the following code which is equivalent:

// the top of create_fragment
let each_1_anchor;
let each_value = /*myMap*/ ctx[0].entries();
let each_blocks = [];
for (const item of each_value) {
	each_blocks.append(create_each_block(get_each_context(ctx, item)));
}

// the loop in the p function generated by create_fragment
let each_value_i = 0;
for (const item of each_value) {
	const child_ctx = get_each_context(ctx, item);
	if (each_blocks[each_value_i]) {
		each_blocks[each_value_i].p(child_ctx, dirty);
	} else {
		each_blocks[each_value_i] = create_each_block(child_ctx);
		each_blocks[each_value_i].c();
		each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);	
	}
}

With get_each_context rewritten to be:

function get_each_context(ctx, item) {
	const child_ctx = ctx.slice();
	child_ctx[1] = item;
	return child_ctx;
}

It's worth noting that the for...of syntax isn't supported by IE11 but the svelte compiler appears to recommend the use of the spread operator which also isn't supported in IE11 so perhaps this isn't an issue.

Alternatives considered

The alternative here is to leave it as it is and tell people to use Array.from and or [...mySet]. It isn't the end of the world.

But given that it seems so trivial to support arbitrary iterables and that javascript can loop over iterables I can't see why svelte wouldn't implement this. Is it because you want any infinite loops to be in user code rather than generated code?

Importance

nice to have

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions