From f314b1d695732d8ebcff58a3e01229b17ee088aa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 16 Aug 2017 08:59:56 -0400 Subject: [PATCH] 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; - `); -}