diff --git a/src/compile/Component.ts b/src/compile/Component.ts index a0ee6a5564..f1bf634786 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -5,7 +5,7 @@ import Stats from '../Stats'; import { globals, reserved } from '../utils/names'; import { namespaces, valid_namespaces } from '../utils/namespaces'; 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 { test } from '../config'; import Fragment from './nodes/Fragment'; @@ -19,6 +19,7 @@ import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; import { remove_indentation, add_indentation } from '../utils/indentation'; import get_object from './utils/get_object'; +import unwrap_parens from './utils/unwrap_parens'; type ComponentOptions = { namespace?: string; @@ -93,7 +94,7 @@ export default class Component { node_for_declaration: Map = new Map(); partly_hoisted: string[] = []; fully_hoisted: string[] = []; - reactive_declarations: Array<{ assignees: Set, dependencies: Set, node: Node, injected: boolean }> = []; + reactive_declarations: Array<{ assignees: Set, dependencies: Set, node: Node, declaration: Node }> = []; reactive_declaration_nodes: Set = new Set(); has_reactive_assignments = false; injected_reactive_declaration_vars: Set = new Set(); @@ -590,9 +591,11 @@ export default class Component { script.content.body.forEach(node => { if (node.type !== 'LabeledStatement') 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] !== '$') { this.injected_reactive_declaration_vars.add(name); } @@ -1058,15 +1061,10 @@ export default class Component { } if (node.type === 'AssignmentExpression') { - if (node.left.type === 'MemberExpression' || node.left.type === 'Identifier') { - const identifier = get_object(node.left) - assignee_nodes.add(identifier); - assignees.add(identifier.name); - } else { - extract_names(node.left).forEach(name => { - assignees.add(name); - }); - } + extract_identifiers(get_object(node.left)).forEach(node => { + assignee_nodes.add(node); + assignees.add(node.name); + }); } else if (node.type === 'UpdateExpression') { const identifier = get_object(node.argument); assignees.add(identifier.name); @@ -1096,18 +1094,10 @@ export default class Component { add_indentation(this.code, node.body, 2); - unsorted_reactive_declarations.push({ - assignees, - dependencies, - node, - 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; - }) - ) - }); + const expression = node.body.expression && unwrap_parens(node.body.expression); + const declaration = expression && expression.left; + + unsorted_reactive_declarations.push({ assignees, dependencies, node, declaration }); } }); diff --git a/src/compile/render-ssr/index.ts b/src/compile/render-ssr/index.ts index 1566962f6d..10f82beb5e 100644 --- a/src/compile/render-ssr/index.ts +++ b/src/compile/render-ssr/index.ts @@ -3,6 +3,7 @@ import Component from '../Component'; import { CompileOptions } from '../../interfaces'; import { stringify } from '../utils/stringify'; import Renderer from './Renderer'; +import { extract_names } from '../utils/scope'; export default function ssr( component: Component, @@ -63,8 +64,32 @@ export default function ssr( : []; const reactive_declarations = component.reactive_declarations.map(d => { - const snippet = `[✂${d.node.body.start}-${d.node.end}✂]`; - return d.injected ? `let ${snippet}` : snippet; + let snippet = `[✂${d.node.body.start}-${d.node.end}✂]`; + + 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 diff --git a/src/compile/utils/get_object.ts b/src/compile/utils/get_object.ts index 4888328562..641ebdcb9c 100644 --- a/src/compile/utils/get_object.ts +++ b/src/compile/utils/get_object.ts @@ -1,7 +1,8 @@ import { Node } from '../../interfaces'; +import unwrap_parens from './unwrap_parens'; 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; return node; } diff --git a/src/compile/utils/scope.ts b/src/compile/utils/scope.ts index 6c9371b243..d4427c44ff 100644 --- a/src/compile/utils/scope.ts +++ b/src/compile/utils/scope.ts @@ -101,37 +101,41 @@ export class Scope { } export function extract_names(param: Node) { - const names: string[] = []; - extractors[param.type](names, param); - return names; + return extract_identifiers(param).map(node => node.name); +} + +export function extract_identifiers(param: Node) { + const nodes: Node[] = []; + extractors[param.type](nodes, param); + return nodes; } const extractors = { - Identifier(names: string[], param: Node) { - names.push(param.name); + Identifier(nodes: Node[], param: Node) { + nodes.push(param); }, - ObjectPattern(names: string[], param: Node) { + ObjectPattern(nodes: Node[], param: Node) { param.properties.forEach((prop: Node) => { if (prop.type === 'RestElement') { - names.push(prop.argument.name); + nodes.push(prop.argument); } 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) => { - if (element) extractors[element.type](names, element); + if (element) extractors[element.type](nodes, element); }); }, - RestElement(names: string[], param: Node) { - extractors[param.argument.type](names, param.argument); + RestElement(nodes: Node[], param: Node) { + extractors[param.argument.type](nodes, param.argument); }, - AssignmentPattern(names: string[], param: Node) { - extractors[param.left.type](names, param.left); + AssignmentPattern(nodes: Node[], param: Node) { + extractors[param.left.type](nodes, param.left); } }; diff --git a/src/compile/utils/unwrap_parens.ts b/src/compile/utils/unwrap_parens.ts new file mode 100644 index 0000000000..71a03b2b69 --- /dev/null +++ b/src/compile/utils/unwrap_parens.ts @@ -0,0 +1,6 @@ +import { Node } from '../../interfaces'; + +export default function unwrap_parens(node: Node) { + while (node.type === 'ParenthesizedExpression') node = node.expression; + return node; +} \ No newline at end of file diff --git a/test/runtime/samples/reactive-values-implicit-destructured/_config.js b/test/runtime/samples/reactive-values-implicit-destructured/_config.js index f5e6b1784c..80e4b8ea44 100644 --- a/test/runtime/samples/reactive-values-implicit-destructured/_config.js +++ b/test/runtime/samples/reactive-values-implicit-destructured/_config.js @@ -1,16 +1,25 @@ export default { props: { - coords: [0, 0] + coords: [0, 0], + numbers: { answer: 42 } }, html: `

0,0

+

42

`, test({ assert, component, target }) { component.coords = [1, 2]; assert.htmlEqual(target.innerHTML, `

1,2

+

42

+ `); + + component.numbers = { answer: 43 }; + assert.htmlEqual(target.innerHTML, ` +

1,2

+

43

`); } }; diff --git a/test/runtime/samples/reactive-values-implicit-destructured/main.svelte b/test/runtime/samples/reactive-values-implicit-destructured/main.svelte index 0f49ebb590..abf5f01471 100644 --- a/test/runtime/samples/reactive-values-implicit-destructured/main.svelte +++ b/test/runtime/samples/reactive-values-implicit-destructured/main.svelte @@ -1,7 +1,10 @@ -

{x},{y}

\ No newline at end of file +

{x},{y}

+

{answer}

\ No newline at end of file diff --git a/test/runtime/samples/reactive-values-implicit-self-dependency/_config.js b/test/runtime/samples/reactive-values-implicit-self-dependency/_config.js new file mode 100644 index 0000000000..d7f2bbf920 --- /dev/null +++ b/test/runtime/samples/reactive-values-implicit-self-dependency/_config.js @@ -0,0 +1,20 @@ +export default { + show: 1, + html: ` +

1 / 1

+ `, + + test({ assert, component, target }) { + component.num = 3; + + assert.htmlEqual(target.innerHTML, ` +

3 / 3

+ `); + + component.num = 2; + + assert.htmlEqual(target.innerHTML, ` +

2 / 3

+ `); + } +}; diff --git a/test/runtime/samples/reactive-values-implicit-self-dependency/main.svelte b/test/runtime/samples/reactive-values-implicit-self-dependency/main.svelte new file mode 100644 index 0000000000..378b71d326 --- /dev/null +++ b/test/runtime/samples/reactive-values-implicit-self-dependency/main.svelte @@ -0,0 +1,7 @@ + + +

{num} / {max}

\ No newline at end of file