Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only update each blocks when their dependencies have changed #489

Merged
merged 11 commits into from
Apr 17, 2017
9 changes: 6 additions & 3 deletions src/generators/Generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export default class Generator {
}

contextualise ( block, expression, context, isEventHandler ) {
if ( expression._contextualised ) return expression._contextualised;

this.addSourcemapLocations( expression );

const usedContexts = [];
Expand Down Expand Up @@ -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 } ) {
Expand Down
40 changes: 25 additions & 15 deletions src/generators/dom/Block.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ 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 }) {
this.generator = generator;
this.name = name;
this.key = key;
this.expression = expression;
this.context = context;
constructor ( options ) {
this.generator = options.generator;
this.name = options.name;
this.key = options.key;
this.expression = options.expression;
this.context = options.context;

this.contexts = contexts;
this.indexes = indexes;
this.contextDependencies = contextDependencies;
this.contexts = options.contexts;
this.indexes = options.indexes;
this.contextDependencies = options.contextDependencies;
this.dependencies = new Set();

this.params = params;
this.indexNames = indexNames;
this.listNames = listNames;
this.params = options.params;
this.indexNames = options.indexNames;
this.listNames = options.listNames;

this.listName = options.listName;

this.builders = {
create: new CodeBuilder(),
Expand All @@ -26,10 +29,17 @@ export default class Block {
destroy: new CodeBuilder()
};

this.getUniqueName = generator.getUniqueNameMaker( params );
this.getUniqueName = this.generator.getUniqueNameMaker( options.params );

// unique names
this.component = this.getUniqueName( 'component' );
this.target = this.getUniqueName( 'target' );
}

addDependencies ( dependencies ) {
dependencies.forEach( dependency => {
this.dependencies.add( dependency );
});
}

addElement ( name, renderStatement, parentNode, needsIdentifier = false ) {
Expand Down Expand Up @@ -66,7 +76,7 @@ export default class Block {
if ( parentNode ) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
} else {
this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, target, anchor );` );
this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, ${this.target}, anchor );` );
}
}

Expand Down Expand Up @@ -99,7 +109,7 @@ export default class Block {
properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
mount: function ( target, anchor ) {
mount: function ( ${this.target}, anchor ) {
${this.builders.mount}
},
` );
Expand Down
26 changes: 4 additions & 22 deletions src/generators/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -47,25 +47,7 @@ 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 block = preprocess( generator, parsed.html.children );

const state = {
namespace,
Expand All @@ -74,10 +56,10 @@ export default function dom ( parsed, source, options ) {
};

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(),
Expand Down
155 changes: 155 additions & 0 deletions src/generators/dom/preprocess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 );
block.addDependencies( dependencies );
},

IfBlock: ( generator, block, node ) => {
function attachBlocks ( node ) {
const { dependencies } = block.contextualise( node.expression );
block.addDependencies( dependencies );

node._block = block.child({
name: generator.getUniqueName( `create_if_block` )
});

preprocessChildren( generator, node._block, node.children );
block.addDependencies( node._block.dependencies );

if ( isElseIf( node.else ) ) {
attachBlocks( node.else.children[0] );
} else if ( node.else ) {
node.else._block = block.child({
name: generator.getUniqueName( `create_if_block` )
});

preprocessChildren( generator, node.else._block, node.else.children );
block.addDependencies( node.else._block.dependencies );
}
}

attachBlocks( node );
},

EachBlock: ( generator, block, node ) => {
const { dependencies } = block.contextualise( node.expression );
block.addDependencies( dependencies );

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,

listName,
indexName,

indexNames,
listNames,
params: block.params.concat( listName, context, indexName )
});

preprocessChildren( generator, node._block, node.children );
block.addDependencies( node._block.dependencies );

if ( node.else ) {
node.else._block = block.child({
name: generator.getUniqueName( `${node._block.name}_else` )
});

preprocessChildren( generator, node.else._block, node.else.children );
}
},

Element: ( generator, block, node ) => {
node.attributes.forEach( attribute => {
if ( attribute.type === 'Attribute' && attribute.value !== true ) {
attribute.value.forEach( chunk => {
if ( chunk.type !== 'Text' ) {
const { dependencies } = block.contextualise( chunk.expression );
block.addDependencies( dependencies );
}
});
}

else if ( attribute.type === 'Binding' ) {
const { dependencies } = block.contextualise( attribute.value );
block.addDependencies( dependencies );
}
});

const isComponent = generator.components.has( node.name ) || node.name === ':Self';

if ( isComponent ) {
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );

node._block = block.child({
name: generator.getUniqueName( `create_${name}_yield_fragment` )
});

preprocessChildren( generator, node._block, node.children );
}

else {
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 ) {
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()
});

preprocessChildren( generator, block, children );

return block;
}
8 changes: 4 additions & 4 deletions src/generators/dom/visitors/Component/Attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}
Expand All @@ -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( ' + ' )
);
Expand Down
2 changes: 1 addition & 1 deletion src/generators/dom/visitors/Component/Binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -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!' );

Expand Down
12 changes: 3 additions & 9 deletions src/generators/dom/visitors/Component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import visitEventHandler from './EventHandler.js';
import visitBinding from './Binding.js';
import visitRef from './Ref.js';

function capDown ( name ) {
return `${name[0].toLowerCase()}${name.slice( 1 )}`;
}

function stringifyProps ( props ) {
if ( !props.length ) return '{}';

Expand Down Expand Up @@ -38,7 +34,7 @@ const visitors = {

export default function visitComponent ( generator, block, state, node ) {
const hasChildren = node.children.length > 0;
const name = block.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );

const childState = Object.assign( {}, state, {
parentNode: null
Expand Down Expand Up @@ -105,9 +101,7 @@ export default function visitComponent ( generator, block, state, node ) {
if ( hasChildren ) {
const params = block.params.join( ', ' );

const childBlock = block.child({
name: generator.getUniqueName( `create_${name}_yield_fragment` ) // TODO should getUniqueName happen inside Fragment? probably
});
const childBlock = node._block;

node.children.forEach( child => {
visit( generator, childBlock, childState, child );
Expand Down Expand Up @@ -162,7 +156,7 @@ export default function visitComponent ( generator, block, state, node ) {
` );

if ( isToplevel ) {
block.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
block.builders.mount.addLine( `${name}._fragment.mount( ${block.target}, anchor );` );
}

if ( local.dynamicAttributes.length ) {
Expand Down
Loading