allow reactive declarations to reference self - fixes #2386

pull/2398/head
Richard Harris 6 years ago
parent d7b32fb22c
commit a88749af27

@ -5,7 +5,7 @@ import Stats from '../Stats';
import { globals, reserved } from '../utils/names'; import { globals, reserved } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces'; import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module'; import create_module from './create_module';
import { create_scopes, extract_names, Scope } from './utils/scope'; import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
@ -19,6 +19,7 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import { remove_indentation, add_indentation } from '../utils/indentation'; import { remove_indentation, add_indentation } from '../utils/indentation';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import unwrap_parens from './utils/unwrap_parens';
type ComponentOptions = { type ComponentOptions = {
namespace?: string; namespace?: string;
@ -93,7 +94,7 @@ export default class Component {
node_for_declaration: Map<string, Node> = new Map(); node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: string[] = []; partly_hoisted: string[] = [];
fully_hoisted: string[] = []; fully_hoisted: string[] = [];
reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, node: Node, injected: boolean }> = []; reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, node: Node, declaration: Node }> = [];
reactive_declaration_nodes: Set<Node> = new Set(); reactive_declaration_nodes: Set<Node> = new Set();
has_reactive_assignments = false; has_reactive_assignments = false;
injected_reactive_declaration_vars: Set<string> = new Set(); injected_reactive_declaration_vars: Set<string> = new Set();
@ -590,9 +591,11 @@ export default class Component {
script.content.body.forEach(node => { script.content.body.forEach(node => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
if (node.body.expression.type !== 'AssignmentExpression') return;
extract_names(node.body.expression.left).forEach(name => { const expression = unwrap_parens(node.body.expression);
if (expression.type !== 'AssignmentExpression') return;
extract_names(expression.left).forEach(name => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
@ -1058,15 +1061,10 @@ export default class Component {
} }
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
if (node.left.type === 'MemberExpression' || node.left.type === 'Identifier') { extract_identifiers(get_object(node.left)).forEach(node => {
const identifier = get_object(node.left) assignee_nodes.add(node);
assignee_nodes.add(identifier); assignees.add(node.name);
assignees.add(identifier.name); });
} else {
extract_names(node.left).forEach(name => {
assignees.add(name);
});
}
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
@ -1096,18 +1094,10 @@ export default class Component {
add_indentation(this.code, node.body, 2); add_indentation(this.code, node.body, 2);
unsorted_reactive_declarations.push({ const expression = node.body.expression && unwrap_parens(node.body.expression);
assignees, const declaration = expression && expression.left;
dependencies,
node, unsorted_reactive_declarations.push({ assignees, dependencies, node, declaration });
injected: (
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression' &&
extract_names(node.body.expression.left).every(name => {
return name[0] !== '$' && this.var_lookup.get(name).injected;
})
)
});
} }
}); });

@ -3,6 +3,7 @@ import Component from '../Component';
import { CompileOptions } from '../../interfaces'; import { CompileOptions } from '../../interfaces';
import { stringify } from '../utils/stringify'; import { stringify } from '../utils/stringify';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { extract_names } from '../utils/scope';
export default function ssr( export default function ssr(
component: Component, component: Component,
@ -63,8 +64,32 @@ export default function ssr(
: []; : [];
const reactive_declarations = component.reactive_declarations.map(d => { const reactive_declarations = component.reactive_declarations.map(d => {
const snippet = `[✂${d.node.body.start}-${d.node.end}✂]`; let snippet = `[✂${d.node.body.start}-${d.node.end}✂]`;
return d.injected ? `let ${snippet}` : snippet;
if (d.declaration) {
const declared = extract_names(d.declaration);
const injected = declared.filter(name => {
return name[0] !== '$' && component.var_lookup.get(name).injected;
});
const self_dependencies = injected.filter(name => d.dependencies.has(name));
if (injected.length) {
// in some cases we need to do `let foo; [expression]`, in
// others we can do `let [expression]`
const separate = (
self_dependencies.length > 0 ||
declared.length > injected.length ||
d.node.body.expression.type === 'ParenthesizedExpression'
);
snippet = separate
? `let ${injected.join(', ')}; ${snippet}`
: `let ${snippet}`;
}
}
return snippet;
}); });
const main = renderer.has_bindings const main = renderer.has_bindings

@ -1,7 +1,8 @@
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import unwrap_parens from './unwrap_parens';
export default function get_object(node: Node) { export default function get_object(node: Node) {
while (node.type === 'ParenthesizedExpression') node = node.expression; node = unwrap_parens(node);
while (node.type === 'MemberExpression') node = node.object; while (node.type === 'MemberExpression') node = node.object;
return node; return node;
} }

@ -101,37 +101,41 @@ export class Scope {
} }
export function extract_names(param: Node) { export function extract_names(param: Node) {
const names: string[] = []; return extract_identifiers(param).map(node => node.name);
extractors[param.type](names, param); }
return names;
export function extract_identifiers(param: Node) {
const nodes: Node[] = [];
extractors[param.type](nodes, param);
return nodes;
} }
const extractors = { const extractors = {
Identifier(names: string[], param: Node) { Identifier(nodes: Node[], param: Node) {
names.push(param.name); nodes.push(param);
}, },
ObjectPattern(names: string[], param: Node) { ObjectPattern(nodes: Node[], param: Node) {
param.properties.forEach((prop: Node) => { param.properties.forEach((prop: Node) => {
if (prop.type === 'RestElement') { if (prop.type === 'RestElement') {
names.push(prop.argument.name); nodes.push(prop.argument);
} else { } else {
extractors[prop.value.type](names, prop.value); extractors[prop.value.type](nodes, prop.value);
} }
}); });
}, },
ArrayPattern(names: string[], param: Node) { ArrayPattern(nodes: Node[], param: Node) {
param.elements.forEach((element: Node) => { param.elements.forEach((element: Node) => {
if (element) extractors[element.type](names, element); if (element) extractors[element.type](nodes, element);
}); });
}, },
RestElement(names: string[], param: Node) { RestElement(nodes: Node[], param: Node) {
extractors[param.argument.type](names, param.argument); extractors[param.argument.type](nodes, param.argument);
}, },
AssignmentPattern(names: string[], param: Node) { AssignmentPattern(nodes: Node[], param: Node) {
extractors[param.left.type](names, param.left); extractors[param.left.type](nodes, param.left);
} }
}; };

@ -0,0 +1,6 @@
import { Node } from '../../interfaces';
export default function unwrap_parens(node: Node) {
while (node.type === 'ParenthesizedExpression') node = node.expression;
return node;
}

@ -1,16 +1,25 @@
export default { export default {
props: { props: {
coords: [0, 0] coords: [0, 0],
numbers: { answer: 42 }
}, },
html: ` html: `
<p>0,0</p> <p>0,0</p>
<p>42</p>
`, `,
test({ assert, component, target }) { test({ assert, component, target }) {
component.coords = [1, 2]; component.coords = [1, 2];
assert.htmlEqual(target.innerHTML, ` assert.htmlEqual(target.innerHTML, `
<p>1,2</p> <p>1,2</p>
<p>42</p>
`);
component.numbers = { answer: 43 };
assert.htmlEqual(target.innerHTML, `
<p>1,2</p>
<p>43</p>
`); `);
} }
}; };

@ -1,7 +1,10 @@
<script> <script>
export let coords; export let coords;
export let numbers;
$: [x, y] = coords; $: [x, y] = coords;
$: ({ answer } = numbers);
</script> </script>
<p>{x},{y}</p> <p>{x},{y}</p>
<p>{answer}</p>

@ -0,0 +1,20 @@
export default {
show: 1,
html: `
<p>1 / 1</p>
`,
test({ assert, component, target }) {
component.num = 3;
assert.htmlEqual(target.innerHTML, `
<p>3 / 3</p>
`);
component.num = 2;
assert.htmlEqual(target.innerHTML, `
<p>2 / 3</p>
`);
}
};

@ -0,0 +1,7 @@
<script>
export let num = 1;
$: max = Math.max(num, max || 0);
</script>
<p>{num} / {max}</p>
Loading…
Cancel
Save