unsubscribe and resubscribe when stores are reassigned - fixes #2014

pull/2099/head
Richard Harris 6 years ago
parent 106ae45dc8
commit f124f3c081

@ -750,7 +750,15 @@ export default class Component {
}); });
} }
rewrite_props(get_insert: (name: string) => string) { invalidate(name, value = name) {
const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) {
return `$$subscribe_${name}(), $$invalidate('${name}', ${value})`;
}
return `$$invalidate('${name}', ${value})`;
}
rewrite_props(get_insert: (variable: Var) => string) {
const component = this; const component = this;
const { code, instance_scope, instance_scope_map: map, componentOptions } = this; const { code, instance_scope, instance_scope_map: map, componentOptions } = this;
let scope = instance_scope; let scope = instance_scope;
@ -788,7 +796,7 @@ export default class Component {
} }
if (variable.subscribable) { if (variable.subscribable) {
inserts.push(get_insert(name)); inserts.push(get_insert(variable));
} }
}); });
@ -834,7 +842,7 @@ export default class Component {
coalesced_declarations.push({ coalesced_declarations.push({
kind: node.kind, kind: node.kind,
declarators: [declarator], declarators: [declarator],
insert: get_insert(name) insert: get_insert(variable)
}); });
} else { } else {
if (current_group && current_group.kind !== node.kind) { if (current_group && current_group.kind !== node.kind) {
@ -852,7 +860,7 @@ export default class Component {
current_group = null; current_group = null;
if (variable.subscribable) { if (variable.subscribable) {
let insert = get_insert(name); let insert = get_insert(variable);
if (next) { if (next) {
code.overwrite(declarator.end, next.start, `; ${insert}; ${node.kind} `); code.overwrite(declarator.end, next.start, `; ${insert}; ${node.kind} `);

@ -290,7 +290,7 @@ export default class Expression {
if (dirty.length) component.has_reactive_assignments = true; if (dirty.length) component.has_reactive_assignments = true;
code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; ')); code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else { } else {
names.forEach(name => { names.forEach(name => {
if (scope.declarations.has(name)) return; if (scope.declarations.has(name)) return;
@ -356,7 +356,7 @@ export default class Expression {
let body = code.slice(node.body.start, node.body.end).trim(); let body = code.slice(node.body.start, node.body.end).trim();
if (node.body.type !== 'BlockStatement') { if (node.body.type !== 'BlockStatement') {
if (pending_assignments.size > 0) { if (pending_assignments.size > 0) {
const insert = Array.from(pending_assignments).map(name => `$$invalidate('${name}', ${name})`).join('; '); const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set(); pending_assignments = new Set();
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
@ -431,7 +431,7 @@ export default class Expression {
const insert = ( const insert = (
(has_semi ? ' ' : '; ') + (has_semi ? ' ' : '; ') +
Array.from(pending_assignments).map(name => `$$invalidate('${name}', ${name})`).join('; ') Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ')
); );
if (/^(Break|Continue|Return)Statement/.test(node.type)) { if (/^(Break|Continue|Return)Statement/.test(node.type)) {

@ -79,12 +79,13 @@ export default function dom(
${component.componentOptions.props && deindent` ${component.componentOptions.props && deindent`
if (!${component.componentOptions.props}) ${component.componentOptions.props} = {}; if (!${component.componentOptions.props}) ${component.componentOptions.props} = {};
@assign(${component.componentOptions.props}, $$props); @assign(${component.componentOptions.props}, $$props);
$$invalidate('${component.componentOptions.props_object}', ${component.componentOptions.props_object}); ${component.invalidate(component.componentOptions.props_object)};
`} `}
${writable_props.map(prop => ${writable_props.map(prop =>
`if ('${prop.export_name}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.export_name});`)} `if ('${prop.export_name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = $$props.${prop.export_name}`)};`
)}
${renderer.slots.size > 0 && ${renderer.slots.size > 0 &&
`if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`} `if ('$$scope' in $$props) ${component.invalidate('$$scope', `$$scope = $$props.$$scope`)};`}
} }
` `
: null; : null;
@ -175,7 +176,7 @@ export default function dom(
if (dirty.length) component.has_reactive_assignments = true; if (dirty.length) component.has_reactive_assignments = true;
code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; ')); code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else { } else {
names.forEach(name => { names.forEach(name => {
const owner = scope.findOwner(name); const owner = scope.findOwner(name);
@ -204,7 +205,7 @@ export default function dom(
if (pending_assignments.size > 0) { if (pending_assignments.size > 0) {
if (node.type === 'ArrowFunctionExpression') { if (node.type === 'ArrowFunctionExpression') {
const insert = Array.from(pending_assignments).map(name => `$$invalidate('${name}', ${name})`).join(';'); const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set(); pending_assignments = new Set();
code.prependRight(node.body.start, `{ const $$result = `); code.prependRight(node.body.start, `{ const $$result = `);
@ -214,7 +215,7 @@ export default function dom(
} }
else if (/Statement/.test(node.type)) { else if (/Statement/.test(node.type)) {
const insert = Array.from(pending_assignments).map(name => `$$invalidate('${name}', ${name})`).join('; '); const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
if (/^(Break|Continue|Return)Statement/.test(node.type)) { if (/^(Break|Continue|Return)Statement/.test(node.type)) {
if (node.argument) { if (node.argument) {
@ -240,12 +241,18 @@ export default function dom(
throw new Error(`TODO this should not happen!`); throw new Error(`TODO this should not happen!`);
} }
component.rewrite_props(name => { component.rewrite_props(({ name, reassigned }) => {
const value = `$${name}`; const value = `$${name}`;
const callback = `$value => { ${value} = $$value; $$invalidate('${value}', ${value}) }`;
if (reassigned) {
return `$$subscribe_${name}()`;
}
const subscribe = component.helper('subscribe'); const subscribe = component.helper('subscribe');
let insert = `${subscribe}($$self, ${name}, $$value => { ${value} = $$value; $$invalidate('${value}', ${value}) })`; let insert = `${subscribe}($$self, ${name}, $${callback})`;
if (component.compileOptions.dev) { if (component.compileOptions.dev) {
const validate_store = component.helper('validate_store'); const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`; insert = `${validate_store}(${name}, '${name}'); ${insert}`;
@ -346,6 +353,13 @@ export default function dom(
@subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); }); @subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); });
`); `);
const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable.reassigned;
})
.map(({ name }) => `$$self.$$.on_destroy.push(() => $$unsubscribe_${name.slice(1)}());`);
if (has_definition) { if (has_definition) {
const reactive_declarations = component.reactive_declarations.map(d => { const reactive_declarations = component.reactive_declarations.map(d => {
const condition = Array.from(d.dependencies) const condition = Array.from(d.dependencies)
@ -371,12 +385,26 @@ export default function dom(
return variable.injected; return variable.injected;
}); });
const reactive_store_declarations = reactive_stores.map(variable => {
const $name = variable.name;
const name = $name.slice(1);
const store = component.var_lookup.get(name);
if (store.reassigned) {
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = ${name}.subscribe($$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`
}
return $name;
});
builder.addBlock(deindent` builder.addBlock(deindent`
function ${definition}(${args.join(', ')}) { function ${definition}(${args.join(', ')}) {
${reactive_stores.length > 0 && `let ${reactive_stores.map(store => store.name).join(', ')};`} ${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`}
${reactive_store_subscriptions} ${reactive_store_subscriptions}
${resubscribable_reactive_store_unsubscribers}
${user_code} ${user_code}
${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`} ${renderer.slots.size && `let { ${[...renderer.slots].map(name => `$$slot_${sanitize(name)}`).join(', ')}, $$scope } = $$props;`}

@ -465,7 +465,7 @@ export default class ElementWrapper extends Wrapper {
this.renderer.component.partly_hoisted.push(deindent` this.renderer.component.partly_hoisted.push(deindent`
function ${handler}(${contextual_dependencies.size > 0 ? `{ ${Array.from(contextual_dependencies).join(', ')} }` : ``}) { function ${handler}(${contextual_dependencies.size > 0 ? `{ ${Array.from(contextual_dependencies).join(', ')} }` : ``}) {
${group.bindings.map(b => b.handler.mutation)} ${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => `$$invalidate('${dep}', ${dep});`)} ${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => `${this.renderer.component.invalidate(dep)};`)}
} }
`); `);
@ -532,7 +532,7 @@ export default class ElementWrapper extends Wrapper {
renderer.component.partly_hoisted.push(deindent` renderer.component.partly_hoisted.push(deindent`
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) { function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation} ${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
$$invalidate('${object}', ${object}); ${renderer.component.invalidate(object)};
} }
`); `);

@ -266,7 +266,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.partly_hoisted.push(deindent` component.partly_hoisted.push(deindent`
function ${fn}($$component) { function ${fn}($$component) {
${lhs} = $$component; ${lhs} = $$component;
${object && `$$invalidate('${object}', ${object});`} ${object && component.invalidate(object)}
} }
`); `);
@ -341,7 +341,7 @@ export default class InlineComponentWrapper extends Wrapper {
const body = deindent` const body = deindent`
function ${name}(${args.join(', ')}) { function ${name}(${args.join(', ')}) {
${lhs} = value; ${lhs} = value;
return $$invalidate('${dependencies[0]}', ${dependencies[0]}); return ${component.invalidate(dependencies[0])}
} }
`; `;

@ -40,7 +40,7 @@ export default function ssr(
let user_code; let user_code;
if (component.javascript) { if (component.javascript) {
component.rewrite_props(name => { component.rewrite_props(({ name }) => {
const value = `$${name}`; const value = `$${name}`;
const get_store_value = component.helper('get_store_value'); const get_store_value = component.helper('get_store_value');

@ -0,0 +1,36 @@
export default {
html: `
<h1>0</h1>
<button>+1</button>
<button>reset</button>
`,
async test({ assert, component, target, window }) {
const buttons = target.querySelectorAll('button');
const click = new window.MouseEvent('click');
await buttons[0].dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<h1>1</h1>
<button>+1</button>
<button>reset</button>
`);
await buttons[1].dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<h1>0</h1>
<button>+1</button>
<button>reset</button>
`);
await buttons[0].dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `
<h1>1</h1>
<button>+1</button>
<button>reset</button>
`);
}
};

@ -0,0 +1,8 @@
<script>
import { writable } from 'svelte/store';
let foo = writable(0);
</script>
<h1>{$foo}</h1>
<button on:click="{() => foo.update(n => n + 1)}">+1</button>
<button on:click="{() => foo = writable(0)}">reset</button>
Loading…
Cancel
Save