diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 8ca4852df58e..659cf972cc7e 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -20,7 +20,6 @@ import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; import { remove_indentation, add_indentation } from '../utils/indentation'; import getObject from '../utils/getObject'; -import deindent from '../utils/deindent'; import globalWhitelist from '../utils/globalWhitelist'; type ComponentOptions = { @@ -66,9 +65,10 @@ export default class Component { node_for_declaration: Map = new Map(); partly_hoisted: string[] = []; fully_hoisted: string[] = []; - reactive_declarations: Array<{ assignees: Set, dependencies: Set, snippet: string }> = []; + reactive_declarations: Array<{ assignees: Set, dependencies: Set, node: Node, injected: boolean }> = []; reactive_declaration_nodes: Set = new Set(); has_reactive_assignments = false; + injected_reactive_declaration_vars: Set = new Set(); indirectDependencies: Map> = new Map(); @@ -554,6 +554,19 @@ export default class Component { this.addSourcemapLocations(script.content); + // inject vars for reactive declarations + script.content.body.forEach(node => { + if (node.type !== 'LabeledStatement') return; + if (node.body.type !== 'ExpressionStatement') return; + if (node.body.expression.type !== 'AssignmentExpression') return; + + const { type, name } = node.body.expression.left; + + if (type === 'Identifier' && !this.var_lookup.has(name)) { + this.injected_reactive_declaration_vars.add(name); + } + }); + let { scope: instance_scope, map, globals } = createScopes(script.content); this.instance_scope = instance_scope; this.instance_scope_map = map; @@ -589,9 +602,17 @@ export default class Component { }); globals.forEach(name => { - if (this.module_scope && this.module_scope.declarations.has(name)) return; + if (this.var_lookup.has(name)) return; - if (name[0] === '$') { + if (this.injected_reactive_declaration_vars.has(name)) { + this.add_var({ + name, + injected: true, + writable: true, + reassigned: true, + initialised: true + }); + } else if (name[0] === '$') { this.add_var({ name, injected: true, @@ -1002,12 +1023,12 @@ export default class Component { assignees, dependencies, node, - snippet: node.body.type === 'BlockStatement' - ? `[✂${node.body.start}-${node.end}✂]` - : deindent` - { - [✂${node.body.start}-${node.end}✂] - }` + injected: ( + node.body.type === 'ExpressionStatement' && + node.body.expression.type === 'AssignmentExpression' && + node.body.expression.left.type === 'Identifier' && + this.var_lookup.get(node.body.expression.left.name).injected + ) }); } }); @@ -1082,8 +1103,7 @@ export default class Component { } if (allow_implicit && !this.ast.instance && !this.ast.module) return; - if (this.instance_scope && this.instance_scope.declarations.has(name)) return; - if (this.module_scope && this.module_scope.declarations.has(name)) return; + if (this.var_lookup.has(name)) return; if (template_scope && template_scope.names.has(name)) return; if (globalWhitelist.has(name)) return; diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 24058fc98456..310800c19745 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -178,10 +178,11 @@ export default function dom( code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; ')); } else { names.forEach(name => { - if (scope.findOwner(name) !== component.instance_scope) return; + const owner = scope.findOwner(name); + if (owner && owner !== component.instance_scope) return; const variable = component.var_lookup.get(name); - if (variable && variable.hoistable) return; + if (variable && variable.hoistable || variable.global || variable.module) return; pending_assignments.add(name); component.has_reactive_assignments = true; @@ -312,6 +313,24 @@ export default function dom( .join('\n\n'); if (has_definition) { + const reactive_declarations = component.reactive_declarations.map(d => { + const condition = Array.from(d.dependencies).map(n => `$$dirty.${n}`).join(' || '); + const snippet = d.node.body.type === 'BlockStatement' + ? `[✂${d.node.body.start}-${d.node.end}✂]` + : deindent` + { + [✂${d.node.body.start}-${d.node.end}✂] + }`; + + return deindent` + if (${condition}) ${snippet}` + }); + + const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => { + const variable = component.var_lookup.get(name); + return variable.injected; + }); + builder.addBlock(deindent` function ${definition}(${args.join(', ')}) { ${user_code} @@ -326,10 +345,10 @@ export default function dom( ${set && `$$self.$set = ${set};`} - ${component.reactive_declarations.length > 0 && deindent` + ${reactive_declarations.length > 0 && deindent` + ${injected.length && `let ${injected.join(', ')};`} $$self.$$.update = ($$dirty = { ${Array.from(all_reactive_dependencies).map(n => `${n}: 1`).join(', ')} }) => { - ${component.reactive_declarations.map(d => deindent` - if (${Array.from(d.dependencies).map(n => `$$dirty.${n}`).join(' || ')}) ${d.snippet}`)} + ${reactive_declarations} }; `} diff --git a/src/compile/render-ssr/index.ts b/src/compile/render-ssr/index.ts index 0ade0da8b46d..a6d3123a33c0 100644 --- a/src/compile/render-ssr/index.ts +++ b/src/compile/render-ssr/index.ts @@ -50,6 +50,11 @@ 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; + }); + const main = renderer.has_bindings ? deindent` let $$settled; @@ -60,7 +65,7 @@ export default function ssr( ${reactive_store_values} - ${component.reactive_declarations.map(d => d.snippet)} + ${reactive_declarations} $$rendered = \`${renderer.code}\`; } while (!$$settled); @@ -70,7 +75,7 @@ export default function ssr( : deindent` ${reactive_store_values} - ${component.reactive_declarations.map(d => d.snippet)} + ${reactive_declarations} return \`${renderer.code}\`;`; diff --git a/test/runtime/samples/reactive-values-implicit/_config.js b/test/runtime/samples/reactive-values-implicit/_config.js new file mode 100644 index 000000000000..fae6f9e701d8 --- /dev/null +++ b/test/runtime/samples/reactive-values-implicit/_config.js @@ -0,0 +1,14 @@ +export default { + html: ` +

1 + 2 = 3

+

3 * 3 = 9

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

3 + 2 = 5

+

5 * 5 = 25

+ `); + } +}; diff --git a/test/runtime/samples/reactive-values-implicit/main.html b/test/runtime/samples/reactive-values-implicit/main.html new file mode 100644 index 000000000000..745ba643e11c --- /dev/null +++ b/test/runtime/samples/reactive-values-implicit/main.html @@ -0,0 +1,10 @@ + + +

{a} + {b} = {c}

+

{c} * {c} = {cSquared}

\ No newline at end of file diff --git a/test/stats/samples/implicit-reactive/_config.js b/test/stats/samples/implicit-reactive/_config.js new file mode 100644 index 000000000000..0137632d2c0d --- /dev/null +++ b/test/stats/samples/implicit-reactive/_config.js @@ -0,0 +1,26 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, [ + { + name: 'a', + injected: false, + export_name: null, + module: false, + mutated: false, + reassigned: false, + referenced: true, + writable: true + }, + { + name: 'b', + injected: true, + export_name: null, + module: false, + mutated: false, + reassigned: true, + referenced: true, + writable: true + } + ]); + }, +}; diff --git a/test/stats/samples/implicit-reactive/input.html b/test/stats/samples/implicit-reactive/input.html new file mode 100644 index 000000000000..5958474d1bcb --- /dev/null +++ b/test/stats/samples/implicit-reactive/input.html @@ -0,0 +1,6 @@ + + +

{a} + 1 = {b}

\ No newline at end of file