From 6366a4f55e4211495c9b65e2f052e0f946932866 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Aug 2017 08:37:42 -0400 Subject: [PATCH 1/8] update component bindings together. WIP --- src/generators/dom/index.ts | 1 + .../dom/visitors/Component/Binding.ts | 54 +++++------ .../dom/visitors/Component/Component.ts | 90 +++++++++++++++++-- src/shared/index.js | 1 + .../samples/bindings-coalesced/Foo.html | 20 +++++ .../samples/bindings-coalesced/_config.js | 17 ++++ .../samples/bindings-coalesced/main.html | 12 +++ 7 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 test/runtime/samples/bindings-coalesced/Foo.html create mode 100644 test/runtime/samples/bindings-coalesced/_config.js create mode 100644 test/runtime/samples/bindings-coalesced/main.html diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 2513623c49..c77ff17158 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -178,6 +178,7 @@ export default function dom( this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; ${generator.stylesheet.hasStyles && options.css !== false && diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index 75cdd637e3..afd768c5bd 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -45,9 +45,11 @@ export default function visitBinding( local.bindings.push({ name: attribute.name, - value: snippet, + value: attribute.value, + snippet: snippet, obj, prop, + dependencies }); const setter = getSetter({ @@ -69,31 +71,33 @@ export default function visitBinding( const observer = block.getUniqueName('observer'); const value = block.getUniqueName('value'); - local.create.addBlock(deindent` - function ${observer} ( value ) { - if ( ${updating} ) return; - ${updating} = true; - ${setter} - ${updating} = false; - } + //console.log({ setter }); - ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); + // local.create.addBlock(deindent` + // function ${observer} ( value ) { + // if ( ${updating} ) return; + // ${updating} = true; + // ${setter} + // ${updating} = false; + // } - #component._root._beforecreate.push( function () { - var value = ${local.name}.get( '${attribute.name}' ); - if ( @differs( value, ${snippet} ) ) { - ${observer}.call( ${local.name}, value ); - } - }); - `); + // ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); - local.update.addBlock(deindent` - if ( !${updating} && ${dependencies - .map(dependency => `changed.${dependency}`) - .join(' || ')} ) { - ${updating} = true; - ${local.name}._set({ ${attribute.name}: ${snippet} }); - ${updating} = false; - } - `); + // #component._root._beforecreate.push( function () { + // var value = ${local.name}.get( '${attribute.name}' ); + // if ( @differs( value, ${snippet} ) ) { + // ${observer}.call( ${local.name}, value ); + // } + // }); + // `); + + // local.update.addBlock(deindent` + // if ( !${updating} && ${dependencies + // .map(dependency => `changed.${dependency}`) + // .join(' || ')} ) { + // ${updating} = true; + // ${local.name}._set({ ${attribute.name}: ${snippet} }); + // ${updating} = false; + // } + // `); } diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 0e966cdc0f..6e00f9069a 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -7,6 +7,8 @@ import visitBinding from './Binding'; import visitRef from './Ref'; import { DomGenerator } from '../../index'; import Block from '../../Block'; +import getTailSnippet from '../../../../utils/getTailSnippet'; +import getObject from '../../../../utils/getObject'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; @@ -149,6 +151,8 @@ export default function visitComponent( } const statements: string[] = []; + const name_updating = local.bindings.length && block.alias(`${name}_updating`); + if (local.bindings.length) block.addVariable(name_updating, '{}'); if ( local.staticAttributes.length || @@ -168,11 +172,52 @@ export default function visitComponent( local.bindings.forEach(binding => { statements.push( - `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` + `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.snippet};` ); }); componentInitProperties.push(`data: ${initialData}`); + + componentInitProperties.push(deindent` + _bind: function(changed, childState) { + var state = #component.get(), newState = {}; + ${local.bindings.map(binding => { + const { name: key } = getObject(binding.value); + + if (block.contexts.has(key)) { + const prop = binding.dependencies[0]; + const computed = isComputed(binding.value); + const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; + + return deindent` + if (changed.${binding.name}) { + var list = ${name}._context.${block.listNames.get(key)}; + var index = ${name}._context.${block.indexNames.get(key)}; + list[index]${tail} = childState.${binding.name}; + + ${binding.dependencies + .map((prop: string) => `newState.${prop} = state.${prop};`) + .join('\n')} + } + `; + } + + if (binding.value.type === 'MemberExpression') { + return deindent` + if (changed.${binding.name}) { + ${binding.snippet} = childState.${binding.name}; + ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} + } + `; + } + + return `if (changed.${key}) newState.${binding.value.name} = childState.${key};`; + })} + ${name_updating} = changed; + #component._set(newState); + ${name_updating} = {}; + } + `); } else if (initialProps.length) { componentInitProperties.push(`data: ${initialPropString}`); } @@ -188,21 +233,41 @@ export default function visitComponent( var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); + + #component._root._beforecreate.push(function () { + var state = component.get(), newState = {}; + // TODO + #component._set(newState); + }); `); - if (local.dynamicAttributes.length) { - const updates = local.dynamicAttributes.map(attribute => { + if (local.dynamicAttributes.length || local.bindings.length) { + const updates: string[] = []; + + local.dynamicAttributes.forEach(attribute => { if (attribute.dependencies.length) { - return deindent` + updates.push(deindent` if ( ${attribute.dependencies .map(dependency => `changed.${dependency}`) .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; - `; + `); } - // TODO this is an odd situation to encounter – I *think* it should only happen with - // each block indices, in which case it may be possible to optimise this - return `${name}_changes.${attribute.name} = ${attribute.value};`; + else { + // TODO this is an odd situation to encounter – I *think* it should only happen with + // each block indices, in which case it may be possible to optimise this + updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); + } + }); + + local.bindings.forEach(binding => { + if (binding.dependencies.length) { + updates.push(deindent` + if ( !${name_updating}.${binding.name} && ${binding.dependencies + .map((dependency: string) => `changed.${dependency}`) + .join(' || ')} ) ${name}_changes.${binding.name} = ${binding.snippet}; + `); + } }); local.update.addBlock(deindent` @@ -233,3 +298,12 @@ export default function visitComponent( if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); } + +function isComputed(node: Node) { + while (node.type === 'MemberExpression') { + if (node.computed) return true; + node = node.object; + } + + return false; +} \ No newline at end of file diff --git a/src/shared/index.js b/src/shared/index.js index 901fbfdb3b..b103f2e91d 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -143,6 +143,7 @@ export function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); diff --git a/test/runtime/samples/bindings-coalesced/Foo.html b/test/runtime/samples/bindings-coalesced/Foo.html new file mode 100644 index 0000000000..5d453fbb73 --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/Foo.html @@ -0,0 +1,20 @@ +

bar in Foo: {{bar}}

+

baz in Foo: {{baz}}

+ + \ No newline at end of file diff --git a/test/runtime/samples/bindings-coalesced/_config.js b/test/runtime/samples/bindings-coalesced/_config.js new file mode 100644 index 0000000000..2fe5cbf5cd --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/_config.js @@ -0,0 +1,17 @@ +export default { + test(assert, component) { + const { foo, p } = component.refs; + + const values = []; + + Object.defineProperty(p.childNodes[0], 'data', { + set(value) { + values.push(value); + } + }); + + foo.double(); + + assert.deepEqual(values, [6]); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bindings-coalesced/main.html b/test/runtime/samples/bindings-coalesced/main.html new file mode 100644 index 0000000000..fc17036077 --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/main.html @@ -0,0 +1,12 @@ + +

{{bar + baz}}

+ + \ No newline at end of file From 12e9a9276cc337dc8291ec55d555bb86996bbcb7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Aug 2017 14:23:57 -0400 Subject: [PATCH 2/8] almost working --- .../dom/visitors/Component/Component.ts | 139 +++++++++++------- src/shared/index.js | 6 +- 2 files changed, 85 insertions(+), 60 deletions(-) diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 6e00f9069a..56289f38be 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -151,8 +151,71 @@ export default function visitComponent( } const statements: string[] = []; - const name_updating = local.bindings.length && block.alias(`${name}_updating`); - if (local.bindings.length) block.addVariable(name_updating, '{}'); + let name_updating: string; + let initialData: string; + let bindings = []; + + if (local.bindings.length) { + name_updating = block.alias(`${name}_updating`); + initialData = block.getUniqueName(`${name}_initial_data`); + + block.addVariable(name_updating, '{}'); + + bindings = local.bindings.map(binding => { + let setParentFromChild; + + const { name: key } = getObject(binding.value); + + if (block.contexts.has(key)) { + const prop = binding.dependencies[0]; + const computed = isComputed(binding.value); + const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; + + setParentFromChild = deindent` + var list = ${name}._context.${block.listNames.get(key)}; + var index = ${name}._context.${block.indexNames.get(key)}; + list[index]${tail} = childState.${binding.name}; + + ${binding.dependencies + .map((prop: string) => `newState.${prop} = state.${prop};`) + .join('\n')} + `; + } + + else if (binding.value.type === 'MemberExpression') { + setParentFromChild = deindent` + ${binding.snippet} = childState.${binding.name}; + ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} + `; + } + + else { + setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; + } + + return { + init: deindent` + if ( ${binding.prop} in ${binding.obj} ) { + ${initialData}.${binding.name} = ${binding.snippet}; + ${name_updating}.${binding.name} = true; + }`, + bind: deindent` + if (!${name_updating}.${binding.name} && changed.${binding.name}) { + ${setParentFromChild} + } + `, + setParentFromChild, + + // TODO could binding.dependencies.length ever be 0? + update: binding.dependencies.length && deindent` + if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) { + ${name}_changes.${binding.name} = ${binding.snippet}; + ${name_updating}.${binding.name} = true; + } + ` + } + }); + } if ( local.staticAttributes.length || @@ -166,14 +229,10 @@ export default function visitComponent( const initialPropString = stringifyProps(initialProps); if (local.bindings.length) { - const initialData = block.getUniqueName(`${name}_initial_data`); - statements.push(`var ${initialData} = ${initialPropString};`); - local.bindings.forEach(binding => { - statements.push( - `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.snippet};` - ); + bindings.forEach(binding => { + statements.push(binding.init); }); componentInitProperties.push(`data: ${initialData}`); @@ -181,38 +240,7 @@ export default function visitComponent( componentInitProperties.push(deindent` _bind: function(changed, childState) { var state = #component.get(), newState = {}; - ${local.bindings.map(binding => { - const { name: key } = getObject(binding.value); - - if (block.contexts.has(key)) { - const prop = binding.dependencies[0]; - const computed = isComputed(binding.value); - const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; - - return deindent` - if (changed.${binding.name}) { - var list = ${name}._context.${block.listNames.get(key)}; - var index = ${name}._context.${block.indexNames.get(key)}; - list[index]${tail} = childState.${binding.name}; - - ${binding.dependencies - .map((prop: string) => `newState.${prop} = state.${prop};`) - .join('\n')} - } - `; - } - - if (binding.value.type === 'MemberExpression') { - return deindent` - if (changed.${binding.name}) { - ${binding.snippet} = childState.${binding.name}; - ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} - } - `; - } - - return `if (changed.${key}) newState.${binding.value.name} = childState.${key};`; - })} + ${bindings.map(binding => binding.bind).join('\n')} ${name_updating} = changed; #component._set(newState); ${name_updating} = {}; @@ -233,14 +261,20 @@ export default function visitComponent( var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); - - #component._root._beforecreate.push(function () { - var state = component.get(), newState = {}; - // TODO - #component._set(newState); - }); `); + if (bindings.length) { + local.create.addBlock(deindent` + #component._root._beforecreate.push(function () { + var state = #component.get(), childState = ${name}.get(), newState = {}; + ${bindings.map(binding => binding.setParentFromChild).join('\n')} + ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; + #component._set(newState); + ${name_updating} = {}; + }); + `); + } + if (local.dynamicAttributes.length || local.bindings.length) { const updates: string[] = []; @@ -260,22 +294,15 @@ export default function visitComponent( } }); - local.bindings.forEach(binding => { - if (binding.dependencies.length) { - updates.push(deindent` - if ( !${name_updating}.${binding.name} && ${binding.dependencies - .map((dependency: string) => `changed.${dependency}`) - .join(' || ')} ) ${name}_changes.${binding.name} = ${binding.snippet}; - `); - } + bindings.forEach(binding => { + if (binding.update) updates.push(binding.update); }); local.update.addBlock(deindent` var ${name}_changes = {}; - ${updates.join('\n')} - ${name}._set( ${name}_changes ); + ${bindings.length && `${name_updating} = {};`} `); } diff --git a/src/shared/index.js b/src/shared/index.js index b103f2e91d..7813987837 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -5,14 +5,12 @@ export * from './transitions.js'; export * from './utils.js'; export function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } export function destroyDev(detach) { From e8b151cfeee8d0646a236ae85bae5b024484b824 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Aug 2017 14:39:44 -0400 Subject: [PATCH 3/8] all tests passing --- src/generators/dom/visitors/Component/Component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 56289f38be..6e088ccd37 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -204,7 +204,11 @@ export default function visitComponent( ${setParentFromChild} } `, - setParentFromChild, + setParentFromChild: deindent` + if (!${name_updating}.${binding.name}) { + ${setParentFromChild} + } + `, // TODO could binding.dependencies.length ever be 0? update: binding.dependencies.length && deindent` @@ -267,6 +271,7 @@ export default function visitComponent( local.create.addBlock(deindent` #component._root._beforecreate.push(function () { var state = #component.get(), childState = ${name}.get(), newState = {}; + if (!childState) return; ${bindings.map(binding => binding.setParentFromChild).join('\n')} ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; #component._set(newState); From 8818357fc4735585fa82982432619045a02246c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Aug 2017 15:15:22 -0400 Subject: [PATCH 4/8] tidy up --- .../dom/visitors/Component/Binding.ts | 48 ------------- .../dom/visitors/Component/Component.ts | 10 +-- .../dom/visitors/Element/Binding.ts | 67 ++++++++++++++++--- .../dom/visitors/shared/binding/getSetter.ts | 59 ---------------- .../expected-bundle.js | 8 +-- .../expected.js | 1 + .../computed-collapsed-if/expected-bundle.js | 8 +-- .../samples/computed-collapsed-if/expected.js | 1 + .../css-media-query/expected-bundle.js | 8 +-- test/js/samples/css-media-query/expected.js | 1 + .../expected-bundle.js | 8 +-- .../each-block-changed-check/expected.js | 1 + .../event-handlers-custom/expected-bundle.js | 8 +-- .../samples/event-handlers-custom/expected.js | 1 + .../if-block-no-update/expected-bundle.js | 8 +-- .../js/samples/if-block-no-update/expected.js | 1 + .../if-block-simple/expected-bundle.js | 8 +-- test/js/samples/if-block-simple/expected.js | 1 + .../non-imported-component/expected-bundle.js | 8 +-- .../non-imported-component/expected.js | 1 + .../expected-bundle.js | 8 +-- .../onrender-onteardown-rewritten/expected.js | 1 + .../samples/setup-method/expected-bundle.js | 8 +-- test/js/samples/setup-method/expected.js | 1 + .../expected-bundle.js | 8 +-- .../use-elements-as-anchors/expected.js | 1 + 26 files changed, 116 insertions(+), 167 deletions(-) delete mode 100644 src/generators/dom/visitors/shared/binding/getSetter.ts diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index afd768c5bd..e011dfacdd 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -1,6 +1,5 @@ import deindent from '../../../../utils/deindent'; import flattenReference from '../../../../utils/flattenReference'; -import getSetter from '../shared/binding/getSetter'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; @@ -52,52 +51,5 @@ export default function visitBinding( dependencies }); - const setter = getSetter({ - block, - name, - snippet, - _this: 'this', - props: '_context', - attribute, - dependencies, - value: 'value', - }); - generator.hasComplexBindings = true; - - const updating = block.getUniqueName(`${local.name}_updating`); - block.addVariable(updating, 'false'); - - const observer = block.getUniqueName('observer'); - const value = block.getUniqueName('value'); - - //console.log({ setter }); - - // local.create.addBlock(deindent` - // function ${observer} ( value ) { - // if ( ${updating} ) return; - // ${updating} = true; - // ${setter} - // ${updating} = false; - // } - - // ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); - - // #component._root._beforecreate.push( function () { - // var value = ${local.name}.get( '${attribute.name}' ); - // if ( @differs( value, ${snippet} ) ) { - // ${observer}.call( ${local.name}, value ); - // } - // }); - // `); - - // local.update.addBlock(deindent` - // if ( !${updating} && ${dependencies - // .map(dependency => `changed.${dependency}`) - // .join(' || ')} ) { - // ${updating} = true; - // ${local.name}._set({ ${attribute.name}: ${snippet} }); - // ${updating} = false; - // } - // `); } diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 6e088ccd37..506384c5d6 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -152,12 +152,12 @@ export default function visitComponent( const statements: string[] = []; let name_updating: string; - let initialData: string; + let name_initial_data: string; let bindings = []; if (local.bindings.length) { name_updating = block.alias(`${name}_updating`); - initialData = block.getUniqueName(`${name}_initial_data`); + name_initial_data = block.getUniqueName(`${name}_initial_data`); block.addVariable(name_updating, '{}'); @@ -196,7 +196,7 @@ export default function visitComponent( return { init: deindent` if ( ${binding.prop} in ${binding.obj} ) { - ${initialData}.${binding.name} = ${binding.snippet}; + ${name_initial_data}.${binding.name} = ${binding.snippet}; ${name_updating}.${binding.name} = true; }`, bind: deindent` @@ -233,13 +233,13 @@ export default function visitComponent( const initialPropString = stringifyProps(initialProps); if (local.bindings.length) { - statements.push(`var ${initialData} = ${initialPropString};`); + statements.push(`var ${name_initial_data} = ${initialPropString};`); bindings.forEach(binding => { statements.push(binding.init); }); - componentInitProperties.push(`data: ${initialData}`); + componentInitProperties.push(`data: ${name_initial_data}`); componentInitProperties.push(deindent` _bind: function(changed, childState) { diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index ceadb57b7e..77801c7876 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -1,6 +1,5 @@ import deindent from '../../../../utils/deindent'; import flattenReference from '../../../../utils/flattenReference'; -import getSetter from '../shared/binding/getSetter'; import getStaticAttributeValue from './getStaticAttributeValue'; import { DomGenerator } from '../../index'; import Block from '../../Block'; @@ -50,16 +49,7 @@ export default function visitBinding( type ); - let setter = getSetter({ - block, - name, - snippet, - _this: state.parentNode, - props: '_svelte', - attribute, - dependencies, - value, - }); + let setter = getSetter(block, name, snippet, state.parentNode, attribute, dependencies, value); let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`; const lock = `#${state.parentNode}_updating`; let updateCondition = `!${lock}`; @@ -271,3 +261,58 @@ function getBindingGroup(generator: DomGenerator, value: Node) { return index; } + +function getSetter( + block: Block, + name: string, + snippet: string, + _this: string, + attribute: Node, + dependencies: string[], + value: string, +) { + const tail = attribute.value.type === 'MemberExpression' + ? getTailSnippet(attribute.value) + : ''; + + if (block.contexts.has(name)) { + const prop = dependencies[0]; + const computed = isComputed(attribute.value); + + return deindent` + var list = ${_this}._svelte.${block.listNames.get(name)}; + var index = ${_this}._svelte.${block.indexNames.get(name)}; + ${computed && `var state = #component.get();`} + list[index]${tail} = ${value}; + + ${computed + ? `#component.set({ ${dependencies + .map((prop: string) => `${prop}: state.${prop}`) + .join(', ')} });` + : `#component.set({ ${dependencies + .map((prop: string) => `${prop}: #component.get( '${prop}' )`) + .join(', ')} });`} + `; + } + + if (attribute.value.type === 'MemberExpression') { + return deindent` + var state = #component.get(); + ${snippet} = ${value}; + #component.set({ ${dependencies + .map((prop: string) => `${prop}: state.${prop}`) + .join(', ')} }); + `; + } + + return `#component.set({ ${name}: ${value} });`; +} + +function isComputed(node: Node) { + while (node.type === 'MemberExpression') { + if (node.computed) return true; + node = node.object; + } + + return false; +} \ No newline at end of file diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts deleted file mode 100644 index 6c25d67a72..0000000000 --- a/src/generators/dom/visitors/shared/binding/getSetter.ts +++ /dev/null @@ -1,59 +0,0 @@ -import deindent from '../../../../../utils/deindent'; -import getTailSnippet from '../../../../../utils/getTailSnippet'; -import { Node } from '../../../../../interfaces'; - -export default function getSetter({ - block, - name, - snippet, - _this, - props, - attribute, - dependencies, - value, -}) { - const tail = attribute.value.type === 'MemberExpression' - ? getTailSnippet(attribute.value) - : ''; - - if (block.contexts.has(name)) { - const prop = dependencies[0]; - const computed = isComputed(attribute.value); - - return deindent` - var list = ${_this}.${props}.${block.listNames.get(name)}; - var index = ${_this}.${props}.${block.indexNames.get(name)}; - ${computed && `var state = #component.get();`} - list[index]${tail} = ${value}; - - ${computed - ? `#component.set({ ${dependencies - .map((prop: string) => `${prop}: state.${prop}`) - .join(', ')} });` - : `#component.set({ ${dependencies - .map((prop: string) => `${prop}: #component.get( '${prop}' )`) - .join(', ')} });`} - `; - } - - if (attribute.value.type === 'MemberExpression') { - return deindent` - var state = #component.get(); - ${snippet} = ${value}; - #component.set({ ${dependencies - .map((prop: string) => `${prop}: state.${prop}`) - .join(', ')} }); - `; - } - - return `#component.set({ ${name}: ${value} });`; -} - -function isComputed(node: Node) { - while (node.type === 'MemberExpression') { - if (node.computed) return true; - node = node.object; - } - - return false; -} diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index 10a7a08930..b24896ca8a 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -38,14 +38,12 @@ function setAttribute(node, attribute, value) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -144,6 +142,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -230,6 +229,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css(); diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 60a7180f1d..6bb93f0f89 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -65,6 +65,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css(); diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 20fe6ef0fa..385e2e4fd8 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -14,14 +14,12 @@ function assign(target) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -120,6 +118,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -179,6 +178,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index 6318b3b5cf..f28a843cad 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -38,6 +38,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index 868ca03e71..cc643a64c1 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -34,14 +34,12 @@ function setAttribute(node, attribute, value) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -140,6 +138,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -212,6 +211,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !document.getElementById( 'svelte-2363328337-style' ) ) add_css(); diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 8fb4bde21a..bb51bba637 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -51,6 +51,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !document.getElementById( 'svelte-2363328337-style' ) ) add_css(); diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 2d14d12151..ec91d9f270 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -47,14 +47,12 @@ function createText(data) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -153,6 +151,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -326,6 +325,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 9bfc875e78..1cadd16f18 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -152,6 +152,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 97090f092d..c298f39038 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -34,14 +34,12 @@ function createText(data) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -140,6 +138,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -223,6 +222,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index 54e9111aff..21454cfd98 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -62,6 +62,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index a36be53f93..96fa7ee3e7 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -38,14 +38,12 @@ function createComment() { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -144,6 +142,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -265,6 +264,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index 025e0a0e43..fe393dda7a 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -100,6 +100,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index c16dbcaaa3..e36acc90b7 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -38,14 +38,12 @@ function createComment() { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -144,6 +142,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -241,6 +240,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index 2cc3be36f9..6e3a378337 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -76,6 +76,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 09576689bc..ddfd1cd82d 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -28,14 +28,12 @@ function createText(data) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -134,6 +132,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -215,6 +214,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !options._root ) { this._oncreate = []; diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index ce86538e00..a7944a3976 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -62,6 +62,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; if ( !options._root ) { this._oncreate = []; diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 227ac71e7c..a8e48098e0 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -14,14 +14,12 @@ function assign(target) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -120,6 +118,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -178,6 +177,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; var oncreate = template.oncreate.bind( this ); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index a5125745c6..ba6c744ee2 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -37,6 +37,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; var oncreate = template.oncreate.bind( this ); diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 0ecdbafa5b..4f4a638d8f 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -14,14 +14,12 @@ function assign(target) { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -120,6 +118,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -188,6 +187,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index bf9da033ad..a2111f98e8 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -47,6 +47,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 33f1b49df3..4b7feda54d 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -38,14 +38,12 @@ function createComment() { } function destroy(detach) { - this.destroy = this.set = noop; + this.destroy = this.set = this.get = noop; this.fire('destroy'); if (detach !== false) this._fragment.unmount(); this._fragment.destroy(); - this._fragment = null; - - this._state = {}; + this._fragment = this._state = null; } function differs(a, b) { @@ -144,6 +142,7 @@ function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); @@ -425,6 +424,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 28f0a660d4..45f339c416 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -260,6 +260,7 @@ function SvelteComponent ( options ) { this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; this._fragment = create_main_fragment( this._state, this ); From ee5a4b3d0b7c8761ae289b2bfebae57f6ecb8c1f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 16 Aug 2017 08:05:28 -0400 Subject: [PATCH 5/8] tidy up a bit --- .../dom/visitors/Component/Component.ts | 216 +++++++++--------- 1 file changed, 106 insertions(+), 110 deletions(-) diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 506384c5d6..1be67726ae 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -153,74 +153,9 @@ export default function visitComponent( const statements: string[] = []; let name_updating: string; let name_initial_data: string; + let _beforecreate: string = null; let bindings = []; - if (local.bindings.length) { - name_updating = block.alias(`${name}_updating`); - name_initial_data = block.getUniqueName(`${name}_initial_data`); - - block.addVariable(name_updating, '{}'); - - bindings = local.bindings.map(binding => { - let setParentFromChild; - - const { name: key } = getObject(binding.value); - - if (block.contexts.has(key)) { - const prop = binding.dependencies[0]; - const computed = isComputed(binding.value); - const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; - - setParentFromChild = deindent` - var list = ${name}._context.${block.listNames.get(key)}; - var index = ${name}._context.${block.indexNames.get(key)}; - list[index]${tail} = childState.${binding.name}; - - ${binding.dependencies - .map((prop: string) => `newState.${prop} = state.${prop};`) - .join('\n')} - `; - } - - else if (binding.value.type === 'MemberExpression') { - setParentFromChild = deindent` - ${binding.snippet} = childState.${binding.name}; - ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} - `; - } - - else { - setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; - } - - return { - init: deindent` - if ( ${binding.prop} in ${binding.obj} ) { - ${name_initial_data}.${binding.name} = ${binding.snippet}; - ${name_updating}.${binding.name} = true; - }`, - bind: deindent` - if (!${name_updating}.${binding.name} && changed.${binding.name}) { - ${setParentFromChild} - } - `, - setParentFromChild: deindent` - if (!${name_updating}.${binding.name}) { - ${setParentFromChild} - } - `, - - // TODO could binding.dependencies.length ever be 0? - update: binding.dependencies.length && deindent` - if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) { - ${name}_changes.${binding.name} = ${binding.snippet}; - ${name_updating}.${binding.name} = true; - } - ` - } - }); - } - if ( local.staticAttributes.length || local.dynamicAttributes.length || @@ -232,9 +167,90 @@ export default function visitComponent( const initialPropString = stringifyProps(initialProps); + const updates: string[] = []; + + local.dynamicAttributes.forEach(attribute => { + if (attribute.dependencies.length) { + updates.push(deindent` + if ( ${attribute.dependencies + .map(dependency => `changed.${dependency}`) + .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; + `); + } + + else { + // TODO this is an odd situation to encounter – I *think* it should only happen with + // each block indices, in which case it may be possible to optimise this + updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); + } + }); + if (local.bindings.length) { + name_updating = block.alias(`${name}_updating`); + name_initial_data = block.getUniqueName(`${name}_initial_data`); + + block.addVariable(name_updating, '{}'); statements.push(`var ${name_initial_data} = ${initialPropString};`); + bindings = local.bindings.map(binding => { + let setParentFromChild; + + const { name: key } = getObject(binding.value); + + if (block.contexts.has(key)) { + const prop = binding.dependencies[0]; + const computed = isComputed(binding.value); + const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; + + setParentFromChild = deindent` + var list = ${name}._context.${block.listNames.get(key)}; + var index = ${name}._context.${block.indexNames.get(key)}; + list[index]${tail} = childState.${binding.name}; + + ${binding.dependencies + .map((prop: string) => `newState.${prop} = state.${prop};`) + .join('\n')} + `; + } + + else if (binding.value.type === 'MemberExpression') { + setParentFromChild = deindent` + ${binding.snippet} = childState.${binding.name}; + ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} + `; + } + + else { + setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; + } + + return { + init: deindent` + if ( ${binding.prop} in ${binding.obj} ) { + ${name_initial_data}.${binding.name} = ${binding.snippet}; + ${name_updating}.${binding.name} = true; + }`, + bind: deindent` + if (!${name_updating}.${binding.name} && changed.${binding.name}) { + ${setParentFromChild} + } + `, + setParentFromChild: deindent` + if (!${name_updating}.${binding.name}) { + ${setParentFromChild} + } + `, + + // TODO could binding.dependencies.length ever be 0? + update: binding.dependencies.length && deindent` + if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) { + ${name}_changes.${binding.name} = ${binding.snippet}; + ${name_updating}.${binding.name} = true; + } + ` + } + }); + bindings.forEach(binding => { statements.push(binding.init); }); @@ -250,9 +266,31 @@ export default function visitComponent( ${name_updating} = {}; } `); + + bindings.forEach(binding => { + if (binding.update) updates.push(binding.update); + }); + + _beforecreate = deindent` + #component._root._beforecreate.push(function () { + var state = #component.get(), childState = ${name}.get(), newState = {}; + if (!childState) return; + ${bindings.map(binding => binding.setParentFromChild).join('\n')} + ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; + #component._set(newState); + ${name_updating} = {}; + }); + `; } else if (initialProps.length) { componentInitProperties.push(`data: ${initialPropString}`); } + + local.update.addBlock(deindent` + var ${name}_changes = {}; + ${updates.join('\n')} + ${name}._set( ${name}_changes ); + ${bindings.length && `${name_updating} = {};`} + `); } const expression = node.name === ':Self' @@ -265,51 +303,9 @@ export default function visitComponent( var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); - `); - - if (bindings.length) { - local.create.addBlock(deindent` - #component._root._beforecreate.push(function () { - var state = #component.get(), childState = ${name}.get(), newState = {}; - if (!childState) return; - ${bindings.map(binding => binding.setParentFromChild).join('\n')} - ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; - #component._set(newState); - ${name_updating} = {}; - }); - `); - } - - if (local.dynamicAttributes.length || local.bindings.length) { - const updates: string[] = []; - - local.dynamicAttributes.forEach(attribute => { - if (attribute.dependencies.length) { - updates.push(deindent` - if ( ${attribute.dependencies - .map(dependency => `changed.${dependency}`) - .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; - `); - } - - else { - // TODO this is an odd situation to encounter – I *think* it should only happen with - // each block indices, in which case it may be possible to optimise this - updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); - } - }); - - bindings.forEach(binding => { - if (binding.update) updates.push(binding.update); - }); - local.update.addBlock(deindent` - var ${name}_changes = {}; - ${updates.join('\n')} - ${name}._set( ${name}_changes ); - ${bindings.length && `${name_updating} = {};`} - `); - } + ${_beforecreate} + `); if (isTopLevel) block.builders.unmount.addLine(`${name}._fragment.unmount();`); From 507d6a4062733dbac8ae38b03c60fdeebc9fd5f5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 16 Aug 2017 08:59:56 -0400 Subject: [PATCH 6/8] move event handlers and refs inside Component.ts --- .../dom/visitors/Component/Component.ts | 229 ++++++++++-------- .../dom/visitors/Component/EventHandler.ts | 57 ----- src/generators/dom/visitors/Component/Ref.ts | 22 -- 3 files changed, 132 insertions(+), 176 deletions(-) delete mode 100644 src/generators/dom/visitors/Component/EventHandler.ts delete mode 100644 src/generators/dom/visitors/Component/Ref.ts diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 1be67726ae..d104e53b10 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -2,9 +2,7 @@ import deindent from '../../../../utils/deindent'; import CodeBuilder from '../../../../utils/CodeBuilder'; import visit from '../../visit'; import visitAttribute from './Attribute'; -import visitEventHandler from './EventHandler'; import visitBinding from './Binding'; -import visitRef from './Ref'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import getTailSnippet from '../../../../utils/getTailSnippet'; @@ -26,16 +24,12 @@ function stringifyProps(props: string[]) { const order = { Attribute: 1, - EventHandler: 2, - Binding: 3, - Ref: 4, + Binding: 3 }; const visitors = { Attribute: visitAttribute, - EventHandler: visitEventHandler, - Binding: visitBinding, - Ref: visitRef, + Binding: visitBinding }; export default function visitComponent( @@ -53,17 +47,10 @@ export default function visitComponent( const childState = node._state; const local = { - name, - namespace: state.namespace, - isComponent: true, - allUsedContexts: [], staticAttributes: [], dynamicAttributes: [], - bindings: [], - - create: new CodeBuilder(), - update: new CodeBuilder(), + bindings: [] }; const isTopLevel = !state.parentNode; @@ -73,7 +60,7 @@ export default function visitComponent( node.attributes .sort((a, b) => order[a.type] - order[b.type]) .forEach(attribute => { - visitors[attribute.type]( + visitors[attribute.type] && visitors[attribute.type]( generator, block, childState, @@ -83,38 +70,6 @@ export default function visitComponent( ); }); - if (local.allUsedContexts.length) { - const initialProps = local.allUsedContexts - .map(contextName => { - if (contextName === 'state') return `state: state`; - - const listName = block.listNames.get(contextName); - const indexName = block.indexNames.get(contextName); - - return `${listName}: ${listName},\n${indexName}: ${indexName}`; - }) - .join(',\n'); - - const updates = local.allUsedContexts - .map(contextName => { - if (contextName === 'state') return `${name}._context.state = state;`; - - const listName = block.listNames.get(contextName); - const indexName = block.indexNames.get(contextName); - - return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; - }) - .join('\n'); - - local.create.addBlock(deindent` - ${name}._context = { - ${initialProps} - }; - `); - - local.update.addBlock(updates); - } - const componentInitProperties = [`_root: #component._root`]; // Component has children, put them in a separate {{yield}} block @@ -127,34 +82,33 @@ export default function visitComponent( visit(generator, childBlock, childState, child, elementStack); }); - const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); + const yield_fragment = block.getUniqueName(`${name}_yield_fragment`); block.builders.init.addLine( - `var ${yieldFragment} = ${childBlock.name}( ${params}, #component );` + `var ${yield_fragment} = ${childBlock.name}( ${params}, #component );` ); - block.builders.create.addLine(`${yieldFragment}.create();`); + block.builders.create.addLine(`${yield_fragment}.create();`); block.builders.claim.addLine( - `${yieldFragment}.claim( ${state.parentNodes} );` + `${yield_fragment}.claim( ${state.parentNodes} );` ); if (childBlock.hasUpdateMethod) { block.builders.update.addLine( - `${yieldFragment}.update( changed, ${params} );` + `${yield_fragment}.update( changed, ${params} );` ); } - block.builders.destroy.addLine(`${yieldFragment}.destroy();`); + block.builders.destroy.addLine(`${yield_fragment}.destroy();`); - componentInitProperties.push(`_yield: ${yieldFragment}`); + componentInitProperties.push(`_yield: ${yield_fragment}`); } const statements: string[] = []; let name_updating: string; let name_initial_data: string; - let _beforecreate: string = null; - let bindings = []; + let beforecreate: string = null; if ( local.staticAttributes.length || @@ -192,7 +146,10 @@ export default function visitComponent( block.addVariable(name_updating, '{}'); statements.push(`var ${name_initial_data} = ${initialPropString};`); - bindings = local.bindings.map(binding => { + const setParentFromChildOnChange = new CodeBuilder(); + const setParentFromChildOnInit = new CodeBuilder(); + + local.bindings.forEach(binding => { let setParentFromChild; const { name: key } = getObject(binding.value); @@ -224,58 +181,51 @@ export default function visitComponent( setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; } - return { - init: deindent` - if ( ${binding.prop} in ${binding.obj} ) { - ${name_initial_data}.${binding.name} = ${binding.snippet}; - ${name_updating}.${binding.name} = true; - }`, - bind: deindent` - if (!${name_updating}.${binding.name} && changed.${binding.name}) { - ${setParentFromChild} - } - `, - setParentFromChild: deindent` - if (!${name_updating}.${binding.name}) { - ${setParentFromChild} - } - `, - - // TODO could binding.dependencies.length ever be 0? - update: binding.dependencies.length && deindent` + statements.push(deindent` + if ( ${binding.prop} in ${binding.obj} ) { + ${name_initial_data}.${binding.name} = ${binding.snippet}; + ${name_updating}.${binding.name} = true; + }` + ); + + setParentFromChildOnChange.addConditional( + `!${name_updating}.${binding.name} && changed.${binding.name}`, + setParentFromChild + ); + + setParentFromChildOnInit.addConditional( + `!${name_updating}.${binding.name}`, + setParentFromChild + ); + + // TODO could binding.dependencies.length ever be 0? + if (binding.dependencies.length) { + updates.push(deindent` if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) { ${name}_changes.${binding.name} = ${binding.snippet}; ${name_updating}.${binding.name} = true; } - ` + `); } }); - bindings.forEach(binding => { - statements.push(binding.init); - }); - componentInitProperties.push(`data: ${name_initial_data}`); componentInitProperties.push(deindent` _bind: function(changed, childState) { var state = #component.get(), newState = {}; - ${bindings.map(binding => binding.bind).join('\n')} + ${setParentFromChildOnChange} ${name_updating} = changed; #component._set(newState); ${name_updating} = {}; } `); - bindings.forEach(binding => { - if (binding.update) updates.push(binding.update); - }); - - _beforecreate = deindent` + beforecreate = deindent` #component._root._beforecreate.push(function () { var state = #component.get(), childState = ${name}.get(), newState = {}; if (!childState) return; - ${bindings.map(binding => binding.setParentFromChild).join('\n')} + ${setParentFromChildOnInit} ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; #component._set(newState); ${name_updating} = {}; @@ -285,11 +235,11 @@ export default function visitComponent( componentInitProperties.push(`data: ${initialPropString}`); } - local.update.addBlock(deindent` + block.builders.update.addBlock(deindent` var ${name}_changes = {}; ${updates.join('\n')} ${name}._set( ${name}_changes ); - ${bindings.length && `${name_updating} = {};`} + ${local.bindings.length && `${name_updating} = {};`} `); } @@ -298,21 +248,19 @@ export default function visitComponent( : generator.importedComponents.get(node.name) || `@template.components.${node.name}`; - local.create.addBlockAtStart(deindent` + block.builders.init.addBlock(deindent` ${statements.join('\n')} var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); - ${_beforecreate} + ${beforecreate} `); if (isTopLevel) block.builders.unmount.addLine(`${name}._fragment.unmount();`); block.builders.destroy.addLine(`${name}.destroy( false );`); - block.builders.init.addBlock(local.create); - const targetNode = state.parentNode || '#target'; const anchorNode = state.parentNode ? 'null' : 'anchor'; @@ -324,7 +272,94 @@ export default function visitComponent( `${name}._fragment.mount( ${targetNode}, ${anchorNode} );` ); - if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); + // event handlers + node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => { + const usedContexts: string[] = []; + + if (handler.expression) { + generator.addSourcemapLocations(handler.expression); + generator.code.prependRight( + handler.expression.start, + `${block.alias('component')}.` + ); + + handler.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, null, true); + + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + if (!~local.allUsedContexts.indexOf(context)) + local.allUsedContexts.push(context); + }); + }); + } + + // TODO hoist event handlers? can do `this.__component.method(...)` + const declarations = usedContexts.map(name => { + if (name === 'state') return 'var state = this._context.state;'; + + const listName = block.listNames.get(name); + const indexName = block.indexNames.get(name); + + return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; + }); + + const handlerBody = + (declarations.length ? declarations.join('\n') + '\n\n' : '') + + (handler.expression ? + `[✂${handler.expression.start}-${handler.expression.end}✂];` : + `${block.alias('component')}.fire('${handler.name}', event);`); + + block.builders.init.addBlock(deindent` + ${name}.on( '${handler.name}', function ( event ) { + ${handlerBody} + }); + `); + }); + + // refs + node.attributes.filter((a: Node) => a.type === 'Ref').forEach((ref: Node) => { + generator.usesRefs = true; + + block.builders.init.addLine(`#component.refs.${ref.name} = ${name};`); + + block.builders.destroy.addLine(deindent` + if ( #component.refs.${ref.name} === ${name} ) #component.refs.${ref.name} = null; + `); + }); + + // maintain component context + if (local.allUsedContexts.length) { + const initialProps = local.allUsedContexts + .map(contextName => { + if (contextName === 'state') return `state: state`; + + const listName = block.listNames.get(contextName); + const indexName = block.indexNames.get(contextName); + + return `${listName}: ${listName},\n${indexName}: ${indexName}`; + }) + .join(',\n'); + + const updates = local.allUsedContexts + .map(contextName => { + if (contextName === 'state') return `${name}._context.state = state;`; + + const listName = block.listNames.get(contextName); + const indexName = block.indexNames.get(contextName); + + return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; + }) + .join('\n'); + + block.builders.init.addBlock(deindent` + ${name}._context = { + ${initialProps} + }; + `); + + block.builders.update.addBlock(updates); + } } function isComputed(node: Node) { diff --git a/src/generators/dom/visitors/Component/EventHandler.ts b/src/generators/dom/visitors/Component/EventHandler.ts deleted file mode 100644 index 0abef43821..0000000000 --- a/src/generators/dom/visitors/Component/EventHandler.ts +++ /dev/null @@ -1,57 +0,0 @@ -import deindent from '../../../../utils/deindent'; -import { DomGenerator } from '../../index'; -import Block from '../../Block'; -import { Node } from '../../../../interfaces'; -import { State } from '../../interfaces'; - -export default function visitEventHandler( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - attribute: Node, - local -) { - // TODO verify that it's a valid callee (i.e. built-in or declared method) - const usedContexts: string[] = []; - - if (attribute.expression) { - generator.addSourcemapLocations(attribute.expression); - generator.code.prependRight( - attribute.expression.start, - `${block.alias('component')}.` - ); - - attribute.expression.arguments.forEach((arg: Node) => { - const { contexts } = block.contextualise(arg, null, true); - - contexts.forEach(context => { - if (!~usedContexts.indexOf(context)) usedContexts.push(context); - if (!~local.allUsedContexts.indexOf(context)) - local.allUsedContexts.push(context); - }); - }); - } - - // TODO hoist event handlers? can do `this.__component.method(...)` - const declarations = usedContexts.map(name => { - if (name === 'state') return 'var state = this._context.state;'; - - const listName = block.listNames.get(name); - const indexName = block.indexNames.get(name); - - return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; - }); - - const handlerBody = - (declarations.length ? declarations.join('\n') + '\n\n' : '') + - (attribute.expression ? - `[✂${attribute.expression.start}-${attribute.expression.end}✂];` : - `${block.alias('component')}.fire('${attribute.name}', event);`); - - local.create.addBlock(deindent` - ${local.name}.on( '${attribute.name}', function ( event ) { - ${handlerBody} - }); - `); -} diff --git a/src/generators/dom/visitors/Component/Ref.ts b/src/generators/dom/visitors/Component/Ref.ts deleted file mode 100644 index 98afb02953..0000000000 --- a/src/generators/dom/visitors/Component/Ref.ts +++ /dev/null @@ -1,22 +0,0 @@ -import deindent from '../../../../utils/deindent'; -import { DomGenerator } from '../../index'; -import Block from '../../Block'; -import { Node } from '../../../../interfaces'; -import { State } from '../../interfaces'; - -export default function visitRef( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - attribute: Node, - local -) { - generator.usesRefs = true; - - local.create.addLine(`#component.refs.${attribute.name} = ${local.name};`); - - block.builders.destroy.addLine(deindent` - if ( #component.refs.${attribute.name} === ${local.name} ) #component.refs.${attribute.name} = null; - `); -} From 6f28f25a68295fb20549779da5fe4317c61ea858 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 16 Aug 2017 09:31:16 -0400 Subject: [PATCH 7/8] consolidate everything in Component.ts - much cleaner --- src/generators/Generator.ts | 13 +- .../dom/visitors/{Component => }/Component.ts | 280 ++++++++++++------ .../dom/visitors/Component/Attribute.ts | 77 ----- .../dom/visitors/Component/Binding.ts | 55 ---- .../dom/visitors/Element/Element.ts | 2 +- 5 files changed, 201 insertions(+), 226 deletions(-) rename src/generators/dom/visitors/{Component => }/Component.ts (58%) delete mode 100644 src/generators/dom/visitors/Component/Attribute.ts delete mode 100644 src/generators/dom/visitors/Component/Binding.ts diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 7798cf3c86..b94f74e488 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -122,11 +122,16 @@ export default class Generator { expression: Node, context: string, isEventHandler: boolean - ) { + ): { + dependencies: string[], + contexts: Set, + indexes: Set, + snippet: string + } { this.addSourcemapLocations(expression); - const usedContexts = new Set(); - const usedIndexes = new Set(); + const usedContexts: Set = new Set(); + const usedIndexes: Set = new Set(); const { code, helpers } = this; const { contexts, indexes } = block; @@ -208,7 +213,7 @@ export default class Generator { }, }); - const dependencies = new Set(expression._dependencies || []); + const dependencies: Set = new Set(expression._dependencies || []); if (expression._dependencies) { expression._dependencies.forEach((prop: string) => { diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component.ts similarity index 58% rename from src/generators/dom/visitors/Component/Component.ts rename to src/generators/dom/visitors/Component.ts index d104e53b10..7fe0c27c53 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -1,14 +1,13 @@ -import deindent from '../../../../utils/deindent'; -import CodeBuilder from '../../../../utils/CodeBuilder'; -import visit from '../../visit'; -import visitAttribute from './Attribute'; -import visitBinding from './Binding'; -import { DomGenerator } from '../../index'; -import Block from '../../Block'; -import getTailSnippet from '../../../../utils/getTailSnippet'; -import getObject from '../../../../utils/getObject'; -import { Node } from '../../../../interfaces'; -import { State } from '../../interfaces'; +import deindent from '../../../utils/deindent'; +import CodeBuilder from '../../../utils/CodeBuilder'; +import visit from '../visit'; +import { DomGenerator } from '../index'; +import Block from '../Block'; +import getTailSnippet from '../../../utils/getTailSnippet'; +import getObject from '../../../utils/getObject'; +import { stringify } from '../../../utils/stringify'; +import { Node } from '../../../interfaces'; +import { State } from '../interfaces'; function stringifyProps(props: string[]) { if (!props.length) return '{}'; @@ -22,15 +21,22 @@ function stringifyProps(props: string[]) { return `{ ${joined} }`; } -const order = { - Attribute: 1, - Binding: 3 -}; +interface Attribute { + name: string; + value: any; + dynamic: boolean; + dependencies?: string[] +} -const visitors = { - Attribute: visitAttribute, - Binding: visitBinding -}; +interface Binding { + name: string; + value: Node; + contexts: Set; + snippet: string; + obj: string; + prop: string; + dependencies: string[]; +} export default function visitComponent( generator: DomGenerator, @@ -39,47 +45,22 @@ export default function visitComponent( node: Node, elementStack: Node[] ) { - const hasChildren = node.children.length > 0; + generator.hasComponents = true; + const name = block.getUniqueName( (node.name === ':Self' ? generator.name : node.name).toLowerCase() ); - const childState = node._state; - - const local = { - allUsedContexts: [], - staticAttributes: [], - dynamicAttributes: [], - bindings: [] - }; - - const isTopLevel = !state.parentNode; - - generator.hasComponents = true; - - node.attributes - .sort((a, b) => order[a.type] - order[b.type]) - .forEach(attribute => { - visitors[attribute.type] && visitors[attribute.type]( - generator, - block, - childState, - node, - attribute, - local - ); - }); - const componentInitProperties = [`_root: #component._root`]; // Component has children, put them in a separate {{yield}} block - if (hasChildren) { + if (node.children.length > 0) { const params = block.params.join(', '); const childBlock = node._block; node.children.forEach((child: Node) => { - visit(generator, childBlock, childState, child, elementStack); + visit(generator, childBlock, node._state, child, elementStack); }); const yield_fragment = block.getUniqueName(`${name}_yield_fragment`); @@ -105,41 +86,51 @@ export default function visitComponent( componentInitProperties.push(`_yield: ${yield_fragment}`); } + const allContexts = new Set(); const statements: string[] = []; + const name_context = block.getUniqueName(`${name}_context`); + let name_updating: string; let name_initial_data: string; let beforecreate: string = null; - if ( - local.staticAttributes.length || - local.dynamicAttributes.length || - local.bindings.length - ) { - const initialProps = local.staticAttributes - .concat(local.dynamicAttributes) - .map(attribute => `${attribute.name}: ${attribute.value}`); + const attributes = node.attributes + .filter((a: Node) => a.type === 'Attribute') + .map((a: Node) => mungeAttribute(a, block)); + + const bindings = node.attributes + .filter((a: Node) => a.type === 'Binding') + .map((a: Node) => mungeBinding(a, block)); + + if (attributes.length || bindings.length) { + const initialProps = attributes + .map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`); const initialPropString = stringifyProps(initialProps); const updates: string[] = []; - local.dynamicAttributes.forEach(attribute => { - if (attribute.dependencies.length) { - updates.push(deindent` - if ( ${attribute.dependencies - .map(dependency => `changed.${dependency}`) - .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; - `); - } - - else { - // TODO this is an odd situation to encounter – I *think* it should only happen with - // each block indices, in which case it may be possible to optimise this - updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); - } - }); + attributes + .filter((attribute: Attribute) => attribute.dynamic) + .forEach((attribute: Attribute) => { + if (attribute.dependencies.length) { + updates.push(deindent` + if ( ${attribute.dependencies + .map(dependency => `changed.${dependency}`) + .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; + `); + } + + else { + // TODO this is an odd situation to encounter – I *think* it should only happen with + // each block indices, in which case it may be possible to optimise this + updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); + } + }); + + if (bindings.length) { + generator.hasComplexBindings = true; - if (local.bindings.length) { name_updating = block.alias(`${name}_updating`); name_initial_data = block.getUniqueName(`${name}_initial_data`); @@ -149,9 +140,13 @@ export default function visitComponent( const setParentFromChildOnChange = new CodeBuilder(); const setParentFromChildOnInit = new CodeBuilder(); - local.bindings.forEach(binding => { + bindings.forEach((binding: Binding) => { let setParentFromChild; + binding.contexts.forEach(context => { + allContexts.add(context); + }); + const { name: key } = getObject(binding.value); if (block.contexts.has(key)) { @@ -160,8 +155,8 @@ export default function visitComponent( const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; setParentFromChild = deindent` - var list = ${name}._context.${block.listNames.get(key)}; - var index = ${name}._context.${block.indexNames.get(key)}; + var list = ${name_context}.${block.listNames.get(key)}; + var index = ${name_context}.${block.indexNames.get(key)}; list[index]${tail} = childState.${binding.name}; ${binding.dependencies @@ -226,7 +221,7 @@ export default function visitComponent( var state = #component.get(), childState = ${name}.get(), newState = {}; if (!childState) return; ${setParentFromChildOnInit} - ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; + ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; #component._set(newState); ${name_updating} = {}; }); @@ -239,7 +234,7 @@ export default function visitComponent( var ${name}_changes = {}; ${updates.join('\n')} ${name}._set( ${name}_changes ); - ${local.bindings.length && `${name_updating} = {};`} + ${bindings.length && `${name_updating} = {};`} `); } @@ -257,8 +252,7 @@ export default function visitComponent( ${beforecreate} `); - if (isTopLevel) - block.builders.unmount.addLine(`${name}._fragment.unmount();`); + if (!state.parentNode) block.builders.unmount.addLine(`${name}._fragment.unmount();`); block.builders.destroy.addLine(`${name}.destroy( false );`); const targetNode = state.parentNode || '#target'; @@ -288,20 +282,19 @@ export default function visitComponent( contexts.forEach(context => { if (!~usedContexts.indexOf(context)) usedContexts.push(context); - if (!~local.allUsedContexts.indexOf(context)) - local.allUsedContexts.push(context); + allContexts.add(context); }); }); } // TODO hoist event handlers? can do `this.__component.method(...)` const declarations = usedContexts.map(name => { - if (name === 'state') return 'var state = this._context.state;'; + if (name === 'state') return `var state = ${name_context}.state;`; const listName = block.listNames.get(name); const indexName = block.indexNames.get(name); - return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; + return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_context}.${indexName}, ${name} = ${listName}[${indexName}]`; }); const handlerBody = @@ -329,8 +322,10 @@ export default function visitComponent( }); // maintain component context - if (local.allUsedContexts.length) { - const initialProps = local.allUsedContexts + if (allContexts.size) { + const contexts = Array.from(allContexts); + + const initialProps = contexts .map(contextName => { if (contextName === 'state') return `state: state`; @@ -341,19 +336,19 @@ export default function visitComponent( }) .join(',\n'); - const updates = local.allUsedContexts + const updates = contexts .map(contextName => { - if (contextName === 'state') return `${name}._context.state = state;`; + if (contextName === 'state') return `${name_context}.state = state;`; const listName = block.listNames.get(contextName); const indexName = block.indexNames.get(contextName); - return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; + return `${name_context}.${listName} = ${listName};\n${name_context}.${indexName} = ${indexName};`; }) .join('\n'); block.builders.init.addBlock(deindent` - ${name}._context = { + var ${name_context} = { ${initialProps} }; `); @@ -362,6 +357,113 @@ export default function visitComponent( } } +function mungeAttribute(attribute: Node, block: Block): Attribute { + if (attribute.value === true) { + // attributes without values, e.g.