diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts index c5e8f0bed4..97b09ea6cb 100644 --- a/src/generators/dom/visit.ts +++ b/src/generators/dom/visit.ts @@ -12,6 +12,7 @@ export default function visit( elementStack: Node[], componentStack: Node[] ) { + throw new Error('do not use visit') const visitor = visitors[node.type]; visitor(generator, block, state, node, elementStack, componentStack); } diff --git a/src/generators/dom/visitors/AwaitBlock.ts b/src/generators/dom/visitors/AwaitBlock.ts deleted file mode 100644 index 6e014af289..0000000000 --- a/src/generators/dom/visitors/AwaitBlock.ts +++ /dev/null @@ -1,159 +0,0 @@ -import deindent from '../../../utils/deindent'; -import visit from '../visit'; -import { DomGenerator } from '../index'; -import Block from '../Block'; -import isDomNode from './shared/isDomNode'; -import { Node } from '../../../interfaces'; -import { State } from '../interfaces'; - -export default function visitAwaitBlock( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - elementStack: Node[], - componentStack: Node[] -) { - const name = node.var; - - const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator); - const anchor = needsAnchor - ? block.getUniqueName(`${name}_anchor`) - : (node.next && node.next.var) || 'null'; - - const params = block.params.join(', '); - - block.contextualise(node.expression); - const { snippet } = node.metadata; - - if (needsAnchor) { - block.addElement( - anchor, - `@createComment()`, - `@createComment()`, - state.parentNode - ); - } - - const promise = block.getUniqueName(`promise`); - const resolved = block.getUniqueName(`resolved`); - const await_block = block.getUniqueName(`await_block`); - const await_block_type = block.getUniqueName(`await_block_type`); - const token = block.getUniqueName(`token`); - const await_token = block.getUniqueName(`await_token`); - const handle_promise = block.getUniqueName(`handle_promise`); - const replace_await_block = block.getUniqueName(`replace_await_block`); - const old_block = block.getUniqueName(`old_block`); - const value = block.getUniqueName(`value`); - const error = block.getUniqueName(`error`); - const create_pending_block = node.pending._block.name; - const create_then_block = node.then._block.name; - const create_catch_block = node.catch._block.name; - - block.addVariable(await_block); - block.addVariable(await_block_type); - block.addVariable(await_token); - block.addVariable(promise); - block.addVariable(resolved); - - block.builders.init.addBlock(deindent` - function ${replace_await_block}(${token}, type, ${value}, ${params}) { - if (${token} !== ${await_token}) return; - - var ${old_block} = ${await_block}; - ${await_block} = (${await_block_type} = type)(${params}, ${resolved} = ${value}, #component); - - if (${old_block}) { - ${old_block}.u(); - ${old_block}.d(); - ${await_block}.c(); - ${await_block}.m(${state.parentNode || `${anchor}.parentNode`}, ${anchor}); - } - } - - function ${handle_promise}(${promise}, ${params}) { - var ${token} = ${await_token} = {}; - - if (@isPromise(${promise})) { - ${promise}.then(function(${value}) { - ${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params}); - }, function (${error}) { - ${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params}); - }); - - // if we previously had a then/catch block, destroy it - if (${await_block_type} !== ${create_pending_block}) { - ${replace_await_block}(${token}, ${create_pending_block}, null, ${params}); - return true; - } - } else { - ${resolved} = ${promise}; - if (${await_block_type} !== ${create_then_block}) { - ${replace_await_block}(${token}, ${create_then_block}, ${resolved}, ${params}); - return true; - } - } - } - - ${handle_promise}(${promise} = ${snippet}, ${params}); - `); - - block.builders.create.addBlock(deindent` - ${await_block}.c(); - `); - - block.builders.claim.addBlock(deindent` - ${await_block}.l(${state.parentNodes}); - `); - - const targetNode = state.parentNode || '#target'; - const anchorNode = state.parentNode ? 'null' : 'anchor'; - - block.builders.mount.addBlock(deindent` - ${await_block}.m(${targetNode}, ${anchorNode}); - `); - - const conditions = []; - if (node.metadata.dependencies) { - conditions.push( - `(${node.metadata.dependencies.map(dep => `'${dep}' in changed`).join(' || ')})` - ); - } - - conditions.push( - `${promise} !== (${promise} = ${snippet})`, - `${handle_promise}(${promise}, ${params})` - ); - - if (node.pending._block.hasUpdateMethod) { - block.builders.update.addBlock(deindent` - if (${conditions.join(' && ')}) { - // nothing - } else { - ${await_block}.p(changed, ${params}, ${resolved}); - } - `); - } else { - block.builders.update.addBlock(deindent` - if (${conditions.join(' && ')}) { - ${await_block}.c(); - ${await_block}.m(${anchor}.parentNode, ${anchor}); - } - `); - } - - block.builders.unmount.addBlock(deindent` - ${await_block}.u(); - `); - - block.builders.destroy.addBlock(deindent` - ${await_token} = null; - ${await_block}.d(); - `); - - [node.pending, node.then, node.catch].forEach(status => { - status.children.forEach(child => { - visit(generator, status._block, status._state, child, elementStack, componentStack); - }); - }); -} \ No newline at end of file diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts deleted file mode 100644 index 2271b499f9..0000000000 --- a/src/generators/dom/visitors/Component.ts +++ /dev/null @@ -1,588 +0,0 @@ -import deindent from '../../../utils/deindent'; -import CodeBuilder from '../../../utils/CodeBuilder'; -import visit from '../visit'; -import { DomGenerator } from '../index'; -import Block from '../Block'; -import isDomNode from './shared/isDomNode'; -import getTailSnippet from '../../../utils/getTailSnippet'; -import getObject from '../../../utils/getObject'; -import getExpressionPrecedence from '../../../utils/getExpressionPrecedence'; -import getStaticAttributeValue from '../../../utils/getStaticAttributeValue'; -import { stringify } from '../../../utils/stringify'; -import stringifyProps from '../../../utils/stringifyProps'; -import { Node } from '../../../interfaces'; -import { State } from '../interfaces'; - -interface Attribute { - name: string; - value: any; - dynamic: boolean; - dependencies?: string[] -} - -interface Binding { - name: string; - value: Node; - contexts: Set; - snippet: string; - obj: string; - prop: string; - dependencies: string[]; -} - -export default function visitComponent( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - elementStack: Node[], - componentStack: Node[] -) { - generator.hasComponents = true; - - const name = node.var; - - const componentInitProperties = [`_root: #component._root`]; - - if (node.children.length > 0) { - const slots = Array.from(node._slots).map(name => `${name}: @createFragment()`); - componentInitProperties.push(`slots: { ${slots.join(', ')} }`); - - node.children.forEach((child: Node) => { - visit(generator, block, node._state, child, elementStack, componentStack.concat(node)); - }); - } - - 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; - - const attributes = node.attributes - .filter(a => a.type === 'Attribute') - .map(a => mungeAttribute(a, block)); - - const bindings = node.attributes - .filter(a => a.type === 'Binding') - .map(a => mungeBinding(a, block)); - - const eventHandlers = node.attributes - .filter((a: Node) => a.type === 'EventHandler') - .map(a => mungeEventHandler(generator, node, a, block, name_context, allContexts)); - - const ref = node.attributes.find((a: Node) => a.type === 'Ref'); - if (ref) generator.usesRefs = true; - - const updates: string[] = []; - - if (attributes.length || bindings.length) { - const initialProps = attributes - .map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`); - - const initialPropString = stringifyProps(initialProps); - - 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; - - 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};`); - - const setParentFromChildOnChange = new CodeBuilder(); - const setParentFromChildOnInit = new CodeBuilder(); - - bindings.forEach((binding: Binding) => { - let setParentFromChild; - - binding.contexts.forEach(context => { - allContexts.add(context); - }); - - 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};`; - } - - 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; - } - `); - } - }); - - componentInitProperties.push(`data: ${name_initial_data}`); - - componentInitProperties.push(deindent` - _bind: function(changed, childState) { - var state = #component.get(), newState = {}; - ${setParentFromChildOnChange} - ${name_updating} = @assign({}, changed); - #component._set(newState); - ${name_updating} = {}; - } - `); - - beforecreate = deindent` - #component._root._beforecreate.push(function() { - var state = #component.get(), childState = ${name}.get(), newState = {}; - if (!childState) return; - ${setParentFromChildOnInit} - ${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} }; - #component._set(newState); - ${name_updating} = {}; - }); - `; - } else if (initialProps.length) { - componentInitProperties.push(`data: ${initialPropString}`); - } - } - - const isDynamicComponent = node.name === ':Component'; - - const switch_vars = isDynamicComponent && { - value: block.getUniqueName('switch_value'), - props: block.getUniqueName('switch_props') - }; - - const expression = ( - node.name === ':Self' ? generator.name : - isDynamicComponent ? switch_vars.value : - `%components-${node.name}` - ); - - if (isDynamicComponent) { - block.contextualise(node.expression); - const { dependencies, snippet } = node.metadata; - - const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator); - const anchor = needsAnchor - ? block.getUniqueName(`${name}_anchor`) - : (node.next && node.next.var) || 'null'; - - if (needsAnchor) { - block.addElement( - anchor, - `@createComment()`, - `@createComment()`, - state.parentNode - ); - } - - const params = block.params.join(', '); - - block.builders.init.addBlock(deindent` - var ${switch_vars.value} = ${snippet}; - - function ${switch_vars.props}(${params}) { - return { - ${componentInitProperties.join(',\n')} - }; - } - - if (${switch_vars.value}) { - ${statements.length > 0 && statements.join('\n')} - var ${name} = new ${expression}(${switch_vars.props}(${params})); - - ${beforecreate} - } - - ${eventHandlers.map(handler => deindent` - function ${handler.var}(event) { - ${handler.body} - } - - if (${name}) ${name}.on("${handler.name}", ${handler.var}); - `)} - `); - - block.builders.create.addLine( - `if (${name}) ${name}._fragment.c();` - ); - - block.builders.claim.addLine( - `if (${name}) ${name}._fragment.l(${state.parentNodes});` - ); - - block.builders.mount.addLine( - `if (${name}) ${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` - ); - - block.builders.update.addBlock(deindent` - if (${switch_vars.value} !== (${switch_vars.value} = ${snippet})) { - if (${name}) ${name}.destroy(); - - if (${switch_vars.value}) { - ${name} = new ${switch_vars.value}(${switch_vars.props}(${params})); - ${name}._fragment.c(); - - ${node.children.map(child => remount(generator, child, name))} - ${name}._mount(${anchor}.parentNode, ${anchor}); - - ${eventHandlers.map(handler => deindent` - ${name}.on("${handler.name}", ${handler.var}); - `)} - - ${ref && `#component.refs.${ref.name} = ${name};`} - } - - ${ref && deindent` - else if (#component.refs.${ref.name} === ${name}) { - #component.refs.${ref.name} = null; - }`} - } - `); - - if (updates.length) { - block.builders.update.addBlock(deindent` - else { - var ${name}_changes = {}; - ${updates.join('\n')} - ${name}._set(${name}_changes); - ${bindings.length && `${name_updating} = {};`} - } - `); - } - - if (!state.parentNode) block.builders.unmount.addLine(`if (${name}) ${name}._unmount();`); - - block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`); - } else { - block.builders.init.addBlock(deindent` - ${statements.join('\n')} - var ${name} = new ${expression}({ - ${componentInitProperties.join(',\n')} - }); - - ${beforecreate} - - ${eventHandlers.map(handler => deindent` - ${name}.on("${handler.name}", function(event) { - ${handler.body} - }); - `)} - - ${ref && `#component.refs.${ref.name} = ${name};`} - `); - - block.builders.create.addLine(`${name}._fragment.c();`); - - block.builders.claim.addLine( - `${name}._fragment.l(${state.parentNodes});` - ); - - block.builders.mount.addLine( - `${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` - ); - - if (updates.length) { - block.builders.update.addBlock(deindent` - var ${name}_changes = {}; - ${updates.join('\n')} - ${name}._set(${name}_changes); - ${bindings.length && `${name_updating} = {};`} - `); - } - - if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`); - - block.builders.destroy.addLine(deindent` - ${name}.destroy(false); - ${ref && `if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null;`} - `); - } - - // maintain component context - if (allContexts.size) { - const contexts = Array.from(allContexts); - - const initialProps = contexts - .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 = contexts - .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` - var ${name_context} = { - ${initialProps} - }; - `); - - block.builders.update.addBlock(updates); - } -} - -function mungeAttribute(attribute: Node, block: Block): Attribute { - if (attribute.value === true) { - // attributes without values, e.g.