diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d494f90e..a61e0f7391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Svelte changelog +## 1.2.4 + +* SSR compiler: Implement `{{{tripes}}}` ([#197](https://github.com/sveltejs/svelte/issues/197)) +* SSR compiler: Escape HTML in tags ([#197](https://github.com/sveltejs/svelte/issues/197)) + +## 1.2.3 + +* Add support for `namespace` declaration for SVG (etc) components ([#147](https://github.com/sveltejs/svelte/issues/147)) +* Throw error if methods or lifecycle hooks are arrow functions that use `this` or `arguments` ([#179](https://github.com/sveltejs/svelte/issues/179)) +* Use `setAttribute()` for `list` attributes, to preserve link to `` ([#178](https://github.com/sveltejs/svelte/issues/178)) +* Throw error if default export is not an object literal ([#190](https://github.com/sveltejs/svelte/pull/190)) +* Internal refactoring + ## 1.2.2 * Omit directives in server-side rendering ([#163](https://github.com/sveltejs/svelte/issues/167)) diff --git a/package.json b/package.json index 8494bbb5ec..b3d58157a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "1.2.2", + "version": "1.2.4", "description": "The magical disappearing UI framework", "main": "compiler/svelte.js", "files": [ diff --git a/src/generate/createGenerator.js b/src/generate/createGenerator.js new file mode 100644 index 0000000000..e6e2b59d2f --- /dev/null +++ b/src/generate/createGenerator.js @@ -0,0 +1,225 @@ +import MagicString from 'magic-string'; +import CodeBuilder from '../utils/CodeBuilder.js'; +import { walk } from 'estree-walker'; +import deindent from '../utils/deindent.js'; +import isReference from '../utils/isReference.js'; +import counter from './utils/counter.js'; +import flattenReference from '../utils/flattenReference.js'; +import visitors from './visitors/index.js'; +import globalWhitelist from '../utils/globalWhitelist.js'; + +export default function createGenerator ( parsed, source, names ) { + const generator = { + addElement ( name, renderStatement, needsIdentifier = false ) { + const isToplevel = generator.current.localElementDepth === 0; + if ( needsIdentifier || isToplevel ) { + generator.current.builders.init.addLine( + `var ${name} = ${renderStatement};` + ); + + generator.createMountStatement( name ); + } else { + generator.current.builders.init.addLine( + `${generator.current.target}.appendChild( ${renderStatement} );` + ); + } + + if ( isToplevel ) { + generator.current.builders.detach.addLine( + `${name}.parentNode.removeChild( ${name} );` + ); + } + }, + + createMountStatement ( name ) { + if ( generator.current.target === 'target' ) { + generator.current.builders.mount.addLine( + `target.insertBefore( ${name}, anchor );` + ); + } else { + generator.current.builders.init.addLine( + `${generator.current.target}.appendChild( ${name} );` ); + } + }, + + createAnchor ( _name, description = '' ) { + const name = `${_name}_anchor`; + const statement = `document.createComment( ${JSON.stringify( description )} )`; + generator.addElement( name, statement, true ); + return name; + }, + + generateBlock ( node, name ) { + generator.push({ + name, + target: 'target', + localElementDepth: 0, + builders: generator.getBuilders(), + getUniqueName: generator.getUniqueNameMaker() + }); + // walk the children here + node.children.forEach( generator.visit ); + generator.addRenderer( generator.current ); + generator.pop(); + // unset the children, to avoid them being visited again + node.children = []; + }, + + renderers: [], + + addRenderer ( fragment ) { + if ( fragment.autofocus ) { + fragment.builders.init.addLine( `${fragment.autofocus}.focus();` ); + } + + // 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.detachRaw} + } + ` ); + } + + generator.renderers.push( deindent` + function ${fragment.name} ( ${fragment.params}, component ) { + ${fragment.builders.init} + + return { + mount: function ( target, anchor ) { + ${fragment.builders.mount} + }, + + update: function ( changed, ${fragment.params} ) { + ${fragment.builders.update} + }, + + teardown: function ( detach ) { + ${fragment.builders.teardown} + } + }; + } + ` ); + }, + + addSourcemapLocations ( node ) { + walk( node, { + enter ( node ) { + generator.code.addSourcemapLocation( node.start ); + generator.code.addSourcemapLocation( node.end ); + } + }); + }, + + code: new MagicString( source ), + + components: {}, + + contextualise ( expression, isEventHandler ) { + const usedContexts = []; + const dependencies = []; + + const { contextDependencies, contexts, indexes } = generator.current; + + walk( expression, { + enter ( node, parent ) { + if ( isReference( node, parent ) ) { + const { name } = flattenReference( node ); + + if ( parent && parent.type === 'CallExpression' && node === parent.callee && generator.helpers[ name ] ) { + generator.code.prependRight( node.start, `template.helpers.` ); + return; + } + + if ( name === 'event' && isEventHandler ) { + return; + } + + if ( contexts[ name ] ) { + dependencies.push( ...contextDependencies[ name ] ); + if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name ); + } else if ( indexes[ name ] ) { + const context = indexes[ name ]; + if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); + } else if ( !globalWhitelist[ name ] ) { + dependencies.push( name ); + generator.code.prependRight( node.start, `root.` ); + if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); + } + + this.skip(); + } + } + }); + + return { + dependencies, + contexts: usedContexts, + snippet: `[✂${expression.start}-${expression.end}✂]`, + string: generator.code.slice( expression.start, expression.end ) + }; + }, + + 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 () { + return counter( names ); + }, + + cssId: parsed.css ? `svelte-${parsed.hash}` : '', + + helpers: {}, + + pop () { + const tail = generator.current; + generator.current = tail.parent; + + return tail; + }, + + push ( fragment ) { + const newFragment = Object.assign( {}, generator.current, fragment, { + parent: generator.current + }); + + generator.current = newFragment; + }, + + usesRefs: false, + + source, + + visit ( node ) { + const visitor = visitors[ node.type ]; + if ( !visitor ) throw new Error( `Not implemented: ${node.type}` ); + + if ( visitor.enter ) visitor.enter( generator, node ); + + if ( node.children ) { + node.children.forEach( child => { + generator.visit( child ); + }); + } + + if ( visitor.leave ) visitor.leave( generator, node ); + } + }; + + return generator; +} diff --git a/src/generate/index.js b/src/generate/index.js index 54e4e73982..1fc63d88d7 100644 --- a/src/generate/index.js +++ b/src/generate/index.js @@ -1,224 +1,17 @@ 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'; -import flattenReference from '../utils/flattenReference.js'; import namespaces from '../utils/namespaces.js'; import globalWhitelist from '../utils/globalWhitelist.js'; import getIntro from './utils/getIntro.js'; import getOutro from './utils/getOutro.js'; -import visitors from './visitors/index.js'; import processCss from './css/process.js'; +import createGenerator from './createGenerator.js'; export default function generate ( parsed, source, options, names ) { const format = options.format || 'es'; - const renderers = []; - - const generator = { - addElement ( name, renderStatement, needsIdentifier = false ) { - const isToplevel = generator.current.localElementDepth === 0; - if ( needsIdentifier || isToplevel ) { - generator.current.initStatements.push( deindent` - var ${name} = ${renderStatement}; - ` ); - generator.createMountStatement( name ); - } else { - generator.current.initStatements.push( deindent` - ${generator.current.target}.appendChild( ${renderStatement} ); - ` ); - } - if ( isToplevel ) { - generator.current.detachStatements.push( deindent` - ${name}.parentNode.removeChild( ${name} ); - ` ); - } - }, - - createMountStatement ( name ) { - if ( generator.current.target === 'target' ) { - generator.current.mountStatements.push( deindent` - target.insertBefore( ${name}, anchor ); - ` ); - } else { - generator.current.initStatements.push( deindent` - ${generator.current.target}.appendChild( ${name} ); - ` ); - } - }, - - createAnchor ( _name, description = '' ) { - const name = `${_name}_anchor`; - const statement = `document.createComment( ${JSON.stringify( description )} )`; - generator.addElement( name, statement, true ); - return name; - }, - - generateBlock ( node, name ) { - generator.push({ - name, - target: 'target', - localElementDepth: 0, - - initStatements: [], - mountStatements: [], - updateStatements: [], - detachStatements: [], - teardownStatements: [], - - getUniqueName: generator.getUniqueNameMaker() - }); - // walk the children here - node.children.forEach( generator.visit ); - generator.addRenderer( generator.current ); - generator.pop(); - // unset the children, to avoid them being visited again - node.children = []; - }, - - addRenderer ( fragment ) { - if ( fragment.autofocus ) { - fragment.initStatements.push( `${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}` : ``} - `; - - renderers.push( deindent` - function ${fragment.name} ( ${fragment.params}, component ) { - ${fragment.initStatements.join( '\n\n' )} - - return { - mount: function ( target, anchor ) { - ${fragment.mountStatements.join( '\n\n' )} - }, - - update: function ( changed, ${fragment.params} ) { - ${fragment.updateStatements.join( '\n\n' )} - }, - - teardown: function ( detach ) { - ${teardownBlock} - } - }; - } - ` ); - }, - - addSourcemapLocations ( node ) { - walk( node, { - enter ( node ) { - generator.code.addSourcemapLocation( node.start ); - generator.code.addSourcemapLocation( node.end ); - } - }); - }, - - code: new MagicString( source ), - - components: {}, - - contextualise ( expression, isEventHandler ) { - const usedContexts = []; - const dependencies = []; - - const { contextDependencies, contexts, indexes } = generator.current; - - walk( expression, { - enter ( node, parent ) { - if ( isReference( node, parent ) ) { - const { name } = flattenReference( node ); - - if ( parent && parent.type === 'CallExpression' && node === parent.callee && generator.helpers[ name ] ) { - generator.code.prependRight( node.start, `template.helpers.` ); - return; - } - - if ( name === 'event' && isEventHandler ) { - return; - } - - if ( contexts[ name ] ) { - dependencies.push( ...contextDependencies[ name ] ); - if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name ); - } else if ( indexes[ name ] ) { - const context = indexes[ name ]; - if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); - } else if ( !globalWhitelist[ name ] ) { - dependencies.push( name ); - generator.code.prependRight( node.start, `root.` ); - if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); - } - - this.skip(); - } - } - }); - - return { - dependencies, - contexts: usedContexts, - snippet: `[✂${expression.start}-${expression.end}✂]`, - string: generator.code.slice( expression.start, expression.end ) - }; - }, - - events: {}, - - getUniqueName: counter( names ), - - getUniqueNameMaker () { - return counter( names ); - }, - - cssId: parsed.css ? `svelte-${parsed.hash}` : '', - - helpers: {}, - - pop () { - const tail = generator.current; - generator.current = tail.parent; - - return tail; - }, - - push ( fragment ) { - const newFragment = Object.assign( {}, generator.current, fragment, { - parent: generator.current - }); - - generator.current = newFragment; - }, - - usesRefs: false, - - source, - - visit ( node ) { - const visitor = visitors[ node.type ]; - if ( !visitor ) throw new Error( `Not implemented: ${node.type}` ); - - if ( visitor.enter ) visitor.enter( generator, node ); - - if ( node.children ) { - node.children.forEach( child => { - generator.visit( child ); - }); - } - if ( visitor.leave ) visitor.leave( generator, node ); - } - }; + const generator = createGenerator( parsed, source, names ); const templateProperties = {}; const imports = []; @@ -294,12 +87,6 @@ export default function generate ( parsed, source, options, names ) { elementDepth: 0, localElementDepth: 0, - initStatements: [], - mountStatements: [], - updateStatements: [], - detachStatements: [], - teardownStatements: [], - contexts: {}, indexes: {}, @@ -307,6 +94,7 @@ export default function generate ( parsed, source, options, names ) { indexNames: {}, listNames: {}, + builders: generator.getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); @@ -314,15 +102,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 => { @@ -344,7 +134,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( ', ' )} ); } @@ -353,57 +143,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' ); + imports.forEach( ( declaration, i ) => { + if ( format === 'es' ) { + builders.main.addLine( source.slice( declaration.start, declaration.end ) ); + return; + } - const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`; - declaration.name = name; // hacky but makes life a bit easier later + 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 statements = namedImports.map( specifier => { - return `var ${specifier.local.name} = ${name}.${specifier.imported.name}`; - }); + const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`; + declaration.name = name; // hacky but makes life a bit easier later - if ( defaultImport ) { - statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); - } - - 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' ); @@ -415,33 +197,30 @@ export default function generate ( parsed, source, options, names ) { ` ); } - topLevelStatements.push( ...renderers.reverse() ); + let i = generator.renderers.length; + while ( i-- ) builders.main.addBlock( generator.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 ); ` ); @@ -455,12 +234,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 { @@ -471,7 +250,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 || {}; @@ -522,7 +301,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 ) { @@ -572,15 +351,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+)$/; diff --git a/src/generate/visitors/Component.js b/src/generate/visitors/Component.js index 168b4a1e21..c48bf29a42 100644 --- a/src/generate/visitors/Component.js +++ b/src/generate/visitors/Component.js @@ -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, diff --git a/src/generate/visitors/EachBlock.js b/src/generate/visitors/EachBlock.js index 36c24068c7..0c6a976728 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} );` : ''} @@ -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}];` + ); }); }, diff --git a/src/generate/visitors/Element.js b/src/generate/visitors/Element.js index a428d18006..163378640e 100644 --- a/src/generate/visitors/Element.js +++ b/src/generate/visitors/Element.js @@ -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