From a1225d5adf7000e09a4f0cfe0b6ec379dade62a9 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 9 Dec 2016 09:37:50 -0500 Subject: [PATCH 1/9] add CodeBuilder utility for easier codegen --- src/utils/CodeBuilder.js | 29 ++++++++++++++ src/utils/__test__.js | 85 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/utils/CodeBuilder.js diff --git a/src/utils/CodeBuilder.js b/src/utils/CodeBuilder.js new file mode 100644 index 0000000000..269b413538 --- /dev/null +++ b/src/utils/CodeBuilder.js @@ -0,0 +1,29 @@ +const LINE = {}; +const BLOCK = {}; + +export default class CodeBuilder { + constructor () { + this.result = ''; + this.last = null; + } + + addLine ( line ) { + if ( this.last === BLOCK ) { + this.result += `\n\n${line}`; + } else { + this.result += `\n${line}`; + } + + this.last = LINE; + } + + addBlock ( block ) { + this.result += `\n\n${block}`; + + this.last = BLOCK; + } + + toString () { + return this.result.trim(); + } +} diff --git a/src/utils/__test__.js b/src/utils/__test__.js index f403af2f74..7768021a1d 100644 --- a/src/utils/__test__.js +++ b/src/utils/__test__.js @@ -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,87 @@ 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 + ` ); + }); +}); From 18baae69c1642fc797a1f4c08e68facee2eaffd6 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 9 Dec 2016 09:54:17 -0500 Subject: [PATCH 2/9] move generate/index.js over to CodeBuilder --- src/generate/index.js | 104 +++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/src/generate/index.js b/src/generate/index.js index a1690b6f8a..93dcf79bd6 100644 --- a/src/generate/index.js +++ b/src/generate/index.js @@ -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'; @@ -304,15 +305,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 => { @@ -334,7 +337,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( ', ' )} ); } @@ -343,57 +346,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' ); @@ -405,33 +400,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 ); ` ); @@ -445,12 +437,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 { @@ -461,7 +453,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 || {}; @@ -512,7 +504,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 ) { @@ -562,15 +554,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+)$/; From 08f51b23dd4c428289e609fd7eed66848268a620 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 10 Dec 2016 14:07:29 -0500 Subject: [PATCH 3/9] use builders for init statements --- src/generate/index.js | 36 ++++++++++++++++--------- src/generate/visitors/Component.js | 7 +++-- src/generate/visitors/EachBlock.js | 6 ++--- src/generate/visitors/Element.js | 2 +- src/generate/visitors/IfBlock.js | 4 +-- src/generate/visitors/RawMustacheTag.js | 6 ++--- 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/generate/index.js b/src/generate/index.js index 28d893387d..f9d28bc7bc 100644 --- a/src/generate/index.js +++ b/src/generate/index.js @@ -19,14 +19,15 @@ 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` @@ -41,9 +42,8 @@ export default function generate ( parsed, source, options, names ) { 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} );` ); } }, @@ -60,12 +60,12 @@ export default function generate ( parsed, source, options, names ) { target: 'target', localElementDepth: 0, - initStatements: [], mountStatements: [], updateStatements: [], detachStatements: [], teardownStatements: [], + builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); // walk the children here @@ -78,7 +78,7 @@ 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' ); @@ -96,7 +96,7 @@ export default function generate ( parsed, source, options, names ) { renderers.push( deindent` function ${fragment.name} ( ${fragment.params}, component ) { - ${fragment.initStatements.join( '\n\n' )} + ${fragment.builders.init} return { mount: function ( target, anchor ) { @@ -175,6 +175,16 @@ export default function generate ( parsed, source, options, names ) { events: {}, + getBuilders () { + return { + init: new CodeBuilder(), + mount: new CodeBuilder(), + update: new CodeBuilder(), + detach: new CodeBuilder(), + teardown: new CodeBuilder() + }; + }, + getUniqueName: counter( names ), getUniqueNameMaker () { @@ -294,7 +304,6 @@ export default function generate ( parsed, source, options, names ) { elementDepth: 0, localElementDepth: 0, - initStatements: [], mountStatements: [], updateStatements: [], detachStatements: [], @@ -307,6 +316,7 @@ export default function generate ( parsed, source, options, names ) { indexNames: {}, listNames: {}, + builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); diff --git a/src/generate/visitors/Component.js b/src/generate/visitors/Component.js index 168b4a1e21..bd6c8f341b 100644 --- a/src/generate/visitors/Component.js +++ b/src/generate/visitors/Component.js @@ -37,7 +37,10 @@ export default { generator.generateBlock( node, yieldName ); - generator.current.initStatements.push(`var ${name}_yieldFragment = ${yieldName}( root, component );`); + generator.current.builders.init.addLine( + `var ${name}_yieldFragment = ${yieldName}( root, component );` + ); + generator.current.updateStatements.push(`${name}_yieldFragment.update ( changed, root );`); componentInitProperties.push(`yield: ${name}_yieldFragment`); @@ -107,7 +110,7 @@ export default { local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` ); - generator.current.initStatements.push( local.init.join( '\n' ) ); + generator.current.builders.init.addBlock( 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' ) ); diff --git a/src/generate/visitors/EachBlock.js b/src/generate/visitors/EachBlock.js index 36c24068c7..72a7b7fd36 100644 --- a/src/generate/visitors/EachBlock.js +++ b/src/generate/visitors/EachBlock.js @@ -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} );` : ''} @@ -136,7 +136,6 @@ export default { listNames, params: blockParams, - initStatements: [], mountStatements: [], updateStatements: [ Object.keys( contexts ).map( contextName => { const listName = listNames[ contextName ]; @@ -147,6 +146,7 @@ export default { detachStatements: [], teardownStatements: [], + builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); }, diff --git a/src/generate/visitors/Element.js b/src/generate/visitors/Element.js index a428d18006..3373ba3aee 100644 --- a/src/generate/visitors/Element.js +++ b/src/generate/visitors/Element.js @@ -79,7 +79,7 @@ export default { local.update.push( `${name}.__value = ${name}.textContent` ); } - generator.current.initStatements.push( local.init.join( '\n' ) ); + generator.current.builders.init.addBlock( 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' ) ); diff --git a/src/generate/visitors/IfBlock.js b/src/generate/visitors/IfBlock.js index 50e1f8dd60..8440559447 100644 --- a/src/generate/visitors/IfBlock.js +++ b/src/generate/visitors/IfBlock.js @@ -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};`; @@ -54,7 +54,7 @@ export default { if ( isToplevel ) { generator.current.mountStatements.push( mountStatement ); } else { - generator.current.initStatements.push( mountStatement ); + generator.current.builders.init.addLine( mountStatement ); } generator.current.updateStatements.push( deindent` diff --git a/src/generate/visitors/RawMustacheTag.js b/src/generate/visitors/RawMustacheTag.js index e57844509a..cc989dc5e1 100644 --- a/src/generate/visitors/RawMustacheTag.js +++ b/src/generate/visitors/RawMustacheTag.js @@ -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 ); @@ -28,7 +26,7 @@ export default { if ( isToplevel ) { generator.current.mountStatements.push(mountStatement); } else { - generator.current.initStatements.push(mountStatement); + generator.current.builders.init.addLine( mountStatement ); } generator.current.updateStatements.push( deindent` From 5fa2cd24c43a4c11708958f357ac483688570bcd Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 10 Dec 2016 14:38:20 -0500 Subject: [PATCH 4/9] use builders everywhere --- src/generate/index.js | 51 +++++++------------ src/generate/visitors/Component.js | 12 +++-- src/generate/visitors/EachBlock.js | 31 ++++++----- src/generate/visitors/Element.js | 8 +-- src/generate/visitors/IfBlock.js | 10 ++-- src/generate/visitors/MustacheTag.js | 2 +- src/generate/visitors/RawMustacheTag.js | 22 ++++---- src/generate/visitors/YieldTag.js | 12 +++-- src/utils/CodeBuilder.js | 4 ++ test/generator/hello-world/_config.js | 6 ++- test/generator/if-block-expression/_config.js | 2 +- 11 files changed, 78 insertions(+), 82 deletions(-) diff --git a/src/generate/index.js b/src/generate/index.js index f9d28bc7bc..a5749ffd27 100644 --- a/src/generate/index.js +++ b/src/generate/index.js @@ -29,18 +29,19 @@ export default function generate ( parsed, source, options, names ) { `${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.builders.init.addLine( `${generator.current.target}.appendChild( ${name} );` ); @@ -59,12 +60,6 @@ export default function generate ( parsed, source, options, names ) { name, target: 'target', localElementDepth: 0, - - mountStatements: [], - updateStatements: [], - detachStatements: [], - teardownStatements: [], - builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); @@ -81,18 +76,13 @@ export default function generate ( parsed, source, options, names ) { 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} - } - `; - - const teardownBlock = deindent` - ${teardownStatements}${detachStatements ? `\n\n${detachBlock}` : ``} - `; + if ( !fragment.builders.detach.isEmpty() ) { + fragment.builders.teardown.addBlock( deindent` + if ( detach ) { + ${fragment.builders.detach} + } + ` ); + } renderers.push( deindent` function ${fragment.name} ( ${fragment.params}, component ) { @@ -100,15 +90,15 @@ export default function generate ( parsed, source, options, names ) { 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} } }; } @@ -303,12 +293,7 @@ export default function generate ( parsed, source, options, names ) { target: 'target', elementDepth: 0, localElementDepth: 0, - - mountStatements: [], - updateStatements: [], - detachStatements: [], - teardownStatements: [], - + contexts: {}, indexes: {}, diff --git a/src/generate/visitors/Component.js b/src/generate/visitors/Component.js index bd6c8f341b..274ea2a583 100644 --- a/src/generate/visitors/Component.js +++ b/src/generate/visitors/Component.js @@ -41,7 +41,9 @@ export default { `var ${name}_yieldFragment = ${yieldName}( root, component );` ); - generator.current.updateStatements.push(`${name}_yieldFragment.update ( changed, root );`); + generator.current.builders.update.addLine( + `${name}_yieldFragment.update( changed, root );` + ); componentInitProperties.push(`yield: ${name}_yieldFragment`); } @@ -111,10 +113,10 @@ export default { local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` ); generator.current.builders.init.addBlock( 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' ) ); + if ( local.update.length ) generator.current.builders.update.addBlock( local.update.join( '\n' ) ); + if ( local.mount.length ) generator.current.builders.mount.addBlock( local.mount.join( '\n' ) ); + if ( local.detach.length ) generator.current.builders.detach.addBlock( local.detach.join( '\n' ) ); + generator.current.builders.teardown.addBlock( local.teardown.join( '\n' ) ); generator.push({ namespace: local.namespace, diff --git a/src/generate/visitors/EachBlock.js b/src/generate/visitors/EachBlock.js index 72a7b7fd36..0c6a976728 100644 --- a/src/generate/visitors/EachBlock.js +++ b/src/generate/visitors/EachBlock.js @@ -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,19 +136,18 @@ export default { listNames, params: blockParams, - mountStatements: [], - updateStatements: [ Object.keys( contexts ).map( contextName => { - const listName = listNames[ contextName ]; - const indexName = indexNames[ contextName ]; - - return `var ${contextName} = ${listName}[${indexName}];`; - }).join( '\n' ) ], - detachStatements: [], - teardownStatements: [], - builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); + + Object.keys( contexts ).forEach( contextName => { + const listName = listNames[ contextName ]; + const indexName = indexNames[ contextName ]; + + generator.current.builders.update.addLine( + `var ${contextName} = ${listName}[${indexName}];` + ); + }); }, leave ( generator ) { diff --git a/src/generate/visitors/Element.js b/src/generate/visitors/Element.js index 3373ba3aee..4154c810a7 100644 --- a/src/generate/visitors/Element.js +++ b/src/generate/visitors/Element.js @@ -80,10 +80,10 @@ export default { } generator.current.builders.init.addBlock( 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' ) ); + if ( local.update.length ) generator.current.builders.update.addBlock( local.update.join( '\n' ) ); + if ( local.mount.length ) generator.current.builders.mount.addBlock( local.mount.join( '\n' ) ); + if ( local.detach.length ) generator.current.builders.detach.addBlock( local.detach.join( '\n' ) ); + generator.current.builders.teardown.addBlock( local.teardown.join( '\n' ) ); generator.createMountStatement( name ); diff --git a/src/generate/visitors/IfBlock.js b/src/generate/visitors/IfBlock.js index 8440559447..bf4cd96953 100644 --- a/src/generate/visitors/IfBlock.js +++ b/src/generate/visitors/IfBlock.js @@ -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.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'} );` + ); } }; diff --git a/src/generate/visitors/MustacheTag.js b/src/generate/visitors/MustacheTag.js index eb5b10bf73..0222d714b6 100644 --- a/src/generate/visitors/MustacheTag.js +++ b/src/generate/visitors/MustacheTag.js @@ -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}; ` ); } diff --git a/src/generate/visitors/RawMustacheTag.js b/src/generate/visitors/RawMustacheTag.js index cc989dc5e1..68314c9a23 100644 --- a/src/generate/visitors/RawMustacheTag.js +++ b/src/generate/visitors/RawMustacheTag.js @@ -24,22 +24,20 @@ export default { `; if ( isToplevel ) { - generator.current.mountStatements.push(mountStatement); + generator.current.builders.mount.addLine( mountStatement ); } else { 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); - } + // 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); + // } } }; diff --git a/src/generate/visitors/YieldTag.js b/src/generate/visitors/YieldTag.js index 5ec8727ad1..68c850b66b 100644 --- a/src/generate/visitors/YieldTag.js +++ b/src/generate/visitors/YieldTag.js @@ -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 );` + ); } }; diff --git a/src/utils/CodeBuilder.js b/src/utils/CodeBuilder.js index 269b413538..33abf15f9c 100644 --- a/src/utils/CodeBuilder.js +++ b/src/utils/CodeBuilder.js @@ -23,6 +23,10 @@ export default class CodeBuilder { this.last = BLOCK; } + isEmpty () { + return this.result === ''; + } + toString () { return this.result.trim(); } diff --git a/test/generator/hello-world/_config.js b/test/generator/hello-world/_config.js index 3771a01231..cc4f568358 100644 --- a/test/generator/hello-world/_config.js +++ b/test/generator/hello-world/_config.js @@ -2,12 +2,14 @@ export default { data: { name: 'world' }, + html: '

