Skip to content

Commit 6af23ba

Browse files
authored
Fix contextual bind:this (#2806)
1 parent 46b2e77 commit 6af23ba

File tree

9 files changed

+141
-68
lines changed

9 files changed

+141
-68
lines changed

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import add_event_handlers from '../shared/add_event_handlers';
1919
import add_actions from '../shared/add_actions';
2020
import create_debugging_comment from '../shared/create_debugging_comment';
2121
import { get_context_merger } from '../shared/get_context_merger';
22+
import bind_this from '../shared/bind_this';
2223

2324
const events = [
2425
{
@@ -540,38 +541,9 @@ export default class ElementWrapper extends Wrapper {
540541

541542
const this_binding = this.bindings.find(b => b.node.name === 'this');
542543
if (this_binding) {
543-
const name = renderer.component.get_unique_name(`${this.var}_binding`);
544+
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
544545

545-
renderer.component.add_var({
546-
name,
547-
internal: true,
548-
referenced: true
549-
});
550-
551-
const { handler, object } = this_binding;
552-
553-
const args = [];
554-
for (const arg of handler.contextual_dependencies) {
555-
args.push(arg);
556-
block.add_variable(arg, `ctx.${arg}`);
557-
}
558-
559-
renderer.component.partly_hoisted.push(deindent`
560-
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
561-
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
562-
${renderer.component.invalidate(object)};
563-
}
564-
`);
565-
566-
block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
567-
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
568-
block.builders.update.add_line(deindent`
569-
if (changed.items) {
570-
ctx.${name}(${['null', this.var].concat(args).join(', ')});
571-
${args.map(a => `${a} = ctx.${a}`).join(', ')};
572-
ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
573-
}`
574-
);
546+
block.builders.mount.add_line(binding_callback);
575547
}
576548
}
577549

src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import add_to_set from '../../../utils/add_to_set';
99
import deindent from '../../../utils/deindent';
1010
import Attribute from '../../../nodes/Attribute';
1111
import get_object from '../../../utils/get_object';
12-
import flatten_reference from '../../../utils/flatten_reference';
1312
import create_debugging_comment from '../shared/create_debugging_comment';
1413
import { get_context_merger } from '../shared/get_context_merger';
1514
import EachBlock from '../../../nodes/EachBlock';
1615
import TemplateScope from '../../../nodes/shared/TemplateScope';
16+
import bind_this from '../shared/bind_this';
1717

1818
export default class InlineComponentWrapper extends Wrapper {
1919
var: string;
@@ -252,41 +252,7 @@ export default class InlineComponentWrapper extends Wrapper {
252252
component.has_reactive_assignments = true;
253253

254254
if (binding.name === 'this') {
255-
const fn = component.get_unique_name(`${this.var}_binding`);
256-
257-
component.add_var({
258-
name: fn,
259-
internal: true,
260-
referenced: true
261-
});
262-
263-
let lhs;
264-
let object;
265-
266-
if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
267-
// bind:x={y} — we can't just do `y = x`, we need to
268-
// to `array[index] = x;
269-
const { name } = binding.expression.node;
270-
const { snippet } = block.bindings.get(name);
271-
lhs = snippet;
272-
273-
// TODO we need to invalidate... something
274-
} else {
275-
object = flatten_reference(binding.expression.node).name;
276-
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
277-
}
278-
279-
const contextual_dependencies = [...binding.expression.contextual_dependencies];
280-
281-
component.partly_hoisted.push(deindent`
282-
function ${fn}(${['$$component', ...contextual_dependencies].join(', ')}) {
283-
${lhs} = $$component;
284-
${object && component.invalidate(object)}
285-
}
286-
`);
287-
288-
block.builders.destroy.add_line(`ctx.${fn}(null);`);
289-
return `@add_binding_callback(() => ctx.${fn}(${[this.var, ...contextual_dependencies.map(name => `ctx.${name}`)].join(', ')}));`;
255+
return bind_this(component, block, binding, this.var);
290256
}
291257

292258
const name = component.get_unique_name(`${this.var}_${binding.name}_binding`);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import flatten_reference from '../../../utils/flatten_reference';
2+
import deindent from '../../../utils/deindent';
3+
import Component from '../../../Component';
4+
import Block from '../../Block';
5+
import Binding from '../../../nodes/Binding';
6+
7+
export default function bind_this(component: Component, block: Block, binding: Binding, variable: string) {
8+
const fn = component.get_unique_name(`${variable}_binding`);
9+
10+
component.add_var({
11+
name: fn,
12+
internal: true,
13+
referenced: true
14+
});
15+
16+
let lhs;
17+
let object;
18+
19+
if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
20+
// bind:x={y} — we can't just do `y = x`, we need to
21+
// to `array[index] = x;
22+
const { name } = binding.expression.node;
23+
const { snippet } = block.bindings.get(name);
24+
lhs = snippet;
25+
26+
// TODO we need to invalidate... something
27+
} else {
28+
object = flatten_reference(binding.expression.node).name;
29+
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
30+
}
31+
32+
const contextual_dependencies = Array.from(binding.expression.contextual_dependencies);
33+
34+
if (contextual_dependencies.length) {
35+
component.partly_hoisted.push(deindent`
36+
function ${fn}(${['$$value', ...contextual_dependencies].join(', ')}) {
37+
if (${lhs} === $$value) return;
38+
${lhs} = $$value;
39+
${object && component.invalidate(object)}
40+
}
41+
`);
42+
43+
const args = [];
44+
for (const arg of contextual_dependencies) {
45+
args.push(arg);
46+
block.add_variable(arg, `ctx.${arg}`);
47+
}
48+
49+
const assign = block.get_unique_name(`assign_${variable}`);
50+
const unassign = block.get_unique_name(`unassign_${variable}`);
51+
52+
block.builders.init.add_block(deindent`
53+
const ${assign} = () => ctx.${fn}(${[variable].concat(args).join(', ')});
54+
const ${unassign} = () => ctx.${fn}(${['null'].concat(args).join(', ')});
55+
`);
56+
57+
const condition = Array.from(contextual_dependencies).map(name => `${name} !== ctx.${name}`).join(' || ');
58+
59+
block.builders.update.add_line(deindent`
60+
if (${condition}) {
61+
${unassign}();
62+
${args.map(a => `${a} = ctx.${a}`).join(', ')};
63+
@add_binding_callback(${assign});
64+
}`
65+
);
66+
67+
block.builders.destroy.add_line(`${unassign}();`);
68+
return `@add_binding_callback(${assign});`;
69+
}
70+
71+
component.partly_hoisted.push(deindent`
72+
function ${fn}($$value) {
73+
${lhs} = $$value;
74+
${object && component.invalidate(object)}
75+
}
76+
`);
77+
78+
block.builders.destroy.add_line(`ctx.${fn}(null);`);
79+
return `@add_binding_callback(() => ctx.${fn}(${variable}));`;
80+
}

src/runtime/internal/scheduler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function flush() {
4646
update(component.$$);
4747
}
4848

49-
while (binding_callbacks.length) binding_callbacks.shift()();
49+
while (binding_callbacks.length) binding_callbacks.pop()();
5050

5151
// then, once components are updated, call
5252
// afterUpdate functions. This may cause
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
export function isFoo() {
3+
return true;
4+
}
5+
</script>
6+
7+
<p><slot></slot></p>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
html: ``,
3+
4+
async test({ assert, component, target }) {
5+
component.visible = true;
6+
assert.htmlEqual(target.innerHTML, `
7+
<p>a</p>
8+
`);
9+
10+
assert.ok(component.items[0].ref.isFoo());
11+
}
12+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Foo from './Foo.svelte';
3+
4+
export let visible = false;
5+
6+
export let items = [{ value: 'a', ref: null }];
7+
</script>
8+
9+
{#if visible}
10+
{#each items as item}
11+
<Foo bind:this={item.ref}>{item.value}</Foo>
12+
{/each}
13+
{/if}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
html: ``,
3+
4+
async test({ assert, component, target }) {
5+
component.visible = true;
6+
assert.htmlEqual(target.innerHTML, `
7+
<div>a</div>
8+
`);
9+
10+
assert.equal(component.items[0].ref, target.querySelector('div'));
11+
}
12+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
export let visible = false;
3+
4+
export let items = [{ value: 'a', ref: null }];
5+
</script>
6+
7+
{#if visible}
8+
{#each items as item}
9+
<div bind:this={item.ref}>{item.value}</div>
10+
{/each}
11+
{/if}

0 commit comments

Comments
 (0)