Merge pull request #183 from sveltejs/builder

CodeBuilder
pull/198/head
Rich Harris 8 years ago committed by GitHub
commit 212a3560ee

@ -1,5 +1,6 @@
import MagicString, { Bundle } from 'magic-string';
import { walk } from 'estree-walker';
import CodeBuilder from '../utils/CodeBuilder.js';
import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js';
import counter from './utils/counter.js';
@ -18,31 +19,32 @@ export default function generate ( parsed, source, options, names ) {
addElement ( name, renderStatement, needsIdentifier = false ) {
const isToplevel = generator.current.localElementDepth === 0;
if ( needsIdentifier || isToplevel ) {
generator.current.initStatements.push( deindent`
var ${name} = ${renderStatement};
` );
generator.current.builders.init.addLine(
`var ${name} = ${renderStatement};`
);
generator.createMountStatement( name );
} else {
generator.current.initStatements.push( deindent`
${generator.current.target}.appendChild( ${renderStatement} );
` );
generator.current.builders.init.addLine(
`${generator.current.target}.appendChild( ${renderStatement} );`
);
}
if ( isToplevel ) {
generator.current.detachStatements.push( deindent`
${name}.parentNode.removeChild( ${name} );
` );
generator.current.builders.detach.addLine(
`${name}.parentNode.removeChild( ${name} );`
);
}
},
createMountStatement ( name ) {
if ( generator.current.target === 'target' ) {
generator.current.mountStatements.push( deindent`
target.insertBefore( ${name}, anchor );
` );
generator.current.builders.mount.addLine(
`target.insertBefore( ${name}, anchor );`
);
} else {
generator.current.initStatements.push( deindent`
${generator.current.target}.appendChild( ${name} );
` );
generator.current.builders.init.addLine(
`${generator.current.target}.appendChild( ${name} );` );
}
},
@ -58,13 +60,7 @@ export default function generate ( parsed, source, options, names ) {
name,
target: 'target',
localElementDepth: 0,
initStatements: [],
mountStatements: [],
updateStatements: [],
detachStatements: [],
teardownStatements: [],
builders: generator.getBuilders(),
getUniqueName: generator.getUniqueNameMaker()
});
// walk the children here
@ -77,37 +73,36 @@ export default function generate ( parsed, source, options, names ) {
addRenderer ( fragment ) {
if ( fragment.autofocus ) {
fragment.initStatements.push( `${fragment.autofocus}.focus();` );
fragment.builders.init.addLine( `${fragment.autofocus}.focus();` );
}
const detachStatements = fragment.detachStatements.join( '\n\n' );
const teardownStatements = fragment.teardownStatements.join( '\n\n' );
const detachBlock = deindent`
if ( detach ) {
${detachStatements}
}
`;
// minor hack we need to ensure that any {{{triples}}} are detached
// first, so we append normal detach statements to detachRaw
fragment.builders.detachRaw.addBlock( fragment.builders.detach );
const teardownBlock = deindent`
${teardownStatements}${detachStatements ? `\n\n${detachBlock}` : ``}
`;
if ( !fragment.builders.detachRaw.isEmpty() ) {
fragment.builders.teardown.addBlock( deindent`
if ( detach ) {
${fragment.builders.detachRaw}
}
` );
}
renderers.push( deindent`
function ${fragment.name} ( ${fragment.params}, component ) {
${fragment.initStatements.join( '\n\n' )}
${fragment.builders.init}
return {
mount: function ( target, anchor ) {
${fragment.mountStatements.join( '\n\n' )}
${fragment.builders.mount}
},
update: function ( changed, ${fragment.params} ) {
${fragment.updateStatements.join( '\n\n' )}
${fragment.builders.update}
},
teardown: function ( detach ) {
${teardownBlock}
${fragment.builders.teardown}
}
};
}
@ -174,6 +169,17 @@ export default function generate ( parsed, source, options, names ) {
events: {},
getBuilders () {
return {
init: new CodeBuilder(),
mount: new CodeBuilder(),
update: new CodeBuilder(),
detach: new CodeBuilder(),
detachRaw: new CodeBuilder(),
teardown: new CodeBuilder()
};
},
getUniqueName: counter( names ),
getUniqueNameMaker () {
@ -293,12 +299,6 @@ export default function generate ( parsed, source, options, names ) {
elementDepth: 0,
localElementDepth: 0,
initStatements: [],
mountStatements: [],
updateStatements: [],
detachStatements: [],
teardownStatements: [],
contexts: {},
indexes: {},
@ -306,6 +306,7 @@ export default function generate ( parsed, source, options, names ) {
indexNames: {},
listNames: {},
builders: generator.getBuilders(),
getUniqueName: generator.getUniqueNameMaker()
});
@ -313,15 +314,17 @@ export default function generate ( parsed, source, options, names ) {
generator.addRenderer( generator.pop() );
const topLevelStatements = [];
const builders = {
main: new CodeBuilder(),
init: new CodeBuilder(),
set: new CodeBuilder()
};
const setStatements = [ deindent`
var oldState = state;
state = Object.assign( {}, oldState, newState );
` ];
builders.set.addLine( 'var oldState = state;' );
builders.set.addLine( 'state = Object.assign( {}, oldState, newState );' );
if ( templateProperties.computed ) {
const statements = [];
const builder = new CodeBuilder();
const dependencies = new Map();
templateProperties.computed.properties.forEach( prop => {
@ -343,7 +346,7 @@ export default function generate ( parsed, source, options, names ) {
const deps = dependencies.get( key );
deps.forEach( visit );
statements.push( deindent`
builder.addBlock( deindent`
if ( ${deps.map( dep => `( '${dep}' in newState && typeof state.${dep} === 'object' || state.${dep} !== oldState.${dep} )` ).join( ' || ' )} ) {
state.${key} = newState.${key} = template.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );
}
@ -352,57 +355,49 @@ export default function generate ( parsed, source, options, names ) {
templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) );
topLevelStatements.push( deindent`
builders.main.addBlock( deindent`
function applyComputations ( state, newState, oldState ) {
${statements.join( '\n\n' )}
${builder}
}
` );
setStatements.push( `applyComputations( state, newState, oldState )` );
builders.set.addLine( `applyComputations( state, newState, oldState )` );
}
setStatements.push( deindent`
builders.set.addBlock( deindent`
dispatchObservers( observers.immediate, newState, oldState );
if ( mainFragment ) mainFragment.update( newState, state );
dispatchObservers( observers.deferred, newState, oldState );
` );
const importBlock = imports
.map( ( declaration, i ) => {
if ( format === 'es' ) {
return source.slice( declaration.start, declaration.end );
}
const defaultImport = declaration.specifiers.find( x => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
const namespaceImport = declaration.specifiers.find( x => x.type === 'ImportNamespaceSpecifier' );
const namedImports = declaration.specifiers.filter( x => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later
imports.forEach( ( declaration, i ) => {
if ( format === 'es' ) {
builders.main.addLine( source.slice( declaration.start, declaration.end ) );
return;
}
const statements = namedImports.map( specifier => {
return `var ${specifier.local.name} = ${name}.${specifier.imported.name}`;
});
const defaultImport = declaration.specifiers.find( x => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
const namespaceImport = declaration.specifiers.find( x => x.type === 'ImportNamespaceSpecifier' );
const namedImports = declaration.specifiers.filter( x => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
if ( defaultImport ) {
statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` );
}
const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later
return statements.join( '\n' );
})
.filter( Boolean )
.join( '\n' );
namedImports.forEach( specifier => {
builders.main.addLine( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` );
});
if ( parsed.js ) {
if ( imports.length ) {
topLevelStatements.push( importBlock );
if ( defaultImport ) {
builders.main.addLine( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` );
}
});
topLevelStatements.push( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
if ( parsed.js ) {
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
}
if ( parsed.css && options.css !== false ) {
topLevelStatements.push( deindent`
builders.main.addBlock( deindent`
let addedCss = false;
function addCss () {
var style = document.createElement( 'style' );
@ -414,33 +409,30 @@ export default function generate ( parsed, source, options, names ) {
` );
}
topLevelStatements.push( ...renderers.reverse() );
let i = renderers.length;
while ( i-- ) builders.main.addBlock( renderers[i] );
const constructorName = options.name || 'SvelteComponent';
const initStatements = [];
if ( parsed.css && options.css !== false ) {
initStatements.push( `if ( !addedCss ) addCss();` );
builders.init.addLine( `if ( !addedCss ) addCss();` );
}
if ( generator.hasComponents ) {
initStatements.push( deindent`
this.__renderHooks = [];
` );
builders.init.addLine( `this.__renderHooks = [];` );
}
if ( generator.hasComplexBindings ) {
initStatements.push( deindent`
builders.init.addBlock( deindent`
this.__bindings = [];
var mainFragment = renderMainFragment( state, this );
if ( options.target ) this._mount( options.target );
while ( this.__bindings.length ) this.__bindings.pop()();
` );
setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` );
builders.set.addLine( `while ( this.__bindings.length ) this.__bindings.pop()();` );
} else {
initStatements.push( deindent`
builders.init.addBlock( deindent`
var mainFragment = renderMainFragment( state, this );
if ( options.target ) this._mount( options.target );
` );
@ -454,12 +446,12 @@ export default function generate ( parsed, source, options, names ) {
}
`;
initStatements.push( statement );
setStatements.push( statement );
builders.init.addBlock( statement );
builders.set.addBlock( statement );
}
if ( templateProperties.onrender ) {
initStatements.push( deindent`
builders.init.addBlock( deindent`
if ( options.root ) {
options.root.__renderHooks.push({ fn: template.onrender, context: this });
} else {
@ -470,7 +462,7 @@ export default function generate ( parsed, source, options, names ) {
const initialState = templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`;
topLevelStatements.push( deindent`
builders.main.addBlock( deindent`
function ${constructorName} ( options ) {
options = options || {};
@ -521,7 +513,7 @@ export default function generate ( parsed, source, options, names ) {
};
this.set = function set ( newState ) {
${setStatements.join( '\n\n' )}
${builders.set}
};
this._mount = function mount ( target, anchor ) {
@ -571,15 +563,15 @@ export default function generate ( parsed, source, options, names ) {
this.root = options.root;
this.yield = options.yield;
${initStatements.join( '\n\n' )}
${builders.init}
}
` );
if ( templateProperties.methods ) {
topLevelStatements.push( `${constructorName}.prototype = template.methods;` );
builders.main.addBlock( `${constructorName}.prototype = template.methods;` );
}
const result = topLevelStatements.join( '\n\n' );
const result = builders.main.toString();
const pattern = /\[✂(\d+)-(\d+)$/;

@ -1,4 +1,5 @@
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import addComponentAttributes from './attributes/addComponentAttributes.js';
export default {
@ -13,11 +14,8 @@ export default {
allUsedContexts: new Set(),
init: [],
mount: [],
update: [],
detach: [],
teardown: []
init: new CodeBuilder(),
update: new CodeBuilder()
};
const isToplevel = generator.current.localElementDepth === 0;
@ -37,8 +35,13 @@ export default {
generator.generateBlock( node, yieldName );
generator.current.initStatements.push(`var ${name}_yieldFragment = ${yieldName}( root, component );`);
generator.current.updateStatements.push(`${name}_yieldFragment.update ( changed, root );`);
generator.current.builders.init.addLine(
`var ${name}_yieldFragment = ${yieldName}( root, component );`
);
generator.current.builders.update.addLine(
`${name}_yieldFragment.update( changed, root );`
);
componentInitProperties.push(`yield: ${name}_yieldFragment`);
}
@ -72,7 +75,7 @@ export default {
componentInitProperties.push(`data: ${name}_initialData`);
}
local.init.unshift( deindent`
local.init.addBlockAtStart( deindent`
${statements.join( '\n\n' )}
var ${name} = new template.components.${node.name}({
${componentInitProperties.join(',\n')}
@ -80,7 +83,7 @@ export default {
` );
if ( isToplevel ) {
local.mount.unshift( `${name}._mount( target, anchor );` );
generator.current.builders.mount.addLine( `${name}._mount( target, anchor );` );
}
if ( local.dynamicAttributes.length ) {
@ -96,7 +99,7 @@ export default {
return `${name}_changes.${attribute.name} = ${attribute.value};`;
});
local.update.push( deindent`
local.update.addBlock( deindent`
var ${name}_changes = {};
${updates.join( '\n' )}
@ -105,13 +108,10 @@ export default {
` );
}
local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` );
generator.current.builders.teardown.addLine( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` );
generator.current.initStatements.push( local.init.join( '\n' ) );
if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) );
if ( local.mount.length ) generator.current.mountStatements.push( local.mount.join( '\n' ) );
if ( local.detach.length ) generator.current.detachStatements.push( local.detach.join( '\n' ) );
generator.current.teardownStatements.push( local.teardown.join( '\n' ) );
generator.current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
generator.push({
namespace: local.namespace,

@ -20,7 +20,7 @@ export default {
const anchor = generator.createAnchor( name, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` );
generator.current.initStatements.push( deindent`
generator.current.builders.init.addBlock( deindent`
var ${name}_value = ${snippet};
var ${iterations} = [];
${node.else ? `var ${elseName} = null;` : ''}
@ -31,7 +31,7 @@ export default {
}
` );
if ( node.else ) {
generator.current.initStatements.push( deindent`
generator.current.builders.init.addBlock( deindent`
if ( !${name}_value.length ) {
${elseName} = ${renderElse}( ${params}, component );
${!isToplevel ? `${elseName}.mount( ${anchor}.parentNode, ${anchor} );` : ''}
@ -40,13 +40,13 @@ export default {
}
if ( isToplevel ) {
generator.current.mountStatements.push( deindent`
generator.current.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
}
` );
if ( node.else ) {
generator.current.mountStatements.push( deindent`
generator.current.builders.mount.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
@ -54,7 +54,7 @@ export default {
}
}
generator.current.updateStatements.push( deindent`
generator.current.builders.update.addBlock( deindent`
var ${name}_value = ${snippet};
for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) {
@ -74,7 +74,7 @@ export default {
` );
if ( node.else ) {
generator.current.updateStatements.push( deindent`
generator.current.builders.update.addBlock( deindent`
if ( !${name}_value.length && ${elseName} ) {
${elseName}.update( changed, ${params} );
} else if ( !${name}_value.length ) {
@ -86,14 +86,14 @@ export default {
` );
}
generator.current.teardownStatements.push( deindent`
generator.current.builders.teardown.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].teardown( ${isToplevel ? 'detach' : 'false'} );
}
` );
if ( node.else ) {
generator.current.teardownStatements.push( deindent`
generator.current.builders.teardown.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} );
}
@ -136,18 +136,17 @@ export default {
listNames,
params: blockParams,
initStatements: [],
mountStatements: [],
updateStatements: [ Object.keys( contexts ).map( contextName => {
const listName = listNames[ contextName ];
const indexName = indexNames[ contextName ];
builders: generator.getBuilders(),
getUniqueName: generator.getUniqueNameMaker()
});
return `var ${contextName} = ${listName}[${indexName}];`;
}).join( '\n' ) ],
detachStatements: [],
teardownStatements: [],
Object.keys( contexts ).forEach( contextName => {
const listName = listNames[ contextName ];
const indexName = indexNames[ contextName ];
getUniqueName: generator.getUniqueNameMaker()
generator.current.builders.update.addLine(
`var ${contextName} = ${listName}[${indexName}];`
);
});
},

@ -1,3 +1,4 @@
import CodeBuilder from '../../utils/CodeBuilder.js';
import deindent from '../../utils/deindent.js';
import addElementAttributes from './attributes/addElementAttributes.js';
import Component from './Component.js';
@ -18,11 +19,8 @@ export default {
allUsedContexts: new Set(),
init: [],
mount: [],
update: [],
detach: [],
teardown: []
init: new CodeBuilder(),
update: new CodeBuilder()
};
const isToplevel = generator.current.localElementDepth === 0;
@ -50,13 +48,13 @@ export default {
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' );
local.init.push( deindent`
local.init.addBlock( deindent`
${name}.__svelte = {
${initialProps}
};
` );
local.update.push( updates );
local.update.addBlock( updates );
}
let render = local.namespace ?
@ -67,23 +65,22 @@ export default {
render += `\n${name}.setAttribute( '${generator.cssId}', '' );`;
}
local.init.unshift( render );
local.init.addLineAtStart( render );
if ( isToplevel ) {
local.detach.push( `${name}.parentNode.removeChild( ${name} );` );
generator.current.builders.detach.addLine( `${name}.parentNode.removeChild( ${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 dynamic = node.children.length > 1 || node.children[0].type !== 'Text';
// TODO do this in init for static values... have to do it in `leave`, because they don't exist yet
local.update.push( `${name}.__value = ${name}.textContent` );
local.update.addLine(
`${name}.__value = ${name}.textContent`
);
}
generator.current.initStatements.push( local.init.join( '\n' ) );
if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) );
if ( local.mount.length ) generator.current.mountStatements.push( local.mount.join( '\n' ) );
if ( local.detach.length ) generator.current.detachStatements.push( local.detach.join( '\n' ) );
generator.current.teardownStatements.push( local.teardown.join( '\n' ) );
generator.current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
generator.createMountStatement( name );

@ -39,7 +39,7 @@ export default {
const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` );
generator.current.initStatements.push( deindent`
generator.current.builders.init.addBlock( deindent`
function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
@ -52,12 +52,12 @@ export default {
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
if ( isToplevel ) {
generator.current.mountStatements.push( mountStatement );
generator.current.builders.mount.addLine( mountStatement );
} else {
generator.current.initStatements.push( mountStatement );
generator.current.builders.init.addLine( mountStatement );
}
generator.current.updateStatements.push( deindent`
generator.current.builders.update.addBlock( deindent`
var _${currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} );
if ( _${currentBlock} === ${currentBlock} && ${name}) {
@ -69,8 +69,8 @@ export default {
}
` );
generator.current.teardownStatements.push( deindent`
if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );
` );
generator.current.builders.teardown.addLine(
`if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );`
);
}
};

@ -9,7 +9,7 @@ export default {
generator.addElement( name, `document.createTextNode( ${snippet} )`, true );
generator.current.updateStatements.push( deindent`
generator.current.builders.update.addBlock( deindent`
${name}.data = ${snippet};
` );
}

@ -16,9 +16,7 @@ export default {
const isToplevel = generator.current.localElementDepth === 0;
const mountStatement = deindent`
${before}.insertAdjacentHTML( 'afterend', ${snippet} );
`;
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${snippet} );`;
const detachStatement = deindent`
while ( ${before}.nextSibling && ${before}.nextSibling !== ${after} ) {
${before}.parentNode.removeChild( ${before}.nextSibling );
@ -26,22 +24,14 @@ export default {
`;
if ( isToplevel ) {
generator.current.mountStatements.push(mountStatement);
generator.current.builders.mount.addLine( mountStatement );
} else {
generator.current.initStatements.push(mountStatement);
generator.current.builders.init.addLine( mountStatement );
}
generator.current.updateStatements.push( deindent`
${detachStatement}
${mountStatement}
` );
generator.current.builders.update.addBlock( detachStatement );
generator.current.builders.update.addBlock( mountStatement );
if ( isToplevel ) {
const { detachStatements } = generator.current;
// we need `before` and `after` to still be in the DOM when running the
// detach code, so splice in the detach code *before* detaching
// `before`/`after`.
detachStatements.splice( detachStatements.length - 2, 0, detachStatement);
}
generator.current.builders.detachRaw.addBlock( detachStatement );
}
};

@ -1,7 +1,13 @@
export default {
enter ( generator ) {
const anchor = generator.createAnchor( 'yield', 'yield' );
generator.current.mountStatements.push(`component.yield && component.yield.mount( ${generator.current.target}, ${anchor} );`);
generator.current.teardownStatements.push(`component.yield && component.yield.teardown( detach );`);
const anchor = generator.createAnchor( 'yield', 'yield' );
generator.current.builders.mount.addLine(
`component.yield && component.yield.mount( ${generator.current.target}, ${anchor} );`
);
generator.current.builders.teardown.addLine(
`component.yield && component.yield.teardown( detach );`
);
}
};

@ -104,7 +104,7 @@ export default function addComponentAttributes ( generator, node, local ) {
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
local.init.push( deindent`
local.init.addBlock( deindent`
${local.name}.on( '${attribute.name}', function ( event ) {
${handlerBody}
});
@ -118,11 +118,11 @@ export default function addComponentAttributes ( generator, node, local ) {
else if ( attribute.type === 'Ref' ) {
generator.usesRefs = true;
local.init.push( deindent`
component.refs.${attribute.name} = ${local.name};
` );
local.init.addLine(
`component.refs.${attribute.name} = ${local.name};`
);
local.teardown.push( deindent`
generator.current.builders.teardown.addLine( deindent`
if ( component.refs.${attribute.name} === ${local.name} ) component.refs.${attribute.name} = null;
` );
}

@ -17,13 +17,13 @@ export default function addElementAttributes ( generator, node, local ) {
if ( attribute.value === true ) {
// attributes without values, e.g. <textarea readonly>
if ( propertyName ) {
local.init.push( deindent`
${local.name}.${propertyName} = true;
` );
local.init.addLine(
`${local.name}.${propertyName} = true;`
);
} else {
local.init.push( deindent`
${local.name}.setAttribute( '${attribute.name}', true );
` );
local.init.addLine(
`${local.name}.setAttribute( '${attribute.name}', true );`
);
}
// special case autofocus. has to be handled in a bit of a weird way
@ -34,13 +34,13 @@ export default function addElementAttributes ( generator, node, local ) {
else if ( attribute.value.length === 0 ) {
if ( propertyName ) {
local.init.push( deindent`
${local.name}.${propertyName} = '';
` );
local.init.addLine(
`${local.name}.${propertyName} = '';`
);
} else {
local.init.push( deindent`
${local.name}.setAttribute( '${attribute.name}', '' );
` );
local.init.addLine(
`${local.name}.setAttribute( '${attribute.name}', '' );`
);
}
}
@ -58,13 +58,13 @@ export default function addElementAttributes ( generator, node, local ) {
// TODO this attribute must be static enforce at compile time
local.namespace = value.data;
} else if ( propertyName ) {
local.init.push( deindent`
${local.name}.${propertyName} = ${result};
` );
local.init.addLine(
`${local.name}.${propertyName} = ${result};`
);
} else {
local.init.push( deindent`
${local.name}.setAttribute( '${attribute.name}', ${result} );
` );
local.init.addLine(
`${local.name}.setAttribute( '${attribute.name}', ${result} );`
);
}
}
@ -78,8 +78,8 @@ export default function addElementAttributes ( generator, node, local ) {
`${local.name}.${propertyName} = ${snippet};` :
`${local.name}.setAttribute( '${attribute.name}', ${snippet} );`; // TODO use snippet both times see note below
local.init.push( updater );
local.update.push( updater );
local.init.addLine( updater );
local.update.addLine( updater );
}
}
@ -103,12 +103,12 @@ export default function addElementAttributes ( generator, node, local ) {
`${local.name}.${propertyName} = ${value};` :
`${local.name}.setAttribute( '${attribute.name}', ${value} );`;
local.init.push( updater );
local.update.push( updater );
local.init.addLine( updater );
local.update.addLine( updater );
}
if ( isBoundOptionValue ) {
( dynamic ? local.update : local.init ).push( `${local.name}.value = ${local.name}.__value` );
( dynamic ? local.update : local.init ).addLine( `${local.name}.value = ${local.name}.__value` );
}
}
@ -146,17 +146,17 @@ export default function addElementAttributes ( generator, node, local ) {
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
if ( attribute.name in generator.events ) {
local.init.push( deindent`
local.init.addBlock( deindent`
var ${handlerName} = template.events.${attribute.name}.call( component, ${local.name}, function ( event ) {
${handlerBody}
});
` );
local.teardown.push( deindent`
generator.current.builders.teardown.addLine( deindent`
${handlerName}.teardown();
` );
} else {
local.init.push( deindent`
local.init.addBlock( deindent`
function ${handlerName} ( event ) {
${handlerBody}
}
@ -164,7 +164,7 @@ export default function addElementAttributes ( generator, node, local ) {
${local.name}.addEventListener( '${attribute.name}', ${handlerName}, false );
` );
local.teardown.push( deindent`
generator.current.builders.teardown.addLine( deindent`
${local.name}.removeEventListener( '${attribute.name}', ${handlerName}, false );
` );
}
@ -177,11 +177,11 @@ export default function addElementAttributes ( generator, node, local ) {
else if ( attribute.type === 'Ref' ) {
generator.usesRefs = true;
local.init.push( deindent`
component.refs.${attribute.name} = ${local.name};
` );
local.init.addLine(
`component.refs.${attribute.name} = ${local.name};`
);
local.teardown.push( deindent`
generator.current.builders.teardown.addLine( deindent`
if ( component.refs.${attribute.name} === ${local.name} ) component.refs.${attribute.name} = null;
` );
}

@ -84,7 +84,7 @@ export default function createBinding ( generator, node, attribute, current, loc
if ( local.isComponent ) {
generator.hasComplexBindings = true;
local.init.push( deindent`
local.init.addBlock( deindent`
var ${local.name}_updating = false;
component.__bindings.push( function () {
@ -96,7 +96,7 @@ export default function createBinding ( generator, node, attribute, current, loc
});
` );
local.update.push( deindent`
local.update.addBlock( deindent`
if ( !${local.name}_updating && '${parts[0]}' in changed ) {
${local.name}.set({ ${attribute.name}: ${contextual ? attribute.value : `root.${attribute.value}`} });
}
@ -104,7 +104,7 @@ export default function createBinding ( generator, node, attribute, current, loc
} else {
const updateElement = `${local.name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`}`;
local.init.push( deindent`
local.init.addBlock( deindent`
var ${local.name}_updating = false;
function ${handler} () {
@ -117,17 +117,17 @@ export default function createBinding ( generator, node, attribute, current, loc
${updateElement};
` );
local.update.push( deindent`
if ( !${local.name}_updating ) ${updateElement};
` );
local.update.addLine(
`if ( !${local.name}_updating ) ${updateElement};`
);
local.teardown.push( deindent`
generator.current.builders.teardown.addLine( deindent`
${local.name}.removeEventListener( '${eventName}', ${handler}, false );
` );
}
if ( node.name === 'select' ) {
generator.hasComplexBindings = true;
local.init.push( `component.__bindings.push( ${handler} )` );
local.init.addLine( `component.__bindings.push( ${handler} )` );
}
}

@ -0,0 +1,67 @@
const LINE = {};
const BLOCK = {};
export default class CodeBuilder {
constructor () {
this.result = '';
this.first = null;
this.last = null;
}
addLine ( line ) {
if ( this.last === BLOCK ) {
this.result += `\n\n${line}`;
} else if ( this.last === LINE ) {
this.result += `\n${line}`;
} else {
this.result += line;
}
this.last = LINE;
if ( !this.first ) this.first = LINE;
}
addLineAtStart ( line ) {
if ( this.first === BLOCK ) {
this.result = `${line}\n\n${this.result}`;
} else if ( this.first === LINE ) {
this.result = `${line}\n${this.result}`;
} else {
this.result += line;
}
this.first = LINE;
if ( !this.last ) this.last = LINE;
}
addBlock ( block ) {
if ( this.result ) {
this.result += `\n\n${block}`;
} else {
this.result += block;
}
this.last = BLOCK;
if ( !this.first ) this.first = BLOCK;
}
addBlockAtStart ( block ) {
if ( this.result ) {
this.result = `${block}\n\n${this.result}`;
} else {
this.result += block;
}
this.first = BLOCK;
if ( !this.last ) this.last = BLOCK;
}
isEmpty () {
return this.result === '';
}
toString () {
return this.result.trim();
}
}

@ -1,5 +1,6 @@
import * as assert from 'assert';
import deindent from './deindent.js';
import CodeBuilder from './CodeBuilder.js';
describe( 'deindent', () => {
it( 'deindents a simple string', () => {
@ -34,3 +35,138 @@ describe( 'deindent', () => {
assert.equal( deindented, `before\n\tline one\n\tline two\nafter` );
});
});
describe( 'CodeBuilder', () => {
it( 'creates an empty block', () => {
const builder = new CodeBuilder();
assert.equal( builder.toString(), '' );
});
it( 'creates a block with a line', () => {
const builder = new CodeBuilder();
builder.addLine( 'var answer = 42;' );
assert.equal( builder.toString(), 'var answer = 42;' );
});
it( 'creates a block with two lines', () => {
const builder = new CodeBuilder();
builder.addLine( 'var problems = 99;' );
builder.addLine( 'var answer = 42;' );
assert.equal( builder.toString(), 'var problems = 99;\nvar answer = 42;' );
});
it( 'adds newlines around blocks', () => {
const builder = new CodeBuilder();
builder.addLine( '// line 1' );
builder.addLine( '// line 2' );
builder.addBlock( deindent`
if ( foo ) {
bar();
}
` );
builder.addLine( '// line 3' );
builder.addLine( '// line 4' );
assert.equal( builder.toString(), deindent`
// line 1
// line 2
if ( foo ) {
bar();
}
// line 3
// line 4
` );
});
it( 'nests codebuilders with correct indentation', () => {
const child = new CodeBuilder();
child.addBlock( deindent`
var obj = {
answer: 42
};
` );
const builder = new CodeBuilder();
builder.addLine( '// line 1' );
builder.addLine( '// line 2' );
builder.addBlock( deindent`
if ( foo ) {
${child}
}
` );
builder.addLine( '// line 3' );
builder.addLine( '// line 4' );
assert.equal( builder.toString(), deindent`
// line 1
// line 2
if ( foo ) {
var obj = {
answer: 42
};
}
// line 3
// line 4
` );
});
it( 'adds a line at start', () => {
const builder = new CodeBuilder();
builder.addLine( '// second' );
builder.addLineAtStart( '// first' );
assert.equal( builder.toString(), deindent`
// first
// second
` );
});
it( 'adds a line at start before a block', () => {
const builder = new CodeBuilder();
builder.addBlock( '// second' );
builder.addLineAtStart( '// first' );
assert.equal( builder.toString(), deindent`
// first
// second
` );
});
it( 'adds a block at start', () => {
const builder = new CodeBuilder();
builder.addLine( '// second' );
builder.addBlockAtStart( '// first' );
assert.equal( builder.toString(), deindent`
// first
// second
` );
});
it( 'adds a block at start before a block', () => {
const builder = new CodeBuilder();
builder.addBlock( '// second' );
builder.addBlockAtStart( '// first' );
assert.equal( builder.toString(), deindent`
// first
// second
` );
});
});

@ -2,12 +2,14 @@ export default {
data: {
name: 'world'
},
html: '<h1>Hello world!</h1>',
test ( assert, component, target ) {
component.set({ name: 'everybody' });
assert.equal( target.innerHTML, '<h1>Hello everybody!</h1>' );
assert.htmlEqual( target.innerHTML, '<h1>Hello everybody!</h1>' );
component.teardown();
assert.equal( target.innerHTML, '' );
assert.htmlEqual( target.innerHTML, '' );
}
};

@ -1,3 +1,3 @@
export default {
html: '<p>two is greater than one</p><!--#if 2 > 1-->'
html: '<p>two is greater than one</p>'
};

Loading…
Cancel
Save