turn dom generator visitors into functions

pull/453/head
Rich-Harris 8 years ago
parent e7d324f305
commit b8705a5b0c

@ -2,7 +2,5 @@ import visitors from './visitors/index.js';
export default function visit ( node, generator ) { export default function visit ( node, generator ) {
const visitor = visitors[ node.type ]; const visitor = visitors[ node.type ];
visitor( generator, node );
if ( visitor.enter ) visitor.enter( generator, node );
if ( visitor.leave ) visitor.leave( generator, node );
} }

@ -1,3 +1,3 @@
export default { export default function visitComment () {
// do nothing // do nothing
}; }

@ -19,163 +19,161 @@ function stringifyProps ( props ) {
return `{ ${joined} }`; return `{ ${joined} }`;
} }
export default { export default function visitComponent ( generator, node ) {
enter ( generator, node ) { const hasChildren = node.children.length > 0;
const hasChildren = node.children.length > 0; const { current } = generator;
const { current } = generator; const name = current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const name = current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const local = { const local = {
name, name,
namespace: current.namespace, namespace: current.namespace,
isComponent: true, isComponent: true,
allUsedContexts: [], allUsedContexts: [],
init: new CodeBuilder(), init: new CodeBuilder(),
update: new CodeBuilder() update: new CodeBuilder()
}; };
const isToplevel = current.localElementDepth === 0; const isToplevel = current.localElementDepth === 0;
generator.hasComponents = true; generator.hasComponents = true;
addComponentAttributes( generator, node, local ); addComponentAttributes( generator, node, local );
if ( local.allUsedContexts.length ) { if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => { const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`; if ( contextName === 'root' ) return `root: root`;
const listName = current.listNames.get( contextName ); const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName ); const indexName = current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`; return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' ); }).join( ',\n' );
const updates = local.allUsedContexts.map( contextName => { const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}._context.root = root;`; if ( contextName === 'root' ) return `${name}._context.root = root;`;
const listName = current.listNames.get( contextName ); const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName ); const indexName = current.indexNames.get( contextName );
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' ); }).join( '\n' );
local.init.addBlock( deindent` local.init.addBlock( deindent`
${name}._context = { ${name}._context = {
${initialProps} ${initialProps}
}; };
` ); ` );
local.update.addBlock( updates ); local.update.addBlock( updates );
} }
const componentInitProperties = [ const componentInitProperties = [
`target: ${!isToplevel ? current.target: 'null'}`, `target: ${!isToplevel ? current.target: 'null'}`,
`_root: ${current.component}._root || ${current.component}` `_root: ${current.component}._root || ${current.component}`
]; ];
// Component has children, put them in a separate {{yield}} block // Component has children, put them in a separate {{yield}} block
if ( hasChildren ) { if ( hasChildren ) {
const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` ); const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` );
const params = current.params.join( ', ' ); const params = current.params.join( ', ' );
generator.generateBlock( node, yieldName, 'block' ); generator.generateBlock( node, yieldName, 'block' );
const yieldFragment = current.getUniqueName( `${name}_yield_fragment` ); const yieldFragment = current.getUniqueName( `${name}_yield_fragment` );
current.builders.init.addLine( current.builders.init.addLine(
`var ${yieldFragment} = ${yieldName}( ${params}, ${current.component} );` `var ${yieldFragment} = ${yieldName}( ${params}, ${current.component} );`
); );
current.builders.update.addLine( current.builders.update.addLine(
`${yieldFragment}.update( changed, ${params} );` `${yieldFragment}.update( changed, ${params} );`
); );
componentInitProperties.push( `_yield: ${yieldFragment}`); componentInitProperties.push( `_yield: ${yieldFragment}`);
} }
const statements = []; const statements = [];
if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) { if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) {
const initialProps = local.staticAttributes const initialProps = local.staticAttributes
.concat( local.dynamicAttributes ) .concat( local.dynamicAttributes )
.map( attribute => `${attribute.name}: ${attribute.value}` ); .map( attribute => `${attribute.name}: ${attribute.value}` );
const initialPropString = stringifyProps( initialProps ); const initialPropString = stringifyProps( initialProps );
if ( local.bindings.length ) { if ( local.bindings.length ) {
const initialData = current.getUniqueName( `${name}_initial_data` ); const initialData = current.getUniqueName( `${name}_initial_data` );
statements.push( `var ${name}_initial_data = ${initialPropString};` ); statements.push( `var ${name}_initial_data = ${initialPropString};` );
local.bindings.forEach( binding => { local.bindings.forEach( binding => {
statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` ); statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` );
}); });
componentInitProperties.push( `data: ${initialData}` ); componentInitProperties.push( `data: ${initialData}` );
} else if ( initialProps.length ) { } else if ( initialProps.length ) {
componentInitProperties.push( `data: ${initialPropString}` ); componentInitProperties.push( `data: ${initialPropString}` );
}
} }
}
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`; const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`;
local.init.addBlockAtStart( deindent` local.init.addBlockAtStart( deindent`
${statements.join( '\n' )} ${statements.join( '\n' )}
var ${name} = new ${expression}({ var ${name} = new ${expression}({
${componentInitProperties.join(',\n')} ${componentInitProperties.join(',\n')}
}); });
` ); ` );
if ( isToplevel ) { if ( isToplevel ) {
current.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` ); current.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
} }
if ( local.dynamicAttributes.length ) { if ( local.dynamicAttributes.length ) {
const updates = local.dynamicAttributes.map( attribute => { const updates = local.dynamicAttributes.map( attribute => {
if ( attribute.dependencies.length ) { if ( attribute.dependencies.length ) {
return deindent` return deindent`
if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value}; if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value};
`; `;
} }
// TODO this is an odd situation to encounter I *think* it should only happen with
// each block indices, in which case it may be possible to optimise this
return `${name}_changes.${attribute.name} = ${attribute.value};`;
});
local.update.addBlock( deindent` // TODO this is an odd situation to encounter I *think* it should only happen with
var ${name}_changes = {}; // each block indices, in which case it may be possible to optimise this
return `${name}_changes.${attribute.name} = ${attribute.value};`;
});
${updates.join( '\n' )} local.update.addBlock( deindent`
var ${name}_changes = {};
if ( Object.keys( ${name}_changes ).length ) ${name}.set( ${name}_changes ); ${updates.join( '\n' )}
` );
}
current.builders.teardown.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` ); if ( Object.keys( ${name}_changes ).length ) ${name}.set( ${name}_changes );
` );
}
current.builders.init.addBlock( local.init ); current.builders.teardown.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` );
if ( !local.update.isEmpty() ) current.builders.update.addBlock( local.update );
generator.push({ current.builders.init.addBlock( local.init );
type: 'component', if ( !local.update.isEmpty() ) current.builders.update.addBlock( local.update );
namespace: local.namespace,
target: name,
parent: current,
localElementDepth: current.localElementDepth + 1,
key: null
});
generator.elementDepth += 1; generator.push({
type: 'component',
namespace: local.namespace,
target: name,
parent: current,
localElementDepth: current.localElementDepth + 1,
key: null
});
node.children.forEach( child => { generator.elementDepth += 1;
visit( child, generator );
});
generator.elementDepth -= 1; node.children.forEach( child => {
visit( child, generator );
});
generator.pop(); generator.elementDepth -= 1;
}
}; generator.pop();
}

