diff --git a/src/generators/Generator.js b/src/generators/Generator.js index a7d0196f2d..7241f0c048 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -62,14 +62,14 @@ export default class Generator { return alias; } - contextualise ( expression, isEventHandler ) { + contextualise ( fragment, expression, isEventHandler ) { this.addSourcemapLocations( expression ); const usedContexts = []; const dependencies = []; const { code, helpers } = this; - const { contextDependencies, contexts, indexes } = this.current; + const { contextDependencies, contexts, indexes } = fragment; let scope = annotateWithScopes( expression ); diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 00f6a0ae7e..7d49523fd4 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -17,7 +17,7 @@ export default function visitEachBlock ( generator, fragment, node ) { generator.addSourcemapLocations( node.expression ); - const { dependencies, snippet } = generator.contextualise( node.expression ); + const { dependencies, snippet } = generator.contextualise( fragment, node.expression ); const anchor = fragment.getUniqueName( `${name}_anchor` ); fragment.createAnchor( anchor ); @@ -221,17 +221,19 @@ export default function visitEachBlock ( generator, fragment, node ) { generator.pop(); if ( node.else ) { - const childFragment = this.current.child({ + const childFragment = fragment.child({ type: 'block', name: renderElse, target: 'target', localElementDepth: 0, builders: getBuilders(), - getUniqueName: this.getUniqueNameMaker( this.current.params ) + getUniqueName: generator.getUniqueNameMaker( fragment.params ) }); node.else.children.forEach( child => { visit( generator, childFragment, child ); }); + + generator.addRenderer( childFragment ); } } \ No newline at end of file diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 4d1adcb4f7..16e88398e0 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -7,7 +7,7 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { const name = generator.getUniqueName( `${_name}_${i}` ); const conditionsAndBlocks = [{ - condition: generator.contextualise( node.expression ).snippet, + condition: generator.contextualise( fragment, node.expression ).snippet, block: name }]; @@ -35,7 +35,7 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { function generateBlock ( generator, fragment, node, name, type ) { const childFragment = fragment.child({ type, - isIfBlock: true, + isIfBlock: true, name, target: 'target', builders: getBuilders(), diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index aaa558704a..010fc7d4d1 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -4,7 +4,7 @@ import findBlock from '../utils/findBlock.js'; export default function visitMustacheTag ( generator, fragment, node ) { const name = fragment.getUniqueName( 'text' ); - const { snippet } = generator.contextualise( node.expression ); + const { snippet } = generator.contextualise( fragment, node.expression ); fragment.builders.create.addLine( `var last_${name} = ${snippet};` ); fragment.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true ); diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js index 6c2da83207..dcfc3dec8f 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -4,7 +4,7 @@ import findBlock from '../utils/findBlock.js'; export default function visitRawMustacheTag ( generator, fragment, node ) { const name = fragment.getUniqueName( 'raw' ); - const { snippet } = generator.contextualise( node.expression ); + const { snippet } = generator.contextualise( fragment, node.expression ); // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. diff --git a/src/generators/dom/visitors/attributes/addComponentAttributes.js b/src/generators/dom/visitors/attributes/addComponentAttributes.js index 094c6b37e9..a713232f8f 100644 --- a/src/generators/dom/visitors/attributes/addComponentAttributes.js +++ b/src/generators/dom/visitors/attributes/addComponentAttributes.js @@ -37,7 +37,7 @@ export default function addComponentAttributes ( generator, fragment, node, loca else { // simple dynamic attributes - const { dependencies, string } = generator.contextualise( value.expression ); + const { dependencies, string } = generator.contextualise( fragment, value.expression ); // TODO only update attributes that have changed local.dynamicAttributes.push({ @@ -57,7 +57,7 @@ export default function addComponentAttributes ( generator, fragment, node, loca if ( chunk.type === 'Text' ) { return JSON.stringify( chunk.data ); } else { - const { dependencies, string } = generator.contextualise( chunk.expression ); + const { dependencies, string } = generator.contextualise( fragment, chunk.expression ); dependencies.forEach( dependency => { if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency ); }); @@ -82,7 +82,7 @@ export default function addComponentAttributes ( generator, fragment, node, loca const usedContexts = []; attribute.expression.arguments.forEach( arg => { - const { contexts } = generator.contextualise( arg, true ); + const { contexts } = generator.contextualise( fragment, arg, true ); contexts.forEach( context => { if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); diff --git a/src/generators/dom/visitors/attributes/addComponentBinding.js b/src/generators/dom/visitors/attributes/addComponentBinding.js index 0025c6f7ee..fb1f67ce43 100644 --- a/src/generators/dom/visitors/attributes/addComponentBinding.js +++ b/src/generators/dom/visitors/attributes/addComponentBinding.js @@ -4,7 +4,7 @@ import getSetter from './binding/getSetter.js'; export default function addComponentBinding ( generator, node, attribute, fragment, local ) { const { name, keypath } = flattenReference( attribute.value ); - const { snippet, contexts, dependencies } = generator.contextualise( attribute.value ); + const { snippet, contexts, dependencies } = generator.contextualise( fragment, attribute.value ); if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); diff --git a/src/generators/dom/visitors/attributes/addElementAttributes.js b/src/generators/dom/visitors/attributes/addElementAttributes.js index 6c01036e78..8e33966aa4 100644 --- a/src/generators/dom/visitors/attributes/addElementAttributes.js +++ b/src/generators/dom/visitors/attributes/addElementAttributes.js @@ -93,7 +93,7 @@ export default function addElementAttributes ( generator, fragment, node, local dynamic = true; // dynamic – but potentially non-string – attributes - const { snippet } = generator.contextualise( value.expression ); + const { snippet } = generator.contextualise( fragment, value.expression ); const last = `last_${local.name}_${name.replace( /-/g, '_')}`; local.create.addLine( `var ${last} = ${snippet};` ); @@ -128,7 +128,7 @@ export default function addElementAttributes ( generator, fragment, node, local if ( chunk.type === 'Text' ) { return JSON.stringify( chunk.data ); } else { - const { snippet } = generator.contextualise( chunk.expression ); + const { snippet } = generator.contextualise( fragment, chunk.expression ); return `( ${snippet} )`; } }).join( ' + ' ) @@ -165,7 +165,7 @@ export default function addElementAttributes ( generator, fragment, node, local const usedContexts = []; attribute.expression.arguments.forEach( arg => { - const { contexts } = generator.contextualise( arg, true ); + const { contexts } = generator.contextualise( fragment, arg, true ); contexts.forEach( context => { if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); diff --git a/src/generators/dom/visitors/attributes/addElementBinding.js b/src/generators/dom/visitors/attributes/addElementBinding.js index 091e3957b9..56e9c913ce 100644 --- a/src/generators/dom/visitors/attributes/addElementBinding.js +++ b/src/generators/dom/visitors/attributes/addElementBinding.js @@ -5,7 +5,7 @@ import getStaticAttributeValue from './binding/getStaticAttributeValue.js'; export default function addElementBinding ( generator, node, attribute, fragment, local ) { const { name, keypath } = flattenReference( attribute.value ); - const { snippet, contexts, dependencies } = generator.contextualise( attribute.value ); + const { snippet, contexts, dependencies } = generator.contextualise( fragment, attribute.value ); if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); diff --git a/src/generators/server-side-rendering/Fragment.js b/src/generators/server-side-rendering/Fragment.js new file mode 100644 index 0000000000..545619b9b8 --- /dev/null +++ b/src/generators/server-side-rendering/Fragment.js @@ -0,0 +1,30 @@ +import deindent from '../../utils/deindent.js'; +import flattenReference from '../../utils/flattenReference.js'; + +export default class Fragment { + constructor ( options ) { + Object.assign( this, options ); + } + + addBinding ( binding, name ) { + const conditions = [ `!( '${binding.name}' in root )`].concat( // TODO handle contextual bindings... + this.conditions.map( c => `(${c})` ) + ); + + const { keypath } = flattenReference( binding.value ); + + this.generator.bindings.push( deindent` + if ( ${conditions.join( '&&' )} ) { + tmp = ${name}.data(); + if ( '${keypath}' in tmp ) { + root.${binding.name} = tmp.${keypath}; + settled = false; + } + } + ` ); + } + + child ( options ) { + return new Fragment( Object.assign( {}, this, options, { parent: this } ) ); + } +} \ No newline at end of file diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index 3189fdd583..a5bef40135 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -2,6 +2,7 @@ import deindent from '../../utils/deindent.js'; import CodeBuilder from '../../utils/CodeBuilder.js'; import flattenReference from '../../utils/flattenReference.js'; import Generator from '../Generator.js'; +import Fragment from './Fragment.js'; import visit from './visit.js'; class SsrGenerator extends Generator { @@ -50,14 +51,15 @@ export default function ssr ( parsed, source, options ) { }; // create main render() function - generator.push({ + const mainFragment = new Fragment({ + generator, contexts: new Map(), indexes: new Map(), conditions: [] }); parsed.html.children.forEach( node => { - visit( node, generator ); + visit( generator, mainFragment, node ); }); builders.render.addLine( diff --git a/src/generators/server-side-rendering/visit.js b/src/generators/server-side-rendering/visit.js index e9837098c4..1b96ed510b 100644 --- a/src/generators/server-side-rendering/visit.js +++ b/src/generators/server-side-rendering/visit.js @@ -1,6 +1,6 @@ import visitors from './visitors/index.js'; -export default function visit ( node, generator ) { +export default function visit ( generator, fragment, node ) { const visitor = visitors[ node.type ]; - visitor( generator, node ); + visitor( generator, fragment, node ); } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/Component.js b/src/generators/server-side-rendering/visitors/Component.js index 4b1793f047..abd5ef78b8 100644 --- a/src/generators/server-side-rendering/visitors/Component.js +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -1,11 +1,11 @@ import flattenReference from '../../../utils/flattenReference.js'; import visit from '../visit.js'; -export default function visitComponent ( generator, node ) { +export default function visitComponent ( generator, fragment, node ) { function stringify ( chunk ) { if ( chunk.type === 'Text' ) return chunk.data; if ( chunk.type === 'MustacheTag' ) { - const { snippet } = generator.contextualise( chunk.expression ); + const { snippet } = generator.contextualise( fragment, chunk.expression ); return '${__escape( ' + snippet + ')}'; } } @@ -34,7 +34,7 @@ export default function visitComponent ( generator, node ) { if ( chunk.type === 'Text' ) { value = isNaN( chunk.data ) ? JSON.stringify( chunk.data ) : chunk.data; } else { - const { snippet } = generator.contextualise( chunk.expression ); + const { snippet } = generator.contextualise( fragment, chunk.expression ); value = snippet; } } else { @@ -45,7 +45,7 @@ export default function visitComponent ( generator, node ) { }) .concat( bindings.map( binding => { const { name, keypath } = flattenReference( binding.value ); - const value = generator.current.contexts.has( name ) ? keypath : `root.${keypath}`; + const value = fragment.contexts.has( name ) ? keypath : `root.${keypath}`; return `${binding.name}: ${value}`; })) .join( ', ' ); @@ -53,7 +53,7 @@ export default function visitComponent ( generator, node ) { const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`; bindings.forEach( binding => { - generator.addBinding( binding, expression ); + fragment.addBinding( binding, expression ); }); let open = `\${${expression}.render({${props}}`; @@ -67,7 +67,7 @@ export default function visitComponent ( generator, node ) { generator.elementDepth += 1; node.children.forEach( child => { - visit( child, generator ); + visit( generator, fragment, child ); }); generator.elementDepth -= 1; diff --git a/src/generators/server-side-rendering/visitors/EachBlock.js b/src/generators/server-side-rendering/visitors/EachBlock.js index 93cca86df9..37d2e7568a 100644 --- a/src/generators/server-side-rendering/visitors/EachBlock.js +++ b/src/generators/server-side-rendering/visitors/EachBlock.js @@ -1,34 +1,32 @@ import visit from '../visit.js'; -export default function visitEachBlock ( generator, node ) { - const { dependencies, snippet } = generator.contextualise( node.expression ); +export default function visitEachBlock ( generator, fragment, node ) { + const { dependencies, snippet } = generator.contextualise( fragment, node.expression ); const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``; generator.append( open ); // TODO should this be the generator's job? It's duplicated between // here and the equivalent DOM compiler visitor - const contexts = new Map( generator.current.contexts ); + const contexts = new Map( fragment.contexts ); contexts.set( node.context, node.context ); - const indexes = new Map( generator.current.indexes ); + const indexes = new Map( fragment.indexes ); if ( node.index ) indexes.set( node.index, node.context ); - const contextDependencies = new Map( generator.current.contextDependencies ); + const contextDependencies = new Map( fragment.contextDependencies ); contextDependencies.set( node.context, dependencies ); - generator.push({ + const childFragment = fragment.child({ contexts, indexes, contextDependencies }); node.children.forEach( child => { - visit( child, generator ); + visit( generator, childFragment, child ); }); const close = `\` ).join( '' )}`; generator.append( close ); - - generator.pop(); } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js index 97bc489ec9..d0393b73c4 100644 --- a/src/generators/server-side-rendering/visitors/Element.js +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -7,13 +7,13 @@ const meta = { ':Window': visitWindow }; -export default function visitElement ( generator, node ) { +export default function visitElement ( generator, fragment, node ) { if ( node.name in meta ) { - return meta[ node.name ]( generator, node ); + return meta[ node.name ]( generator, fragment, node ); } if ( generator.components.has( node.name ) || node.name === ':Self' ) { - visitComponent( generator, node ); + visitComponent( generator, fragment, node ); return; } @@ -30,7 +30,7 @@ export default function visitElement ( generator, node ) { return chunk.data; } - const { snippet } = generator.contextualise( chunk.expression ); + const { snippet } = generator.contextualise( fragment, chunk.expression ); return '${' + snippet + '}'; }).join( '' ) + `"`; } @@ -49,7 +49,7 @@ export default function visitElement ( generator, node ) { generator.elementDepth += 1; node.children.forEach( child => { - visit( child, generator ); + visit( generator, fragment, child ); }); generator.elementDepth -= 1; diff --git a/src/generators/server-side-rendering/visitors/IfBlock.js b/src/generators/server-side-rendering/visitors/IfBlock.js index f024f04ec3..418518d0a0 100644 --- a/src/generators/server-side-rendering/visitors/IfBlock.js +++ b/src/generators/server-side-rendering/visitors/IfBlock.js @@ -1,27 +1,25 @@ import visit from '../visit.js'; -export default function visitIfBlock ( generator, node ) { - const { snippet } = generator.contextualise( node.expression ); +export default function visitIfBlock ( generator, fragment, node ) { + const { snippet } = generator.contextualise( fragment, node.expression ); generator.append( '${ ' + snippet + ' ? `' ); - generator.push({ - conditions: generator.current.conditions.concat( snippet ) + const childFragment = fragment.child({ + conditions: fragment.conditions.concat( snippet ) }); node.children.forEach( child => { - visit( child, generator ); + visit( generator, childFragment, child ); }); generator.append( '` : `' ); if ( node.else ) { node.else.children.forEach( child => { - visit( child, generator ); + visit( generator, childFragment, child ); }); } generator.append( '` }' ); - - generator.pop(); } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/MustacheTag.js b/src/generators/server-side-rendering/visitors/MustacheTag.js index 99102a7ccb..731451ae8c 100644 --- a/src/generators/server-side-rendering/visitors/MustacheTag.js +++ b/src/generators/server-side-rendering/visitors/MustacheTag.js @@ -1,4 +1,4 @@ -export default function visitMustacheTag ( generator, node ) { - const { snippet } = generator.contextualise( node.expression ); +export default function visitMustacheTag ( generator, fragment, node ) { + const { snippet } = generator.contextualise( fragment, node.expression ); generator.append( '${__escape( ' + snippet + ' )}' ); } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/RawMustacheTag.js b/src/generators/server-side-rendering/visitors/RawMustacheTag.js index 0c70f363e3..ee3cb5da86 100644 --- a/src/generators/server-side-rendering/visitors/RawMustacheTag.js +++ b/src/generators/server-side-rendering/visitors/RawMustacheTag.js @@ -1,4 +1,4 @@ -export default function visitRawMustacheTag ( generator, node ) { - const { snippet } = generator.contextualise( node.expression ); +export default function visitRawMustacheTag ( generator, fragment, node ) { + const { snippet } = generator.contextualise( fragment, node.expression ); generator.append( '${' + snippet + '}' ); } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/Text.js b/src/generators/server-side-rendering/visitors/Text.js index d8510d8393..c6f2dc85a6 100644 --- a/src/generators/server-side-rendering/visitors/Text.js +++ b/src/generators/server-side-rendering/visitors/Text.js @@ -1,3 +1,3 @@ -export default function visitText ( generator, node ) { +export default function visitText ( generator, fragment, node ) { generator.append( node.data.replace( /\${/g, '\\${' ) ); } \ No newline at end of file diff --git a/test/runtime/samples/each-block-else/_config.js b/test/runtime/samples/each-block-else/_config.js index 313b7fa7e3..35ba9637be 100644 --- a/test/runtime/samples/each-block-else/_config.js +++ b/test/runtime/samples/each-block-else/_config.js @@ -3,15 +3,35 @@ export default { animals: [ 'alpaca', 'baboon', 'capybara' ], foo: 'something else' }, - html: 'before\n

alpaca

baboon

capybara

\nafter', + + html: ` + before +

alpaca

+

baboon

+

capybara

+ after + `, + test ( assert, component, target ) { component.set({ animals: [] }); - assert.htmlEqual( target.innerHTML, 'before\n

no animals, but rather something else

\nafter' ); + assert.htmlEqual( target.innerHTML, ` + before +

no animals, but rather something else

+ after + ` ); component.set({ foo: 'something other' }); - assert.htmlEqual( target.innerHTML, 'before\n

no animals, but rather something other

\nafter' ); - + assert.htmlEqual( target.innerHTML, ` + before +

no animals, but rather something other

+ after + ` ); + component.set({ animals: ['wombat'] }); - assert.htmlEqual( target.innerHTML, 'before\n

wombat

\nafter' ); + assert.htmlEqual( target.innerHTML, ` + before +

wombat

+ after + ` ); } };