Skip to content

Commit 2f61907

Browse files
authored
[fix] allow invalidating variables from @const declared function (#7858)
1 parent 5eb1ff9 commit 2f61907

File tree

3 files changed

+96
-36
lines changed

3 files changed

+96
-36
lines changed

src/compiler/compile/nodes/shared/Expression.ts

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -274,41 +274,7 @@ export default class Expression {
274274
);
275275

276276
const declaration = b`const ${id} = ${node}`;
277-
278-
if (owner.type === 'ConstTag') {
279-
let child_scope = scope;
280-
walk(node, {
281-
enter(node: Node, parent: any) {
282-
if (map.has(node)) child_scope = map.get(node);
283-
if (node.type === 'Identifier' && is_reference(node, parent)) {
284-
if (child_scope.has(node.name)) return;
285-
this.replace(block.renderer.reference(node, ctx));
286-
}
287-
},
288-
leave(node: Node) {
289-
if (map.has(node)) child_scope = child_scope.parent;
290-
}
291-
});
292-
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
293-
// we can hoist this out of the component completely
294-
component.fully_hoisted.push(declaration);
295-
296-
this.replace(id as any);
297-
298-
component.add_var(node, {
299-
name: id.name,
300-
internal: true,
301-
hoistable: true,
302-
referenced: true
303-
});
304-
} else if (contextual_dependencies.size === 0) {
305-
// function can be hoisted inside the component init
306-
component.partly_hoisted.push(declaration);
307-
308-
block.renderer.add_to_context(id.name);
309-
this.replace(block.renderer.reference(id));
310-
} else {
311-
// we need a combo block/init recipe
277+
const extract_functions = () => {
312278
const deps = Array.from(contextual_dependencies);
313279
const function_expression = node as FunctionExpression;
314280

@@ -318,7 +284,7 @@ export default class Expression {
318284
...function_expression.params
319285
];
320286

321-
const context_args = deps.map(name => block.renderer.reference(name));
287+
const context_args = deps.map(name => block.renderer.reference(name, ctx));
322288

323289
component.partly_hoisted.push(declaration);
324290

@@ -334,6 +300,50 @@ export default class Expression {
334300
: b`function ${id}() {
335301
return ${callee}(${context_args});
336302
}`;
303+
return { deps, func_declaration };
304+
};
305+
306+
if (owner.type === 'ConstTag') {
307+
// we need a combo block/init recipe
308+
if (contextual_dependencies.size === 0) {
309+
let child_scope = scope;
310+
walk(node, {
311+
enter(node: Node, parent: any) {
312+
if (map.has(node)) child_scope = map.get(node);
313+
if (node.type === 'Identifier' && is_reference(node, parent)) {
314+
if (child_scope.has(node.name)) return;
315+
this.replace(block.renderer.reference(node, ctx));
316+
}
317+
},
318+
leave(node: Node) {
319+
if (map.has(node)) child_scope = child_scope.parent;
320+
}
321+
});
322+
} else {
323+
const { func_declaration } = extract_functions();
324+
this.replace(func_declaration[0]);
325+
}
326+
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
327+
// we can hoist this out of the component completely
328+
component.fully_hoisted.push(declaration);
329+
330+
this.replace(id as any);
331+
332+
component.add_var(node, {
333+
name: id.name,
334+
internal: true,
335+
hoistable: true,
336+
referenced: true
337+
});
338+
} else if (contextual_dependencies.size === 0) {
339+
// function can be hoisted inside the component init
340+
component.partly_hoisted.push(declaration);
341+
342+
block.renderer.add_to_context(id.name);
343+
this.replace(block.renderer.reference(id));
344+
} else {
345+
// we need a combo block/init recipe
346+
const { deps, func_declaration } = extract_functions();
337347

338348
if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
339349
const dep_scopes = new Set<INode>(deps.map(name => template_scope.get_owner(name)));
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default {
2+
html: `
3+
<div>[Y] A <button>Toggle</button></div>
4+
<div>[N] B <button>Toggle</button></div>
5+
<div>[N] C <button>Toggle</button></div>
6+
`,
7+
async test({ component, target, assert, window }) {
8+
const [btn1, btn2, btn3] = target.querySelectorAll('button');
9+
await btn1.dispatchEvent(new window.MouseEvent('click'));
10+
await btn2.dispatchEvent(new window.MouseEvent('click'));
11+
12+
assert.htmlEqual(target.innerHTML, `
13+
<div>[N] A <button>Toggle</button></div>
14+
<div>[Y] B <button>Toggle</button></div>
15+
<div>[N] C <button>Toggle</button></div>
16+
`);
17+
18+
await btn2.dispatchEvent(new window.MouseEvent('click'));
19+
20+
assert.htmlEqual(target.innerHTML, `
21+
<div>[N] A <button>Toggle</button></div>
22+
<div>[N] B <button>Toggle</button></div>
23+
<div>[N] C <button>Toggle</button></div>
24+
`);
25+
26+
await btn3.dispatchEvent(new window.MouseEvent('click'));
27+
28+
assert.htmlEqual(target.innerHTML, `
29+
<div>[N] A <button>Toggle</button></div>
30+
<div>[N] B <button>Toggle</button></div>
31+
<div>[Y] C <button>Toggle</button></div>
32+
`);
33+
}
34+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
let items = [
3+
{ name: 'A', selected: true },
4+
{ name: 'B', selected: false },
5+
{ name: 'C', selected: false },
6+
]
7+
</script>
8+
9+
{#each items as item}
10+
{@const toggle = () => item.selected = !item.selected}
11+
<div>
12+
{item.selected ? '[Y]' : '[N]'}
13+
{item.name}
14+
<button on:click={toggle}>Toggle</button>
15+
</div>
16+
{/each}

0 commit comments

Comments
 (0)