From 421f3d698f3a41ab1f5e9b90de6be7443d28aa94 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 8 Apr 2017 14:37:29 -0400 Subject: [PATCH] separate current *fragment* from current *generator state* --- src/generators/Generator.js | 2 -- src/generators/dom/Fragment.js | 18 ++++++------ src/generators/dom/index.js | 11 +++++--- src/generators/dom/visit.js | 4 +-- src/generators/dom/visitors/Component.js | 17 ++++++----- src/generators/dom/visitors/EachBlock.js | 19 +++++++------ src/generators/dom/visitors/Element.js | 28 ++++++++----------- src/generators/dom/visitors/IfBlock.js | 26 +++++++++-------- src/generators/dom/visitors/MustacheTag.js | 4 +-- src/generators/dom/visitors/RawMustacheTag.js | 8 +++--- src/generators/dom/visitors/Text.js | 6 ++-- src/generators/dom/visitors/YieldTag.js | 6 ++-- .../samples/attribute-dynamic/_config.js | 1 + 13 files changed, 76 insertions(+), 74 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index bd80f4fefa..37d8de90b6 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -30,8 +30,6 @@ export default class Generator { // in dev mode this.expectedProperties = new Set(); - this.elementDepth = 0; - this.code = new MagicString( source ); this.css = parsed.css ? processCss( parsed, this.code ) : null; this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; diff --git a/src/generators/dom/Fragment.js b/src/generators/dom/Fragment.js index 16b3093d3e..6c1d8513a1 100644 --- a/src/generators/dom/Fragment.js +++ b/src/generators/dom/Fragment.js @@ -3,16 +3,16 @@ export default class Fragment { Object.assign( this, options ); } - addElement ( name, renderStatement, needsIdentifier = false ) { - const isToplevel = this.localElementDepth === 0; + addElement ( name, renderStatement, target, localElementDepth, needsIdentifier = false ) { + const isToplevel = localElementDepth === 0; if ( needsIdentifier || isToplevel ) { this.builders.create.addLine( `var ${name} = ${renderStatement};` ); - this.createMountStatement( name ); + this.createMountStatement( name, target ); } else { - this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${this.target} );` ); + this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${target} );` ); } if ( isToplevel ) { @@ -24,16 +24,16 @@ export default class Fragment { return new Fragment( Object.assign( {}, this, options, { parent: this } ) ); } - createAnchor ( name ) { + createAnchor ( name, target, localElementDepth ) { const renderStatement = `${this.generator.helper( 'createComment' )}()`; - this.addElement( name, renderStatement, true ); + this.addElement( name, renderStatement, target, localElementDepth, true ); } - createMountStatement ( name ) { - if ( this.target === 'target' ) { + createMountStatement ( name, target ) { + if ( target === 'target' ) { this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, target, anchor );` ); } else { - this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${this.target} );` ); + this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${target} );` ); } } } \ No newline at end of file diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 969bc95fac..5d467e7447 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -112,9 +112,6 @@ export default function dom ( parsed, source, options ) { type: 'block', generator, name: generator.alias( 'create_main_fragment' ), - namespace, - target: 'target', - localElementDepth: 0, key: null, component, @@ -130,8 +127,14 @@ export default function dom ( parsed, source, options ) { getUniqueName }); + const state = { + namespace, + target: 'target', + localElementDepth: 0 + }; + parsed.html.children.forEach( node => { - visit( generator, mainFragment, node ); + visit( generator, mainFragment, state, node ); }); generator.addRenderer( mainFragment ); diff --git a/src/generators/dom/visit.js b/src/generators/dom/visit.js index 1b96ed510b..9184903295 100644 --- a/src/generators/dom/visit.js +++ b/src/generators/dom/visit.js @@ -1,6 +1,6 @@ import visitors from './visitors/index.js'; -export default function visit ( generator, fragment, node ) { +export default function visit ( generator, fragment, state, node ) { const visitor = visitors[ node.type ]; - visitor( generator, fragment, node ); + visitor( generator, fragment, state, node ); } \ No newline at end of file diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js index 1db9f3e665..d8969725e5 100644 --- a/src/generators/dom/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -20,13 +20,13 @@ function stringifyProps ( props ) { return `{ ${joined} }`; } -export default function visitComponent ( generator, fragment, node ) { +export default function visitComponent ( generator, fragment, state, node ) { const hasChildren = node.children.length > 0; const name = fragment.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) ); const local = { name, - namespace: fragment.namespace, + namespace: state.namespace, isComponent: true, allUsedContexts: [], @@ -35,7 +35,7 @@ export default function visitComponent ( generator, fragment, node ) { update: new CodeBuilder() }; - const isToplevel = fragment.localElementDepth === 0; + const isToplevel = state.localElementDepth === 0; generator.hasComponents = true; @@ -70,7 +70,7 @@ export default function visitComponent ( generator, fragment, node ) { } const componentInitProperties = [ - `target: ${!isToplevel ? fragment.target: 'null'}`, + `target: ${!isToplevel ? state.target: 'null'}`, `_root: ${fragment.component}._root || ${fragment.component}` ]; @@ -81,13 +81,16 @@ export default function visitComponent ( generator, fragment, node ) { const childFragment = fragment.child({ type: 'component', name: generator.getUniqueName( `render_${name}_yield_fragment` ), // TODO should getUniqueName happen inside Fragment? probably - target: 'target', - localElementDepth: 0, builders: getBuilders() }); + const childState = Object.assign( {}, state, { + target: 'target', + localElementDepth: 0 + }); + node.children.forEach( child => { - visit( generator, childFragment, child ); + visit( generator, childFragment, childState, child ); }); const yieldFragment = fragment.getUniqueName( `${name}_yield_fragment` ); diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 6a3bea8eec..4d0c8414c2 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -3,7 +3,7 @@ import deindent from '../../../utils/deindent.js'; import getBuilders from '../utils/getBuilders.js'; import visit from '../visit.js'; -export default function visitEachBlock ( generator, fragment, node ) { +export default function visitEachBlock ( generator, fragment, state, node ) { const name = generator.getUniqueName( `each_block` ); const renderer = generator.getUniqueName( `render_each_block` ); const elseName = generator.getUniqueName( `${name}_else` ); @@ -13,14 +13,14 @@ export default function visitEachBlock ( generator, fragment, node ) { const listName = fragment.getUniqueName( `${name}_value` ); - const isToplevel = fragment.localElementDepth === 0; + const isToplevel = state.localElementDepth === 0; generator.addSourcemapLocations( node.expression ); const { dependencies, snippet } = generator.contextualise( fragment, node.expression ); const anchor = fragment.getUniqueName( `${name}_anchor` ); - fragment.createAnchor( anchor ); + fragment.createAnchor( anchor, state.target, state.localElementDepth ); const localVars = {}; @@ -191,11 +191,9 @@ export default function visitEachBlock ( generator, fragment, node ) { const childFragment = fragment.child({ type: 'block', name: renderer, - target: 'target', expression: node.expression, context: node.context, key: node.key, - localElementDepth: 0, component: getUniqueName( 'component' ), @@ -211,8 +209,13 @@ export default function visitEachBlock ( generator, fragment, node ) { getUniqueName }); + const childState = Object.assign( {}, state, { + target: 'target', + localElementDepth: 0 + }); + node.children.forEach( child => { - visit( generator, childFragment, child ); + visit( generator, childFragment, childState, child ); }); generator.addRenderer( childFragment ); @@ -221,14 +224,12 @@ export default function visitEachBlock ( generator, fragment, node ) { const childFragment = fragment.child({ type: 'block', name: renderElse, - target: 'target', - localElementDepth: 0, builders: getBuilders(), getUniqueName: generator.getUniqueNameMaker( fragment.params ) }); node.else.children.forEach( child => { - visit( generator, childFragment, child ); + visit( generator, childFragment, childState, child ); }); generator.addRenderer( childFragment ); diff --git a/src/generators/dom/visitors/Element.js b/src/generators/dom/visitors/Element.js index d24c7c267e..5099b875f5 100644 --- a/src/generators/dom/visitors/Element.js +++ b/src/generators/dom/visitors/Element.js @@ -9,20 +9,20 @@ const meta = { ':Window': visitWindow }; -export default function visitElement ( generator, fragment, node ) { +export default function visitElement ( generator, fragment, state, node ) { if ( node.name in meta ) { return meta[ node.name ]( generator, fragment, node ); } if ( generator.components.has( node.name ) || node.name === ':Self' ) { - return visitComponent( generator, fragment, node ); + return visitComponent( generator, fragment, state, node ); } const name = fragment.getUniqueName( node.name ); const local = { name, - namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : fragment.namespace, + namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace, isComponent: false, allUsedContexts: [], @@ -32,7 +32,7 @@ export default function visitElement ( generator, fragment, node ) { destroy: new CodeBuilder() }; - const isToplevel = fragment.localElementDepth === 0; + const isToplevel = state.localElementDepth === 0; addElementAttributes( generator, fragment, node, local ); @@ -76,7 +76,7 @@ export default function visitElement ( generator, fragment, node ) { render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`; } - if ( generator.cssId && !generator.elementDepth ) { + if ( generator.cssId && !state.elementDepth ) { render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`; } @@ -96,25 +96,19 @@ export default function visitElement ( generator, fragment, node ) { if ( !local.update.isEmpty() ) fragment.builders.update.addBlock( local.update ); if ( !local.destroy.isEmpty() ) fragment.builders.destroy.addBlock( local.destroy ); - fragment.createMountStatement( name ); + fragment.createMountStatement( name, state.target ); - const childFragment = fragment.child({ - type: 'element', - namespace: local.namespace, + const childState = Object.assign( {}, state, { + elementDepth: state.elementDepth + 1, + localElementDepth: state.localElementDepth + 1, target: name, - parent: fragment, - localElementDepth: fragment.localElementDepth + 1, - key: null + namespace: local.namespace }); - generator.elementDepth += 1; - node.children.forEach( child => { - visit( generator, childFragment, child ); + visit( generator, fragment, childState, child ); }); - generator.elementDepth -= 1; - if ( node.initialUpdate ) { fragment.builders.create.addBlock( node.initialUpdate ); } diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 16e88398e0..66e60ed7a1 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -2,7 +2,7 @@ import deindent from '../../../utils/deindent.js'; import getBuilders from '../utils/getBuilders.js'; import visit from '../visit.js'; -function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { +function getConditionsAndBlocks ( generator, fragment, state, node, _name, i = 0 ) { generator.addSourcemapLocations( node.expression ); const name = generator.getUniqueName( `${_name}_${i}` ); @@ -11,12 +11,12 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { block: name }]; - generateBlock( generator, fragment, node, name, 'block' ); + generateBlock( generator, fragment, state, node, name, 'block' ); if ( node.else && node.else.children.length === 1 && node.else.children[0].type === 'IfBlock' ) { conditionsAndBlocks.push( - ...getConditionsAndBlocks( generator, fragment, node.else.children[0], _name, i + 1 ) + ...getConditionsAndBlocks( generator, fragment, state, node.else.children[0], _name, i + 1 ) ); } else { const name = generator.getUniqueName( `${_name}_${i + 1}` ); @@ -26,42 +26,44 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { }); if ( node.else ) { - generateBlock( generator, fragment, node.else, name, 'block' ); + generateBlock( generator, fragment, state, node.else, name, 'block' ); } } return conditionsAndBlocks; } -function generateBlock ( generator, fragment, node, name, type ) { +function generateBlock ( generator, fragment, state, node, name, type ) { const childFragment = fragment.child({ type, - isIfBlock: true, name, + builders: getBuilders() + }); + + const childState = Object.assign( {}, state, { target: 'target', - builders: getBuilders(), localElementDepth: 0 }); // walk the children here node.children.forEach( node => { - visit( generator, childFragment, node ); + visit( generator, childFragment, childState, node ); }); generator.addRenderer( childFragment ); } -export default function visitIfBlock ( generator, fragment, node ) { +export default function visitIfBlock ( generator, fragment, state, node ) { const params = fragment.params.join( ', ' ); const name = generator.getUniqueName( `if_block` ); const getBlock = fragment.getUniqueName( `get_block` ); const currentBlock = fragment.getUniqueName( `current_block` ); const _currentBlock = fragment.getUniqueName( `_current_block` ); - const isToplevel = fragment.localElementDepth === 0; - const conditionsAndBlocks = getConditionsAndBlocks( generator, fragment, node, generator.getUniqueName( `render_if_block` ) ); + const isToplevel = state.localElementDepth === 0; + const conditionsAndBlocks = getConditionsAndBlocks( generator, fragment, state, node, generator.getUniqueName( `render_if_block` ) ); const anchor = `${name}_anchor`; - fragment.createAnchor( anchor ); + fragment.createAnchor( anchor, state.target, state.localElementDepth ); fragment.builders.create.addBlock( deindent` function ${getBlock} ( ${params} ) { diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index 010fc7d4d1..78c1b78e6f 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -1,13 +1,13 @@ import deindent from '../../../utils/deindent.js'; import findBlock from '../utils/findBlock.js'; -export default function visitMustacheTag ( generator, fragment, node ) { +export default function visitMustacheTag ( generator, fragment, state, node ) { const name = fragment.getUniqueName( 'text' ); const { snippet } = generator.contextualise( fragment, node.expression ); fragment.builders.create.addLine( `var last_${name} = ${snippet};` ); - fragment.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true ); + fragment.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, state.target, state.localElementDepth, true ); // TODO this should be unnecessary once we separate fragments from state const parentFragment = findBlock( fragment ); diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js index dcfc3dec8f..e425cdf5a3 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -1,7 +1,7 @@ import deindent from '../../../utils/deindent.js'; import findBlock from '../utils/findBlock.js'; -export default function visitRawMustacheTag ( generator, fragment, node ) { +export default function visitRawMustacheTag ( generator, fragment, state, node ) { const name = fragment.getUniqueName( 'raw' ); const { snippet } = generator.contextualise( fragment, node.expression ); @@ -9,12 +9,12 @@ export default function visitRawMustacheTag ( generator, fragment, node ) { // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. const before = `${name}_before`; - fragment.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); + fragment.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, state.target, state.localElementDepth, true ); const after = `${name}_after`; - fragment.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); + fragment.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, state.target, state.localElementDepth, true ); - const isToplevel = fragment.localElementDepth === 0; + const isToplevel = state.localElementDepth === 0; fragment.builders.create.addLine( `var last_${name} = ${snippet};` ); const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`; diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js index dde8ec85b0..7f0e06008e 100644 --- a/src/generators/dom/visitors/Text.js +++ b/src/generators/dom/visitors/Text.js @@ -1,8 +1,8 @@ -export default function visitText ( generator, fragment, node ) { - if ( fragment.namespace && !/\S/.test( node.data ) ) { +export default function visitText ( generator, fragment, state, node ) { + if ( state.namespace && !/\S/.test( node.data ) ) { return; } const name = fragment.getUniqueName( `text` ); - fragment.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, false ); + fragment.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.target, state.localElementDepth, false ); } \ No newline at end of file diff --git a/src/generators/dom/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js index 95f1b1d58f..0848836a66 100644 --- a/src/generators/dom/visitors/YieldTag.js +++ b/src/generators/dom/visitors/YieldTag.js @@ -1,9 +1,9 @@ -export default function visitYieldTag ( generator, fragment ) { +export default function visitYieldTag ( generator, fragment, state ) { const anchor = `yield_anchor`; - fragment.createAnchor( anchor ); + fragment.createAnchor( anchor, state.target, state.localElementDepth ); fragment.builders.mount.addLine( - `${fragment.component}._yield && ${fragment.component}._yield.mount( ${fragment.target}, ${anchor} );` + `${fragment.component}._yield && ${fragment.component}._yield.mount( ${state.target}, ${anchor} );` ); fragment.builders.destroy.addLine( diff --git a/test/runtime/samples/attribute-dynamic/_config.js b/test/runtime/samples/attribute-dynamic/_config.js index 2b92e8ed60..9b33022e62 100644 --- a/test/runtime/samples/attribute-dynamic/_config.js +++ b/test/runtime/samples/attribute-dynamic/_config.js @@ -1,5 +1,6 @@ export default { html: `
red
`, + test ( assert, component, target ) { const div = target.querySelector( 'div' );