From 04388f7a0e1e3d39d8657e9b10544c7ba58d45af Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 23 Nov 2016 23:51:28 -0500 Subject: [PATCH] initialise fragments with data, rather than waiting for first update --- compiler/generate/index.js | 42 +++++++++----- compiler/generate/visitors/EachBlock.js | 27 +++++---- compiler/generate/visitors/Element.js | 57 ++++++++++++++++--- compiler/generate/visitors/IfBlock.js | 36 +++++++++--- compiler/generate/visitors/MustacheTag.js | 31 ++-------- .../attributes/addComponentAttributes.js | 19 +++++-- .../attributes/addElementAttributes.js | 36 +++++------- .../visitors/attributes/binding/index.js | 13 ++++- .../attribute-dynamic-multiple/_config.js | 1 + test/compiler/autofocus/_config.js | 3 +- .../_config.js | 17 +++++- .../binding-input-text-deep/_config.js | 2 + .../component-data-dynamic-late/Widget.html | 1 + .../component-data-dynamic-late/_config.js | 10 ++++ .../component-data-dynamic-late/main.html | 17 ++++++ test/compiler/component-events/_config.js | 4 +- .../each-block-containing-if/_config.js | 4 +- test/compiler/each-blocks-nested/_config.js | 4 ++ 18 files changed, 223 insertions(+), 101 deletions(-) create mode 100644 test/compiler/component-data-dynamic-late/Widget.html create mode 100644 test/compiler/component-data-dynamic-late/_config.js create mode 100644 test/compiler/component-data-dynamic-late/main.html diff --git a/compiler/generate/index.js b/compiler/generate/index.js index d169ed29d9..7b203f4683 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -17,11 +17,11 @@ export default function generate ( parsed, source, options ) { } renderers.push( deindent` - function ${fragment.name} ( component, target${fragment.useAnchor ? ', anchor' : ''} ) { + function ${fragment.name} ( ${fragment.params}, component, target${fragment.useAnchor ? ', anchor' : ''} ) { ${fragment.initStatements.join( '\n\n' )} return { - update: function ( ${fragment.params.join( ', ' )} ) { + update: function ( changed, ${fragment.params} ) { ${fragment.updateStatements.join( '\n\n' )} }, @@ -86,7 +86,8 @@ export default function generate ( parsed, source, options ) { return { dependencies, contexts: usedContexts, - snippet: `[✂${expression.start}-${expression.end}✂]` + snippet: `[✂${expression.start}-${expression.end}✂]`, + string: generator.code.slice( expression.start, expression.end ) }; }, @@ -211,7 +212,7 @@ export default function generate ( parsed, source, options ) { contexts: {}, indexes: {}, - params: [ 'changed', 'root' ], + params: 'root', indexNames: {}, listNames: {}, @@ -222,12 +223,15 @@ export default function generate ( parsed, source, options ) { generator.addRenderer( generator.pop() ); + const topLevelStatements = []; + const setStatements = [ deindent` const oldState = state; state = Object.assign( {}, oldState, newState ); ` ]; if ( templateProperties.computed ) { + const statements = []; const dependencies = new Map(); templateProperties.computed.properties.forEach( prop => { @@ -249,7 +253,7 @@ export default function generate ( parsed, source, options ) { const deps = dependencies.get( key ); deps.forEach( visit ); - setStatements.push( deindent` + statements.push( deindent` if ( ${deps.map( dep => `( '${dep}' in newState && typeof state.${dep} === 'object' || state.${dep} !== oldState.${dep} )` ).join( ' || ' )} ) { state.${key} = newState.${key} = template.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} ); } @@ -257,6 +261,14 @@ export default function generate ( parsed, source, options ) { } templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); + + topLevelStatements.push( deindent` + function applyComputations ( state, newState, oldState ) { + ${statements.join( '\n\n' )} + } + ` ); + + setStatements.push( `applyComputations( state, newState, oldState )` ); } setStatements.push( deindent` @@ -265,8 +277,6 @@ export default function generate ( parsed, source, options ) { dispatchObservers( observers.deferred, newState, oldState ); ` ); - const topLevelStatements = []; - if ( parsed.js ) { if ( imports.length ) { topLevelStatements.push( imports.join( '' ).trim() ); @@ -290,23 +300,27 @@ export default function generate ( parsed, source, options ) { } if ( generator.hasComplexBindings ) { - initStatements.push( `this.__bindings = [];` ); + initStatements.push( deindent` + this.__bindings = []; + var mainFragment = renderMainFragment( state, this, options.target ); + while ( this.__bindings.length ) this.__bindings.pop()(); + ` ); + setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` ); + } else { + initStatements.push( `var mainFragment = renderMainFragment( state, this, options.target );` ); } - initStatements.push( deindent` - var mainFragment = renderMainFragment( this, options.target ); - this.set( ${templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`} ); - ` ); - if ( templateProperties.onrender ) { initStatements.push( `template.onrender.call( this );` ); } + const initialState = templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`; + topLevelStatements.push( deindent` export default function ${constructorName} ( options ) { var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``} - var state = {}; + var state = ${initialState};${templateProperties.computed ? `\napplyComputations( state, state, {} );` : ``} var observers = { immediate: Object.create( null ), diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index 8ade6b53a8..50a5ccbadb 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -9,27 +9,34 @@ export default { const listName = `${name}_value`; + generator.addSourcemapLocations( node.expression ); + + const { dependencies, snippet, string } = generator.contextualise( node.expression ); + generator.current.initStatements.push( deindent` var ${name}_anchor = document.createComment( ${JSON.stringify( `#each ${generator.source.slice( node.expression.start, node.expression.end )}` )} ); ${generator.current.target}.appendChild( ${name}_anchor ); + + var ${name}_value = ${snippet}; + var ${name}_fragment = document.createDocumentFragment(); var ${name}_iterations = []; - const ${name}_fragment = document.createDocumentFragment(); - ` ); - generator.addSourcemapLocations( node.expression ); + for ( var i = 0; i < ${name}_value.length; i += 1 ) { + ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + } - const { dependencies, snippet } = generator.contextualise( node.expression ); + ${name}_anchor.parentNode.insertBefore( ${name}_fragment, ${name}_anchor ); + ` ); generator.current.updateStatements.push( deindent` - var ${name}_value = ${snippet}; + var ${name}_value = ${string}; for ( var i = 0; i < ${name}_value.length; i += 1 ) { if ( !${name}_iterations[i] ) { - ${name}_iterations[i] = ${renderer}( component, ${name}_fragment ); + ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + } else { + ${name}_iterations[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); } - - const iteration = ${name}_iterations[i]; - ${name}_iterations[i].update( ${generator.current.params.join( ', ' )}, ${listName}, ${listName}[i], i ); } for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { @@ -63,7 +70,7 @@ export default { const contextDependencies = Object.assign( {}, generator.current.contextDependencies ); contextDependencies[ node.context ] = dependencies; - const params = generator.current.params.concat( listName, node.context, indexName ); + const params = generator.current.params + `, ${listName}, ${node.context}, ${indexName}`; generator.current = { useAnchor: false, diff --git a/compiler/generate/visitors/Element.js b/compiler/generate/visitors/Element.js index 101069dc43..e545128654 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -22,13 +22,39 @@ export default { if ( isComponent ) { addComponentAttributes( generator, node, local ); - if ( local.staticAttributes.length ) { + if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) { + const initialProps = local.staticAttributes + .concat( local.dynamicAttributes ) + .map( attribute => `${attribute.name}: ${attribute.value}` ); + + const statements = []; + + if ( initialProps.length ) { + statements.push( deindent` + var ${name}_initialData = { + ${initialProps.join( ',\n' )} + }; + ` ); + } else { + statements.push( `var ${name}_initialData = {};` ); + } + + if ( local.bindings.length ) { + const bindings = local.bindings.map( binding => { + const parts = binding.value.split( '.' ); + const tail = parts.pop(); + return `if ( '${tail}' in ${parts.join( '.' )} ) ${name}_initialData.${binding.name} = ${binding.value};`; + }); + + statements.push( bindings.join( '\n' ) ); + } + local.init.unshift( deindent` + ${statements.join( '\n\n' )} + var ${name} = new template.components.${node.name}({ target: ${generator.current.target}, - data: { - ${local.staticAttributes.join( ',\n' )} - } + data: ${name}_initialData }); ` ); } else { @@ -62,11 +88,18 @@ export default { addElementAttributes( generator, node, local ); if ( local.allUsedContexts.size ) { - local.init.push( deindent` - ${name}.__svelte = {}; - ` ); + const contextNames = [...local.allUsedContexts]; + + const initialProps = contextNames.map( contextName => { + if ( contextName === 'root' ) return `root: root`; - const declarations = [...local.allUsedContexts].map( contextName => { + const listName = generator.current.listNames[ contextName ]; + const indexName = generator.current.indexNames[ contextName ]; + + return `${listName}: ${listName},\n${indexName}: ${indexName}`; + }).join( ',\n' ); + + const updates = contextNames.map( contextName => { if ( contextName === 'root' ) return `${name}.__svelte.root = root;`; const listName = generator.current.listNames[ contextName ]; @@ -75,7 +108,13 @@ export default { return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`; }).join( '\n' ); - local.update.push( declarations ); + local.init.push( deindent` + ${name}.__svelte = { + ${initialProps} + }; + ` ); + + local.update.push( updates ); } let render = local.namespace ? diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index e2ec51c9b2..ee3c7bfa04 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -10,18 +10,36 @@ export default { const elseName = `elseBlock_${i}`; const elseRenderer = `renderElseBlock_${i}`; + generator.addSourcemapLocations( node.expression ); + const { snippet, string } = generator.contextualise( node.expression ); + generator.current.initStatements.push( deindent` var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} ); ${generator.current.target}.appendChild( ${name}_anchor ); - var ${name} = null;${node.else ? `\nvar ${elseName} = null;` : ``} ` ); - generator.addSourcemapLocations( node.expression ); - const { snippet } = generator.contextualise( node.expression ); + if ( node.else ) { + generator.current.initStatements.push( deindent` + var ${name} = null; + var ${elseName} = null; + + if ( ${snippet} ) { + ${name} = ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); + } else { + ${elseName} = ${elseRenderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); + } + ` ); + } else { + generator.current.initStatements.push( deindent` + var ${name} = ${snippet} ? ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ) : null; + ` ); + } const ifTrue = [ deindent` if ( !${name } ) { - ${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor ); + ${name} = ${renderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); + } else { + ${name}.update( changed, ${generator.current.params} ); } ` ]; @@ -44,25 +62,25 @@ export default { if ( node.else ) { ifFalse.push( deindent` if ( !${elseName } ) { - ${elseName} = ${elseRenderer}( component, ${generator.current.target}, ${name}_anchor ); + ${elseName} = ${elseRenderer}( ${generator.current.params}, component, ${generator.current.target}, ${name}_anchor ); + } else { + ${elseName}.update( changed, ${generator.current.params} ); } ` ); } let update = deindent` - if ( ${snippet} ) { + if ( ${string} ) { ${ifTrue.join( '\n\n' )} } else { ${ifFalse.join( '\n\n' )} } - - if ( ${name} ) ${name}.update( ${generator.current.params.join( ', ' )} ); `; if ( node.else ) { - update += `\nif ( ${elseName} ) ${elseName}.update( ${generator.current.params.join( ', ' )} );`; + update += `\nif ( ${elseName} ) ${elseName}.update( changed, ${generator.current.params} );`; } generator.current.updateStatements.push( update ); diff --git a/compiler/generate/visitors/MustacheTag.js b/compiler/generate/visitors/MustacheTag.js index b04f2e6a94..1415ba0495 100644 --- a/compiler/generate/visitors/MustacheTag.js +++ b/compiler/generate/visitors/MustacheTag.js @@ -5,36 +5,17 @@ export default { enter ( generator, node ) { const name = generator.current.counter( 'text' ); + const { snippet, string } = generator.contextualise( node.expression ); + generator.current.initStatements.push( deindent` - var ${name} = document.createTextNode( '' ); - var ${name}_value = ''; + var ${name} = document.createTextNode( ${snippet} ); ${generator.current.target}.appendChild( ${name} ); ` ); generator.addSourcemapLocations( node.expression ); - const { contexts, snippet } = generator.contextualise( node.expression ); - - if ( isReference( node.expression ) ) { - const reference = `${generator.source.slice( node.expression.start, node.expression.end )}`; - const qualified = contexts[0] === 'root' ? `root.${reference}` : reference; - - generator.current.updateStatements.push( deindent` - if ( ${snippet} !== ${name}_value ) { - ${name}_value = ${qualified}; - ${name}.data = ${name}_value; - } - ` ); - } else { - const temp = generator.getName( 'temp' ); - - generator.current.updateStatements.push( deindent` - var ${temp} = ${snippet}; - if ( ${temp} !== ${name}_value ) { - ${name}_value = ${temp}; - ${name}.data = ${name}_value; - } - ` ); - } + generator.current.updateStatements.push( deindent` + ${name}.data = ${string}; + ` ); } }; diff --git a/compiler/generate/visitors/attributes/addComponentAttributes.js b/compiler/generate/visitors/attributes/addComponentAttributes.js index eb923ef60e..f574f17459 100644 --- a/compiler/generate/visitors/attributes/addComponentAttributes.js +++ b/compiler/generate/visitors/attributes/addComponentAttributes.js @@ -4,12 +4,16 @@ import deindent from '../../utils/deindent.js'; export default function addComponentAttributes ( generator, node, local ) { local.staticAttributes = []; local.dynamicAttributes = []; + local.bindings = []; node.attributes.forEach( attribute => { if ( attribute.type === 'Attribute' ) { if ( attribute.value === true ) { // attributes without values, e.g.