preprocess template, so that we can discover dependencies ahead of time

pull/489/head
Rich-Harris 9 years ago
parent d00348d3d6
commit ab19649dfa

@ -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,7 +2,20 @@ 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 }) {
constructor ({
generator,
name,
key,
expression,
context,
contextDependencies,
dependencies,
contexts,
indexes,
params,
indexNames,
listNames
}) {
this.generator = generator;
this.name = name;
this.key = key;
@ -12,6 +25,7 @@ export default class Block {
this.contexts = contexts;
this.indexes = indexes;
this.contextDependencies = contextDependencies;
this.dependencies = dependencies;
this.params = params;
this.indexNames = indexNames;

@ -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,37 +47,13 @@ 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 state = {
namespace,
parentNode: null,
isTopLevel: true
};
const { block, state } = preprocess( generator, parsed.html.children, namespace );
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,118 @@
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 );
dependencies.forEach( dependency => {
block.dependencies.add( dependency );
});
},
IfBlock: ( generator, block, node ) => {
function attachBlocks ( node ) {
const { dependencies } = block.contextualise( node.expression );
node._block = block.child({
name: generator.getUniqueName( `create_if_block` )
});
preprocessChildren( generator, node._block, node.children );
if ( isElseIf( node.else ) ) {
attachBlocks( node.else );
} else if ( node.else ) {
node.else._block = block.child({
name: generator.getUniqueName( `create_if_block` )
});
preprocessChildren( generator, node.else._block, node.else.children );
}
}
attachBlocks ( node );
},
EachBlock: ( generator, block, node ) => {
const { dependencies } = block.contextualise( node.expression );
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,
indexNames,
listNames,
params: block.params.concat( listName, context, indexName )
});
preprocessChildren( generator, node._block, node.children );
},
Element: ( generator, block, node ) => {
// TODO attributes and bindings (and refs?)...
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, namespace ) {
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()
});
const state = {
namespace,
parentNode: null,
isTopLevel: true
};
preprocessChildren( generator, block, children );
return { block, state };
}

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

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

@ -5,7 +5,7 @@ import visit from '../visit.js';
export default function visitEachBlock ( generator, block, state, node ) {
const each_block = generator.getUniqueName( `each_block` );
const each_block_else = generator.getUniqueName( `${each_block}_else` );
const create_each_block = generator.getUniqueName( `create_each_block` );
const create_each_block = node._block.name;
const create_each_block_else = generator.getUniqueName( `${create_each_block}_else` );
const listName = block.getUniqueName( `${each_block}_value` );
const iterations = block.getUniqueName( `${each_block}_iterations` );
@ -15,7 +15,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
const vars = { each_block, create_each_block, listName, iterations, i, params, anchor };
const { dependencies, snippet } = generator.contextualise( block, node.expression );
const { dependencies, snippet } = block.contextualise( node.expression );
block.createAnchor( anchor, state.parentNode );
block.builders.create.addLine( `var ${listName} = ${snippet};` );
@ -75,37 +75,38 @@ export default function visitEachBlock ( generator, block, state, node ) {
` );
}
const indexNames = new Map( block.indexNames );
const indexName = node.index || block.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName );
// 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 );
listNames.set( node.context, listName );
// const listNames = new Map( block.listNames );
// listNames.set( node.context, listName );
const context = generator.getUniqueName( node.context );
const contexts = new Map( block.contexts );
contexts.set( node.context, context );
// 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 indexes = new Map( block.indexes );
// if ( node.index ) indexes.set( indexName, node.context );
const contextDependencies = new Map( block.contextDependencies );
contextDependencies.set( node.context, dependencies );
// const contextDependencies = new Map( block.contextDependencies );
// contextDependencies.set( node.context, dependencies );
const childBlock = block.child({
name: vars.create_each_block,
expression: node.expression,
context: node.context,
key: node.key,
// const childBlock = block.child({
// name: vars.create_each_block,
// expression: node.expression,
// context: node.context,
// key: node.key,
contextDependencies,
contexts,
indexes,
// contextDependencies,
// contexts,
// indexes,
indexNames,
listNames,
params: block.params.concat( listName, context, indexName )
});
// indexNames,
// listNames,
// params: block.params.concat( listName, context, indexName )
// });
const childBlock = node._block;
const childState = Object.assign( {}, state, {
parentNode: null,

@ -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,18 +1,21 @@
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
function isElseIf ( node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) {
const name = generator.getUniqueName( `${_name}_${i}` );
const conditionsAndBlocks = [{
condition: generator.contextualise( block, node.expression ).snippet,
condition: block.contextualise( node.expression ).snippet,
block: name
}];
generateBlock( generator, block, state, node, name );
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 )
);
@ -27,6 +30,7 @@ function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 )
generateBlock( generator, block, state, node.else, name );
}
}
return conditionsAndBlocks;
}
@ -53,7 +57,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 +73,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.

@ -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 + '}' );
}
Loading…
Cancel
Save