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 ) {
const visitor = visitors[ node.type ];
if ( visitor.enter ) visitor.enter( generator, node );
if ( visitor.leave ) visitor.leave( generator, node );
visitor( generator, node );
}

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

@ -19,163 +19,161 @@ function stringifyProps ( props ) {
return `{ ${joined} }`;
}
export default {
enter ( generator, node ) {
const hasChildren = node.children.length > 0;
const { current } = generator;
const name = current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
export default function visitComponent ( generator, node ) {
const hasChildren = node.children.length > 0;
const { current } = generator;
const name = current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const local = {
name,
namespace: current.namespace,
isComponent: true,
const local = {
name,
namespace: current.namespace,
isComponent: true,
allUsedContexts: [],
allUsedContexts: [],
init: new CodeBuilder(),
update: new CodeBuilder()
};
init: 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 ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}._context.root = root;`;
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}._context.root = root;`;
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' );
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' );
local.init.addBlock( deindent`
${name}._context = {
${initialProps}
};
` );
local.init.addBlock( deindent`
${name}._context = {
${initialProps}
};
` );
local.update.addBlock( updates );
}
local.update.addBlock( updates );
}
const componentInitProperties = [
`target: ${!isToplevel ? current.target: 'null'}`,
`_root: ${current.component}._root || ${current.component}`
];
const componentInitProperties = [
`target: ${!isToplevel ? current.target: 'null'}`,
`_root: ${current.component}._root || ${current.component}`
];
// Component has children, put them in a separate {{yield}} block
if ( hasChildren ) {
const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` );
const params = current.params.join( ', ' );
// Component has children, put them in a separate {{yield}} block
if ( hasChildren ) {
const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` );
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(
`var ${yieldFragment} = ${yieldName}( ${params}, ${current.component} );`
);
current.builders.init.addLine(
`var ${yieldFragment} = ${yieldName}( ${params}, ${current.component} );`
);
current.builders.update.addLine(
`${yieldFragment}.update( changed, ${params} );`
);
current.builders.update.addLine(
`${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 ) {
const initialProps = local.staticAttributes
.concat( local.dynamicAttributes )
.map( attribute => `${attribute.name}: ${attribute.value}` );
if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) {
const initialProps = local.staticAttributes
.concat( local.dynamicAttributes )
.map( attribute => `${attribute.name}: ${attribute.value}` );
const initialPropString = stringifyProps( initialProps );
const initialPropString = stringifyProps( initialProps );
if ( local.bindings.length ) {
const initialData = current.getUniqueName( `${name}_initial_data` );
if ( local.bindings.length ) {
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 => {
statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` );
});
local.bindings.forEach( binding => {
statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` );
});
componentInitProperties.push( `data: ${initialData}` );
} else if ( initialProps.length ) {
componentInitProperties.push( `data: ${initialPropString}` );
}
componentInitProperties.push( `data: ${initialData}` );
} else if ( initialProps.length ) {
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`
${statements.join( '\n' )}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
` );
local.init.addBlockAtStart( deindent`
${statements.join( '\n' )}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
` );
if ( isToplevel ) {
current.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
}
if ( isToplevel ) {
current.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
}
if ( local.dynamicAttributes.length ) {
const updates = local.dynamicAttributes.map( attribute => {
if ( attribute.dependencies.length ) {
return deindent`
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};`;
});
if ( local.dynamicAttributes.length ) {
const updates = local.dynamicAttributes.map( attribute => {
if ( attribute.dependencies.length ) {
return deindent`
if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value};
`;
}
local.update.addBlock( deindent`
var ${name}_changes = {};
// 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};`;
});
${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 );
if ( !local.update.isEmpty() ) current.builders.update.addBlock( local.update );
current.builders.teardown.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` );
generator.push({
type: 'component',
namespace: local.namespace,
target: name,
parent: current,
localElementDepth: current.localElementDepth + 1,
key: null
});
current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) current.builders.update.addBlock( local.update );
generator.elementDepth += 1;
generator.push({
type: 'component',
namespace: local.namespace,
target: name,
parent: current,
localElementDepth: current.localElementDepth + 1,
key: null
});
node.children.forEach( child => {
visit( child, generator );
});
generator.elementDepth += 1;
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 visit from '../visit.js';
export default {
enter ( generator, node ) {
const name = generator.getUniqueName( `each_block` );
const renderer = generator.getUniqueName( `render_each_block` );
const elseName = generator.getUniqueName( `${name}_else` );
const renderElse = generator.getUniqueName( `${renderer}_else` );
const i = generator.current.getUniqueName( `i` );
const params = generator.current.params.join( ', ' );
export default function visitEachBlock ( generator, node ) {
const name = generator.getUniqueName( `each_block` );
const renderer = generator.getUniqueName( `render_each_block` );
const elseName = generator.getUniqueName( `${name}_else` );
const renderElse = generator.getUniqueName( `${renderer}_else` );
const i = generator.current.getUniqueName( `i` );
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` );
generator.createAnchor( anchor );
const anchor = generator.current.getUniqueName( `${name}_anchor` );
generator.createAnchor( anchor );
const localVars = {};
const localVars = {};
localVars.iteration = generator.current.getUniqueName( `${name}_iteration` );
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.iteration = generator.current.getUniqueName( `${name}_iteration` );
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` );
generator.current.builders.init.addLine( `var ${listName} = ${snippet};` );
generator.current.builders.init.addLine( `var ${localVars.iterations} = [];` );
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;` );
generator.current.builders.init.addLine( `var ${listName} = ${snippet};` );
generator.current.builders.init.addLine( `var ${localVars.iterations} = [];` );
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;` );
const initialRender = new CodeBuilder();
const initialRender = new CodeBuilder();
if ( node.key ) {
localVars.fragment = generator.current.getUniqueName( 'fragment' );
localVars.value = generator.current.getUniqueName( 'value' );
localVars.key = generator.current.getUniqueName( 'key' );
if ( node.key ) {
localVars.fragment = generator.current.getUniqueName( 'fragment' );
localVars.value = generator.current.getUniqueName( 'value' );
localVars.key = generator.current.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}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
` );
} else {
initialRender.addLine(
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );`
);
}
initialRender.addBlock( deindent`
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}` : `` } );
` );
} else {
initialRender.addLine(
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );`
);
}
if ( !isToplevel ) {
initialRender.addLine(
`${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );`
);
if ( !isToplevel ) {
initialRender.addLine(
`${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`
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
${initialRender}
if ( !${listName}.length ) {
${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 ) {
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`
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
if ( node.else ) {
generator.current.builders.mount.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
}
}
}
if ( node.key ) {
generator.current.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
var ${localVars._iterations} = [];
var ${localVars._lookup} = Object.create( null );
if ( node.key ) {
generator.current.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
var ${localVars._iterations} = [];
var ${localVars._lookup} = Object.create( null );
var ${localVars.fragment} = document.createDocumentFragment();
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};
// 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}, ${generator.current.component}${node.key ? `, ${localVars.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}` : `` } );
}
${localVars._iterations}[${i}].mount( ${localVars.fragment}, null );
}
${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
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} );
${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};
${localVars.lookup} = ${localVars._lookup};
` );
} else {
generator.current.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}, ${generator.current.component} );
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${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 ) {
generator.current.builders.update.addBlock( deindent`
if ( !${listName}.length && ${elseName} ) {
${elseName}.update( changed, ${params} );
} else if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${elseName} ) {
${elseName}.teardown( true );
}
` );
}
if ( node.else ) {
generator.current.builders.update.addBlock( deindent`
if ( !${listName}.length && ${elseName} ) {
${elseName}.update( changed, ${params} );
} else if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${elseName} ) {
${elseName}.teardown( true );
}
` );
}
generator.current.builders.teardown.addBlock(
`${generator.helper( 'teardownEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` );
generator.current.builders.teardown.addBlock(
`${generator.helper( 'teardownEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` );
if ( node.else ) {
generator.current.builders.teardown.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} );
}
` );
}
if ( node.else ) {
generator.current.builders.teardown.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} );
}
` );
}
if ( node.else ) {
generator.generateBlock( node.else, renderElse, 'block' );
}
if ( node.else ) {
generator.generateBlock( node.else, renderElse, 'block' );
}
const indexNames = new Map( generator.current.indexNames );
const indexName = node.index || generator.current.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName );
const indexNames = new Map( generator.current.indexNames );
const indexName = node.index || generator.current.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName );
const listNames = new Map( generator.current.listNames );
listNames.set( node.context, listName );
const listNames = new Map( generator.current.listNames );
listNames.set( node.context, listName );
const context = generator.getUniqueName( node.context );
const contexts = new Map( generator.current.contexts );
contexts.set( node.context, context );
const context = generator.getUniqueName( node.context );
const contexts = new Map( generator.current.contexts );
contexts.set( node.context, context );
const indexes = new Map( generator.current.indexes );
if ( node.index ) indexes.set( indexName, node.context );
const indexes = new Map( generator.current.indexes );
if ( node.index ) indexes.set( indexName, node.context );
const contextDependencies = new Map( generator.current.contextDependencies );
contextDependencies.set( node.context, dependencies );
const contextDependencies = new Map( generator.current.contextDependencies );
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({
type: 'block',
name: renderer,
target: 'target',
expression: node.expression,
context: node.context,
key: node.key,
localElementDepth: 0,
generator.push({
type: 'block',
name: renderer,
target: 'target',
expression: node.expression,
context: node.context,
key: node.key,
localElementDepth: 0,
component: getUniqueName( 'component' ),
component: getUniqueName( 'component' ),
contextDependencies,
contexts,
indexes,
contextDependencies,
contexts,
indexes,
indexNames,
listNames,
params: blockParams,
indexNames,
listNames,
params: blockParams,
builders: getBuilders(),
getUniqueName,
});
builders: getBuilders(),
getUniqueName,
});
node.children.forEach( child => {
visit( child, generator );
});
node.children.forEach( child => {
visit( child, generator );
});
generator.addRenderer( generator.current );
generator.pop();
}
};
generator.addRenderer( generator.current );
generator.pop();
}

