From 7f709cef10c27d2965e11c054f3e68ed7fd4ce65 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Sep 2017 18:14:31 -0400 Subject: [PATCH] refactor mounting logic --- src/generators/dom/Block.ts | 15 ++-------- src/generators/dom/index.ts | 3 ++ src/generators/dom/mountChildren.ts | 30 +++++++++++++++++++ src/generators/dom/visitors/Component.ts | 5 +++- src/generators/dom/visitors/EachBlock.ts | 24 +++++++++++---- .../dom/visitors/Element/Element.ts | 13 ++++---- src/generators/dom/visitors/IfBlock.ts | 15 ++++++++-- src/generators/dom/visitors/RawMustacheTag.ts | 28 +++++++++-------- src/generators/dom/visitors/Slot.ts | 26 ++++++---------- src/utils/CodeBuilder.ts | 28 ++++++++++++++--- 10 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 src/generators/dom/mountChildren.ts diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 6b696b5ad5..50cbf204f9 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -130,15 +130,11 @@ export default class Block { claimStatement: string, parentNode: string ) { - const isToplevel = !parentNode; - this.addVariable(name); this.builders.create.addLine(`${name} = ${renderStatement};`); this.builders.claim.addLine(`${name} = ${claimStatement};`); - this.mount(name, parentNode); - - if (isToplevel) { + if (!parentNode) { this.builders.unmount.addLine(`@detachNode(${name});`); } } @@ -182,14 +178,6 @@ export default class Block { ); } - mount(name: string, parentNode: string) { - if (parentNode) { - this.builders.mount.addLine(`@appendNode(${name}, ${parentNode});`); - } else { - this.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`); - } - } - toString() { let introing; const hasIntros = !this.builders.intro.isEmpty(); @@ -223,6 +211,7 @@ export default class Block { if (this.first) { properties.addBlock(`first: null,`); this.builders.hydrate.addLine(`this.first = ${this.first};`); + this.builders.mount.addLineAtStart(`@insertNode(${this.first}, #target, anchor);`); } if (this.builders.create.isEmpty()) { diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index bccfa3288b..7250341607 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -13,6 +13,7 @@ import Generator from '../Generator'; import Stylesheet from '../../css/Stylesheet'; import preprocess from './preprocess'; import Block from './Block'; +import mountChildren from './mountChildren'; import { version } from '../../../package.json'; import { Parsed, CompileOptions, Node } from '../../interfaces'; @@ -105,6 +106,8 @@ export default function dom( visit(generator, block, state, node, [], []); }); + block.builders.mount.addBlock(mountChildren(parsed.html)); + const builder = new CodeBuilder(); const computationBuilder = new CodeBuilder(); const computationDeps = new Set(); diff --git a/src/generators/dom/mountChildren.ts b/src/generators/dom/mountChildren.ts new file mode 100644 index 0000000000..5ec66cfa37 --- /dev/null +++ b/src/generators/dom/mountChildren.ts @@ -0,0 +1,30 @@ +import CodeBuilder from '../../utils/CodeBuilder'; +import { Node } from '../../interfaces'; +import Block from './Block'; + +export default function mountChildren(node: Node, parentNode?: string) { + const builder = new CodeBuilder(); + + node.children.forEach((child: Node) => { + if (child.mountStatement) { + // TODO determining whether to use line or block should probably + // happen inside CodeBuilder + if (/\n/.test(child.mountStatement)) { + builder.addBlock(child.mountStatement); + } else { + builder.addLine(child.mountStatement); + } + } else { + if (child.shouldSkip) return; + if (child.type === 'Element' && child.name === ':Window') return; + + if (parentNode) { + builder.addLine(`@appendNode(${child.var}, ${parentNode});`); + } else { + builder.addLine(`@insertNode(${child.var}, #target, anchor);`); + } + } + }); + + return builder; +} \ No newline at end of file diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index 1a06ebadec..854924379f 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 mountChildren from '../mountChildren'; import getTailSnippet from '../../../utils/getTailSnippet'; import getObject from '../../../utils/getObject'; import getExpressionPrecedence from '../../../utils/getExpressionPrecedence'; @@ -60,6 +61,8 @@ export default function visitComponent( node.children.forEach((child: Node) => { visit(generator, block, node._state, child, elementStack, componentStack.concat(node)); }); + + block.builders.mount.addBlock(mountChildren(node, node._state.parentNode)); } const allContexts = new Set(); @@ -233,7 +236,7 @@ export default function visitComponent( `${name}._fragment.l( ${state.parentNodes} );` ); - block.builders.mount.addLine( + node.mountStatement = ( `${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` ); diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index a9259adf06..8cc688e838 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -2,6 +2,7 @@ import deindent from '../../../utils/deindent'; import visit from '../visit'; import { DomGenerator } from '../index'; import Block from '../Block'; +import mountChildren from '../mountChildren'; import isDomNode from './shared/isDomNode'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; @@ -79,11 +80,11 @@ export default function visitEachBlock( } `); - block.builders.mount.addBlock(deindent` + node.mountStatement += '\n\n' + deindent` if (${each_block_else}) { ${each_block_else}.${mountOrIntro}(${state.parentNode || '#target'}, null); } - `); + `; const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -126,14 +127,23 @@ export default function visitEachBlock( `); } + // TODO do this elsewhere? + if (needsAnchor) node.mountStatement += '\n\n' + ( + state.parentNode ? `@appendNode(${anchor}, ${state.parentNode});` : `@insertNode(${anchor}, #target, anchor);` + ); + node.children.forEach((child: Node) => { visit(generator, node._block, node._state, child, elementStack, componentStack); }); + node._block.builders.mount.addBlock(mountChildren(node)); + if (node.else) { node.else.children.forEach((child: Node) => { visit(generator, node.else._block, node.else._state, child, elementStack, componentStack); }); + + node.else._block.builders.mount.addBlock(mountChildren(node.else)); } } @@ -164,6 +174,8 @@ function keyed( block.addVariable(head); block.addVariable(last); + let first; + if (node.children[0] && node.children[0].type === 'Element' && !generator.components.has(node.children[0].name)) { // TODO or text/tag/raw node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing @@ -209,13 +221,13 @@ function keyed( } `); - block.builders.mount.addBlock(deindent` + node.mountStatement = deindent` var ${iteration} = ${head}; while (${iteration}) { ${iteration}.${mountOrIntro}(${targetNode}, ${anchorNode}); ${iteration} = ${iteration}.next; } - `); + `; const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -396,11 +408,11 @@ function unkeyed( } `); - block.builders.mount.addBlock(deindent` + node.mountStatement = deindent` for (var #i = 0; #i < ${iterations}.length; #i += 1) { ${iterations}[#i].${mountOrIntro}(${targetNode}, ${anchorNode}); } - `); + `; const dependencies = block.findDependencies(node.expression); const allDependencies = new Set(node._block.dependencies); diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 8b42d40a18..5c067b8b44 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -13,6 +13,7 @@ import isVoidElementName from '../../../../utils/isVoidElementName'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; +import mountChildren from '../../mountChildren'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; import reservedNames from '../../../../utils/reservedNames'; @@ -86,12 +87,10 @@ export default function visitElement( `); } - if (parentNode) { - block.builders.mount.addLine( - `@appendNode(${name}, ${parentNode});` - ); - } else { - block.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`); + // TODO this is kinda messy — this is a hack to prevent the mount statement + // going in the usual place + if (node.slotted) { + node.mountStatement = `@appendNode(${node.var}, ${parentNode});`; } // add CSS encapsulation attribute @@ -216,6 +215,8 @@ export default function visitElement( node.children.forEach((child: Node) => { visit(generator, block, childState, child, elementStack.concat(node), componentStack); }); + + block.builders.mount.addBlock(mountChildren(node, node.var)); } if (node.lateUpdate) { diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 6c939a698e..acaa14397f 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -2,6 +2,7 @@ import deindent from '../../../utils/deindent'; import visit from '../visit'; import { DomGenerator } from '../index'; import Block from '../Block'; +import mountChildren from '../mountChildren'; import isDomNode from './shared/isDomNode'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; @@ -68,6 +69,8 @@ function visitChildren( node.children.forEach((child: Node) => { visit(generator, node._block, node._state, child, elementStack, componentStack); }); + + node._block.builders.mount.addBlock(mountChildren(node)); } export default function visitIfBlock( @@ -127,6 +130,11 @@ export default function visitIfBlock( `@createComment()`, state.parentNode ); + + // TODO do this elsewhere? + node.mountStatement += '\n\n' + ( + state.parentNode ? `@appendNode(${anchor}, ${state.parentNode})` : `@insertNode(${anchor}, #target, anchor)` + ); } } @@ -148,7 +156,7 @@ function simple( const targetNode = state.parentNode || '#target'; const anchorNode = state.parentNode ? 'null' : 'anchor'; - block.builders.mount.addLine( + node.mountStatement = ( `if (${name}) ${name}.${mountOrIntro}(${targetNode}, ${anchorNode});` ); @@ -251,7 +259,8 @@ function compound( const targetNode = state.parentNode || '#target'; const anchorNode = state.parentNode ? 'null' : 'anchor'; - block.builders.mount.addLine( + + node.mountStatement = ( `${if_name}${name}.${mountOrIntro}(${targetNode}, ${anchorNode});` ); @@ -350,7 +359,7 @@ function compoundWithOutros( const targetNode = state.parentNode || '#target'; const anchorNode = state.parentNode ? 'null' : 'anchor'; - block.builders.mount.addLine( + node.mountStatement = ( `${if_current_block_type_index}${if_blocks}[${current_block_type_index}].${mountOrIntro}(${targetNode}, ${anchorNode});` ); diff --git a/src/generators/dom/visitors/RawMustacheTag.ts b/src/generators/dom/visitors/RawMustacheTag.ts index a6df2c07be..b6583f9ac0 100644 --- a/src/generators/dom/visitors/RawMustacheTag.ts +++ b/src/generators/dom/visitors/RawMustacheTag.ts @@ -55,8 +55,8 @@ export default function visitRawMustacheTag( ` ); - // we would have used comments here, but the `insertAdjacentHTML` api only - // exists for `Element`s. + let mountStatements: string[] = []; + if (needsAnchorBefore) { block.addElement( anchorBefore, @@ -64,6 +64,10 @@ export default function visitRawMustacheTag( `@createElement('noscript')`, state.parentNode ); + + mountStatements.push( + state.parentNode ? `@appendNode(${anchorBefore}, ${state.parentNode});` : `@insertNode(${anchorBefore}, #target, anchor);` + ); } function addAnchorAfter() { @@ -73,19 +77,17 @@ export default function visitRawMustacheTag( `@createElement('noscript')`, state.parentNode ); - } - if (needsAnchorAfter && anchorBefore === 'null') { - // anchorAfter needs to be in the DOM before we - // insert the HTML... - addAnchorAfter(); + mountStatements.push( + state.parentNode ? `@appendNode(${anchorAfter}, ${state.parentNode});` : `@insertNode(${anchorAfter}, #target, anchor);` + ); } - block.builders.mount.addLine(insert(init)); - block.builders.detachRaw.addBlock(detach); + if (needsAnchorAfter && anchorBefore === 'null') addAnchorAfter(); + mountStatements.push(insert(init)); + if (needsAnchorAfter && anchorBefore !== 'null') addAnchorAfter(); - if (needsAnchorAfter && anchorBefore !== 'null') { - // ...otherwise it should go afterwards - addAnchorAfter(); - } + node.mountStatement = mountStatements.join('\n'); + + block.builders.detachRaw.addBlock(detach); } \ No newline at end of file diff --git a/src/generators/dom/visitors/Slot.ts b/src/generators/dom/visitors/Slot.ts index ef3651e1bf..a72f6e68d7 100644 --- a/src/generators/dom/visitors/Slot.ts +++ b/src/generators/dom/visitors/Slot.ts @@ -2,6 +2,7 @@ import { DomGenerator } from '../index'; import deindent from '../../../utils/deindent'; import visit from '../visit'; import Block from '../Block'; +import mountChildren from '../mountChildren'; import getStaticAttributeValue from '../../../utils/getStaticAttributeValue'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; @@ -34,39 +35,30 @@ export default function visitSlot( if (needsAnchorBefore) block.addVariable(anchorBefore); if (needsAnchorAfter) block.addVariable(anchorAfter); - block.builders.create.pushCondition(`!${content_name}`); - block.builders.hydrate.pushCondition(`!${content_name}`); - block.builders.mount.pushCondition(`!${content_name}`); - block.builders.unmount.pushCondition(`!${content_name}`); - block.builders.destroy.pushCondition(`!${content_name}`); - node.children.forEach((child: Node) => { visit(generator, block, state, child, elementStack, componentStack); }); - block.builders.create.popCondition(); - block.builders.hydrate.popCondition(); - block.builders.mount.popCondition(); - block.builders.unmount.popCondition(); - block.builders.destroy.popCondition(); - - // TODO can we use an else here? if (state.parentNode) { - block.builders.mount.addBlock(deindent` + node.mountStatement = deindent` if (${content_name}) { ${needsAnchorBefore && `@appendNode(${anchorBefore} || (${anchorBefore} = @createComment()), ${state.parentNode});`} @appendNode(${content_name}, ${state.parentNode}); ${needsAnchorAfter && `@appendNode(${anchorAfter} || (${anchorAfter} = @createComment()), ${state.parentNode});`} + } else { + ${mountChildren(node, state.parentNode)} } - `); + `; } else { - block.builders.mount.addBlock(deindent` + node.mountStatement = deindent` if (${content_name}) { ${needsAnchorBefore && `@insertNode(${anchorBefore} || (${anchorBefore} = @createComment()), #target, anchor);`} @insertNode(${content_name}, #target, anchor); ${needsAnchorAfter && `@insertNode(${anchorAfter} || (${anchorAfter} = @createComment()), #target, anchor);`} + } else { + ${mountChildren(node, state.parentNode)} } - `); + `; } // if the slot is unmounted, move nodes back into the document fragment, diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 5d0477ce68..2b5911c4a7 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -51,7 +51,12 @@ export default class CodeBuilder { this.last = ChunkType.Block; } - addLine(line: string) { + addLine(line: string | CodeBuilder) { + if (line instanceof CodeBuilder) { + if (!line.isEmpty()) this.addLine(line.toString()); + return; + } + this.reifyConditions(); if (this.lastCondition) { @@ -71,7 +76,12 @@ export default class CodeBuilder { if (!this.first) this.first = ChunkType.Line; } - addLineAtStart(line: string) { + addLineAtStart(line: string | CodeBuilder) { + if (line instanceof CodeBuilder) { + if (!line.isEmpty()) this.addLineAtStart(line.toString()); + return; + } + this.reifyConditions(); if (this.first === ChunkType.Block) { @@ -86,7 +96,12 @@ export default class CodeBuilder { if (!this.last) this.last = ChunkType.Line; } - addBlock(block: string) { + addBlock(block: string | CodeBuilder) { + if (block instanceof CodeBuilder) { + if (!block.isEmpty()) this.addBlock(block.toString()); + return; + } + this.reifyConditions(); if (this.indent) block = block.replace(/^/gm, `${this.indent}`); @@ -106,7 +121,12 @@ export default class CodeBuilder { if (!this.first) this.first = ChunkType.Block; } - addBlockAtStart(block: string) { + addBlockAtStart(block: string | CodeBuilder) { + if (block instanceof CodeBuilder) { + if (!block.isEmpty()) this.addBlockAtStart(block.toString()); + return; + } + this.reifyConditions(); if (this.result) {