@ -3,224 +3,222 @@ import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js'; import getBuilders from '../utils/getBuilders.js';
import visit from '../visit.js'; import visit from '../visit.js';
export default { export default function visitEachBlock ( generator, node ) {
enter ( generator, node ) { const name = generator.getUniqueName( `each_block` );
const name = generator.getUniqueName( `each_block` ); const renderer = generator.getUniqueName( `render_each_block` );
const renderer = generator.getUniqueName( `render_each_block` ); const elseName = generator.getUniqueName( `${name}_else` );
const elseName = generator.getUniqueName( `${name}_else` ); const renderElse = generator.getUniqueName( `${renderer}_else` );
const renderElse = generator.getUniqueName( `${renderer}_else` ); const i = generator.current.getUniqueName( `i` );
const i = generator.current.getUniqueName( `i` ); const params = generator.current.params.join( ', ' );
const params = generator.current.params.join( ', ' );
const listName = generator.current.getUniqueName( `${name}_value` ); const listName = generator.current.getUniqueName( `${name}_value` );
const isToplevel = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
const { dependencies, snippet } = generator.contextualise( node.expression ); const { dependencies, snippet } = generator.contextualise( node.expression );
const anchor = generator.current.getUniqueName( `${name}_anchor` ); const anchor = generator.current.getUniqueName( `${name}_anchor` );
generator.createAnchor( anchor ); generator.createAnchor( anchor );
const localVars = {}; const localVars = {};
localVars.iteration = generator.current.getUniqueName( `${name}_iteration` ); localVars.iteration = generator.current.getUniqueName( `${name}_iteration` );
localVars.iterations = generator.current.getUniqueName( `${name}_iterations` ); localVars.iterations = generator.current.getUniqueName( `${name}_iterations` );
localVars._iterations = generator.current.getUniqueName( `_${name}_iterations` ); localVars._iterations = generator.current.getUniqueName( `_${name}_iterations` );
localVars.lookup = generator.current.getUniqueName( `${name}_lookup` ); localVars.lookup = generator.current.getUniqueName( `${name}_lookup` );
localVars._lookup = generator.current.getUniqueName( `_${name}_lookup` ); localVars._lookup = generator.current.getUniqueName( `_${name}_lookup` );
generator.current.builders.init.addLine( `var ${listName} = ${snippet};` ); generator.current.builders.init.addLine( `var ${listName} = ${snippet};` );
generator.current.builders.init.addLine( `var ${localVars.iterations} = [];` ); generator.current.builders.init.addLine( `var ${localVars.iterations} = [];` );
if ( node.key ) generator.current.builders.init.addLine( `var ${localVars.lookup} = Object.create( null );` ); if ( node.key ) generator.current.builders.init.addLine( `var ${localVars.lookup} = Object.create( null );` );
if ( node.else ) generator.current.builders.init.addLine( `var ${elseName} = null;` ); if ( node.else ) generator.current.builders.init.addLine( `var ${elseName} = null;` );
const initialRender = new CodeBuilder(); const initialRender = new CodeBuilder();
if ( node.key ) { if ( node.key ) {
localVars.fragment = generator.current.getUniqueName( 'fragment' ); localVars.fragment = generator.current.getUniqueName( 'fragment' );
localVars.value = generator.current.getUniqueName( 'value' ); localVars.value = generator.current.getUniqueName( 'value' );
localVars.key = generator.current.getUniqueName( 'key' ); localVars.key = generator.current.getUniqueName( 'key' );
initialRender.addBlock( deindent` initialRender.addBlock( deindent`
var ${localVars.key} = ${listName}[${i}].${node.key}; var ${localVars.key} = ${listName}[${i}].${node.key};
${localVars.iterations}[${i}] = ${localVars.lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } ); ${localVars.iterations}[${i}] = ${localVars.lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
` ); ` );
} else { } else {
initialRender.addLine( initialRender.addLine(
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );` `${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );`
); );
} }
if ( !isToplevel ) { if ( !isToplevel ) {
initialRender.addLine( initialRender.addLine(
`${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );` `${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );`
); );
}
generator.current.builders.init.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
${initialRender}
} }
` );
if ( node.else ) {
generator.current.builders.init.addBlock( deindent` generator.current.builders.init.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { if ( !${listName}.length ) {
${initialRender} ${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${!isToplevel ? `${elseName}.mount( ${anchor}.parentNode, ${anchor} );` : ''}
} }
` ); ` );
}
if ( isToplevel ) {
generator.current.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
}
` );
if ( node.else ) { if ( node.else ) {
generator.current.builders.init.addBlock( deindent`
if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${!isToplevel ? `${elseName}.mount( ${anchor}.parentNode, ${anchor} );` : ''}
}
` );
}
if ( isToplevel ) {
generator.current.builders.mount.addBlock( deindent` generator.current.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) { if ( ${elseName} ) {
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); ${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} }
` ); ` );
if ( node.else ) {
generator.current.builders.mount.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
}
} }
}
if ( node.key ) { if ( node.key ) {
generator.current.builders.update.addBlock( deindent` generator.current.builders.update.addBlock( deindent`
var ${listName} = ${snippet}; var ${listName} = ${snippet};
var ${localVars._iterations} = []; var ${localVars._iterations} = [];
var ${localVars._lookup} = Object.create( null ); var ${localVars._lookup} = Object.create( null );
var ${localVars.fragment} = document.createDocumentFragment(); var ${localVars.fragment} = document.createDocumentFragment();
// create new iterations as necessary // create new iterations as necessary
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) { for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
var ${localVars.value} = ${listName}[${i}]; var ${localVars.value} = ${listName}[${i}];
var ${localVars.key} = ${localVars.value}.${node.key}; 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}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
}
if ( ${localVars.lookup}[ ${localVars.key} ] ) { ${localVars._iterations}[${i}].mount( ${localVars.fragment}, null );
${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}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
}
${localVars._iterations}[${i}].mount( ${localVars.fragment}, null ); // 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}.teardown( true );
} }
}
// remove old iterations ${anchor}.parentNode.insertBefore( ${localVars.fragment}, ${anchor} );
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
var ${localVars.iteration} = ${localVars.iterations}[${i}];
if ( !${localVars._lookup}[ ${localVars.iteration}.key ] ) {
${localVars.iteration}.teardown( true );
}
}
${anchor}.parentNode.insertBefore( ${localVars.fragment}, ${anchor} ); ${localVars.iterations} = ${localVars._iterations};
${localVars.lookup} = ${localVars._lookup};
` );
} else {
generator.current.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
${localVars.iterations} = ${localVars._iterations}; for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
${localVars.lookup} = ${localVars._lookup}; if ( !${localVars.iterations}[${i}] ) {
` ); ${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );
} else { ${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
generator.current.builders.update.addBlock( deindent` } else {
var ${listName} = ${snippet}; ${localVars.iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
if ( !${localVars.iterations}[${i}] ) {
${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${localVars.iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
}
} }
}
teardownEach( ${localVars.iterations}, true, ${listName}.length ); teardownEach( ${localVars.iterations}, true, ${listName}.length );
${localVars.iterations}.length = ${listName}.length; ${localVars.iterations}.length = ${listName}.length;
` ); ` );
} }
if ( node.else ) { if ( node.else ) {
generator.current.builders.update.addBlock( deindent` generator.current.builders.update.addBlock( deindent`
if ( !${listName}.length && ${elseName} ) { if ( !${listName}.length && ${elseName} ) {
${elseName}.update( changed, ${params} ); ${elseName}.update( changed, ${params} );
} else if ( !${listName}.length ) { } else if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} ); ${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${elseName}.mount( ${anchor}.parentNode, ${anchor} ); ${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${elseName} ) { } else if ( ${elseName} ) {
${elseName}.teardown( true ); ${elseName}.teardown( true );
} }
` ); ` );
} }
generator.current.builders.teardown.addBlock( generator.current.builders.teardown.addBlock(
`${generator.helper( 'teardownEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` ); `${generator.helper( 'teardownEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` );
if ( node.else ) { if ( node.else ) {
generator.current.builders.teardown.addBlock( deindent` generator.current.builders.teardown.addBlock( deindent`
if ( ${elseName} ) { if ( ${elseName} ) {
${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} ); ${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} );
} }
` ); ` );
} }
if ( node.else ) { if ( node.else ) {
generator.generateBlock( node.else, renderElse, 'block' ); generator.generateBlock( node.else, renderElse, 'block' );
} }
const indexNames = new Map( generator.current.indexNames ); const indexNames = new Map( generator.current.indexNames );
const indexName = node.index || generator.current.getUniqueName( `${node.context}_index` ); const indexName = node.index || generator.current.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName ); indexNames.set( node.context, indexName );
const listNames = new Map( generator.current.listNames ); const listNames = new Map( generator.current.listNames );
listNames.set( node.context, listName ); listNames.set( node.context, listName );
const context = generator.getUniqueName( node.context ); const context = generator.getUniqueName( node.context );
const contexts = new Map( generator.current.contexts ); const contexts = new Map( generator.current.contexts );
contexts.set( node.context, context ); contexts.set( node.context, context );
const indexes = new Map( generator.current.indexes ); const indexes = new Map( generator.current.indexes );
if ( node.index ) indexes.set( indexName, node.context ); if ( node.index ) indexes.set( indexName, node.context );
const contextDependencies = new Map( generator.current.contextDependencies ); const contextDependencies = new Map( generator.current.contextDependencies );
contextDependencies.set( node.context, dependencies ); contextDependencies.set( node.context, dependencies );
const blockParams = generator.current.params.concat( listName, context, indexName ); const blockParams = generator.current.params.concat( listName, context, indexName );
const getUniqueName = generator.getUniqueNameMaker( blockParams ); const getUniqueName = generator.getUniqueNameMaker( blockParams );
generator.push({ generator.push({
type: 'block', type: 'block',
name: renderer, name: renderer,
target: 'target', target: 'target',
expression: node.expression, expression: node.expression,
context: node.context, context: node.context,
key: node.key, key: node.key,
localElementDepth: 0, localElementDepth: 0,
component: getUniqueName( 'component' ), component: getUniqueName( 'component' ),
contextDependencies, contextDependencies,
contexts, contexts,
indexes, indexes,
indexNames, indexNames,
listNames, listNames,
params: blockParams, params: blockParams,
builders: getBuilders(), builders: getBuilders(),
getUniqueName, getUniqueName,
}); });
node.children.forEach( child => { node.children.forEach( child => {
visit( child, generator ); visit( child, generator );
}); });
generator.addRenderer( generator.current ); generator.addRenderer( generator.current );
generator.pop(); generator.pop();
} }
};

