From dba32df84e7ee41c8a3699ce347a7b272a2a2ee1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 3 Dec 2017 16:25:01 -0500 Subject: [PATCH] client-side dynamic components mostly working (#640) --- src/generators/Generator.ts | 4 + src/generators/dom/preprocess.ts | 8 +- src/generators/dom/visitors/Component.ts | 141 ++++++++++++++---- .../dom/visitors/Element/Element.ts | 2 +- src/parse/state/tag.ts | 3 +- test/runtime/index.js | 4 + test/runtime/samples/switch/Bar.html | 1 + test/runtime/samples/switch/Foo.html | 1 + test/runtime/samples/switch/_config.js | 19 +++ test/runtime/samples/switch/main.html | 12 ++ 10 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 test/runtime/samples/switch/Bar.html create mode 100644 test/runtime/samples/switch/Foo.html create mode 100644 test/runtime/samples/switch/_config.js create mode 100644 test/runtime/samples/switch/main.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 323af454c7fa..2dcc3f379730 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -763,6 +763,10 @@ export default class Generator { node.metadata = contextualise(node.expression, contextDependencies, indexes); this.skip(); } + + if (node.type === 'Element' && node.name === ':Switch') { + node.metadata = contextualise(node.expression, contextDependencies, indexes); + } }, leave(node: Node, parent: Node) { diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 62768184a065..a6333c286cd3 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -436,13 +436,17 @@ const preprocessors = { } const isComponent = - generator.components.has(node.name) || node.name === ':Self'; + generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch'; if (isComponent) { cannotUseInnerHTML(node); node.var = block.getUniqueName( - (node.name === ':Self' ? generator.name : node.name).toLowerCase() + ( + node.name === ':Self' ? generator.name : + node.name === ':Switch' ? 'switch_instance' : + node.name + ).toLowerCase() ); node._state = getChildState(state, { diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index bfd52559d679..a9dbd4dbe247 100644 --- a/src/generators/dom/visitors/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -3,6 +3,7 @@ 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'; @@ -67,6 +68,9 @@ export default function visitComponent( .filter((a: Node) => a.type === 'Binding') .map((a: Node) => mungeBinding(a, block)); + const ref = node.attributes.find((a: Node) => a.type === 'Ref'); + if (ref) generator.usesRefs = true; + if (attributes.length || bindings.length) { const initialProps = attributes .map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`); @@ -205,30 +209,122 @@ export default function visitComponent( } } - const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; + const isSwitch = node.name === ':Switch'; - block.builders.init.addBlock(deindent` - ${statements.join('\n')} - var ${name} = new ${expression}({ - ${componentInitProperties.join(',\n')} - }); + const switch_vars = isSwitch && { + value: block.getUniqueName('switch_value'), + props: block.getUniqueName('switch_props') + }; - ${beforecreate} - `); + const expression = ( + node.name === ':Self' ? generator.name : + isSwitch ? switch_vars.value : + `%components-${node.name}` + ); - block.builders.create.addLine(`${name}._fragment.c();`); + if (isSwitch) { + 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 + ); + } - block.builders.claim.addLine( - `${name}._fragment.l(${state.parentNodes});` - ); + const params = block.params.join(', '); - block.builders.mount.addLine( - `${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` - ); + block.builders.init.addBlock(deindent` + var ${switch_vars.value} = ${snippet}; + `); - if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`); + block.builders.init.addBlock(deindent` + 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} + } + `); + + 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(); - block.builders.destroy.addLine(`${name}.destroy(false);`); + if (${switch_vars.value}) { + ${name} = new ${switch_vars.value}(${switch_vars.props}(${params})); + ${name}._fragment.c(); + ${name}._mount(${anchor}.parentNode, ${anchor}); + ${ref && `#component.refs.${ref.name} = ${name};`} + } + + ${ref && deindent` + else if (#component.refs.${ref.name} === ${name}) { + #component.refs.${ref.name} = null; + }`} + } else { + // normal update + } + `); + + 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} + + ${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 (!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;`} + `); + } // event handlers node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => { @@ -274,17 +370,6 @@ export default function visitComponent( `); }); - // 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 (allContexts.size) { const contexts = Array.from(allContexts); diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 8de38c766d01..7dc6eb72626a 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -43,7 +43,7 @@ export default function visitElement( } } - if (generator.components.has(node.name) || node.name === ':Self') { + if (generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch') { return visitComponent(generator, block, state, node, elementStack, componentStack); } diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index d2c17e30fa1b..c62053ffb55b 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -171,6 +171,7 @@ export default function tag(parser: Parser) { element.expression = readExpression(parser); parser.allowWhitespace(); parser.eat('}', true); + parser.allowWhitespace(); } const uniqueNames = new Set(); @@ -181,8 +182,6 @@ export default function tag(parser: Parser) { parser.allowWhitespace(); } - parser.allowWhitespace(); - // special cases – top-level \ No newline at end of file