diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index d726b811a1..1b1558ec17 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -274,41 +274,7 @@ export default class Expression { ); const declaration = b`const ${id} = ${node}`; - - if (owner.type === 'ConstTag') { - let child_scope = scope; - walk(node, { - enter(node: Node, parent: any) { - if (map.has(node)) child_scope = map.get(node); - if (node.type === 'Identifier' && is_reference(node, parent)) { - if (child_scope.has(node.name)) return; - this.replace(block.renderer.reference(node, ctx)); - } - }, - leave(node: Node) { - if (map.has(node)) child_scope = child_scope.parent; - } - }); - } else if (dependencies.size === 0 && contextual_dependencies.size === 0) { - // we can hoist this out of the component completely - component.fully_hoisted.push(declaration); - - this.replace(id as any); - - component.add_var(node, { - name: id.name, - internal: true, - hoistable: true, - referenced: true - }); - } else if (contextual_dependencies.size === 0) { - // function can be hoisted inside the component init - component.partly_hoisted.push(declaration); - - block.renderer.add_to_context(id.name); - this.replace(block.renderer.reference(id)); - } else { - // we need a combo block/init recipe + const extract_functions = () => { const deps = Array.from(contextual_dependencies); const function_expression = node as FunctionExpression; @@ -318,7 +284,7 @@ export default class Expression { ...function_expression.params ]; - const context_args = deps.map(name => block.renderer.reference(name)); + const context_args = deps.map(name => block.renderer.reference(name, ctx)); component.partly_hoisted.push(declaration); @@ -334,6 +300,50 @@ export default class Expression { : b`function ${id}() { return ${callee}(${context_args}); }`; + return { deps, func_declaration }; + }; + + if (owner.type === 'ConstTag') { + // we need a combo block/init recipe + if (contextual_dependencies.size === 0) { + let child_scope = scope; + walk(node, { + enter(node: Node, parent: any) { + if (map.has(node)) child_scope = map.get(node); + if (node.type === 'Identifier' && is_reference(node, parent)) { + if (child_scope.has(node.name)) return; + this.replace(block.renderer.reference(node, ctx)); + } + }, + leave(node: Node) { + if (map.has(node)) child_scope = child_scope.parent; + } + }); + } else { + const { func_declaration } = extract_functions(); + this.replace(func_declaration[0]); + } + } else if (dependencies.size === 0 && contextual_dependencies.size === 0) { + // we can hoist this out of the component completely + component.fully_hoisted.push(declaration); + + this.replace(id as any); + + component.add_var(node, { + name: id.name, + internal: true, + hoistable: true, + referenced: true + }); + } else if (contextual_dependencies.size === 0) { + // function can be hoisted inside the component init + component.partly_hoisted.push(declaration); + + block.renderer.add_to_context(id.name); + this.replace(block.renderer.reference(id)); + } else { + // we need a combo block/init recipe + const { deps, func_declaration } = extract_functions(); if (owner.type === 'Attribute' && owner.parent.name === 'slot') { const dep_scopes = new Set(deps.map(name => template_scope.get_owner(name))); diff --git a/test/runtime/samples/const-tag-invalidate/_config.js b/test/runtime/samples/const-tag-invalidate/_config.js new file mode 100644 index 0000000000..ffb1f14816 --- /dev/null +++ b/test/runtime/samples/const-tag-invalidate/_config.js @@ -0,0 +1,34 @@ +export default { + html: ` +
[Y] A
+
[N] B
+
[N] C
+ `, + async test({ component, target, assert, window }) { + const [btn1, btn2, btn3] = target.querySelectorAll('button'); + await btn1.dispatchEvent(new window.MouseEvent('click')); + await btn2.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +
[N] A
+
[Y] B
+
[N] C
+ `); + + await btn2.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +
[N] A
+
[N] B
+
[N] C
+ `); + + await btn3.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +
[N] A
+
[N] B
+
[Y] C
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-invalidate/main.svelte b/test/runtime/samples/const-tag-invalidate/main.svelte new file mode 100644 index 0000000000..6e58cbd9e2 --- /dev/null +++ b/test/runtime/samples/const-tag-invalidate/main.svelte @@ -0,0 +1,16 @@ + + +{#each items as item} + {@const toggle = () => item.selected = !item.selected} +
+ {item.selected ? '[Y]' : '[N]'} + {item.name} + +
+{/each}