Skip to content

Commit

Permalink
Merge pull request #3533 from sveltejs/gh-3512
Browse files Browse the repository at this point in the history
inline $$invalidate calls
  • Loading branch information
Rich-Harris committed Sep 8, 2019
2 parents fafb390 + 21e0213 commit d8b4dd7
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 206 deletions.
2 changes: 1 addition & 1 deletion src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ export default class Component {
const variable = this.var_lookup.get(name);

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] !== '$') {
Expand Down
121 changes: 20 additions & 101 deletions src/compiler/compile/nodes/shared/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { Node } from '../../../interfaces';
import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';

import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render_dom/Block';
import { INode } from '../interfaces';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { invalidate } from '../../utils/invalidate';

const binary_operators: Record<string, number> = {
'**': 15,
Expand Down Expand Up @@ -241,7 +240,6 @@ export default class Expression {
const { code } = component;

let function_expression;
let pending_assignments: Set<string> = new Set();

let dependencies: Set<string>;
let contextual_dependencies: Set<string>;
Expand Down Expand Up @@ -309,16 +307,6 @@ export default class Expression {
if (map.has(node)) scope = scope.parent;

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(
sanitize(get_function_name(node, owner))
);
Expand All @@ -334,40 +322,11 @@ export default class Expression {
args.push(original_params);
}

let 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();
const body = code.slice(node.body.start, node.body.end).trim();

component.has_reactive_assignments = true;

body = deindent`
{
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}
`;
const fn = node.type === 'FunctionExpression'
? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}`
: `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`;

if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely
Expand Down Expand Up @@ -421,66 +380,26 @@ export default class Expression {
contextual_dependencies = null;
}

if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression'
? [get_object(node.left).name]
: extract_names(node.left);
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;

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('; '));
} else {
names.forEach(name => {
if (scope.declarations.has(name)) return;

const variable = component.var_lookup.get(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}; }`);
const traced: Set<string> = new Set();
names.forEach(name => {
const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) {
dependencies.forEach(name => traced.add(name));
} else {
code.appendLeft(node.end, `${insert};`);
traced.add(name);
}
});

component.has_reactive_assignments = true;
pending_assignments = new Set();
}
invalidate(component, scope, code, node, traced);
}
}
});
Expand Down
99 changes: 10 additions & 89 deletions src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker';
import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set';
import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match';
import { invalidate } from '../utils/invalidate';

export default function dom(
component: Component,
Expand Down Expand Up @@ -158,111 +157,33 @@ export default function dom(
let scope = component.instance_scope;
const map = component.instance_scope_map;

let pending_assignments = new Set();

walk(component.ast.instance.content, {
enter: (node) => {
if (map.has(node)) {
scope = map.get(node);
}
},

leave(node, parent) {
leave(node) {
if (map.has(node)) {
scope = scope.parent;
}

// TODO dry out — most of this is shared with Expression.ts
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
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);
}
// 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 (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;
});
}
}

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();
}

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();
}
invalidate(component, scope, code, node, names);
}
}
});

if (pending_assignments.size > 0) {
throw new Error(`TODO this should not happen!`);
}

component.rewrite_props(({ name, reassigned }) => {
const value = `$${name}`;

Expand Down Expand Up @@ -395,7 +316,7 @@ export default function dom(

const store = component.var_lookup.get(name);
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;
Expand Down
65 changes: 65 additions & 0 deletions src/compiler/compile/utils/invalidate.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
3 changes: 2 additions & 1 deletion src/runtime/internal/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
let ready = false;

$$.ctx = instance
? instance(component, props, (key, value) => {
? instance(component, props, (key, ret, value = ret) => {
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
if ($$.bound[key]) $$.bound[key](value);
if (ready) make_dirty(component, key);
}
return ret;
})
: props;

Expand Down
Loading

0 comments on commit d8b4dd7

Please sign in to comment.