From c7eae0a523ab22c831d294142c6d6ebbf50b3c1c Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 16 Dec 2016 00:04:40 -0500 Subject: [PATCH] create Generator class --- src/generators/Generator.js | 228 +++++++++++++++++++++++++++++ src/generators/createGenerator.js | 233 ------------------------------ src/generators/dom/index.js | 6 +- 3 files changed, 231 insertions(+), 236 deletions(-) create mode 100644 src/generators/Generator.js delete mode 100644 src/generators/createGenerator.js diff --git a/src/generators/Generator.js b/src/generators/Generator.js new file mode 100644 index 0000000000..b5c467ece0 --- /dev/null +++ b/src/generators/Generator.js @@ -0,0 +1,228 @@ +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 './shared/utils/counter.js'; +import flattenReference from '../utils/flattenReference.js'; +import globalWhitelist from '../utils/globalWhitelist.js'; + +export default class Generator { + constructor ( parsed, source, names, visitors ) { + this.parsed = parsed; + this.source = source; + this.names = names; + this.visitors = visitors; + + this.renderers = []; + this.code = new MagicString( source ); + this.components = {}; + this.events = {}; + this.helpers = {}; + this.getUniqueName = counter( names ); + this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; + this.usesRefs = false; + } + + 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} );` + ); + } + } + + createMountStatement ( name ) { + if ( this.current.target === 'target' ) { + this.current.builders.mount.addLine( + `target.insertBefore( ${name}, anchor );` + ); + } else { + this.current.builders.init.addLine( + `${this.current.target}.appendChild( ${name} );` ); + } + } + + createAnchor ( _name, description = '' ) { + const name = `${_name}_anchor`; + const statement = `document.createComment( ${JSON.stringify( description )} )`; + this.addElement( name, statement, true ); + return name; + } + + generateBlock ( node, name ) { + this.push({ + name, + target: 'target', + localElementDepth: 0, + builders: this.getBuilders(), + getUniqueName: this.getUniqueNameMaker() + }); + // walk the children here + node.children.forEach( node => this.visit( node ) ); + this.addRenderer( this.current ); + this.pop(); + // unset the children, to avoid them being visited again + node.children = []; + } + + 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 => { + this.code.addSourcemapLocation( node.start ); + this.code.addSourcemapLocation( node.end ); + } + }); + } + + contextualise ( expression, isEventHandler ) { + const usedContexts = []; + const dependencies = []; + + const { code, helpers } = this; + const { contextDependencies, contexts, indexes } = this.current; + + walk( expression, { + enter ( node, parent ) { + if ( isReference( node, parent ) ) { + const { name } = flattenReference( node ); + + if ( parent && parent.type === 'CallExpression' && node === parent.callee && helpers[ name ] ) { + code.prependRight( node.start, `template.helpers.` ); + } + + else if ( name === 'event' && isEventHandler ) { + // noop + } + + else 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 ] ) { + code.prependRight( node.start, `( '${name}' in root ? root.` ); + code.appendLeft( node.object.end, ` : ${name} )` ); + } else { + code.prependRight( node.start, `root.` ); + } + + dependencies.push( name ); + if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); + } + + this.skip(); + } + } + }); + + return { + dependencies, + contexts: usedContexts, + snippet: `[✂${expression.start}-${expression.end}✂]`, + string: this.code.slice( expression.start, expression.end ) + }; + } + + getBuilders () { + return { + init: new CodeBuilder(), + mount: new CodeBuilder(), + update: new CodeBuilder(), + detach: new CodeBuilder(), + detachRaw: new CodeBuilder(), + teardown: new CodeBuilder() + }; + } + + getUniqueNameMaker () { + return counter( this.names ); + } + + pop () { + const tail = this.current; + this.current = tail.parent; + + return tail; + } + + push ( fragment ) { + const newFragment = Object.assign( {}, this.current, fragment, { + parent: this.current + }); + + this.current = newFragment; + } + + visit ( node ) { + const visitor = this.visitors[ node.type ]; + if ( !visitor ) throw new Error( `Not implemented: ${node.type}` ); + + if ( visitor.enter ) visitor.enter( this, node ); + + if ( node.children ) { + node.children.forEach( child => { + this.visit( child ); + }); + } + + if ( visitor.leave ) visitor.leave( this, node ); + } +} diff --git a/src/generators/createGenerator.js b/src/generators/createGenerator.js deleted file mode 100644 index bde0120561..0000000000 --- a/src/generators/createGenerator.js +++ /dev/null @@ -1,233 +0,0 @@ -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 './shared/utils/counter.js'; -import flattenReference from '../utils/flattenReference.js'; -import globalWhitelist from '../utils/globalWhitelist.js'; - -export default function createGenerator ( parsed, source, names, visitors ) { - 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.` ); - } - - else if ( name === 'event' && isEventHandler ) { - // noop - } - - else 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 ] ) { - generator.code.prependRight( node.start, `( '${name}' in root ? root.` ); - generator.code.appendLeft( node.object.end, ` : ${name} )` ); - } else { - generator.code.prependRight( node.start, `root.` ); - } - - dependencies.push( name ); - 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/generators/dom/index.js b/src/generators/dom/index.js index af98031e75..f4cc0d1e6f 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -6,12 +6,12 @@ import getIntro from '../shared/utils/getIntro.js'; import getOutro from '../shared/utils/getOutro.js'; import processCss from '../shared/css/process.js'; import visitors from './visitors/index.js'; -import createGenerator from '../createGenerator.js'; +import Generator from '../Generator.js'; export default function dom ( parsed, source, options, names ) { const format = options.format || 'es'; - const generator = createGenerator( parsed, source, names, visitors ); + const generator = new Generator( parsed, source, names, visitors ); const templateProperties = {}; const imports = []; @@ -98,7 +98,7 @@ export default function dom ( parsed, source, options, names ) { getUniqueName: generator.getUniqueNameMaker() }); - parsed.html.children.forEach( generator.visit ); + parsed.html.children.forEach( node => generator.visit( node ) ); generator.addRenderer( generator.pop() );