diff --git a/src/generators/Generator.js b/src/generators/Generator.js index c5147c97b1..5d7569be9d 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -63,6 +63,8 @@ export default class Generator { } contextualise ( block, expression, context, isEventHandler ) { + if ( expression._contextualised ) return expression._contextualised; + this.addSourcemapLocations( expression ); const usedContexts = []; @@ -153,12 +155,13 @@ export default class Generator { } }); - return { + expression._contextualised = { dependencies, contexts: usedContexts, - snippet: `[✂${expression.start}-${expression.end}✂]`, - string: this.code.slice( expression.start, expression.end ) + snippet: `[✂${expression.start}-${expression.end}✂]` }; + + return expression._contextualised; } generate ( result, options, { name, format } ) { diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js index aa3a214fc6..0b2b1760fd 100644 --- a/src/generators/dom/Block.js +++ b/src/generators/dom/Block.js @@ -2,7 +2,20 @@ import CodeBuilder from '../../utils/CodeBuilder.js'; import deindent from '../../utils/deindent.js'; export default class Block { - constructor ({ generator, name, key, expression, context, contextDependencies, contexts, indexes, params, indexNames, listNames }) { + constructor ({ + generator, + name, + key, + expression, + context, + contextDependencies, + dependencies, + contexts, + indexes, + params, + indexNames, + listNames + }) { this.generator = generator; this.name = name; this.key = key; @@ -12,6 +25,7 @@ export default class Block { this.contexts = contexts; this.indexes = indexes; this.contextDependencies = contextDependencies; + this.dependencies = dependencies; this.params = params; this.indexNames = indexNames; diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 1470b39342..6bfc88c209 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -7,7 +7,7 @@ import deindent from '../../utils/deindent.js'; import CodeBuilder from '../../utils/CodeBuilder.js'; import visit from './visit.js'; import Generator from '../Generator.js'; -import Block from './Block.js'; +import preprocess from './preprocess.js'; import * as shared from '../../shared/index.js'; class DomGenerator extends Generator { @@ -47,37 +47,13 @@ export default function dom ( parsed, source, options ) { const { computations, hasJs, templateProperties, namespace } = generator.parseJs(); - const getUniqueName = generator.getUniqueNameMaker( [ 'root' ] ); - const component = getUniqueName( 'component' ); - - const mainBlock = new Block({ - generator, - name: generator.alias( 'create_main_fragment' ), - key: null, - - component, - - contexts: new Map(), - indexes: new Map(), - - params: [ 'root' ], - indexNames: new Map(), - listNames: new Map(), - - getUniqueName - }); - - const state = { - namespace, - parentNode: null, - isTopLevel: true - }; + const { block, state } = preprocess( generator, parsed.html.children, namespace ); parsed.html.children.forEach( node => { - visit( generator, mainBlock, state, node ); + visit( generator, block, state, node ); }); - generator.addBlock( mainBlock ); + generator.addBlock( block ); const builders = { main: new CodeBuilder(), diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js new file mode 100644 index 0000000000..6c20b844b8 --- /dev/null +++ b/src/generators/dom/preprocess.js @@ -0,0 +1,118 @@ +import Block from './Block.js'; + +function isElseIf ( node ) { + return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; +} + +const preprocessors = { + MustacheTag: ( generator, block, node ) => { + const { dependencies } = block.contextualise( node.expression ); + dependencies.forEach( dependency => { + block.dependencies.add( dependency ); + }); + }, + + IfBlock: ( generator, block, node ) => { + function attachBlocks ( node ) { + const { dependencies } = block.contextualise( node.expression ); + + node._block = block.child({ + name: generator.getUniqueName( `create_if_block` ) + }); + + preprocessChildren( generator, node._block, node.children ); + + if ( isElseIf( node.else ) ) { + attachBlocks( node.else ); + } else if ( node.else ) { + node.else._block = block.child({ + name: generator.getUniqueName( `create_if_block` ) + }); + + preprocessChildren( generator, node.else._block, node.else.children ); + } + } + + attachBlocks ( node ); + }, + + EachBlock: ( generator, block, node ) => { + const { dependencies } = block.contextualise( node.expression ); + + const indexNames = new Map( block.indexNames ); + const indexName = node.index || block.getUniqueName( `${node.context}_index` ); + indexNames.set( node.context, indexName ); + + const listNames = new Map( block.listNames ); + const listName = block.getUniqueName( `each_block_value` ); + listNames.set( node.context, listName ); + + const context = generator.getUniqueName( node.context ); + const contexts = new Map( block.contexts ); + contexts.set( node.context, context ); + + const indexes = new Map( block.indexes ); + if ( node.index ) indexes.set( indexName, node.context ); + + const contextDependencies = new Map( block.contextDependencies ); + contextDependencies.set( node.context, dependencies ); + + node._block = block.child({ + name: generator.getUniqueName( 'create_each_block' ), + expression: node.expression, + context: node.context, + key: node.key, + + contextDependencies, + contexts, + indexes, + + indexNames, + listNames, + params: block.params.concat( listName, context, indexName ) + }); + + preprocessChildren( generator, node._block, node.children ); + }, + + Element: ( generator, block, node ) => { + // TODO attributes and bindings (and refs?)... + preprocessChildren( generator, block, node.children ); + } +}; + +preprocessors.RawMustacheTag = preprocessors.MustacheTag; + +function preprocessChildren ( generator, block, children ) { + children.forEach( child => { + const preprocess = preprocessors[ child.type ]; + if ( preprocess ) preprocess( generator, block, child ); + }); +} + +export default function preprocess ( generator, children, namespace ) { + const block = new Block({ + generator, + name: generator.alias( 'create_main_fragment' ), + key: null, + + contexts: new Map(), + indexes: new Map(), + + params: [ 'root' ], + indexNames: new Map(), + listNames: new Map(), + + dependencies: new Set() + }); + + const state = { + namespace, + parentNode: null, + isTopLevel: true + }; + + preprocessChildren( generator, block, children ); + + return { block, state }; +} \ No newline at end of file diff --git a/src/generators/dom/visitors/Component/Attribute.js b/src/generators/dom/visitors/Component/Attribute.js index 601bc7049f..8458eeb866 100644 --- a/src/generators/dom/visitors/Component/Attribute.js +++ b/src/generators/dom/visitors/Component/Attribute.js @@ -28,12 +28,12 @@ export default function visitAttribute ( generator, block, state, node, attribut else { // simple dynamic attributes - const { dependencies, string } = generator.contextualise( block, value.expression ); + const { dependencies, snippet } = block.contextualise( value.expression ); // TODO only update attributes that have changed local.dynamicAttributes.push({ name: attribute.name, - value: string, + value: snippet, dependencies }); } @@ -48,12 +48,12 @@ export default function visitAttribute ( generator, block, state, node, attribut if ( chunk.type === 'Text' ) { return JSON.stringify( chunk.data ); } else { - const { dependencies, string } = generator.contextualise( block, chunk.expression ); + const { dependencies, snippet } = block.contextualise( chunk.expression ); dependencies.forEach( dependency => { if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency ); }); - return `( ${string} )`; + return `( ${snippet} )`; } }).join( ' + ' ) ); diff --git a/src/generators/dom/visitors/Component/Binding.js b/src/generators/dom/visitors/Component/Binding.js index f4b86425ec..7ad2e61cb2 100644 --- a/src/generators/dom/visitors/Component/Binding.js +++ b/src/generators/dom/visitors/Component/Binding.js @@ -4,7 +4,7 @@ import getSetter from '../shared/binding/getSetter.js'; export default function visitBinding ( generator, block, state, node, attribute, local ) { const { name, keypath } = flattenReference( attribute.value ); - const { snippet, contexts, dependencies } = generator.contextualise( block, attribute.value ); + const { snippet, contexts, dependencies } = block.contextualise( 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/Component/EventHandler.js b/src/generators/dom/visitors/Component/EventHandler.js index 263f88e4ad..7e2ed9b0da 100644 --- a/src/generators/dom/visitors/Component/EventHandler.js +++ b/src/generators/dom/visitors/Component/EventHandler.js @@ -7,7 +7,7 @@ export default function visitEventHandler ( generator, block, state, node, attri const usedContexts = []; attribute.expression.arguments.forEach( arg => { - const { contexts } = generator.contextualise( block, arg, null, true ); + const { contexts } = block.contextualise( arg, null, true ); contexts.forEach( context => { if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); @@ -31,5 +31,5 @@ export default function visitEventHandler ( generator, block, state, node, attri ${local.name}.on( '${attribute.name}', function ( event ) { ${handlerBody} }); - ` ); + ` ); } \ No newline at end of file diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 0294c9cabb..dce874c7fd 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -5,7 +5,7 @@ import visit from '../visit.js'; export default function visitEachBlock ( generator, block, state, node ) { const each_block = generator.getUniqueName( `each_block` ); const each_block_else = generator.getUniqueName( `${each_block}_else` ); - const create_each_block = generator.getUniqueName( `create_each_block` ); + const create_each_block = node._block.name; const create_each_block_else = generator.getUniqueName( `${create_each_block}_else` ); const listName = block.getUniqueName( `${each_block}_value` ); const iterations = block.getUniqueName( `${each_block}_iterations` ); @@ -15,7 +15,7 @@ export default function visitEachBlock ( generator, block, state, node ) { const vars = { each_block, create_each_block, listName, iterations, i, params, anchor }; - const { dependencies, snippet } = generator.contextualise( block, node.expression ); + const { dependencies, snippet } = block.contextualise( node.expression ); block.createAnchor( anchor, state.parentNode ); block.builders.create.addLine( `var ${listName} = ${snippet};` ); @@ -75,37 +75,38 @@ export default function visitEachBlock ( generator, block, state, node ) { ` ); } - const indexNames = new Map( block.indexNames ); - const indexName = node.index || block.getUniqueName( `${node.context}_index` ); - indexNames.set( node.context, indexName ); + // const indexNames = new Map( block.indexNames ); + // const indexName = node.index || block.getUniqueName( `${node.context}_index` ); + // indexNames.set( node.context, indexName ); - const listNames = new Map( block.listNames ); - listNames.set( node.context, listName ); + // const listNames = new Map( block.listNames ); + // listNames.set( node.context, listName ); - const context = generator.getUniqueName( node.context ); - const contexts = new Map( block.contexts ); - contexts.set( node.context, context ); + // const context = generator.getUniqueName( node.context ); + // const contexts = new Map( block.contexts ); + // contexts.set( node.context, context ); - const indexes = new Map( block.indexes ); - if ( node.index ) indexes.set( indexName, node.context ); + // const indexes = new Map( block.indexes ); + // if ( node.index ) indexes.set( indexName, node.context ); - const contextDependencies = new Map( block.contextDependencies ); - contextDependencies.set( node.context, dependencies ); + // const contextDependencies = new Map( block.contextDependencies ); + // contextDependencies.set( node.context, dependencies ); - const childBlock = block.child({ - name: vars.create_each_block, - expression: node.expression, - context: node.context, - key: node.key, + // const childBlock = block.child({ + // name: vars.create_each_block, + // expression: node.expression, + // context: node.context, + // key: node.key, - contextDependencies, - contexts, - indexes, + // contextDependencies, + // contexts, + // indexes, - indexNames, - listNames, - params: block.params.concat( listName, context, indexName ) - }); + // indexNames, + // listNames, + // params: block.params.concat( listName, context, indexName ) + // }); + const childBlock = node._block; const childState = Object.assign( {}, state, { parentNode: null, diff --git a/src/generators/dom/visitors/Element/Attribute.js b/src/generators/dom/visitors/Element/Attribute.js index b4ecf58d7f..3b9a3cc299 100644 --- a/src/generators/dom/visitors/Element/Attribute.js +++ b/src/generators/dom/visitors/Element/Attribute.js @@ -27,7 +27,7 @@ export default function visitAttribute ( generator, block, state, node, attribut if ( attribute.value.length === 1 ) { // single {{tag}} — may be a non-string - const { snippet } = generator.contextualise( block, attribute.value[0].expression ); + const { snippet } = block.contextualise( attribute.value[0].expression ); value = snippet; } else { // '{{foo}} {{bar}}' — treat as string concatenation @@ -36,7 +36,7 @@ export default function visitAttribute ( generator, block, state, node, attribut if ( chunk.type === 'Text' ) { return JSON.stringify( chunk.data ); } else { - const { snippet } = generator.contextualise( block, chunk.expression ); + const { snippet } = block.contextualise( chunk.expression ); return `( ${snippet} )`; } }).join( ' + ' ) diff --git a/src/generators/dom/visitors/Element/Binding.js b/src/generators/dom/visitors/Element/Binding.js index c9e2c66cbe..7e8f38b947 100644 --- a/src/generators/dom/visitors/Element/Binding.js +++ b/src/generators/dom/visitors/Element/Binding.js @@ -5,7 +5,7 @@ import getStaticAttributeValue from './getStaticAttributeValue.js'; export default function visitBinding ( generator, block, state, node, attribute ) { const { name, keypath } = flattenReference( attribute.value ); - const { snippet, contexts, dependencies } = generator.contextualise( block, attribute.value ); + const { snippet, contexts, dependencies } = block.contextualise( 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/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 837fa559fe..b3f6e2214c 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -1,18 +1,21 @@ import deindent from '../../../utils/deindent.js'; import visit from '../visit.js'; +function isElseIf ( node ) { + return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; +} + function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) { const name = generator.getUniqueName( `${_name}_${i}` ); const conditionsAndBlocks = [{ - condition: generator.contextualise( block, node.expression ).snippet, + condition: block.contextualise( node.expression ).snippet, block: name }]; generateBlock( generator, block, state, node, name ); - if ( node.else && node.else.children.length === 1 && - node.else.children[0].type === 'IfBlock' ) { + if ( isElseIf( node.else ) ) { conditionsAndBlocks.push( ...getConditionsAndBlocks( generator, block, state, node.else.children[0], _name, i + 1 ) ); @@ -27,6 +30,7 @@ function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) generateBlock( generator, block, state, node.else, name ); } } + return conditionsAndBlocks; } @@ -53,7 +57,6 @@ export default function visitIfBlock ( generator, block, state, node ) { const currentBlock = block.getUniqueName( `current_block` ); const _currentBlock = block.getUniqueName( `_current_block` ); - const isToplevel = !state.parentNode; const conditionsAndBlocks = getConditionsAndBlocks( generator, block, state, node, generator.getUniqueName( `create_if_block` ) ); const anchor = `${name}_anchor`; @@ -70,11 +73,12 @@ export default function visitIfBlock ( generator, block, state, node ) { var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} ); ` ); - const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`; + const isToplevel = !state.parentNode; + if ( isToplevel ) { - block.builders.mount.addLine( mountStatement ); + block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` ); } else { - block.builders.create.addLine( mountStatement ); + block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, ${anchor} );` ); } block.builders.update.addBlock( deindent` diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index e6b6ee593a..60e64e1f8e 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -3,7 +3,7 @@ import deindent from '../../../utils/deindent.js'; export default function visitMustacheTag ( generator, block, state, node ) { const name = block.getUniqueName( 'text' ); - const { snippet } = generator.contextualise( block, node.expression ); + const { snippet } = block.contextualise( node.expression ); block.builders.create.addLine( `var last_${name} = ${snippet};` ); block.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, state.parentNode, true ); diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js index 13a9398056..a99d7cba8f 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -3,7 +3,7 @@ import deindent from '../../../utils/deindent.js'; export default function visitRawMustacheTag ( generator, block, state, node ) { const name = block.getUniqueName( 'raw' ); - const { snippet } = generator.contextualise( block, node.expression ); + const { snippet } = block.contextualise( node.expression ); // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. diff --git a/src/generators/server-side-rendering/Block.js b/src/generators/server-side-rendering/Block.js index 9f9a213453..ca08a529e4 100644 --- a/src/generators/server-side-rendering/Block.js +++ b/src/generators/server-side-rendering/Block.js @@ -27,4 +27,8 @@ export default class Block { child ( options ) { return new Block( Object.assign( {}, this, options, { parent: this } ) ); } + + contextualise ( expression, context, isEventHandler ) { + return this.generator.contextualise( this, expression, context, isEventHandler ); + } } \ 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 2c718484c5..fccc7d6bc0 100644 --- a/src/generators/server-side-rendering/visitors/Component.js +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -5,7 +5,7 @@ export default function visitComponent ( generator, block, node ) { function stringify ( chunk ) { if ( chunk.type === 'Text' ) return chunk.data; if ( chunk.type === 'MustacheTag' ) { - const { snippet } = generator.contextualise( block, chunk.expression ); + const { snippet } = block.contextualise( chunk.expression ); return '${__escape( ' + snippet + ')}'; } } @@ -34,7 +34,7 @@ export default function visitComponent ( generator, block, node ) { if ( chunk.type === 'Text' ) { value = isNaN( chunk.data ) ? JSON.stringify( chunk.data ) : chunk.data; } else { - const { snippet } = generator.contextualise( block, chunk.expression ); + const { snippet } = block.contextualise( chunk.expression ); value = snippet; } } else { diff --git a/src/generators/server-side-rendering/visitors/EachBlock.js b/src/generators/server-side-rendering/visitors/EachBlock.js index 5e59a10a4c..b26bbb4222 100644 --- a/src/generators/server-side-rendering/visitors/EachBlock.js +++ b/src/generators/server-side-rendering/visitors/EachBlock.js @@ -1,7 +1,7 @@ import visit from '../visit.js'; export default function visitEachBlock ( generator, block, node ) { - const { dependencies, snippet } = generator.contextualise( block, node.expression ); + const { dependencies, snippet } = block.contextualise( node.expression ); const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``; generator.append( open ); diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js index c473d6b62c..0a6c5f42c4 100644 --- a/src/generators/server-side-rendering/visitors/Element.js +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -30,7 +30,7 @@ export default function visitElement ( generator, block, node ) { return chunk.data; } - const { snippet } = generator.contextualise( block, chunk.expression ); + const { snippet } = block.contextualise( chunk.expression ); return '${' + snippet + '}'; }).join( '' ) + `"`; } diff --git a/src/generators/server-side-rendering/visitors/IfBlock.js b/src/generators/server-side-rendering/visitors/IfBlock.js index cd2575be6e..eb2af2caae 100644 --- a/src/generators/server-side-rendering/visitors/IfBlock.js +++ b/src/generators/server-side-rendering/visitors/IfBlock.js @@ -1,7 +1,7 @@ import visit from '../visit.js'; export default function visitIfBlock ( generator, block, node ) { - const { snippet } = generator.contextualise( block, node.expression ); + const { snippet } = block.contextualise( node.expression ); generator.append( '${ ' + snippet + ' ? `' ); diff --git a/src/generators/server-side-rendering/visitors/MustacheTag.js b/src/generators/server-side-rendering/visitors/MustacheTag.js index d433d0f1cb..4135ef9203 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, block, node ) { - const { snippet } = generator.contextualise( block, node.expression ); + const { snippet } = block.contextualise( 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 27aa695fa1..93b1f3c5f8 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, block, node ) { - const { snippet } = generator.contextualise( block, node.expression ); + const { snippet } = block.contextualise( node.expression ); generator.append( '${' + snippet + '}' ); } \ No newline at end of file