Merge pull request #3533 from sveltejs/gh-3512

inline $$invalidate calls
pull/7738/head
Rich Harris 6 years ago committed by GitHub
commit d442ad822d

@ -813,7 +813,7 @@ export default class Component {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) { if (variable && (variable.subscribable && variable.reassigned)) {
return `$$subscribe_${name}(), $$invalidate('${name}', ${value || name})`; return `$$subscribe_${name}($$invalidate('${name}', ${value || name}))`;
} }
if (name[0] === '$' && name[1] !== '$') { if (name[0] === '$' && name[1] !== '$') {

@ -7,13 +7,12 @@ import { Node } from '../../../interfaces';
import { globals , sanitize } from '../../../utils/names'; import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper'; import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope'; import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render_dom/Block'; import Block from '../../render_dom/Block';
import { INode } from '../interfaces'; import { INode } from '../interfaces';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { invalidate } from '../../utils/invalidate';
const binary_operators: Record<string, number> = { const binary_operators: Record<string, number> = {
'**': 15, '**': 15,
@ -241,7 +240,6 @@ export default class Expression {
const { code } = component; const { code } = component;
let function_expression; let function_expression;
let pending_assignments: Set<string> = new Set();
let dependencies: Set<string>; let dependencies: Set<string>;
let contextual_dependencies: Set<string>; let contextual_dependencies: Set<string>;
@ -309,16 +307,6 @@ export default class Expression {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
if (pending_assignments.size > 0) {
if (node.type !== 'ArrowFunctionExpression') {
// this should never happen!
throw new Error(`Well that's odd`);
}
// TOOD optimisation — if this is an event handler,
// the return value doesn't matter
}
const name = component.get_unique_name( const name = component.get_unique_name(
sanitize(get_function_name(node, owner)) sanitize(get_function_name(node, owner))
); );
@ -334,40 +322,11 @@ export default class Expression {
args.push(original_params); args.push(original_params);
} }
let body = code.slice(node.body.start, node.body.end).trim(); const body = code.slice(node.body.start, node.body.end).trim();
if (node.body.type !== 'BlockStatement') {
if (pending_assignments.size > 0) {
const dependencies = new Set();
pending_assignments.forEach(name => {
if (template_scope.names.has(name)) {
template_scope.dependencies_for_name.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else {
dependencies.add(name);
}
});
const insert = Array.from(dependencies).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set();
component.has_reactive_assignments = true; const fn = node.type === 'FunctionExpression'
? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}`
body = deindent` : `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`;
{
const $$result = ${body};
${insert};
return $$result;
}
`;
} else {
body = `{\n\treturn ${body};\n}`;
}
}
const fn = deindent`
${node.async && 'async '}function${node.generator && '*'} ${name}(${args.join(', ')}) ${body}
`;
if (dependencies.size === 0 && contextual_dependencies.size === 0) { if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely // we can hoist this out of the component completely
@ -421,66 +380,26 @@ export default class Expression {
contextual_dependencies = null; contextual_dependencies = null;
} }
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const names = node.left.type === 'MemberExpression' const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
? [get_object(node.left).name]
: extract_names(node.left);
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
return !scope.declarations.has(name);
});
if (dirty.length) component.has_reactive_assignments = true; // normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
// onto the initial function call
const names = new Set(extract_names(assignee));
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; ')); const traced: Set<string> = new Set();
} else {
names.forEach(name => { names.forEach(name => {
if (scope.declarations.has(name)) return; const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) {
const variable = component.var_lookup.get(name); dependencies.forEach(name => traced.add(name));
if (variable && variable.hoistable) return;
pending_assignments.add(name);
});
}
} else if (node.type === 'UpdateExpression') {
const { name } = get_object(node.argument);
if (scope.declarations.has(name)) return;
const variable = component.var_lookup.get(name);
if (variable && variable.hoistable) return;
pending_assignments.add(name);
}
if (/Statement/.test(node.type)) {
if (pending_assignments.size > 0) {
const has_semi = code.original[node.end - 1] === ';';
const insert = (
(has_semi ? ' ' : '; ') +
Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ')
);
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
if (node.argument) {
code.overwrite(node.start, node.argument.start, `var $$result = `);
code.appendLeft(node.end, `${insert}; return $$result`);
} else {
code.prependRight(node.start, `${insert}; `);
}
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
code.prependRight(node.start, '{ ');
code.appendLeft(node.end, `${insert}; }`);
} else { } else {
code.appendLeft(node.end, `${insert};`); traced.add(name);
} }
});
component.has_reactive_assignments = true; invalidate(component, scope, code, node, traced);
pending_assignments = new Set();
}
} }
} }
}); });

@ -7,9 +7,8 @@ import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { stringify_props } from '../utils/stringify_props'; import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set'; import add_to_set from '../utils/add_to_set';
import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match'; import { invalidate } from '../utils/invalidate';
export default function dom( export default function dom(
component: Component, component: Component,
@ -158,8 +157,6 @@ export default function dom(
let scope = component.instance_scope; let scope = component.instance_scope;
const map = component.instance_scope_map; const map = component.instance_scope_map;
let pending_assignments = new Set();
walk(component.ast.instance.content, { walk(component.ast.instance.content, {
enter: (node) => { enter: (node) => {
if (map.has(node)) { if (map.has(node)) {
@ -167,102 +164,26 @@ export default function dom(
} }
}, },
leave(node, parent) { leave(node) {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
} }
// TODO dry out — most of this is shared with Expression.ts
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
let names = [];
if (assignee.type === 'MemberExpression') {
const left_object_name = get_object(assignee).name;
left_object_name && (names = [left_object_name]);
} else {
names = extract_names(assignee);
}
if (node.operator === '=' && nodes_match(node.left, node.right)) {
const dirty = names.filter(name => {
return name[0] === '$' || scope.find_owner(name) === component.instance_scope;
});
if (dirty.length) component.has_reactive_assignments = true;
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
} else {
const single = (
node.type === 'AssignmentExpression' &&
assignee.type === 'Identifier' &&
parent.type === 'ExpressionStatement' &&
assignee.name[0] !== '$'
);
names.forEach(name => {
const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return;
const variable = component.var_lookup.get(name);
if (variable && (variable.hoistable || variable.global || variable.module)) return;
if (single && !(variable.subscribable && variable.reassigned)) {
if (variable.referenced || variable.is_reactive_dependency || variable.export_name) {
code.prependRight(node.start, `$$invalidate('${name}', `);
code.appendLeft(node.end, `)`);
}
} else {
pending_assignments.add(name);
}
component.has_reactive_assignments = true; // normally (`a = 1`, `b.c = 2`), there'll be a single name
}); // (a or b). In destructuring cases (`[d, e] = [e, d]`) there
} // may be more, in which case we need to tack the extra ones
} // onto the initial function call
const names = new Set(extract_names(assignee));
if (pending_assignments.size > 0) {
if (node.type === 'ArrowFunctionExpression') {
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
pending_assignments = new Set();
code.prependRight(node.body.start, `{ const $$result = `);
code.appendLeft(node.body.end, `; ${insert}; return $$result; }`);
pending_assignments = new Set(); invalidate(component, scope, code, node, names);
}
else if (/Statement/.test(node.type)) {
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
if (node.argument) {
code.overwrite(node.start, node.argument.start, `var $$result = `);
code.appendLeft(node.argument.end, `; ${insert}; return $$result`);
} else {
code.prependRight(node.start, `${insert}; `);
}
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
code.prependRight(node.start, '{ ');
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert}; }`);
} else {
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`);
}
pending_assignments = new Set();
} else if (parent && parent.type !== 'ForStatement' && node.type === 'VariableDeclaration') {
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`);
pending_assignments = new Set();
}
} }
} }
}); });
if (pending_assignments.size > 0) {
throw new Error(`TODO this should not happen!`);
}
component.rewrite_props(({ name, reassigned }) => { component.rewrite_props(({ name, reassigned }) => {
const value = `$${name}`; const value = `$${name}`;
@ -395,7 +316,7 @@ export default function dom(
const store = component.var_lookup.get(name); const store = component.var_lookup.get(name);
if (store && store.reassigned) { if (store && store.reassigned) {
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`; return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => ($$unsubscribe_${name}(), $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }), ${name})`;
} }
return $name; return $name;

@ -0,0 +1,65 @@
import Component from '../Component';
import MagicString from 'magic-string';
import { Node } from '../../interfaces';
import { nodes_match } from '../../utils/nodes_match';
import { Scope } from './scope';
export function invalidate(component: Component, scope: Scope, code: MagicString, node: Node, names: Set<string>) {
const [head, ...tail] = Array.from(names).filter(name => {
const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return false;
const variable = component.var_lookup.get(name);
return variable && (
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.referenced ||
variable.is_reactive_dependency ||
variable.export_name
)
);
});
if (head) {
component.has_reactive_assignments = true;
if (node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
code.overwrite(node.start, node.end, component.invalidate(head));
} else {
let suffix = ')';
if (head[0] === '$') {
code.prependRight(node.start, `${component.helper('set_store_value')}(${head.slice(1)}, `);
} else {
let prefix = `$$invalidate`;
const variable = component.var_lookup.get(head);
if (variable.subscribable && variable.reassigned) {
prefix = `$$subscribe_${head}($$invalidate`;
suffix += `)`;
}
code.prependRight(node.start, `${prefix}('${head}', `);
}
const extra_args = tail.map(name => component.invalidate(name));
const pass_value = (
extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && !node.prefix)
);
if (pass_value) {
extra_args.unshift(head);
}
suffix = `${extra_args.map(arg => `, ${arg}`).join('')}${suffix}`;
code.appendLeft(node.end, suffix);
}
}
}

@ -101,11 +101,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
let ready = false; let ready = false;
$$.ctx = instance $$.ctx = instance
? instance(component, props, (key, value) => { ? instance(component, props, (key, ret, value = ret) => {
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) { if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
if ($$.bound[key]) $$.bound[key](value); if ($$.bound[key]) $$.bound[key](value);
if (ready) make_dirty(component, key); if (ready) make_dirty(component, key);
} }
return ret;
}) })
: props; : props;

@ -100,3 +100,8 @@ export function once(fn) {
export function null_to_empty(value) { export function null_to_empty(value) {
return value == null ? '' : value; return value == null ? '' : value;
} }
export function set_store_value(store, ret, value = ret) {
store.set(value);
return ret;
}

@ -53,9 +53,7 @@ function foo(node, callback) {
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let { bar } = $$props; let { bar } = $$props;
function foo_function() { const foo_function = () => handleFoo(bar);
return handleFoo(bar);
}
$$self.$set = $$props => { $$self.$set = $$props => {
if ('bar' in $$props) $$invalidate('bar', bar = $$props.bar); if ('bar' in $$props) $$invalidate('bar', bar = $$props.bar);

@ -46,9 +46,7 @@ function create_fragment(ctx) {
}; };
} }
function func() { const func = () => import('./Foo.svelte');
return import('./Foo.svelte');
}
class Component extends SvelteComponent { class Component extends SvelteComponent {
constructor(options) { constructor(options) {

@ -40,9 +40,7 @@ function create_fragment(ctx) {
}; };
} }
function touchstart_handler(e) { const touchstart_handler = (e) => e.preventDefault();
return e.preventDefault();
}
class Component extends SvelteComponent { class Component extends SvelteComponent {
constructor(options) { constructor(options) {

@ -60,9 +60,9 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let x = 0; let x = 0;
function click_handler() { const click_handler = () => {
if (true) { x += 1; $$invalidate('x', x); } if (true) $$invalidate('x', x += 1);
} };
return { x, click_handler }; return { x, click_handler };
} }

@ -60,7 +60,7 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let things = []; let things = [];
function click_handler() { things.push(1); $$invalidate('things', things) } const click_handler = () => { things.push(1); $$invalidate('things', things) };
return { things, click_handler }; return { things, click_handler };
} }

@ -0,0 +1,18 @@
export default {
show: 1,
html: `<button>false 0</button>`,
async test({ assert, target, window }) {
const button = target.querySelector('button');
const click = new window.MouseEvent('click');
await button.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `<button>true 1</button>`);
await button.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `<button>false 1</button>`);
await button.dispatchEvent(click);
assert.htmlEqual(target.innerHTML, `<button>true 2</button>`);
}
};

@ -0,0 +1,12 @@
<script>
let current = { active: false };
let count = 0;
function toggle() {
if (current.active = !current.active) {
count += 1;
}
}
</script>
<button on:click={toggle}>{current.active} {count}</button>

@ -5,7 +5,7 @@ export default {
<button>reset</button> <button>reset</button>
`, `,
async test({ assert, component, target, window }) { async test({ assert, target, window }) {
const buttons = target.querySelectorAll('button'); const buttons = target.querySelectorAll('button');
const click = new window.MouseEvent('click'); const click = new window.MouseEvent('click');

Loading…
Cancel
Save