@ -2,131 +2,122 @@ import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import visit from '../visit.js'; import visit from '../visit.js';
import addElementAttributes from './attributes/addElementAttributes.js'; import addElementAttributes from './attributes/addElementAttributes.js';
import Component from './Component.js'; import visitComponent from './Component.js';
import Window from './meta/Window.js'; import visitWindow from './meta/Window.js';
const meta = { const meta = {
':Window': Window ':Window': visitWindow
}; };
export default { export default function visitElement ( generator, node ) {
enter ( generator, node ) { if ( node.name in meta ) {
if ( node.name in meta ) { return meta[ node.name ]( generator, node );
return meta[ node.name ].enter( generator, node ); }
}
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) { if ( generator.components.has( node.name ) || node.name === ':Self' ) {
return Component.enter( generator, node ); return visitComponent( generator, node );
} }
const name = generator.current.getUniqueName( node.name ); const name = generator.current.getUniqueName( node.name );
const local = { const local = {
name, name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace, namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
isComponent: false, isComponent: false,
allUsedContexts: [], allUsedContexts: [],
init: new CodeBuilder(), init: new CodeBuilder(),
update: new CodeBuilder(), update: new CodeBuilder(),
teardown: new CodeBuilder() teardown: new CodeBuilder()
}; };
const isToplevel = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
addElementAttributes( generator, node, local ); addElementAttributes( generator, node, local );
if ( local.allUsedContexts.length ) { if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => { const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`; if ( contextName === 'root' ) return `root: root`;
const listName = generator.current.listNames.get( contextName ); const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName ); const indexName = generator.current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`; return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' ); }).join( ',\n' );
const updates = local.allUsedContexts.map( contextName => { const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`; if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
const listName = generator.current.listNames.get( contextName ); const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName ); const indexName = generator.current.indexNames.get( contextName );
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`; return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' ); }).join( '\n' );
local.init.addBlock( deindent` local.init.addBlock( deindent`
${name}.__svelte = { ${name}.__svelte = {
${initialProps} ${initialProps}
}; };
` ); ` );
local.update.addBlock( updates ); local.update.addBlock( updates );
} }
let render; let render;
if ( local.namespace ) { if ( local.namespace ) {
if ( local.namespace === 'http://www.w3.org/2000/svg' ) { if ( local.namespace === 'http://www.w3.org/2000/svg' ) {
render = `var ${name} = ${generator.helper( 'createSvgElement' )}( '${node.name}' )`; render = `var ${name} = ${generator.helper( 'createSvgElement' )}( '${node.name}' )`;
} else {
render = `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );`;
}
} else { } else {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`; render = `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );`;
}
if ( generator.cssId && !generator.elementDepth ) {
render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`;
}
local.init.addLineAtStart( render );
if ( isToplevel ) {
generator.current.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
} }
} else {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`;
}
// special case bound <option> without a value attribute if ( generator.cssId && !generator.elementDepth ) {
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`;
const statement = `${name}.__value = ${name}.textContent;`; }
local.update.addLine( statement );
node.initialUpdate = statement;
}
generator.current.builders.init.addBlock( local.init ); local.init.addLineAtStart( render );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update ); if ( isToplevel ) {
if ( !local.teardown.isEmpty() ) generator.current.builders.teardown.addBlock( local.teardown ); generator.current.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
}
generator.createMountStatement( name ); // special case bound <option> without a value attribute
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
const statement = `${name}.__value = ${name}.textContent;`;
local.update.addLine( statement );
node.initialUpdate = statement;
}
generator.push({ generator.current.builders.init.addBlock( local.init );
type: 'element', if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
namespace: local.namespace, if ( !local.teardown.isEmpty() ) generator.current.builders.teardown.addBlock( local.teardown );
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1,
key: null
});
generator.elementDepth += 1; generator.createMountStatement( name );
node.children.forEach( child => { generator.push({
visit( child, generator ); type: 'element',
}); namespace: local.namespace,
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1,
key: null
});
generator.elementDepth -= 1; generator.elementDepth += 1;
if ( node.name in meta ) { node.children.forEach( child => {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node ); visit( child, generator );
return; });
}
if ( node.initialUpdate ) { generator.elementDepth -= 1;
generator.current.builders.init.addBlock( node.initialUpdate );
}
generator.pop(); if ( node.initialUpdate ) {
generator.current.builders.init.addBlock( node.initialUpdate );
} }
};
generator.pop();
}

