dont update static subtrees, even with a noop

pull/490/head
Rich-Harris 8 years ago
parent 92b49eed4e
commit 950f2ce2fd

@ -73,7 +73,7 @@ export default class Generator {
const { code, helpers } = this;
const { contextDependencies, contexts, indexes } = block;
let scope = annotateWithScopes( expression );
let scope = annotateWithScopes( expression ); // TODO this already happens in findDependencies
let lexicalDepth = 0;
const self = this;
@ -164,6 +164,44 @@ export default class Generator {
return expression._contextualised;
}
findDependencies ( block, expression, isEventHandler ) {
const dependencies = [];
const { contextDependencies, contexts } = block;
let scope = annotateWithScopes( expression );
walk( expression, {
enter ( node, parent ) {
if ( node._scope ) {
scope = node._scope;
return;
}
if ( isReference( node, parent ) ) {
const { name } = flattenReference( node );
if ( scope.has( name ) ) return;
if ( name === 'event' && isEventHandler ) {
// noop
} else if ( contexts.has( name ) ) {
dependencies.push( ...contextDependencies.get( name ) );
} else {
dependencies.push( name );
}
this.skip();
}
},
leave ( node ) {
if ( node._scope ) scope = scope.parent;
}
});
return dependencies;
}
generate ( result, options, { name, format } ) {
if ( this.imports.length ) {
const statements = [];

@ -34,6 +34,8 @@ export default class Block {
// unique names
this.component = this.getUniqueName( 'component' );
this.target = this.getUniqueName( 'target' );
this.hasUpdateMethod = false; // determined later
}
addDependencies ( dependencies ) {
@ -72,6 +74,10 @@ export default class Block {
this.addElement( name, renderStatement, parentNode, true );
}
findDependencies ( expression, isEventHandler ) {
return this.generator.findDependencies( this, expression, isEventHandler );
}
mount ( name, parentNode ) {
if ( parentNode ) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
@ -115,15 +121,17 @@ export default class Block {
` );
}
if ( this.builders.update.isEmpty() ) {
properties.addBlock( `update: ${this.generator.helper( 'noop' )},` );
} else {
if ( this._tmp ) this.builders.update.addBlockAtStart( `var ${this._tmp};` );
properties.addBlock( deindent`
update: function ( changed, ${this.params.join( ', ' )} ) {
${this.builders.update}
},
` );
if ( this.hasUpdateMethod ) {
if ( this.builders.update.isEmpty() ) {
properties.addBlock( `update: ${this.generator.helper( 'noop' )},` );
} else {
if ( this._tmp ) this.builders.update.addBlockAtStart( `var ${this._tmp};` );
properties.addBlock( deindent`
update: function ( changed, ${this.params.join( ', ' )} ) {
${this.builders.update}
},
` );
}
}
if ( this.builders.destroy.isEmpty() ) {

@ -107,12 +107,9 @@ export default function dom ( parsed, source, options ) {
builders._set.addLine( `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )` );
}
// TODO is the `if` necessary?
builders._set.addBlock( deindent`
${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );
` );
builders._set.addLine( `${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );` );
if ( block.hasUpdateMethod ) builders._set.addLine( `if ( this._fragment ) this._fragment.update( newState, this._state );` ); // TODO is the condition necessary?
builders._set.addLine( `${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );` );
if ( hasJs ) {
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );

@ -6,22 +6,29 @@ function isElseIf ( node ) {
const preprocessors = {
MustacheTag: ( generator, block, node ) => {
const { dependencies } = block.contextualise( node.expression );
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
},
IfBlock: ( generator, block, node ) => {
const blocks = [];
let dynamic = false;
function attachBlocks ( node ) {
const { dependencies } = block.contextualise( node.expression );
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
node._block = block.child({
name: generator.getUniqueName( `create_if_block` )
});
generator.blocks.push( node._block );
blocks.push( node._block );
preprocessChildren( generator, node._block, node.children );
block.addDependencies( node._block.dependencies );
if ( node._block.dependencies.size > 0 ) {
dynamic = true;
block.addDependencies( node._block.dependencies );
}
if ( isElseIf( node.else ) ) {
attachBlocks( node.else.children[0] );
@ -30,17 +37,27 @@ const preprocessors = {
name: generator.getUniqueName( `create_if_block` )
});
generator.blocks.push( node.else._block );
blocks.push( node.else._block );
preprocessChildren( generator, node.else._block, node.else.children );
block.addDependencies( node.else._block.dependencies );
if ( node.else._block.dependencies.size > 0 ) {
dynamic = true;
block.addDependencies( node.else._block.dependencies );
}
}
}
attachBlocks( node );
blocks.forEach( block => {
block.hasUpdateMethod = dynamic;
});
generator.blocks.push( ...blocks );
},
EachBlock: ( generator, block, node ) => {
const { dependencies } = block.contextualise( node.expression );
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
const indexNames = new Map( block.indexNames );
@ -82,6 +99,7 @@ const preprocessors = {
generator.blocks.push( node._block );
preprocessChildren( generator, node._block, node.children );
block.addDependencies( node._block.dependencies );
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
if ( node.else ) {
node.else._block = block.child({
@ -90,6 +108,7 @@ const preprocessors = {
generator.blocks.push( node.else._block );
preprocessChildren( generator, node.else._block, node.else.children );
node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0;
}
},
@ -98,16 +117,24 @@ const preprocessors = {
if ( attribute.type === 'Attribute' && attribute.value !== true ) {
attribute.value.forEach( chunk => {
if ( chunk.type !== 'Text' ) {
const { dependencies } = block.contextualise( chunk.expression );
const dependencies = block.findDependencies( chunk.expression );
block.addDependencies( dependencies );
}
});
}
else if ( attribute.type === 'Binding' ) {
const { dependencies } = block.contextualise( attribute.value );
const dependencies = block.findDependencies( attribute.value );
block.addDependencies( dependencies );
}
// else if ( attribute.type === 'EventHandler' ) {
// // TODO is this necessary?
// attribute.expression.arguments.forEach( arg => {
// const dependencies = block.findDependencies( arg );
// block.addDependencies( dependencies );
// });
// }
});
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
@ -121,6 +148,8 @@ const preprocessors = {
generator.blocks.push( node._block );
preprocessChildren( generator, node._block, node.children );
block.addDependencies( node._block.dependencies );
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
}
else {
@ -156,6 +185,7 @@ export default function preprocess ( generator, children ) {
generator.blocks.push( block );
preprocessChildren( generator, block, children );
block.hasUpdateMethod = block.dependencies.size > 0;
return block;
}

@ -125,6 +125,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
` );
const consequent = node._block.hasUpdateMethod ?
deindent`
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];
${_lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
` :
`${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];`;
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
var ${_iterations} = [];
@ -138,8 +145,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
var ${key} = ${value}.${node.key};
if ( ${lookup}[ ${key} ] ) {
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];
${_lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
${consequent}
} else {
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
}
@ -192,17 +198,28 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
.join( ' || ' );
if ( condition !== '' ) {
const forLoopBody = node._block.hasUpdateMethod ?
deindent`
if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
}
` :
deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
`;
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
if ( ${condition} ) {
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
if ( !${iterations}[${i}] ) {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
}
for ( var ${i} = ${start}; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${forLoopBody}
}
${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length );

@ -5,22 +5,24 @@ function isElseIf ( node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
function getConditionsAndBlocks ( generator, block, state, node ) {
const conditionsAndBlocks = [{
function getBranches ( generator, block, state, node ) {
const branches = [{
condition: block.contextualise( node.expression ).snippet,
block: node._block.name
block: node._block.name,
dynamic: node._block.dependencies.size > 0
}];
visitChildren( generator, block, state, node );
if ( isElseIf( node.else ) ) {
conditionsAndBlocks.push(
...getConditionsAndBlocks( generator, block, state, node.else.children[0] )
branches.push(
...getBranches( generator, block, state, node.else.children[0] )
);
} else {
conditionsAndBlocks.push({
branches.push({
condition: null,
block: node.else ? node.else._block.name : null,
dynamic: node.else ? node.else._block.dependencies.size > 0 : false
});
if ( node.else ) {
@ -28,7 +30,7 @@ function getConditionsAndBlocks ( generator, block, state, node ) {
}
}
return conditionsAndBlocks;
return branches;
}
function visitChildren ( generator, block, state, node ) {
@ -48,14 +50,15 @@ export default function visitIfBlock ( generator, block, state, node ) {
const currentBlock = block.getUniqueName( `current_block` );
const _currentBlock = block.getUniqueName( `_current_block` );
const conditionsAndBlocks = getConditionsAndBlocks( generator, block, state, node, generator.getUniqueName( `create_if_block` ) );
const branches = getBranches( generator, block, state, node, generator.getUniqueName( `create_if_block` ) );
const dynamic = branches.some( branch => branch.dynamic );
const anchor = `${name}_anchor`;
block.createAnchor( anchor, state.parentNode );
block.builders.create.addBlock( deindent`
function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => {
${branches.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )}
}
@ -75,15 +78,28 @@ export default function visitIfBlock ( generator, block, state, node ) {
block.builders.update.addBlock( deindent`
var ${_currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} );
if ( ${_currentBlock} === ${currentBlock} && ${name}) {
${name}.update( changed, ${params} );
} else {
if ( ${name} ) ${name}.destroy( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
if ( dynamic ) {
block.builders.update.addBlock( deindent`
if ( ${_currentBlock} === ${currentBlock} && ${name} ) {
${name}.update( changed, ${params} );
} else {
if ( ${name} ) ${name}.destroy( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
} else {
block.builders.update.addBlock( deindent`
if ( ${_currentBlock} !== ${currentBlock} ) {
if ( ${name} ) ${name}.destroy( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
}
block.builders.destroy.addLine(
`if ( ${name} ) ${name}.destroy( ${isToplevel ? 'detach' : 'false'} );`
);

@ -17,13 +17,11 @@ var template = (function () {
}());
function create_main_fragment ( root, component ) {
return {
mount: noop,
update: noop,
destroy: noop
};
}
@ -32,19 +30,19 @@ function SvelteComponent ( options ) {
options = options || {};
this._state = options.data || {};
recompute( this._state, this._state, {}, true );
this._observers = {
pre: Object.create( null ),
post: Object.create( null )
};
this._handlers = Object.create( null );
this._root = options._root;
this._yield = options._yield;
this._torndown = false;
this._fragment = create_main_fragment( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
}
@ -55,9 +53,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
recompute( this._state, newState, oldState, false )
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -23,11 +23,11 @@ function create_main_fragment ( root, component ) {
if ( 'comments' in changed || 'elapsed' in changed || 'time' in changed ) {
for ( var i = 0; i < each_block_value.length; i += 1 ) {
if ( !each_block_iterations[i] ) {
if ( each_block_iterations[i] ) {
each_block_iterations[i].update( changed, root, each_block_value, each_block_value[i], i );
} else {
each_block_iterations[i] = create_each_block( root, each_block_value, each_block_value[i], i, component );
each_block_iterations[i].mount( each_block_anchor.parentNode, each_block_anchor );
} else {
each_block_iterations[i].update( changed, root, each_block_value, each_block_value[i], i );
}
}
@ -127,7 +127,6 @@ assign( SvelteComponent.prototype, proto );
SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );

@ -1,4 +1,4 @@
import { appendNode, assign, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js";
import { appendNode, assign, createElement, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
var template = (function () {
return {
@ -30,8 +30,6 @@ function create_main_fragment ( root, component ) {
insertNode( button, target, anchor );
},
update: noop,
destroy: function ( detach ) {
foo_handler.teardown();
@ -67,9 +65,7 @@ assign( SvelteComponent.prototype, template.methods, proto );
SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -0,0 +1,115 @@
import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
function create_main_fragment ( root, component ) {
var if_block_anchor = createComment();
function get_block ( root ) {
if ( root.foo ) return create_if_block;
return create_if_block_1;
}
var current_block = get_block( root );
var if_block = current_block && current_block( root, component );
return {
mount: function ( target, anchor ) {
insertNode( if_block_anchor, target, anchor );
if ( if_block ) if_block.mount( target, if_block_anchor );
},
update: function ( changed, root ) {
var _current_block = current_block;
current_block = get_block( root );
if ( _current_block !== current_block ) {
if ( if_block ) if_block.destroy( true );
if_block = current_block && current_block( root, component );
if ( if_block ) if_block.mount( if_block_anchor.parentNode, if_block_anchor );
}
},
destroy: function ( detach ) {
if ( if_block ) if_block.destroy( detach );
if ( detach ) {
detachNode( if_block_anchor );
}
}
};
}
function create_if_block ( root, component ) {
var p = createElement( 'p' );
appendNode( createText( "foo!" ), p );
return {
mount: function ( target, anchor ) {
insertNode( p, target, anchor );
},
destroy: function ( detach ) {
if ( detach ) {
detachNode( p );
}
}
};
}
function create_if_block_1 ( root, component ) {
var p = createElement( 'p' );
appendNode( createText( "not foo!" ), p );
return {
mount: function ( target, anchor ) {
insertNode( p, target, anchor );
},
destroy: function ( detach ) {
if ( detach ) {
detachNode( p );
}
}
};
}
function SvelteComponent ( options ) {
options = options || {};
this._state = options.data || {};
this._observers = {
pre: Object.create( null ),
post: Object.create( null )
};
this._handlers = Object.create( null );
this._root = options._root;
this._yield = options._yield;
this._torndown = false;
this._fragment = create_main_fragment( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
}
assign( SvelteComponent.prototype, proto );
SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};
SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) {
this.fire( 'destroy' );
this._fragment.destroy( detach !== false );
this._fragment = null;
this._state = {};
this._torndown = true;
};
export default SvelteComponent;

@ -0,0 +1,5 @@
{{#if foo}}
<p>foo!</p>
{{else}}
<p>not foo!</p>
{{/if}}
Loading…
Cancel
Save