From c523fc74aa6d2525e30274318b1485caef9ce6fd Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Wed, 23 Nov 2016 12:32:30 -0500 Subject: [PATCH] only update components if their dependencies have changed --- compiler/generate/index.js | 18 ++++++++----- compiler/generate/visitors/EachBlock.js | 12 ++++++--- compiler/generate/visitors/Element.js | 14 +++++++--- compiler/generate/visitors/IfBlock.js | 4 +-- compiler/generate/visitors/MustacheTag.js | 4 +-- .../attributes/addComponentAttributes.js | 26 ++++++++++++++----- .../attributes/addElementAttributes.js | 2 +- .../Foo.html | 1 + .../_config.js | 15 +++++++++++ .../main.html | 14 ++++++++++ 10 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 test/compiler/observe-component-ignores-irrelevant-changes/Foo.html create mode 100644 test/compiler/observe-component-ignores-irrelevant-changes/_config.js create mode 100644 test/compiler/observe-component-ignores-irrelevant-changes/main.html diff --git a/compiler/generate/index.js b/compiler/generate/index.js index 7f16b346f0..ec9c71be5f 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -21,7 +21,7 @@ export default function generate ( parsed, source, options ) { ${fragment.initStatements.join( '\n\n' )} return { - update: function ( ${fragment.contextChain.join( ', ' )} ) { + update: function ( ${fragment.params.join( ', ' )} ) { ${fragment.updateStatements.join( '\n\n' )} }, @@ -48,9 +48,9 @@ export default function generate ( parsed, source, options ) { contextualise ( expression, isEventHandler ) { const usedContexts = []; + const dependencies = []; - const contexts = generator.current.contexts; - const indexes = generator.current.indexes; + const { contextDependencies, contexts, indexes } = generator.current; walk( expression, { enter ( node, parent ) { @@ -67,11 +67,13 @@ export default function generate ( parsed, source, options ) { } if ( contexts[ name ] ) { + dependencies.push( ...contextDependencies[ name ] ); if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name ); } else if ( indexes[ name ] ) { const context = indexes[ name ]; if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); } else { + dependencies.push( node.name ); generator.code.insertRight( node.start, `root.` ); if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); } @@ -81,7 +83,11 @@ export default function generate ( parsed, source, options ) { } }); - return usedContexts; + return { + dependencies, + contexts: usedContexts, + snippet: `[✂${expression.start}-${expression.end}✂]` + }; }, // TODO use getName instead of counters @@ -205,7 +211,7 @@ export default function generate ( parsed, source, options ) { contexts: {}, indexes: {}, - contextChain: [ 'root' ], + params: [ 'changed', 'root' ], indexNames: {}, listNames: {}, @@ -258,7 +264,7 @@ export default function generate ( parsed, source, options ) { setting = true; dispatchObservers( observers.immediate, newState, oldState ); - if ( mainFragment ) mainFragment.update( state ); + if ( mainFragment ) mainFragment.update( newState, state ); dispatchObservers( observers.deferred, newState, oldState ); setting = false; ` ); diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index 391dbd04d9..412641a101 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -18,7 +18,7 @@ export default { generator.addSourcemapLocations( node.expression ); - generator.contextualise( node.expression ); + const { dependencies } = generator.contextualise( node.expression ); const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; generator.current.updateStatements.push( deindent` @@ -30,7 +30,7 @@ export default { } const iteration = ${name}_iterations[i]; - ${name}_iterations[i].update( ${generator.current.contextChain.join( ', ' )}, ${listName}, ${listName}[i], 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 ) { @@ -61,7 +61,10 @@ export default { const indexes = Object.assign( {}, generator.current.indexes ); if ( node.index ) indexes[ indexName ] = node.context; - const contextChain = generator.current.contextChain.concat( listName, node.context, indexName ); + const contextDependencies = Object.assign( {}, generator.current.contextDependencies ); + contextDependencies[ node.context ] = dependencies; + + const params = generator.current.params.concat( listName, node.context, indexName ); generator.current = { useAnchor: false, @@ -70,12 +73,13 @@ export default { expression: node.expression, context: node.context, + contextDependencies, contexts, indexes, indexNames, listNames, - contextChain, + params, initStatements: [], updateStatements: [ Object.keys( contexts ).map( contextName => { diff --git a/compiler/generate/visitors/Element.js b/compiler/generate/visitors/Element.js index 263d1b9344..101069dc43 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -40,10 +40,18 @@ export default { } if ( local.dynamicAttributes.length ) { + const updates = local.dynamicAttributes.map( attribute => { + return deindent` + if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value}; + `; + }); + local.update.push( deindent` - ${name}.set({ - ${local.dynamicAttributes.join( ',\n' )} - }); + var ${name}_changes = {}; + + ${updates.join( '\n' )} + + if ( Object.keys( ${name}_changes ).length ) ${name}.set( ${name}_changes ); ` ); } diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 8ee257bf58..51be1d3fc6 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -60,11 +60,11 @@ export default { ${ifFalse.join( '\n\n' )} } - if ( ${name} ) ${name}.update( ${generator.current.contextChain.join( ', ' )} ); + if ( ${name} ) ${name}.update( ${generator.current.params.join( ', ' )} ); `; if ( node.else ) { - update += `\nif ( ${elseName} ) ${elseName}.update( ${generator.current.contextChain.join( ', ' )} );`; + update += `\nif ( ${elseName} ) ${elseName}.update( ${generator.current.params.join( ', ' )} );`; } generator.current.updateStatements.push( update ); diff --git a/compiler/generate/visitors/MustacheTag.js b/compiler/generate/visitors/MustacheTag.js index ddc9be6c2f..0d0a727542 100644 --- a/compiler/generate/visitors/MustacheTag.js +++ b/compiler/generate/visitors/MustacheTag.js @@ -13,12 +13,12 @@ export default { generator.addSourcemapLocations( node.expression ); - const usedContexts = generator.contextualise( node.expression ); + const { contexts } = generator.contextualise( node.expression ); const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; if ( isReference( node.expression ) ) { const reference = `${generator.source.slice( node.expression.start, node.expression.end )}`; - const qualified = usedContexts[0] === 'root' ? `root.${reference}` : reference; + const qualified = contexts[0] === 'root' ? `root.${reference}` : reference; generator.current.updateStatements.push( deindent` if ( ${snippet} !== ${name}_value ) { diff --git a/compiler/generate/visitors/attributes/addComponentAttributes.js b/compiler/generate/visitors/attributes/addComponentAttributes.js index cf29d7db15..10b2f6fa63 100644 --- a/compiler/generate/visitors/attributes/addComponentAttributes.js +++ b/compiler/generate/visitors/attributes/addComponentAttributes.js @@ -23,16 +23,21 @@ export default function addComponentAttributes ( generator, node, local ) { else { // simple dynamic attributes - generator.contextualise( value.expression ); - const result = `[✂${value.expression.start}-${value.expression.end}✂]`; + const { dependencies, snippet } = generator.contextualise( value.expression ); // TODO only update attributes that have changed - local.dynamicAttributes.push( `${attribute.name}: ${result}` ); + local.dynamicAttributes.push({ + name: attribute.name, + value: snippet, + dependencies + }); } } else { // complex dynamic attributes + const allDependencies = []; + const value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + ( attribute.value.map( chunk => { if ( chunk.type === 'Text' ) { @@ -40,14 +45,21 @@ export default function addComponentAttributes ( generator, node, local ) { } else { generator.addSourcemapLocations( chunk.expression ); - generator.contextualise( chunk.expression ); + const { dependencies } = generator.contextualise( chunk.expression ); + dependencies.forEach( dependency => { + if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency ); + }); + return `( [✂${chunk.expression.start}-${chunk.expression.end}✂] )`; } }).join( ' + ' ) ); - // TODO only update attributes that have changed - local.dynamicAttributes.push( `${attribute.name}: ${value}` ); + local.dynamicAttributes.push({ + name: attribute.name, + value, + dependencies: allDependencies + }); } } @@ -58,7 +70,7 @@ export default function addComponentAttributes ( generator, node, local ) { const usedContexts = new Set(); attribute.expression.arguments.forEach( arg => { - const contexts = generator.contextualise( arg, true, true ); + const { contexts } = generator.contextualise( arg, true, true ); contexts.forEach( context => { usedContexts.add( context ); diff --git a/compiler/generate/visitors/attributes/addElementAttributes.js b/compiler/generate/visitors/attributes/addElementAttributes.js index 0eb9d190ab..100b1782d6 100644 --- a/compiler/generate/visitors/attributes/addElementAttributes.js +++ b/compiler/generate/visitors/attributes/addElementAttributes.js @@ -115,7 +115,7 @@ export default function addElementAttributes ( generator, node, local ) { const usedContexts = new Set(); attribute.expression.arguments.forEach( arg => { - const contexts = generator.contextualise( arg, true ); + const { contexts } = generator.contextualise( arg, true ); contexts.forEach( context => { usedContexts.add( context ); diff --git a/test/compiler/observe-component-ignores-irrelevant-changes/Foo.html b/test/compiler/observe-component-ignores-irrelevant-changes/Foo.html new file mode 100644 index 0000000000..7c89b545c5 --- /dev/null +++ b/test/compiler/observe-component-ignores-irrelevant-changes/Foo.html @@ -0,0 +1 @@ +
diff --git a/test/compiler/observe-component-ignores-irrelevant-changes/_config.js b/test/compiler/observe-component-ignores-irrelevant-changes/_config.js new file mode 100644 index 0000000000..88be0160bd --- /dev/null +++ b/test/compiler/observe-component-ignores-irrelevant-changes/_config.js @@ -0,0 +1,15 @@ +export default { + test ( assert, component ) { + const foo = component.refs.foo; + let count = 0; + + foo.observe( 'x', () => { + count += 1; + }); + + assert.equal( count, 1 ); + + component.set({ y: {} }); + assert.equal( count, 1 ); + } +}; diff --git a/test/compiler/observe-component-ignores-irrelevant-changes/main.html b/test/compiler/observe-component-ignores-irrelevant-changes/main.html new file mode 100644 index 0000000000..53b5586f1d --- /dev/null +++ b/test/compiler/observe-component-ignores-irrelevant-changes/main.html @@ -0,0 +1,14 @@ + + +