Merge pull request #489 from sveltejs/gh-381

Only update each blocks when their dependencies have changed
pull/497/head
Rich Harris 8 years ago committed by GitHub
commit aeedb94d97

@ -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 } ) {

@ -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(),
@ -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 ) {
@ -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 );` );
}
}
@ -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}
},
` );

@ -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,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,
@ -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(),

@ -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;
}

@ -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( ' + ' )
);

@ -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!' );

@ -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 '{}';
@ -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
@ -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 );
@ -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 ) {

@ -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}
});
` );
` );
}

@ -3,219 +3,214 @@ import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
export default function visitEachBlock ( generator, block, state, node ) {
const name = generator.getUniqueName( `each_block` );
const renderer = generator.getUniqueName( `create_each_block` );
const elseName = generator.getUniqueName( `${name}_else` );
const renderElse = generator.getUniqueName( `${renderer}_else` );
const each_block = generator.getUniqueName( `each_block` );
const create_each_block = node._block.name;
const each_block_value = node._block.listName;
const iterations = block.getUniqueName( `${each_block}_iterations` );
const i = block.getUniqueName( `i` );
const params = block.params.join( ', ' );
const anchor = block.getUniqueName( `${each_block}_anchor` );
const listName = block.getUniqueName( `${name}_value` );
const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor };
const isToplevel = !state.parentNode;
const { dependencies, snippet } = generator.contextualise( block, node.expression );
const { snippet } = block.contextualise( node.expression );
const anchor = block.getUniqueName( `${name}_anchor` );
block.createAnchor( anchor, state.parentNode );
const localVars = {};
localVars.iteration = block.getUniqueName( `${name}_iteration` );
localVars.iterations = block.getUniqueName( `${name}_iterations` );
localVars._iterations = block.getUniqueName( `_${name}_iterations` );
localVars.lookup = block.getUniqueName( `${name}_lookup` );
localVars._lookup = block.getUniqueName( `_${name}_lookup` );
block.builders.create.addLine( `var ${listName} = ${snippet};` );
block.builders.create.addLine( `var ${localVars.iterations} = [];` );
if ( node.key ) block.builders.create.addLine( `var ${localVars.lookup} = Object.create( null );` );
if ( node.else ) block.builders.create.addLine( `var ${elseName} = null;` );
const initialRender = new CodeBuilder();
block.builders.create.addLine( `var ${each_block_value} = ${snippet};` );
block.builders.create.addLine( `var ${iterations} = [];` );
if ( node.key ) {
localVars.fragment = block.getUniqueName( 'fragment' );
localVars.value = block.getUniqueName( 'value' );
localVars.key = block.getUniqueName( 'key' );
initialRender.addBlock( deindent`
var ${localVars.key} = ${listName}[${i}].${node.key};
${localVars.iterations}[${i}] = ${localVars.lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${localVars.key}` : `` } );
` );
keyed( generator, block, state, node, snippet, vars );
} else {
initialRender.addLine(
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} );`
);
unkeyed( generator, block, state, node, snippet, vars );
}
if ( !isToplevel ) {
initialRender.addLine(
`${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );`
);
const isToplevel = !state.parentNode;
if ( isToplevel ) {
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].mount( ${block.target}, ${anchor} );
}
` );
}
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
${initialRender}
}
` );
block.builders.destroy.addBlock(
`${generator.helper( 'destroyEach' )}( ${iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` );
if ( node.else ) {
const each_block_else = generator.getUniqueName( `${each_block}_else` );
block.builders.create.addLine( `var ${each_block_else} = null;` );
// TODO neaten this up... will end up with an empty line in the block
block.builders.create.addBlock( deindent`
if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${block.component} );
${!isToplevel ? `${elseName}.mount( ${anchor}.parentNode, ${anchor} );` : ''}
if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, ${anchor} );` : ''}
}
` );
}
if ( isToplevel ) {
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
if ( ${each_block_else} ) {
${each_block_else}.mount( ${state.parentNode || block.target}, ${anchor} );
}
` );
if ( node.else ) {
block.builders.mount.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
}
}
if ( node.key ) {
block.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
var ${localVars._iterations} = [];
var ${localVars._lookup} = Object.create( null );
var ${localVars.fragment} = document.createDocumentFragment();
// create new iterations as necessary
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
var ${localVars.value} = ${listName}[${i}];
var ${localVars.key} = ${localVars.value}.${node.key};
if ( ${localVars.lookup}[ ${localVars.key} ] ) {
${localVars._iterations}[${i}] = ${localVars._lookup}[ ${localVars.key} ] = ${localVars.lookup}[ ${localVars.key} ];
${localVars._lookup}[ ${localVars.key} ].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
} else {
${localVars._iterations}[${i}] = ${localVars._lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${localVars.key}` : `` } );
}
${localVars._iterations}[${i}].mount( ${localVars.fragment}, null );
if ( !${each_block_value}.length && ${each_block_else} ) {
${each_block_else}.update( changed, ${params} );
} else if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${each_block_else}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${each_block_else} ) {
${each_block_else}.destroy( true );
}
` );
// remove old iterations
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
var ${localVars.iteration} = ${localVars.iterations}[${i}];
if ( !${localVars._lookup}[ ${localVars.iteration}.key ] ) {
${localVars.iteration}.destroy( true );
}
block.builders.destroy.addBlock( deindent`
if ( ${each_block_else} ) {
${each_block_else}.destroy( ${isToplevel ? 'detach' : 'false'} );
}
` );
}
${anchor}.parentNode.insertBefore( ${localVars.fragment}, ${anchor} );
const childBlock = node._block;
${localVars.iterations} = ${localVars._iterations};
${localVars.lookup} = ${localVars._lookup};
` );
} else {
block.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
if ( !${localVars.iterations}[${i}] ) {
${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} );
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${localVars.iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
}
}
const childState = Object.assign( {}, state, {
parentNode: null,
inEachBlock: true
});
${generator.helper( 'destroyEach' )}( ${localVars.iterations}, true, ${listName}.length );
node.children.forEach( child => {
visit( generator, childBlock, childState, child );
});
${localVars.iterations}.length = ${listName}.length;
` );
}
generator.addBlock( childBlock );
if ( node.else ) {
block.builders.update.addBlock( deindent`
if ( !${listName}.length && ${elseName} ) {
${elseName}.update( changed, ${params} );
} else if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${block.component} );
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${elseName} ) {
${elseName}.destroy( true );
}
` );
node.else.children.forEach( child => {
visit( generator, node.else._block, childState, child );
});
generator.addBlock( node.else._block );
}
}
block.builders.destroy.addBlock(
`${generator.helper( 'destroyEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` );
function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor } ) {
const fragment = block.getUniqueName( 'fragment' );
const value = block.getUniqueName( 'value' );
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
const _lookup = block.getUniqueName( `_${each_block}_lookup` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
const _iterations = block.getUniqueName( `_${each_block}_iterations` );
if ( node.else ) {
block.builders.destroy.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.destroy( ${isToplevel ? 'detach' : 'false'} );
}
` );
block.builders.create.addLine( `var ${lookup} = Object.create( null );` );
const create = new CodeBuilder();
create.addBlock( deindent`
var ${key} = ${each_block_value}[${i}].${node.key};
${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
` );
if ( state.parentNode ) {
create.addLine(
`${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );`
);
}
const indexNames = new Map( block.indexNames );
const indexName = node.index || block.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName );
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${create}
}
` );
const listNames = new Map( block.listNames );
listNames.set( node.context, listName );
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
var ${_iterations} = [];
var ${_lookup} = Object.create( null );
const context = generator.getUniqueName( node.context );
const contexts = new Map( block.contexts );
contexts.set( node.context, context );
var ${fragment} = document.createDocumentFragment();
const indexes = new Map( block.indexes );
if ( node.index ) indexes.set( indexName, node.context );
// create new iterations as necessary
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${value} = ${each_block_value}[${i}];
var ${key} = ${value}.${node.key};
const contextDependencies = new Map( block.contextDependencies );
contextDependencies.set( node.context, dependencies );
if ( ${lookup}[ ${key} ] ) {
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];
${_lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
}
const childBlock = block.child({
name: renderer,
expression: node.expression,
context: node.context,
key: node.key,
${_iterations}[${i}].mount( ${fragment}, null );
}
contextDependencies,
contexts,
indexes,
// remove old iterations
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
var ${iteration} = ${iterations}[${i}];
if ( !${_lookup}[ ${iteration}.key ] ) {
${iteration}.destroy( true );
}
}
indexNames,
listNames,
params: block.params.concat( listName, context, indexName )
});
${anchor}.parentNode.insertBefore( ${fragment}, ${anchor} );
const childState = Object.assign( {}, state, {
parentNode: null,
inEachBlock: true
});
${iterations} = ${_iterations};
${lookup} = ${_lookup};
` );
}
node.children.forEach( child => {
visit( generator, childBlock, childState, child );
function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor } ) {
const create = new CodeBuilder();
create.addLine(
`${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );`
);
if ( state.parentNode ) {
create.addLine(
`${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );`
);
}
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${create}
}
` );
const { dependencies } = block.contextualise( node.expression );
const allDependencies = new Set( block.dependencies );
dependencies.forEach( dependency => {
allDependencies.add( dependency );
});
generator.addBlock( childBlock );
const condition = Array.from( allDependencies )
.map( dependency => `'${dependency}' in changed` )
.join( ' || ' );
if ( node.else ) {
const childBlock = block.child({
name: renderElse
});
if ( condition !== '' ) {
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} );
}
}
node.else.children.forEach( child => {
visit( generator, childBlock, childState, child );
});
${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length );
generator.addBlock( childBlock );
${iterations}.length = ${each_block_value}.length;
}
` );
}
}

@ -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( ' + ' )

@ -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!' );

@ -1,39 +1,38 @@
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) {
const name = generator.getUniqueName( `${_name}_${i}` );
function isElseIf ( node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
function getConditionsAndBlocks ( generator, block, state, node ) {
const conditionsAndBlocks = [{
condition: generator.contextualise( block, node.expression ).snippet,
block: name
condition: block.contextualise( node.expression ).snippet,
block: node._block.name
}];
generateBlock( generator, block, state, node, name );
generateBlock( generator, block, state, node );
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 )
...getConditionsAndBlocks( generator, block, state, node.else.children[0] )
);
} else {
const name = generator.getUniqueName( `${_name}_${i + 1}` );
conditionsAndBlocks.push({
condition: null,
block: node.else ? name : null,
block: node.else ? node.else._block.name : null,
});
if ( node.else ) {
generateBlock( generator, block, state, node.else, name );
generateBlock( generator, block, state, node.else );
}
}
return conditionsAndBlocks;
}
function generateBlock ( generator, block, state, node, name ) {
const childBlock = block.child({
name
});
function generateBlock ( generator, block, state, node ) {
const childBlock = node._block;
const childState = Object.assign( {}, state, {
parentNode: null
@ -53,7 +52,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 +68,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`

