Skip to content

Commit d8b4dd7

Browse files
authored
Merge pull request #3533 from sveltejs/gh-3512
inline $$invalidate calls
2 parents fafb390 + 21e0213 commit d8b4dd7

File tree

14 files changed

+141
-206
lines changed

14 files changed

+141
-206
lines changed

src/compiler/compile/Component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ export default class Component {
813813
const variable = this.var_lookup.get(name);
814814

815815
if (variable && (variable.subscribable && variable.reassigned)) {
816-
return `$$subscribe_${name}(), $$invalidate('${name}', ${value || name})`;
816+
return `$$subscribe_${name}($$invalidate('${name}', ${value || name}))`;
817817
}
818818

819819
if (name[0] === '$' && name[1] !== '$') {

src/compiler/compile/nodes/shared/Expression.ts

+20-101
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import { Node } from '../../../interfaces';
77
import { globals , sanitize } from '../../../utils/names';
88
import deindent from '../../utils/deindent';
99
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
10-
1110
import TemplateScope from './TemplateScope';
1211
import get_object from '../../utils/get_object';
13-
import { nodes_match } from '../../../utils/nodes_match';
1412
import Block from '../../render_dom/Block';
1513
import { INode } from '../interfaces';
1614
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
15+
import { invalidate } from '../../utils/invalidate';
1716

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

243242
let function_expression;
244-
let pending_assignments: Set<string> = new Set();
245243

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

311309
if (node === function_expression) {
312-
if (pending_assignments.size > 0) {
313-
if (node.type !== 'ArrowFunctionExpression') {
314-
// this should never happen!
315-
throw new Error(`Well that's odd`);
316-
}
317-
318-
// TOOD optimisation — if this is an event handler,
319-
// the return value doesn't matter
320-
}
321-
322310
const name = component.get_unique_name(
323311
sanitize(get_function_name(node, owner))
324312
);
@@ -334,40 +322,11 @@ export default class Expression {
334322
args.push(original_params);
335323
}
336324

337-
let body = code.slice(node.body.start, node.body.end).trim();
338-
if (node.body.type !== 'BlockStatement') {
339-
if (pending_assignments.size > 0) {
340-
const dependencies = new Set();
341-
pending_assignments.forEach(name => {
342-
if (template_scope.names.has(name)) {
343-
template_scope.dependencies_for_name.get(name).forEach(dependency => {
344-
dependencies.add(dependency);
345-
});
346-
} else {
347-
dependencies.add(name);
348-
}
349-
});
350-
351-
const insert = Array.from(dependencies).map(name => component.invalidate(name)).join('; ');
352-
pending_assignments = new Set();
325+
const body = code.slice(node.body.start, node.body.end).trim();
353326

354-
component.has_reactive_assignments = true;
355-
356-
body = deindent`
357-
{
358-
const $$result = ${body};
359-
${insert};
360-
return $$result;
361-
}
362-
`;
363-
} else {
364-
body = `{\n\treturn ${body};\n}`;
365-
}
366-
}
367-
368-
const fn = deindent`
369-
${node.async && 'async '}function${node.generator && '*'} ${name}(${args.join(', ')}) ${body}
370-
`;
327+
const fn = node.type === 'FunctionExpression'
328+
? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}`
329+
: `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`;
371330

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

424-
if (node.type === 'AssignmentExpression') {
425-
const names = node.left.type === 'MemberExpression'
426-
? [get_object(node.left).name]
427-
: extract_names(node.left);
383+
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
384+
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
428385

429-
if (node.operator === '=' && nodes_match(node.left, node.right)) {
430-
const dirty = names.filter(name => {
431-
return !scope.declarations.has(name);
432-
});
433-
434-
if (dirty.length) component.has_reactive_assignments = true;
386+
// normally (`a = 1`, `b.c = 2`), there'll be a single name
387+
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
388+
// may be more, in which case we need to tack the extra ones
389+
// onto the initial function call
390+
const names = new Set(extract_names(assignee));
435391

436-
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
437-
} else {
438-
names.forEach(name => {
439-
if (scope.declarations.has(name)) return;
440-
441-
const variable = component.var_lookup.get(name);
442-
if (variable && variable.hoistable) return;
443-
444-
pending_assignments.add(name);
445-
});
446-
}
447-
} else if (node.type === 'UpdateExpression') {
448-
const { name } = get_object(node.argument);
449-
450-
if (scope.declarations.has(name)) return;
451-
452-
const variable = component.var_lookup.get(name);
453-
if (variable && variable.hoistable) return;
454-
455-
pending_assignments.add(name);
456-
}
457-
458-
if (/Statement/.test(node.type)) {
459-
if (pending_assignments.size > 0) {
460-
const has_semi = code.original[node.end - 1] === ';';
461-
462-
const insert = (
463-
(has_semi ? ' ' : '; ') +
464-
Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ')
465-
);
466-
467-
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
468-
if (node.argument) {
469-
code.overwrite(node.start, node.argument.start, `var $$result = `);
470-
code.appendLeft(node.end, `${insert}; return $$result`);
471-
} else {
472-
code.prependRight(node.start, `${insert}; `);
473-
}
474-
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
475-
code.prependRight(node.start, '{ ');
476-
code.appendLeft(node.end, `${insert}; }`);
392+
const traced: Set<string> = new Set();
393+
names.forEach(name => {
394+
const dependencies = template_scope.dependencies_for_name.get(name);
395+
if (dependencies) {
396+
dependencies.forEach(name => traced.add(name));
477397
} else {
478-
code.appendLeft(node.end, `${insert};`);
398+
traced.add(name);
479399
}
400+
});
480401

481-
component.has_reactive_assignments = true;
482-
pending_assignments = new Set();
483-
}
402+
invalidate(component, scope, code, node, traced);
484403
}
485404
}
486405
});

src/compiler/compile/render_dom/index.ts

+10-89
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import { CompileOptions } from '../../interfaces';
77
import { walk } from 'estree-walker';
88
import { stringify_props } from '../utils/stringify_props';
99
import add_to_set from '../utils/add_to_set';
10-
import get_object from '../utils/get_object';
1110
import { extract_names } from '../utils/scope';
12-
import { nodes_match } from '../../utils/nodes_match';
11+
import { invalidate } from '../utils/invalidate';
1312

1413
export default function dom(
1514
component: Component,
@@ -158,111 +157,33 @@ export default function dom(
158157
let scope = component.instance_scope;
159158
const map = component.instance_scope_map;
160159

161-
let pending_assignments = new Set();
162-
163160
walk(component.ast.instance.content, {
164161
enter: (node) => {
165162
if (map.has(node)) {
166163
scope = map.get(node);
167164
}
168165
},
169166

170-
leave(node, parent) {
167+
leave(node) {
171168
if (map.has(node)) {
172169
scope = scope.parent;
173170
}
174171

172+
// TODO dry out — most of this is shared with Expression.ts
175173
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
176174
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
177-
let names = [];
178175

179-
if (assignee.type === 'MemberExpression') {
180-
const left_object_name = get_object(assignee).name;
181-
left_object_name && (names = [left_object_name]);
182-
} else {
183-
names = extract_names(assignee);
184-
}
176+
// normally (`a = 1`, `b.c = 2`), there'll be a single name
177+
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
178+
// may be more, in which case we need to tack the extra ones
179+
// onto the initial function call
180+
const names = new Set(extract_names(assignee));
185181

186-
if (node.operator === '=' && nodes_match(node.left, node.right)) {
187-
const dirty = names.filter(name => {
188-
return name[0] === '$' || scope.find_owner(name) === component.instance_scope;
189-
});
190-
191-
if (dirty.length) component.has_reactive_assignments = true;
192-
193-
code.overwrite(node.start, node.end, dirty.map(n => component.invalidate(n)).join('; '));
194-
} else {
195-
const single = (
196-
node.type === 'AssignmentExpression' &&
197-
assignee.type === 'Identifier' &&
198-
parent.type === 'ExpressionStatement' &&
199-
assignee.name[0] !== '$'
200-
);
201-
202-
names.forEach(name => {
203-
const owner = scope.find_owner(name);
204-
if (owner && owner !== component.instance_scope) return;
205-
206-
const variable = component.var_lookup.get(name);
207-
if (variable && (variable.hoistable || variable.global || variable.module)) return;
208-
209-
if (single && !(variable.subscribable && variable.reassigned)) {
210-
if (variable.referenced || variable.is_reactive_dependency || variable.export_name) {
211-
code.prependRight(node.start, `$$invalidate('${name}', `);
212-
code.appendLeft(node.end, `)`);
213-
}
214-
} else {
215-
pending_assignments.add(name);
216-
}
217-
218-
component.has_reactive_assignments = true;
219-
});
220-
}
221-
}
222-
223-
if (pending_assignments.size > 0) {
224-
if (node.type === 'ArrowFunctionExpression') {
225-
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
226-
pending_assignments = new Set();
227-
228-
code.prependRight(node.body.start, `{ const $$result = `);
229-
code.appendLeft(node.body.end, `; ${insert}; return $$result; }`);
230-
231-
pending_assignments = new Set();
232-
}
233-
234-
else if (/Statement/.test(node.type)) {
235-
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
236-
237-
if (/^(Break|Continue|Return)Statement/.test(node.type)) {
238-
if (node.argument) {
239-
code.overwrite(node.start, node.argument.start, `var $$result = `);
240-
code.appendLeft(node.argument.end, `; ${insert}; return $$result`);
241-
} else {
242-
code.prependRight(node.start, `${insert}; `);
243-
}
244-
} else if (parent && /(If|For(In|Of)?|While)Statement/.test(parent.type) && node.type !== 'BlockStatement') {
245-
code.prependRight(node.start, '{ ');
246-
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert}; }`);
247-
} else {
248-
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`);
249-
}
250-
251-
pending_assignments = new Set();
252-
} else if (parent && parent.type !== 'ForStatement' && node.type === 'VariableDeclaration') {
253-
const insert = Array.from(pending_assignments).map(name => component.invalidate(name)).join('; ');
254-
255-
code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`);
256-
pending_assignments = new Set();
257-
}
182+
invalidate(component, scope, code, node, names);
258183
}
259184
}
260185
});
261186

