Repository: sveltejs/svelte (JavaScript) — Feature

Issue:
Nice-to-have: Destructuring in each blocks
It would be handy if we could destructure the target item of the iterated array, ie

```handlebars
{{#each Array.from(map.entries()) as [key, value]}}
	...
{{/each}}
```

Currently, this results in `ParseError: Expected name`. [REPL](https://svelte.technology/repl?version=1.40.2&gist=3fe780d02e2a5ed8e4e64edaba44abde).

Vue supports this in their v-for directives, so it might be possible to reference that to get hints on how to properly parse it.


Discussion:
Recently I met this behavior too.

Resolving patch:
diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts
--- a/src/generators/dom/Block.ts
+++ b/src/generators/dom/Block.ts
@@ -29,6 +29,7 @@ export default class Block {
 	name: string;
 	expression: Node;
 	context: string;
+	destructuredContexts?: string[];
 	comment?: string;
 
 	key: string;
@@ -75,6 +76,7 @@ export default class Block {
 		this.name = options.name;
 		this.expression = options.expression;
 		this.context = options.context;
+		this.destructuredContexts = options.destructuredContexts;
 		this.comment = options.comment;
 
 		// for keyed each blocks
diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts
--- a/src/generators/dom/preprocess.ts
+++ b/src/generators/dom/preprocess.ts
@@ -229,6 +229,12 @@ const preprocessors = {
 		const contexts = new Map(block.contexts);
 		contexts.set(node.context, context);
 
+		if (node.destructuredContexts) {
+			for (const i = 0; i < node.destructuredContexts.length; i++) {
+				contexts.set(node.destructuredContexts[i], `${context}[${i}]`);
+			}
+		}
+
 		const indexes = new Map(block.indexes);
 		if (node.index) indexes.set(node.index, node.context);
 
diff --git a/src/generators/server-side-rendering/visitors/EachBlock.ts b/src/generators/server-side-rendering/visitors/EachBlock.ts
--- a/src/generators/server-side-rendering/visitors/EachBlock.ts
+++ b/src/generators/server-side-rendering/visitors/EachBlock.ts
@@ -18,6 +18,12 @@ export default function visitEachBlock(
 	const contexts = new Map(block.contexts);
 	contexts.set(node.context, node.context);
 
+	if (node.destructuredContexts) {
+		for (const i = 0; i < node.destructuredContexts.length; i++) {
+			contexts.set(node.destructuredContexts[i], `${node.context}[${i}]`);
+		}
+	}
+
 	const indexes = new Map(block.indexes);
 	if (node.index) indexes.set(node.index, node.context);
 
diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts
--- a/src/parse/state/mustache.ts
+++ b/src/parse/state/mustache.ts
@@ -161,8 +161,29 @@ export default function mustache(parser: Parser) {
 			parser.eat('as', true);
 			parser.requireWhitespace();
 
-			block.context = parser.read(validIdentifier); // TODO check it's not a keyword
-			if (!block.context) parser.error(`Expected name`);
+			if (parser.eat('[')) {
+				parser.allowWhitespace();
+
+				block.destructuredContexts = [];
+
+				do {
+					parser.allowWhitespace();
+					const destructuredContext = parser.read(validIdentifier);
+					if (!destructuredContext) parser.error(`Expected name`);
+					block.destructuredContexts.push(destructuredContext);
+					parser.allowWhitespace();
+				} while (parser.eat(','));
+
+				if (!block.destructuredContexts.length) parser.error(`Expected name`);
+				block.context = block.destructuredContexts.join('_');
+
+				parser.allowWhitespace();
+				parser.eat(']', true);
+			} else {
+				block.context = parser.read(validIdentifier); // TODO check it's not a keyword
+
+				if (!block.context) parser.error(`Expected name`);
+			}
 
 			parser.allowWhitespace();
 


Test patch:
diff --git a/test/parser/samples/each-block-destructured/input.html b/test/parser/samples/each-block-destructured/input.html
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/each-block-destructured/input.html
@@ -0,0 +1,3 @@
+{{#each animals as [key, value]}}
+	<p>{{key}}: {{value}}</p>
+{{/each}}
diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/each-block-destructured/output.json
@@ -0,0 +1,67 @@
+{
+	"hash": 2621498076,
+	"html": {
+		"start": 0,
+		"end": 70,
+		"type": "Fragment",
+		"children": [
+			{
+				"start": 0,
+				"end": 70,
+				"type": "EachBlock",
+				"expression": {
+					"type": "Identifier",
+					"start": 8,
+					"end": 15,
+					"name": "animals"
+				},
+				"children": [
+					{
+						"start": 35,
+						"end": 60,
+						"type": "Element",
+						"name": "p",
+						"attributes": [],
+						"children": [
+							{
+								"start": 38,
+								"end": 45,
+								"type": "MustacheTag",
+								"expression": {
+									"type": "Identifier",
+									"start": 40,
+									"end": 43,
+									"name": "key"
+								}
+							},
+							{
+								"start": 45,
+								"end": 47,
+								"type": "Text",
+								"data": ": "
+							},
+							{
+								"start": 47,
+								"end": 56,
+								"type": "MustacheTag",
+								"expression": {
+									"type": "Identifier",
+									"start": 49,
+									"end": 54,
+									"name": "value"
+								}
+							}
+						]
+					}
+				],
+				"destructuredContexts": [
+					"key",
+					"value"
+				],
+				"context": "key_value"
+			}
+		]
+	},
+	"css": null,
+	"js": null
+}
\ No newline at end of file
diff --git a/test/runtime/samples/each-block-destructured-array/_config.js b/test/runtime/samples/each-block-destructured-array/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/each-block-destructured-array/_config.js
@@ -0,0 +1,13 @@
+export default {
+	data: {
+		animalPawsEntries: [
+			['raccoon', 'hands'],
+			['eagle', 'wings']
+		]
+	},
+
+	html: `
+		<p>raccoon: hands</p>
+		<p>eagle: wings</p>
+	`
+};
diff --git a/test/runtime/samples/each-block-destructured-array/main.html b/test/runtime/samples/each-block-destructured-array/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/each-block-destructured-array/main.html
@@ -0,0 +1,3 @@
+{{#each animalPawsEntries as [animal, pawType]}}
+	<p>{{animal}}: {{pawType}}</p>
+{{/each}}