From 1e00b145cc355917d72166f0467997a9e5b2516c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 11:55:25 -0500 Subject: [PATCH] =?UTF-8?q?more=20refactoring=20=E2=80=93=20sort=20computa?= =?UTF-8?q?tions,=20move=20addElement=20and=20addRenderer=20out=20of=20gen?= =?UTF-8?q?erator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/generators/Generator.js | 107 +++++++----------- src/generators/dom/index.js | 97 +++++++++++----- src/generators/dom/visitors/EachBlock.js | 2 +- src/generators/dom/visitors/MustacheTag.js | 6 +- src/generators/dom/visitors/RawMustacheTag.js | 13 ++- src/generators/dom/visitors/Text.js | 6 +- 6 files changed, 133 insertions(+), 98 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 5ae7115fc5..5693093fc6 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -14,13 +14,14 @@ export default class Generator { this.names = names; this.visitors = visitors; - this.imports = []; this.templateProperties = {}; this.helpers = {}; this.components = {}; this.events = {}; - this.renderers = []; + this.imports = []; + this.computations = []; + this.code = new MagicString( source ); this.getUniqueName = counter( names ); this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; @@ -31,65 +32,6 @@ export default class Generator { this.init(); } - addElement ( name, renderStatement, needsIdentifier = false ) { - const isToplevel = this.current.localElementDepth === 0; - if ( needsIdentifier || isToplevel ) { - this.current.builders.init.addLine( - `var ${name} = ${renderStatement};` - ); - - this.createMountStatement( name ); - } else { - this.current.builders.init.addLine( - `${this.current.target}.appendChild( ${renderStatement} );` - ); - } - - if ( isToplevel ) { - this.current.builders.detach.addLine( - `${name}.parentNode.removeChild( ${name} );` - ); - } - } - - 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} - } - ` ); - } - - this.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 => { @@ -156,8 +98,14 @@ export default class Generator { createAnchor ( _name, description = '' ) { const name = `${_name}_anchor`; - const statement = `document.createComment( ${JSON.stringify( description )} )`; - this.addElement( name, statement, true ); + const renderStatement = `document.createComment( ${JSON.stringify( description )} )`; + + this.fire( 'addElement', { + name, + renderStatement, + needsIdentifier: true + }); + return name; } @@ -191,7 +139,7 @@ export default class Generator { }); // walk the children here node.children.forEach( node => this.visit( node ) ); - this.addRenderer( this.current ); + this.fire( 'addRenderer', this.current ); this.pop(); // unset the children, to avoid them being visited again node.children = []; @@ -213,8 +161,9 @@ export default class Generator { } init () { - const { imports, source } = this; + const { computations, imports, source } = this; const { js } = this.parsed; + if ( js ) { this.addSourcemapLocations( js.content ); @@ -269,6 +218,34 @@ export default class Generator { }); } }); + + if ( this.templateProperties.computed ) { + const dependencies = new Map(); + + this.templateProperties.computed.properties.forEach( prop => { + const key = prop.key.name; + const value = prop.value; + + const deps = value.params.map( param => param.name ); + dependencies.set( key, deps ); + }); + + const visited = new Set(); + + function visit ( key ) { + if ( !dependencies.has( key ) ) return; // not a computation + + if ( visited.has( key ) ) return; + visited.add( key ); + + const deps = dependencies.get( key ); + deps.forEach( visit ); + + computations.push({ key, deps }); + } + + this.templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); + } } } diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 8adadd1bfe..0e045df9a5 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -13,7 +13,69 @@ export default function dom ( parsed, source, options, names ) { const generator = new Generator( parsed, source, names, visitors ); - const { templateProperties, imports } = generator; + const { computations, imports, templateProperties } = generator; // TODO make this generator.parseJs() or similar? + + const renderers = []; + function 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} + } + ` ); + } + + 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} + } + }; + } + ` ); + } + + generator.on( 'addRenderer', addRenderer ); + + generator.on( 'addElement', function addElement ({ name, renderStatement, needsIdentifier }) { + const isToplevel = this.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} );` + ); + } + }); let namespace = null; if ( templateProperties.namespace ) { @@ -43,7 +105,7 @@ export default function dom ( parsed, source, options, names ) { parsed.html.children.forEach( node => generator.visit( node ) ); - generator.addRenderer( generator.pop() ); + addRenderer( generator.pop() ); const builders = { main: new CodeBuilder(), @@ -54,37 +116,16 @@ export default function dom ( parsed, source, options, names ) { builders.set.addLine( 'var oldState = state;' ); builders.set.addLine( 'state = Object.assign( {}, oldState, newState );' ); - if ( templateProperties.computed ) { + if ( computations.length ) { const builder = new CodeBuilder(); - const dependencies = new Map(); - - templateProperties.computed.properties.forEach( prop => { - const key = prop.key.name; - const value = prop.value; - - const deps = value.params.map( param => param.name ); - dependencies.set( key, deps ); - }); - - const visited = new Set(); - - function visit ( key ) { - if ( !dependencies.has( key ) ) return; // not a computation - - if ( visited.has( key ) ) return; - visited.add( key ); - - const deps = dependencies.get( key ); - deps.forEach( visit ); + computations.forEach( ({ key, deps }) => { 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( ', ' )} ); } ` ); - } - - templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); + }); builders.main.addBlock( deindent` function applyComputations ( state, newState, oldState ) { @@ -140,8 +181,8 @@ export default function dom ( parsed, source, options, names ) { ` ); } - let i = generator.renderers.length; - while ( i-- ) builders.main.addBlock( generator.renderers[i] ); + let i = renderers.length; + while ( i-- ) builders.main.addBlock( renderers[i] ); const constructorName = options.name || 'SvelteComponent'; diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index e7c39e93f2..5e75abd441 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -151,7 +151,7 @@ export default { }, leave ( generator ) { - generator.addRenderer( generator.current ); + generator.fire( 'addRenderer', generator.current ); generator.pop(); } }; diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index f956a7be08..231ba26557 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -7,7 +7,11 @@ export default { generator.addSourcemapLocations( node.expression ); const { snippet } = generator.contextualise( node.expression ); - generator.addElement( name, `document.createTextNode( ${snippet} )`, true ); + generator.fire( 'addElement', { + name, + renderStatement: `document.createTextNode( ${snippet} )`, + needsIdentifier: true + }); generator.current.builders.update.addBlock( deindent` ${name}.data = ${snippet}; diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js index 455833497c..3a2e4429bc 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -10,9 +10,18 @@ export default { // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. const before = `${name}_before`; - generator.addElement( before, `document.createElement( 'noscript' )`, true ); + generator.fire( 'addElement', { + name: before, + renderStatement: `document.createElement( 'noscript' )`, + needsIdentifier: true + }); + const after = `${name}_after`; - generator.addElement( after, `document.createElement( 'noscript' )`, true ); + generator.fire( 'addElement', { + name: after, + renderStatement: `document.createElement( 'noscript' )`, + needsIdentifier: true + }); const isToplevel = generator.current.localElementDepth === 0; diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js index 758c9c4648..58090872bd 100644 --- a/src/generators/dom/visitors/Text.js +++ b/src/generators/dom/visitors/Text.js @@ -5,6 +5,10 @@ export default { } const name = generator.current.getUniqueName( `text` ); - generator.addElement( name, `document.createTextNode( ${JSON.stringify( node.data )} )` ); + generator.fire( 'addElement', { + name, + renderStatement: `document.createTextNode( ${JSON.stringify( node.data )} )`, + needsIdentifier: false + }); } };