262-
if (pending_assignments.size > 0) {
263-
throw new Error(`TODO this should not happen!`);
264-
}
265-
266187
component.rewrite_props(({ name, reassigned }) => {
267188
const value = `$${name}`;
268189

@@ -395,7 +316,7 @@ export default function dom(
395316

396317
const store = component.var_lookup.get(name);
397318
if (store && store.reassigned) {
398-
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => { $$unsubscribe_${name}(); $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }) }`;
319+
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => ($$unsubscribe_${name}(), $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }), ${name})`;
399320
}
400321

401322
return $name;
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Component from '../Component';
2+
import MagicString from 'magic-string';
3+
import { Node } from '../../interfaces';
4+
import { nodes_match } from '../../utils/nodes_match';
5+
import { Scope } from './scope';
6+
7+
export function invalidate(component: Component, scope: Scope, code: MagicString, node: Node, names: Set<string>) {
8+
const [head, ...tail] = Array.from(names).filter(name => {
9+
const owner = scope.find_owner(name);
10+
if (owner && owner !== component.instance_scope) return false;
11+
12+
const variable = component.var_lookup.get(name);
13+
14+
return variable && (
15+
!variable.hoistable &&
16+
!variable.global &&
17+
!variable.module &&
18+
(
19+
variable.referenced ||
20+
variable.is_reactive_dependency ||
21+
variable.export_name
22+
)
23+
);
24+
});
25+
26+
if (head) {
27+
component.has_reactive_assignments = true;
28+
29+
if (node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
30+
code.overwrite(node.start, node.end, component.invalidate(head));
31+
} else {
32+
let suffix = ')';
33+
34+
if (head[0] === '$') {
35+
code.prependRight(node.start, `${component.helper('set_store_value')}(${head.slice(1)}, `);
36+
} else {
37+
let prefix = `$$invalidate`;
38+
39+
const variable = component.var_lookup.get(head);
40+
if (variable.subscribable && variable.reassigned) {
41+
prefix = `$$subscribe_${head}($$invalidate`;
42+
suffix += `)`;
43+
}
44+
45+
code.prependRight(node.start, `${prefix}('${head}', `);
46+
}
47+
48+
const extra_args = tail.map(name => component.invalidate(name));
49+
50+
const pass_value = (
51+
extra_args.length > 0 ||
52+
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
53+
(node.type === 'UpdateExpression' && !node.prefix)
54+
);
55+
56+
if (pass_value) {
57+
extra_args.unshift(head);
58+
}
59+
60+
suffix = `${extra_args.map(arg => `, ${arg}`).join('')}${suffix}`;
61+
62+
code.appendLeft(node.end, suffix);
63+
}
64+
}
65+
}

src/runtime/internal/Component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
101101
let ready = false;
102102

103103
$$.ctx = instance
104-
? instance(component, props, (key, value) => {
104+
? instance(component, props, (key, ret, value = ret) => {
105105
if ($$.ctx && not_equal($$.ctx[key], $$.ctx[key] = value)) {
106106
if ($$.bound[key]) $$.bound[key](value);
107107
if (ready) make_dirty(component, key);
108108
}
109+
return ret;
109110
})
110111
: props;
111112

0 commit comments

Comments
 (0)