@ -30,52 +30,50 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) {
return conditionsAndBlocks; return conditionsAndBlocks;
} }
export default { export default function visitIfBlock ( generator, node ) {
enter ( generator, node ) { const params = generator.current.params.join( ', ' );
const params = generator.current.params.join( ', ' ); const name = generator.getUniqueName( `if_block` );
const name = generator.getUniqueName( `if_block` ); const getBlock = generator.current.getUniqueName( `get_block` );
const getBlock = generator.current.getUniqueName( `get_block` ); const currentBlock = generator.current.getUniqueName( `current_block` );
const currentBlock = generator.current.getUniqueName( `current_block` ); const _currentBlock = generator.current.getUniqueName( `_current_block` );
const _currentBlock = generator.current.getUniqueName( `_current_block` );
const isToplevel = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `render_if_block` ) ); const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `render_if_block` ) );
const anchor = `${name}_anchor`; const anchor = `${name}_anchor`;
generator.createAnchor( anchor ); generator.createAnchor( anchor );
generator.current.builders.init.addBlock( deindent` generator.current.builders.init.addBlock( deindent`
function ${getBlock} ( ${params} ) { function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => { ${conditionsAndBlocks.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`; return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )} } ).join( '\n' )}
} }
var ${currentBlock} = ${getBlock}( ${params} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
` );
var ${currentBlock} = ${getBlock}( ${params} ); const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} ); if ( isToplevel ) {
` ); generator.current.builders.mount.addLine( mountStatement );
} else {
generator.current.builders.init.addLine( mountStatement );
}
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`; generator.current.builders.update.addBlock( deindent`
if ( isToplevel ) { var ${_currentBlock} = ${currentBlock};
generator.current.builders.mount.addLine( mountStatement ); ${currentBlock} = ${getBlock}( ${params} );
if ( ${_currentBlock} === ${currentBlock} && ${name}) {
${name}.update( changed, ${params} );
} else { } else {
generator.current.builders.init.addLine( mountStatement ); if ( ${name} ) ${name}.teardown( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
} }
` );
generator.current.builders.update.addBlock( deindent` generator.current.builders.teardown.addLine(
var ${_currentBlock} = ${currentBlock}; `if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );`
${currentBlock} = ${getBlock}( ${params} ); );
if ( ${_currentBlock} === ${currentBlock} && ${name}) { }
${name}.update( changed, ${params} );
} else {
if ( ${name} ) ${name}.teardown( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
generator.current.builders.teardown.addLine(
`if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );`
);
}
};

@ -1,22 +1,20 @@
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js'; import findBlock from '../utils/findBlock.js';
export default { export default function visitMustacheTag ( generator, node ) {
enter ( generator, node ) { const name = generator.current.getUniqueName( 'text' );
const name = generator.current.getUniqueName( 'text' );
const { snippet } = generator.contextualise( node.expression ); const { snippet } = generator.contextualise( node.expression );
generator.current.builders.init.addLine( `var last_${name} = ${snippet};` ); generator.current.builders.init.addLine( `var last_${name} = ${snippet};` );
generator.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true ); generator.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true );
const fragment = findBlock( generator.current ); const fragment = findBlock( generator.current );
if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' ); if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' );
generator.current.builders.update.addBlock( deindent` generator.current.builders.update.addBlock( deindent`
if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) { if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) {
${name}.data = last_${name} = ${fragment.tmp}; ${name}.data = last_${name} = ${fragment.tmp};
} }
` ); ` );
} }
};

@ -1,43 +1,41 @@
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js'; import findBlock from '../utils/findBlock.js';
export default { export default function visitRawMustacheTag ( generator, node ) {
enter ( generator, node ) { const name = generator.current.getUniqueName( 'raw' );
const name = generator.current.getUniqueName( 'raw' );
const { snippet } = generator.contextualise( node.expression ); const { snippet } = generator.contextualise( node.expression );
// we would have used comments here, but the `insertAdjacentHTML` api only // we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s. // exists for `Element`s.
const before = `${name}_before`; const before = `${name}_before`;
generator.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); generator.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, true );
const after = `${name}_after`; const after = `${name}_after`;
generator.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); generator.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, true );
const isToplevel = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
generator.current.builders.init.addLine( `var last_${name} = ${snippet};` ); generator.current.builders.init.addLine( `var last_${name} = ${snippet};` );
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`; const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`;
const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`; const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`;
if ( isToplevel ) { if ( isToplevel ) {
generator.current.builders.mount.addLine( mountStatement ); generator.current.builders.mount.addLine( mountStatement );
} else { } else {
generator.current.builders.init.addLine( mountStatement ); generator.current.builders.init.addLine( mountStatement );
} }
const fragment = findBlock( generator.current ); const fragment = findBlock( generator.current );
if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' ); if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' );
generator.current.builders.update.addBlock( deindent` generator.current.builders.update.addBlock( deindent`
if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) { if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) {
last_${name} = ${fragment.tmp}; last_${name} = ${fragment.tmp};
${detachStatement} ${detachStatement}
${mountStatement} ${mountStatement}
} }
` ); ` );
generator.current.builders.detachRaw.addBlock( detachStatement ); generator.current.builders.detachRaw.addBlock( detachStatement );
} }
};

@ -1,10 +1,8 @@
export default { export default function visitText ( generator, node ) {
enter ( generator, node ) { if ( generator.current.namespace && !/\S/.test( node.data ) ) {
if ( generator.current.namespace && !/\S/.test( node.data ) ) { return;
return;
}
const name = generator.current.getUniqueName( `text` );
generator.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, false );
} }
};
const name = generator.current.getUniqueName( `text` );
generator.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, false );
}

@ -1,14 +1,12 @@
export default { export default function visitYieldTag ( generator ) {
enter ( generator ) { const anchor = `yield_anchor`;
const anchor = `yield_anchor`; generator.createAnchor( anchor );
generator.createAnchor( anchor );
generator.current.builders.mount.addLine( generator.current.builders.mount.addLine(
`${generator.current.component}._yield && ${generator.current.component}._yield.mount( ${generator.current.target}, ${anchor} );` `${generator.current.component}._yield && ${generator.current.component}._yield.mount( ${generator.current.target}, ${anchor} );`
); );
generator.current.builders.teardown.addLine( generator.current.builders.teardown.addLine(
`${generator.current.component}._yield && ${generator.current.component}._yield.teardown( detach );` `${generator.current.component}._yield && ${generator.current.component}._yield.teardown( detach );`
); );
} }
};

@ -11,74 +11,72 @@ const associatedEvents = {
scrollY: 'scroll' scrollY: 'scroll'
}; };
export default { export default function visitWindow ( generator, node ) {
enter ( generator, node ) { const events = {};
const events = {};
node.attributes.forEach( attribute => {
node.attributes.forEach( attribute => { if ( attribute.type === 'EventHandler' ) {
if ( attribute.type === 'EventHandler' ) { // TODO verify that it's a valid callee (i.e. built-in or declared method)
// TODO verify that it's a valid callee (i.e. built-in or declared method) generator.addSourcemapLocations( attribute.expression );
generator.addSourcemapLocations( attribute.expression );
const flattened = flattenReference( attribute.expression.callee );
const flattened = flattenReference( attribute.expression.callee ); if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
if ( flattened.name !== 'event' && flattened.name !== 'this' ) { // allow event.stopPropagation(), this.select() etc
// allow event.stopPropagation(), this.select() etc generator.code.prependRight( attribute.expression.start, 'component.' );
generator.code.prependRight( attribute.expression.start, 'component.' );
}
const handlerName = generator.current.getUniqueName( `onwindow${attribute.name}` );
generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) {
[${attribute.expression.start}-${attribute.expression.end}];
};
window.addEventListener( '${attribute.name}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
window.removeEventListener( '${attribute.name}', ${handlerName} );
` );
} }
if ( attribute.type === 'Binding' ) { const handlerName = generator.current.getUniqueName( `onwindow${attribute.name}` );
const associatedEvent = associatedEvents[ attribute.name ];
if ( !associatedEvent ) {
throw new Error( `Cannot bind to ${attribute.name} on <:Window>` );
}
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
if ( !events[ associatedEvent ] ) events[ associatedEvent ] = [];
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
// add initial value
generator.builders.metaBindings.addLine(
`this._state.${attribute.value.name} = window.${attribute.name};`
);
}
});
Object.keys( events ).forEach( event => {
const handlerName = generator.current.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' );
generator.current.builders.init.addBlock( deindent` generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) { var ${handlerName} = function ( event ) {
component.set({ [${attribute.expression.start}-${attribute.expression.end}];
${props}
});
}; };
window.addEventListener( '${event}', ${handlerName} ); window.addEventListener( '${attribute.name}', ${handlerName} );
` ); ` );
generator.current.builders.teardown.addBlock( deindent` generator.current.builders.teardown.addBlock( deindent`
window.removeEventListener( '${event}', ${handlerName} ); window.removeEventListener( '${attribute.name}', ${handlerName} );
` ); ` );
}); }
}
}; if ( attribute.type === 'Binding' ) {
const associatedEvent = associatedEvents[ attribute.name ];
if ( !associatedEvent ) {
throw new Error( `Cannot bind to ${attribute.name} on <:Window>` );
}
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
if ( !events[ associatedEvent ] ) events[ associatedEvent ] = [];
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
// add initial value
generator.builders.metaBindings.addLine(
`this._state.${attribute.value.name} = window.${attribute.name};`
);
}
});
Object.keys( events ).forEach( event => {
const handlerName = generator.current.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' );
generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) {
component.set({
${props}
});
};
window.addEventListener( '${event}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
window.removeEventListener( '${event}', ${handlerName} );
` );
});
}
Loading…
Cancel
Save