Hello world!

', test ( assert, component, target ) { component.set({ name: 'everybody' }); - assert.equal( target.innerHTML, '

Hello everybody!

' ); + assert.htmlEqual( target.innerHTML, '

Hello everybody!

' ); + component.teardown(); - assert.equal( target.innerHTML, '' ); + assert.htmlEqual( target.innerHTML, '' ); } }; diff --git a/test/generator/if-block-expression/_config.js b/test/generator/if-block-expression/_config.js index 489261e2af..9ce3ff2b52 100644 --- a/test/generator/if-block-expression/_config.js +++ b/test/generator/if-block-expression/_config.js @@ -1,3 +1,3 @@ export default { - html: '

two is greater than one

' + html: '

two is greater than one

' }; From 83fe123a5c917ca8795c4539757f00416ad357fb Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 10 Dec 2016 14:58:13 -0500 Subject: [PATCH 5/9] fix detach order for triples --- src/generate/index.js | 11 ++++++++--- src/generate/visitors/RawMustacheTag.js | 8 +------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/generate/index.js b/src/generate/index.js index a5749ffd27..52a92b6b52 100644 --- a/src/generate/index.js +++ b/src/generate/index.js @@ -76,10 +76,14 @@ export default function generate ( parsed, source, options, names ) { fragment.builders.init.addLine( `${fragment.autofocus}.focus();` ); } - if ( !fragment.builders.detach.isEmpty() ) { + // 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 ); + + if ( !fragment.builders.detachRaw.isEmpty() ) { fragment.builders.teardown.addBlock( deindent` if ( detach ) { - ${fragment.builders.detach} + ${fragment.builders.detachRaw} } ` ); } @@ -171,6 +175,7 @@ export default function generate ( parsed, source, options, names ) { mount: new CodeBuilder(), update: new CodeBuilder(), detach: new CodeBuilder(), + detachRaw: new CodeBuilder(), teardown: new CodeBuilder() }; }, @@ -293,7 +298,7 @@ export default function generate ( parsed, source, options, names ) { target: 'target', elementDepth: 0, localElementDepth: 0, - + contexts: {}, indexes: {}, diff --git a/src/generate/visitors/RawMustacheTag.js b/src/generate/visitors/RawMustacheTag.js index 68314c9a23..d90e1d7d01 100644 --- a/src/generate/visitors/RawMustacheTag.js +++ b/src/generate/visitors/RawMustacheTag.js @@ -32,12 +32,6 @@ export default { 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 ); } }; From 7c00ea47bc5990a03449b281387cee93d5715bff Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 10 Dec 2016 15:21:16 -0500 Subject: [PATCH 6/9] use builders in place of local.detach and local.teardown --- src/generate/visitors/Component.js | 4 +--- src/generate/visitors/Element.js | 4 +--- src/generate/visitors/attributes/addComponentAttributes.js | 2 +- src/generate/visitors/attributes/addElementAttributes.js | 6 +++--- src/generate/visitors/attributes/binding/index.js | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/generate/visitors/Component.js b/src/generate/visitors/Component.js index 274ea2a583..dda336aaab 100644 --- a/src/generate/visitors/Component.js +++ b/src/generate/visitors/Component.js @@ -110,13 +110,11 @@ export default { ` ); } - local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` ); + generator.current.builders.teardown.addLine( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` ); generator.current.builders.init.addBlock( local.init.join( '\n' ) ); if ( local.update.length ) generator.current.builders.update.addBlock( local.update.join( '\n' ) ); if ( local.mount.length ) generator.current.builders.mount.addBlock( local.mount.join( '\n' ) ); - if ( local.detach.length ) generator.current.builders.detach.addBlock( local.detach.join( '\n' ) ); - generator.current.builders.teardown.addBlock( local.teardown.join( '\n' ) ); generator.push({ namespace: local.namespace, diff --git a/src/generate/visitors/Element.js b/src/generate/visitors/Element.js index 4154c810a7..e00029c57a 100644 --- a/src/generate/visitors/Element.js +++ b/src/generate/visitors/Element.js @@ -69,7 +69,7 @@ export default { local.init.unshift( render ); if ( isToplevel ) { - local.detach.push( `${name}.parentNode.removeChild( ${name} );` ); + generator.current.builders.detach.addLine( `${name}.parentNode.removeChild( ${name} );` ); } // special case – bound