@ -2,131 +2,122 @@ import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
import addElementAttributes from './attributes/addElementAttributes.js';
import Component from './Component.js';
import Window from './meta/Window.js';
import visitComponent from './Component.js';
import visitWindow from './meta/Window.js';
const meta = {
':Window': Window
':Window': visitWindow
};
export default {
enter ( generator, node ) {
if ( node.name in meta ) {
return meta[ node.name ].enter( generator, node );
}
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
export default function visitElement ( generator, node ) {
if ( node.name in meta ) {
return meta[ node.name ]( generator, node );
}
if ( isComponent ) {
return Component.enter( generator, node );
}
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
return visitComponent( generator, node );
}
const name = generator.current.getUniqueName( node.name );
const name = generator.current.getUniqueName( node.name );
const local = {
name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
isComponent: false,
const local = {
name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
isComponent: false,
allUsedContexts: [],
allUsedContexts: [],
init: new CodeBuilder(),
update: new CodeBuilder(),
teardown: new CodeBuilder()
};
init: new CodeBuilder(),
update: 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 ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' );
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' );
local.init.addBlock( deindent`
${name}.__svelte = {
${initialProps}
};
` );
local.init.addBlock( deindent`
${name}.__svelte = {
${initialProps}
};
` );
local.update.addBlock( updates );
}
local.update.addBlock( updates );
}
let render;
let render;
if ( local.namespace ) {
if ( local.namespace === 'http://www.w3.org/2000/svg' ) {
render = `var ${name} = ${generator.helper( 'createSvgElement' )}( '${node.name}' )`;
} else {
render = `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );`;
}
if ( local.namespace ) {
if ( local.namespace === 'http://www.w3.org/2000/svg' ) {
render = `var ${name} = ${generator.helper( 'createSvgElement' )}( '${node.name}' )`;
} else {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${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} );` );
render = `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );`;
}
} else {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.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;
}
if ( generator.cssId && !generator.elementDepth ) {
render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`;
}
generator.current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
if ( !local.teardown.isEmpty() ) generator.current.builders.teardown.addBlock( local.teardown );
local.init.addLineAtStart( render );
if ( isToplevel ) {
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({
type: 'element',
namespace: local.namespace,
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1,
key: null
});
generator.current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
if ( !local.teardown.isEmpty() ) generator.current.builders.teardown.addBlock( local.teardown );
generator.elementDepth += 1;
generator.createMountStatement( name );
node.children.forEach( child => {
visit( child, generator );
});
generator.push({
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 ) {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node );
return;
}
node.children.forEach( child => {
visit( child, generator );
});
if ( node.initialUpdate ) {
generator.current.builders.init.addBlock( node.initialUpdate );
}
generator.elementDepth -= 1;
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;
}
export default {
enter ( generator, node ) {
const params = generator.current.params.join( ', ' );
const name = generator.getUniqueName( `if_block` );
const getBlock = generator.current.getUniqueName( `get_block` );
const currentBlock = generator.current.getUniqueName( `current_block` );
const _currentBlock = generator.current.getUniqueName( `_current_block` );
export default function visitIfBlock ( generator, node ) {
const params = generator.current.params.join( ', ' );
const name = generator.getUniqueName( `if_block` );
const getBlock = generator.current.getUniqueName( `get_block` );
const currentBlock = generator.current.getUniqueName( `current_block` );
const _currentBlock = generator.current.getUniqueName( `_current_block` );
const isToplevel = generator.current.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `render_if_block` ) );
const isToplevel = generator.current.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `render_if_block` ) );
const anchor = `${name}_anchor`;
generator.createAnchor( anchor );
const anchor = `${name}_anchor`;
generator.createAnchor( anchor );
generator.current.builders.init.addBlock( deindent`
function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )}
}
generator.current.builders.init.addBlock( deindent`
function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )}
}
var ${currentBlock} = ${getBlock}( ${params} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
` );
var ${currentBlock} = ${getBlock}( ${params} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
` );
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
if ( isToplevel ) {
generator.current.builders.mount.addLine( mountStatement );
} else {
generator.current.builders.init.addLine( mountStatement );
}
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
if ( isToplevel ) {
generator.current.builders.mount.addLine( mountStatement );
generator.current.builders.update.addBlock( deindent`
var ${_currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} );
if ( ${_currentBlock} === ${currentBlock} && ${name}) {
${name}.update( changed, ${params} );
} 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`
var ${_currentBlock} = ${currentBlock};
${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'} );`
);
}
};
generator.current.builders.teardown.addLine(
`if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );`
);
}

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

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

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

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

@ -11,74 +11,72 @@ const associatedEvents = {
scrollY: 'scroll'
};
export default {
enter ( generator, node ) {
const events = {};
node.attributes.forEach( attribute => {
if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression );
const flattened = flattenReference( attribute.expression.callee );
if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
// allow event.stopPropagation(), this.select() etc
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} );
` );
export default function visitWindow ( generator, node ) {
const events = {};
node.attributes.forEach( attribute => {
if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression );
const flattened = flattenReference( attribute.expression.callee );
if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
// allow event.stopPropagation(), this.select() etc
generator.code.prependRight( attribute.expression.start, 'component.' );
}
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' );
const handlerName = generator.current.getUniqueName( `onwindow${attribute.name}` );
generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) {
component.set({
${props}
});
[${attribute.expression.start}-${attribute.expression.end}];
};
window.addEventListener( '${event}', ${handlerName} );
window.addEventListener( '${attribute.name}', ${handlerName} );
` );
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