@ -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 );

@ -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.

@ -3,7 +3,7 @@ export default function visitYieldTag ( generator, block, state ) {
block.createAnchor( anchor, state.parentNode );
block.builders.mount.addLine(
`${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || 'target'}, ${anchor} );`
`${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || block.target}, ${anchor} );`
);
block.builders.destroy.addLine(

@ -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 );
}
}

@ -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 {

@ -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 );

@ -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( '' ) + `"`;
}

@ -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 + ' ? `' );

@ -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 + ' )}' );
}

@ -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 + '}' );
}

@ -18,9 +18,16 @@ describe( 'js', () => {
dir = path.resolve( 'test/js/samples', dir );
const input = fs.readFileSync( `${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
const actual = svelte.compile( input, {
shared: true
}).code;
let actual;
try {
actual = svelte.compile( input, {
shared: true
}).code;
} catch ( err ) {
console.log( err.frame );
throw err;
}
fs.writeFileSync( `${dir}/_actual.js`, actual );
const expected = fs.readFileSync( `${dir}/expected.js`, 'utf-8' );

@ -0,0 +1,146 @@
import { appendNode, assign, createComment, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
function create_main_fragment ( root, component ) {
var each_block_anchor = createComment();
var each_block_value = root.comments;
var each_block_iterations = [];
for ( var i = 0; i < each_block_value.length; i += 1 ) {
each_block_iterations[i] = create_each_block( root, each_block_value, each_block_value[i], i, component );
}
return {
mount: function ( target, anchor ) {
insertNode( each_block_anchor, target, anchor );
for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
each_block_iterations[i].mount( target, each_block_anchor );
}
},
update: function ( changed, root ) {
var each_block_value = root.comments;
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] ) {
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 );
}
}
destroyEach( each_block_iterations, true, each_block_value.length );
each_block_iterations.length = each_block_value.length;
}
},
destroy: function ( detach ) {
destroyEach( each_block_iterations, detach, 0 );
if ( detach ) {
detachNode( each_block_anchor );
}
}
};
}
function create_each_block ( root, each_block_value, comment, comment_index, component ) {
var div = createElement( 'div' );
div.className = "comment";
var span = createElement( 'span' );
appendNode( span, div );
span.className = "meta";
var last_text = comment.author;
var text = createText( last_text );
appendNode( text, span );
appendNode( createText( " wrote " ), span );
var last_text_2 = root.elapsed(comment.time, root.time);
var text_2 = createText( last_text_2 );
appendNode( text_2, span );
appendNode( createText( " ago:" ), span );
appendNode( createText( "\n\n\t\t" ), div );
var raw_before = createElement( 'noscript' );
appendNode( raw_before, div );
var raw_after = createElement( 'noscript' );
appendNode( raw_after, div );
var last_raw = comment.html;
raw_before.insertAdjacentHTML( 'afterend', last_raw );
return {
mount: function ( target, anchor ) {
insertNode( div, target, anchor );
},
update: function ( changed, root, each_block_value, comment, comment_index ) {
var tmp;
if ( ( tmp = comment.author ) !== last_text ) {
text.data = last_text = tmp;
}
if ( ( tmp = root.elapsed(comment.time, root.time) ) !== last_text_2 ) {
text_2.data = last_text_2 = tmp;
}
if ( ( tmp = comment.html ) !== last_raw ) {
last_raw = tmp;
detachBetween( raw_before, raw_after );
raw_before.insertAdjacentHTML( 'afterend', last_raw );
}
},
destroy: function ( detach ) {
if ( detach ) {
detachBetween( raw_before, raw_after );
detachNode( div );
}
}
};
}
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,9 @@
{{#each comments as comment}}
<div class='comment'>
<span class='meta'>
{{comment.author}} wrote {{elapsed(comment.time, time)}} ago:
</span>
{{{comment.html}}}
</div>
{{/each}}
Loading…
Cancel
Save