From e45b15c3da7ec81d1e85ebf340105f226b827e06 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Thu, 15 Dec 2016 23:31:37 -0500 Subject: [PATCH 01/14] centralise voidElementNames --- src/parse/state/tag.js | 2 +- src/server-side-rendering/compile.js | 3 +-- src/utils/voidElementNames.js | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/utils/voidElementNames.js diff --git a/src/parse/state/tag.js b/src/parse/state/tag.js index c7902535a0..c3fa1a1222 100644 --- a/src/parse/state/tag.js +++ b/src/parse/state/tag.js @@ -4,9 +4,9 @@ import readStyle from '../read/style.js'; import { readEventHandlerDirective, readBindingDirective } from '../read/directives.js'; import { trimStart, trimEnd } from '../utils/trim.js'; import { decodeCharacterReferences } from '../utils/html.js'; +import voidElementNames from '../../utils/voidElementNames.js'; const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; -const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/; const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/; const specials = { diff --git a/src/server-side-rendering/compile.js b/src/server-side-rendering/compile.js index 6bfcfd58e7..0e45a0f2ef 100644 --- a/src/server-side-rendering/compile.js +++ b/src/server-side-rendering/compile.js @@ -4,8 +4,7 @@ import isReference from '../utils/isReference.js'; import flattenReference from '../utils/flattenReference.js'; import MagicString, { Bundle } from 'magic-string'; import processCss from '../generate/css/process.js'; - -const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; +import voidElementNames from '../utils/voidElementNames.js'; export default function compile ( parsed, source, { filename }) { const code = new MagicString( source ); diff --git a/src/utils/voidElementNames.js b/src/utils/voidElementNames.js new file mode 100644 index 0000000000..7df1d452b6 --- /dev/null +++ b/src/utils/voidElementNames.js @@ -0,0 +1 @@ +export default /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/; From f73a87230a891d3377e12d630b88cc445b18abdb Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Thu, 15 Dec 2016 23:56:08 -0500 Subject: [PATCH 02/14] move some files around --- .../createGenerator.js | 5 ++--- src/{generate => generators/dom}/index.js | 21 ++++++++++--------- .../dom}/visitors/Comment.js | 0 .../dom}/visitors/Component.js | 4 ++-- .../dom}/visitors/EachBlock.js | 2 +- .../dom}/visitors/Element.js | 4 ++-- .../dom}/visitors/IfBlock.js | 2 +- .../dom}/visitors/MustacheTag.js | 2 +- .../dom}/visitors/RawMustacheTag.js | 2 +- .../dom}/visitors/Text.js | 0 .../dom}/visitors/YieldTag.js | 0 .../attributes/addComponentAttributes.js | 2 +- .../attributes/addElementAttributes.js | 4 ++-- .../dom}/visitors/attributes/binding/index.js | 6 +++--- .../dom}/visitors/attributes/lookup.js | 0 .../dom}/visitors/index.js | 0 .../server-side-rendering/index.js} | 12 +++++------ .../shared}/css/process.js | 2 +- .../shared}/css/transform.js | 0 .../shared}/utils/counter.js | 0 .../shared}/utils/getGlobals.js | 0 .../shared}/utils/getIntro.js | 2 +- .../shared}/utils/getOutro.js | 0 .../shared}/utils/walkHtml.js | 0 src/index.js | 4 ++-- 25 files changed, 37 insertions(+), 37 deletions(-) rename src/{generate => generators}/createGenerator.js (97%) rename src/{generate => generators/dom}/index.js (95%) rename src/{generate => generators/dom}/visitors/Comment.js (100%) rename src/{generate => generators/dom}/visitors/Component.js (97%) rename src/{generate => generators/dom}/visitors/EachBlock.js (98%) rename src/{generate => generators/dom}/visitors/Element.js (96%) rename src/{generate => generators/dom}/visitors/IfBlock.js (98%) rename src/{generate => generators/dom}/visitors/MustacheTag.js (88%) rename src/{generate => generators/dom}/visitors/RawMustacheTag.js (96%) rename src/{generate => generators/dom}/visitors/Text.js (100%) rename src/{generate => generators/dom}/visitors/YieldTag.js (100%) rename src/{generate => generators/dom}/visitors/attributes/addComponentAttributes.js (98%) rename src/{generate => generators/dom}/visitors/attributes/addElementAttributes.js (98%) rename src/{generate => generators/dom}/visitors/attributes/binding/index.js (95%) rename src/{generate => generators/dom}/visitors/attributes/lookup.js (100%) rename src/{generate => generators/dom}/visitors/index.js (100%) rename src/{server-side-rendering/compile.js => generators/server-side-rendering/index.js} (97%) rename src/{generate => generators/shared}/css/process.js (79%) rename src/{generate => generators/shared}/css/transform.js (100%) rename src/{generate => generators/shared}/utils/counter.js (100%) rename src/{generate => generators/shared}/utils/getGlobals.js (100%) rename src/{generate => generators/shared}/utils/getIntro.js (97%) rename src/{generate => generators/shared}/utils/getOutro.js (100%) rename src/{generate => generators/shared}/utils/walkHtml.js (100%) diff --git a/src/generate/createGenerator.js b/src/generators/createGenerator.js similarity index 97% rename from src/generate/createGenerator.js rename to src/generators/createGenerator.js index 4a6608d967..bde0120561 100644 --- a/src/generate/createGenerator.js +++ b/src/generators/createGenerator.js @@ -3,12 +3,11 @@ 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 counter from './shared/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 ) { +export default function createGenerator ( parsed, source, names, visitors ) { const generator = { addElement ( name, renderStatement, needsIdentifier = false ) { const isToplevel = generator.current.localElementDepth === 0; diff --git a/src/generate/index.js b/src/generators/dom/index.js similarity index 95% rename from src/generate/index.js rename to src/generators/dom/index.js index 7a7226be29..af98031e75 100644 --- a/src/generate/index.js +++ b/src/generators/dom/index.js @@ -1,16 +1,17 @@ import MagicString, { Bundle } from 'magic-string'; -import CodeBuilder from '../utils/CodeBuilder.js'; -import deindent from '../utils/deindent.js'; -import namespaces from '../utils/namespaces.js'; -import getIntro from './utils/getIntro.js'; -import getOutro from './utils/getOutro.js'; -import processCss from './css/process.js'; -import createGenerator from './createGenerator.js'; - -export default function generate ( parsed, source, options, names ) { +import deindent from '../../utils/deindent.js'; +import CodeBuilder from '../../utils/CodeBuilder.js'; +import namespaces from '../../utils/namespaces.js'; +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'; + +export default function dom ( parsed, source, options, names ) { const format = options.format || 'es'; - const generator = createGenerator( parsed, source, names ); + const generator = createGenerator( parsed, source, names, visitors ); const templateProperties = {}; const imports = []; diff --git a/src/generate/visitors/Comment.js b/src/generators/dom/visitors/Comment.js similarity index 100% rename from src/generate/visitors/Comment.js rename to src/generators/dom/visitors/Comment.js diff --git a/src/generate/visitors/Component.js b/src/generators/dom/visitors/Component.js similarity index 97% rename from src/generate/visitors/Component.js rename to src/generators/dom/visitors/Component.js index c48bf29a42..31d52c777e 100644 --- a/src/generate/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -1,5 +1,5 @@ -import deindent from '../../utils/deindent.js'; -import CodeBuilder from '../../utils/CodeBuilder.js'; +import deindent from '../../../utils/deindent.js'; +import CodeBuilder from '../../../utils/CodeBuilder.js'; import addComponentAttributes from './attributes/addComponentAttributes.js'; export default { diff --git a/src/generate/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js similarity index 98% rename from src/generate/visitors/EachBlock.js rename to src/generators/dom/visitors/EachBlock.js index 0c6a976728..e7c39e93f2 100644 --- a/src/generate/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -1,4 +1,4 @@ -import deindent from '../../utils/deindent.js'; +import deindent from '../../../utils/deindent.js'; export default { enter ( generator, node ) { diff --git a/src/generate/visitors/Element.js b/src/generators/dom/visitors/Element.js similarity index 96% rename from src/generate/visitors/Element.js rename to src/generators/dom/visitors/Element.js index 163378640e..39ca4b297e 100644 --- a/src/generate/visitors/Element.js +++ b/src/generators/dom/visitors/Element.js @@ -1,5 +1,5 @@ -import CodeBuilder from '../../utils/CodeBuilder.js'; -import deindent from '../../utils/deindent.js'; +import CodeBuilder from '../../../utils/CodeBuilder.js'; +import deindent from '../../../utils/deindent.js'; import addElementAttributes from './attributes/addElementAttributes.js'; import Component from './Component.js'; diff --git a/src/generate/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js similarity index 98% rename from src/generate/visitors/IfBlock.js rename to src/generators/dom/visitors/IfBlock.js index bf4cd96953..e95fefc7f1 100644 --- a/src/generate/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -1,4 +1,4 @@ -import deindent from '../../utils/deindent.js'; +import deindent from '../../../utils/deindent.js'; function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { generator.addSourcemapLocations( node.expression ); diff --git a/src/generate/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js similarity index 88% rename from src/generate/visitors/MustacheTag.js rename to src/generators/dom/visitors/MustacheTag.js index 0222d714b6..f956a7be08 100644 --- a/src/generate/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -1,4 +1,4 @@ -import deindent from '../../utils/deindent.js'; +import deindent from '../../../utils/deindent.js'; export default { enter ( generator, node ) { diff --git a/src/generate/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js similarity index 96% rename from src/generate/visitors/RawMustacheTag.js rename to src/generators/dom/visitors/RawMustacheTag.js index d90e1d7d01..455833497c 100644 --- a/src/generate/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -1,4 +1,4 @@ -import deindent from '../../utils/deindent.js'; +import deindent from '../../../utils/deindent.js'; export default { enter ( generator, node ) { diff --git a/src/generate/visitors/Text.js b/src/generators/dom/visitors/Text.js similarity index 100% rename from src/generate/visitors/Text.js rename to src/generators/dom/visitors/Text.js diff --git a/src/generate/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js similarity index 100% rename from src/generate/visitors/YieldTag.js rename to src/generators/dom/visitors/YieldTag.js diff --git a/src/generate/visitors/attributes/addComponentAttributes.js b/src/generators/dom/visitors/attributes/addComponentAttributes.js similarity index 98% rename from src/generate/visitors/attributes/addComponentAttributes.js rename to src/generators/dom/visitors/attributes/addComponentAttributes.js index e5c9fec984..d1c70a574a 100644 --- a/src/generate/visitors/attributes/addComponentAttributes.js +++ b/src/generators/dom/visitors/attributes/addComponentAttributes.js @@ -1,5 +1,5 @@ import createBinding from './binding/index.js'; -import deindent from '../../../utils/deindent.js'; +import deindent from '../../../../utils/deindent.js'; export default function addComponentAttributes ( generator, node, local ) { local.staticAttributes = []; diff --git a/src/generate/visitors/attributes/addElementAttributes.js b/src/generators/dom/visitors/attributes/addElementAttributes.js similarity index 98% rename from src/generate/visitors/attributes/addElementAttributes.js rename to src/generators/dom/visitors/attributes/addElementAttributes.js index ec5a67eb7b..71adfa3c08 100644 --- a/src/generate/visitors/attributes/addElementAttributes.js +++ b/src/generators/dom/visitors/attributes/addElementAttributes.js @@ -1,7 +1,7 @@ import attributeLookup from './lookup.js'; import createBinding from './binding/index.js'; -import deindent from '../../../utils/deindent.js'; -import flattenReference from '../../../utils/flattenReference.js'; +import deindent from '../../../../utils/deindent.js'; +import flattenReference from '../../../../utils/flattenReference.js'; export default function addElementAttributes ( generator, node, local ) { node.attributes.forEach( attribute => { diff --git a/src/generate/visitors/attributes/binding/index.js b/src/generators/dom/visitors/attributes/binding/index.js similarity index 95% rename from src/generate/visitors/attributes/binding/index.js rename to src/generators/dom/visitors/attributes/binding/index.js index 585beda5e4..0411357664 100644 --- a/src/generate/visitors/attributes/binding/index.js +++ b/src/generators/dom/visitors/attributes/binding/index.js @@ -1,6 +1,6 @@ -import deindent from '../../../../utils/deindent.js'; -import isReference from '../../../../utils/isReference.js'; -import flattenReference from '../../../../utils/flattenReference.js'; +import deindent from '../../../../../utils/deindent.js'; +import isReference from '../../../../../utils/isReference.js'; +import flattenReference from '../../../../../utils/flattenReference.js'; export default function createBinding ( generator, node, attribute, current, local ) { const parts = attribute.value.split( '.' ); diff --git a/src/generate/visitors/attributes/lookup.js b/src/generators/dom/visitors/attributes/lookup.js similarity index 100% rename from src/generate/visitors/attributes/lookup.js rename to src/generators/dom/visitors/attributes/lookup.js diff --git a/src/generate/visitors/index.js b/src/generators/dom/visitors/index.js similarity index 100% rename from src/generate/visitors/index.js rename to src/generators/dom/visitors/index.js diff --git a/src/server-side-rendering/compile.js b/src/generators/server-side-rendering/index.js similarity index 97% rename from src/server-side-rendering/compile.js rename to src/generators/server-side-rendering/index.js index 0e45a0f2ef..28b7873a92 100644 --- a/src/server-side-rendering/compile.js +++ b/src/generators/server-side-rendering/index.js @@ -1,10 +1,10 @@ -import { walk } from 'estree-walker'; -import deindent from '../utils/deindent.js'; -import isReference from '../utils/isReference.js'; -import flattenReference from '../utils/flattenReference.js'; import MagicString, { Bundle } from 'magic-string'; -import processCss from '../generate/css/process.js'; -import voidElementNames from '../utils/voidElementNames.js'; +import { walk } from 'estree-walker'; +import deindent from '../../utils/deindent.js'; +import isReference from '../../utils/isReference.js'; +import flattenReference from '../../utils/flattenReference.js'; +import voidElementNames from '../../utils/voidElementNames.js'; +import processCss from '../shared/css/process.js'; export default function compile ( parsed, source, { filename }) { const code = new MagicString( source ); diff --git a/src/generate/css/process.js b/src/generators/shared/css/process.js similarity index 79% rename from src/generate/css/process.js rename to src/generators/shared/css/process.js index a75d1cf46e..41b6e01452 100644 --- a/src/generate/css/process.js +++ b/src/generators/shared/css/process.js @@ -1,4 +1,4 @@ -import spaces from '../../utils/spaces.js'; +import spaces from '../../../utils/spaces.js'; import transform from './transform.js'; export default function process ( parsed ) { diff --git a/src/generate/css/transform.js b/src/generators/shared/css/transform.js similarity index 100% rename from src/generate/css/transform.js rename to src/generators/shared/css/transform.js diff --git a/src/generate/utils/counter.js b/src/generators/shared/utils/counter.js similarity index 100% rename from src/generate/utils/counter.js rename to src/generators/shared/utils/counter.js diff --git a/src/generate/utils/getGlobals.js b/src/generators/shared/utils/getGlobals.js similarity index 100% rename from src/generate/utils/getGlobals.js rename to src/generators/shared/utils/getGlobals.js diff --git a/src/generate/utils/getIntro.js b/src/generators/shared/utils/getIntro.js similarity index 97% rename from src/generate/utils/getIntro.js rename to src/generators/shared/utils/getIntro.js index 3d1e3b9e5d..ce0f5525b8 100644 --- a/src/generate/utils/getIntro.js +++ b/src/generators/shared/utils/getIntro.js @@ -1,4 +1,4 @@ -import deindent from '../../utils/deindent.js'; +import deindent from '../../../utils/deindent.js'; import getGlobals from './getGlobals.js'; export default function getIntro ( format, options, imports ) { diff --git a/src/generate/utils/getOutro.js b/src/generators/shared/utils/getOutro.js similarity index 100% rename from src/generate/utils/getOutro.js rename to src/generators/shared/utils/getOutro.js diff --git a/src/generate/utils/walkHtml.js b/src/generators/shared/utils/walkHtml.js similarity index 100% rename from src/generate/utils/walkHtml.js rename to src/generators/shared/utils/walkHtml.js diff --git a/src/index.js b/src/index.js index 79a5665dce..dc2bcefe6a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import parse from './parse/index.js'; import validate from './validate/index.js'; -import generate from './generate/index.js'; -import generateSSR from './server-side-rendering/compile.js'; +import generate from './generators/dom/index.js'; +import generateSSR from './generators/server-side-rendering/index.js'; function normalizeOptions ( options ) { return Object.assign( { From 47a7b896cc037038dc22acfacb38d7c2cb51e62a Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 16 Dec 2016 00:04:40 -0500 Subject: [PATCH 03/14] 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() ); From 865a184adc070b752523db68c57a46e549b71970 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 16 Dec 2016 00:05:46 -0500 Subject: [PATCH 04/14] sort methods --- src/generators/Generator.js | 68 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index b5c467ece0..2d18758c4c 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -45,40 +45,6 @@ export default class Generator { } } - 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();` ); @@ -181,6 +147,40 @@ export default class Generator { }; } + createAnchor ( _name, description = '' ) { + const name = `${_name}_anchor`; + const statement = `document.createComment( ${JSON.stringify( description )} )`; + this.addElement( name, statement, true ); + return 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} );` ); + } + } + + 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 = []; + } + getBuilders () { return { init: new CodeBuilder(), From 7493b6c49b670c3e6d37a18efedfbff9e586c519 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 16 Dec 2016 00:18:01 -0500 Subject: [PATCH 05/14] move some code into generator.init --- src/generators/Generator.js | 87 +++++++++++++++++++++++++++++++++++-- src/generators/dom/index.js | 59 +------------------------ 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 2d18758c4c..5ae7115fc5 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -14,14 +14,21 @@ export default class Generator { this.names = names; this.visitors = visitors; - this.renderers = []; - this.code = new MagicString( source ); + this.imports = []; + this.templateProperties = {}; + this.helpers = {}; this.components = {}; this.events = {}; - this.helpers = {}; + + this.renderers = []; + this.code = new MagicString( source ); this.getUniqueName = counter( names ); this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.usesRefs = false; + + this._callbacks = {}; + + this.init(); } addElement ( name, renderStatement, needsIdentifier = false ) { @@ -165,6 +172,15 @@ export default class Generator { } } + fire ( eventName, data ) { + const handlers = eventName in this._callbacks && this._callbacks[ eventName ].slice(); + if ( !handlers ) return; + + for ( let i = 0; i < handlers.length; i += 1 ) { + handlers[i].call( this, data ); + } + } + generateBlock ( node, name ) { this.push({ name, @@ -196,6 +212,71 @@ export default class Generator { return counter( this.names ); } + init () { + const { imports, source } = this; + const { js } = this.parsed; + if ( js ) { + this.addSourcemapLocations( js.content ); + + // imports need to be hoisted out of the IIFE + for ( let i = 0; i < js.content.body.length; i += 1 ) { + const node = js.content.body[i]; + if ( node.type === 'ImportDeclaration' ) { + let a = node.start; + let b = node.end; + while ( /[ \t]/.test( source[ a - 1 ] ) ) a -= 1; + while ( source[b] === '\n' ) b += 1; + + //imports.push( source.slice( a, b ).replace( /^\s/, '' ) ); + imports.push( node ); + this.code.remove( a, b ); + } + } + + const defaultExport = js.content.body.find( node => node.type === 'ExportDefaultDeclaration' ); + + if ( defaultExport ) { + const finalNode = js.content.body[ js.content.body.length - 1 ]; + if ( defaultExport === finalNode ) { + // export is last property, we can just return it + this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` ); + } else { + // TODO ensure `template` isn't already declared + this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var template = ` ); + + let i = defaultExport.start; + while ( /\s/.test( source[ i - 1 ] ) ) i--; + + const indentation = source.slice( i, defaultExport.start ); + this.code.appendLeft( finalNode.end, `\n\n${indentation}return template;` ); + } + + defaultExport.declaration.properties.forEach( prop => { + this.templateProperties[ prop.key.name ] = prop.value; + }); + + this.code.prependRight( js.content.start, 'var template = (function () {' ); + } else { + this.code.prependRight( js.content.start, '(function () {' ); + } + + this.code.appendLeft( js.content.end, '}());' ); + + [ 'helpers', 'events', 'components' ].forEach( key => { + if ( this.templateProperties[ key ] ) { + this.templateProperties[ key ].properties.forEach( prop => { + this[ key ][ prop.key.name ] = prop.value; + }); + } + }); + } + } + + on ( eventName, handler ) { + const handlers = this._callbacks[ eventName ] || ( this._callbacks[ eventName ] = [] ); + handlers.push( handler ); + } + pop () { const tail = this.current; this.current = tail.parent; diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index f4cc0d1e6f..8adadd1bfe 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -13,64 +13,7 @@ export default function dom ( parsed, source, options, names ) { const generator = new Generator( parsed, source, names, visitors ); - const templateProperties = {}; - const imports = []; - - if ( parsed.js ) { - generator.addSourcemapLocations( parsed.js.content ); - - // imports need to be hoisted out of the IIFE - for ( let i = 0; i < parsed.js.content.body.length; i += 1 ) { - const node = parsed.js.content.body[i]; - if ( node.type === 'ImportDeclaration' ) { - let a = node.start; - let b = node.end; - while ( /[ \t]/.test( source[ a - 1 ] ) ) a -= 1; - while ( source[b] === '\n' ) b += 1; - - //imports.push( source.slice( a, b ).replace( /^\s/, '' ) ); - imports.push( node ); - generator.code.remove( a, b ); - } - } - - const defaultExport = parsed.js.content.body.find( node => node.type === 'ExportDefaultDeclaration' ); - - if ( defaultExport ) { - const finalNode = parsed.js.content.body[ parsed.js.content.body.length - 1 ]; - if ( defaultExport === finalNode ) { - // export is last property, we can just return it - generator.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` ); - } else { - // TODO ensure `template` isn't already declared - generator.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var template = ` ); - - let i = defaultExport.start; - while ( /\s/.test( source[ i - 1 ] ) ) i--; - - const indentation = source.slice( i, defaultExport.start ); - generator.code.appendLeft( finalNode.end, `\n\n${indentation}return template;` ); - } - - defaultExport.declaration.properties.forEach( prop => { - templateProperties[ prop.key.name ] = prop.value; - }); - - generator.code.prependRight( parsed.js.content.start, 'var template = (function () {' ); - } else { - generator.code.prependRight( parsed.js.content.start, '(function () {' ); - } - - generator.code.appendLeft( parsed.js.content.end, '}());' ); - - [ 'helpers', 'events', 'components' ].forEach( key => { - if ( templateProperties[ key ] ) { - templateProperties[ key ].properties.forEach( prop => { - generator[ key ][ prop.key.name ] = prop.value; - }); - } - }); - } + const { templateProperties, imports } = generator; let namespace = null; if ( templateProperties.namespace ) { From 529d3a4a0e1966d9a3ef3fbfba954c2ed2064165 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 11:55:25 -0500 Subject: [PATCH 06/14] =?UTF-8?q?more=20refactoring=20=E2=80=93=20sort=20c?= =?UTF-8?q?omputations,=20move=20addElement=20and=20addRenderer=20out=20of?= =?UTF-8?q?=20generator?= 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 + }); } }; From b71ec52cae9834224319054c528766ddb4dcab1a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 12:02:29 -0500 Subject: [PATCH 07/14] move createAnchor out of generator --- src/generators/Generator.js | 13 ------------- src/generators/dom/index.js | 14 ++++++++++++-- src/generators/dom/visitors/EachBlock.js | 6 +++++- src/generators/dom/visitors/IfBlock.js | 6 +++++- src/generators/dom/visitors/YieldTag.js | 6 +++++- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 5693093fc6..4bf72a085f 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -96,19 +96,6 @@ export default class Generator { }; } - createAnchor ( _name, description = '' ) { - const name = `${_name}_anchor`; - const renderStatement = `document.createComment( ${JSON.stringify( description )} )`; - - this.fire( 'addElement', { - name, - renderStatement, - needsIdentifier: true - }); - - return name; - } - createMountStatement ( name ) { if ( this.current.target === 'target' ) { this.current.builders.mount.addLine( diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 0e045df9a5..878eb98539 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -56,8 +56,8 @@ export default function dom ( parsed, source, options, names ) { generator.on( 'addRenderer', addRenderer ); - generator.on( 'addElement', function addElement ({ name, renderStatement, needsIdentifier }) { - const isToplevel = this.current.localElementDepth === 0; + generator.on( 'addElement', ({ name, renderStatement, needsIdentifier }) => { + const isToplevel = generator.current.localElementDepth === 0; if ( needsIdentifier || isToplevel ) { generator.current.builders.init.addLine( `var ${name} = ${renderStatement};` @@ -77,6 +77,16 @@ export default function dom ( parsed, source, options, names ) { } }); + generator.on( 'createAnchor', ({ name, description = '' }) => { + const renderStatement = `document.createComment( ${JSON.stringify( description )} )`; + + generator.fire( 'addElement', { + name, + renderStatement, + needsIdentifier: true + }); + }); + let namespace = null; if ( templateProperties.namespace ) { const ns = templateProperties.namespace.value; diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 5e75abd441..0c7b28072b 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -18,7 +18,11 @@ export default { const { dependencies, snippet } = generator.contextualise( node.expression ); - const anchor = generator.createAnchor( name, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` ); + const anchor = `${name}_anchor`; + generator.fire( 'createAnchor', { + name: anchor, + description: `#each ${generator.source.slice( node.expression.start, node.expression.end )}` + }); generator.current.builders.init.addBlock( deindent` var ${name}_value = ${snippet}; diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index e95fefc7f1..4099113183 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -37,7 +37,11 @@ export default { const isToplevel = generator.current.localElementDepth === 0; const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `renderIfBlock` ) ); - const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` ); + const anchor = `${name}_anchor`; + generator.fire( 'createAnchor', { + name: anchor, + description: `#if ${generator.source.slice( node.expression.start, node.expression.end )}` + }); generator.current.builders.init.addBlock( deindent` function ${getBlock} ( ${params} ) { diff --git a/src/generators/dom/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js index 68c850b66b..583964d129 100644 --- a/src/generators/dom/visitors/YieldTag.js +++ b/src/generators/dom/visitors/YieldTag.js @@ -1,6 +1,10 @@ export default { enter ( generator ) { - const anchor = generator.createAnchor( 'yield', 'yield' ); + const anchor = `yield_anchor`; + generator.fire( 'createAnchor', { + name: anchor, + description: 'yield' + }); generator.current.builders.mount.addLine( `component.yield && component.yield.mount( ${generator.current.target}, ${anchor} );` From 47f64be928e72a6834f4f30ba6fe0c230ce8dc44 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 13:45:17 -0500 Subject: [PATCH 08/14] more refactoring --- src/generators/Generator.js | 107 ++++++++++++----------- src/generators/dom/index.js | 81 +++++++---------- src/generators/dom/utils/getBuilders.js | 12 +++ src/generators/dom/visitors/Component.js | 5 +- src/generators/dom/visitors/EachBlock.js | 8 +- src/generators/dom/visitors/Element.js | 2 +- src/generators/dom/visitors/IfBlock.js | 11 ++- 7 files changed, 124 insertions(+), 102 deletions(-) create mode 100644 src/generators/dom/utils/getBuilders.js diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 4bf72a085f..fc9decf262 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -1,11 +1,11 @@ -import MagicString from 'magic-string'; -import CodeBuilder from '../utils/CodeBuilder.js'; +import MagicString, { Bundle } from 'magic-string'; 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'; +import getIntro from './shared/utils/getIntro.js'; +import getOutro from './shared/utils/getOutro.js'; export default class Generator { constructor ( parsed, source, names, visitors ) { @@ -14,22 +14,17 @@ export default class Generator { this.names = names; this.visitors = visitors; - this.templateProperties = {}; + this.imports = []; this.helpers = {}; this.components = {}; this.events = {}; - this.imports = []; - this.computations = []; - this.code = new MagicString( source ); this.getUniqueName = counter( names ); this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.usesRefs = false; this._callbacks = {}; - - this.init(); } addSourcemapLocations ( node ) { @@ -96,17 +91,6 @@ export default class Generator { }; } - 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} );` ); - } - } - fire ( eventName, data ) { const handlers = eventName in this._callbacks && this._callbacks[ eventName ].slice(); if ( !handlers ) return; @@ -116,30 +100,45 @@ export default class Generator { } } - generateBlock ( node, name ) { - this.push({ - name, - target: 'target', - localElementDepth: 0, - builders: this.getBuilders(), - getUniqueName: this.getUniqueNameMaker() + generate ( result, options, { constructorName, format } ) { + const pattern = /\[✂(\d+)-(\d+)$/; + + const parts = result.split( '✂]' ); + const finalChunk = parts.pop(); + + const compiled = new Bundle({ separator: '' }); + + function addString ( str ) { + compiled.addSource({ + content: new MagicString( str ) + }); + } + + const intro = getIntro( format, options, this.imports ); + if ( intro ) addString( intro ); + + const { filename } = options; + + parts.forEach( str => { + const chunk = str.replace( pattern, '' ); + if ( chunk ) addString( chunk ); + + const match = pattern.exec( str ); + + const snippet = this.code.snip( +match[1], +match[2] ); + + compiled.addSource({ + filename, + content: snippet + }); }); - // walk the children here - node.children.forEach( node => this.visit( node ) ); - this.fire( 'addRenderer', this.current ); - this.pop(); - // unset the children, to avoid them being visited again - node.children = []; - } - getBuilders () { + addString( finalChunk ); + addString( '\n\n' + getOutro( format, constructorName, options, this.imports ) ); + return { - init: new CodeBuilder(), - mount: new CodeBuilder(), - update: new CodeBuilder(), - detach: new CodeBuilder(), - detachRaw: new CodeBuilder(), - teardown: new CodeBuilder() + code: compiled.toString(), + map: compiled.generateMap({ includeContent: true }) }; } @@ -147,10 +146,14 @@ export default class Generator { return counter( this.names ); } - init () { - const { computations, imports, source } = this; + parseJs () { + const { source } = this; const { js } = this.parsed; + const imports = this.imports; + const computations = []; + const templateProperties = {}; + if ( js ) { this.addSourcemapLocations( js.content ); @@ -188,7 +191,7 @@ export default class Generator { } defaultExport.declaration.properties.forEach( prop => { - this.templateProperties[ prop.key.name ] = prop.value; + templateProperties[ prop.key.name ] = prop.value; }); this.code.prependRight( js.content.start, 'var template = (function () {' ); @@ -199,17 +202,17 @@ export default class Generator { this.code.appendLeft( js.content.end, '}());' ); [ 'helpers', 'events', 'components' ].forEach( key => { - if ( this.templateProperties[ key ] ) { - this.templateProperties[ key ].properties.forEach( prop => { + if ( templateProperties[ key ] ) { + templateProperties[ key ].properties.forEach( prop => { this[ key ][ prop.key.name ] = prop.value; }); } }); - if ( this.templateProperties.computed ) { + if ( templateProperties.computed ) { const dependencies = new Map(); - this.templateProperties.computed.properties.forEach( prop => { + templateProperties.computed.properties.forEach( prop => { const key = prop.key.name; const value = prop.value; @@ -231,9 +234,15 @@ export default class Generator { computations.push({ key, deps }); } - this.templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); + templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); } } + + return { + computations, + imports, + templateProperties + }; } on ( eventName, handler ) { diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 878eb98539..e9ac7b613a 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -1,9 +1,7 @@ -import MagicString, { Bundle } from 'magic-string'; import deindent from '../../utils/deindent.js'; +import getBuilders from './utils/getBuilders.js'; import CodeBuilder from '../../utils/CodeBuilder.js'; import namespaces from '../../utils/namespaces.js'; -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 Generator from '../Generator.js'; @@ -13,7 +11,7 @@ export default function dom ( parsed, source, options, names ) { const generator = new Generator( parsed, source, names, visitors ); - const { computations, imports, templateProperties } = generator; // TODO make this generator.parseJs() or similar? + const { computations, imports, templateProperties } = generator.parseJs(); const renderers = []; function addRenderer ( fragment ) { @@ -63,7 +61,7 @@ export default function dom ( parsed, source, options, names ) { `var ${name} = ${renderStatement};` ); - generator.createMountStatement( name ); + generator.fire( 'createMountStatement', name ); } else { generator.current.builders.init.addLine( `${generator.current.target}.appendChild( ${renderStatement} );` @@ -87,6 +85,35 @@ export default function dom ( parsed, source, options, names ) { }); }); + generator.on( '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} );` ); + } + }); + + generator.on( 'generateBlock', ({ node, name }) => { + generator.push({ + name, + target: 'target', + localElementDepth: 0, + builders: getBuilders(), + getUniqueName: generator.getUniqueNameMaker() + }); + + // walk the children here + node.children.forEach( node => generator.visit( node ) ); + generator.fire( 'addRenderer', generator.current ); + generator.pop(); + + // unset the children, to avoid them being visited again + node.children = []; + }); + let namespace = null; if ( templateProperties.namespace ) { const ns = templateProperties.namespace.value; @@ -109,7 +136,7 @@ export default function dom ( parsed, source, options, names ) { indexNames: {}, listNames: {}, - builders: generator.getBuilders(), + builders: getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); @@ -353,45 +380,5 @@ export default function dom ( parsed, source, options, names ) { builders.main.addBlock( `${constructorName}.prototype = template.methods;` ); } - const result = builders.main.toString(); - - const pattern = /\[✂(\d+)-(\d+)$/; - - const parts = result.split( '✂]' ); - const finalChunk = parts.pop(); - - const compiled = new Bundle({ separator: '' }); - - function addString ( str ) { - compiled.addSource({ - content: new MagicString( str ) - }); - } - - const intro = getIntro( format, options, imports ); - if ( intro ) addString( intro ); - - const { filename } = options; - - parts.forEach( str => { - const chunk = str.replace( pattern, '' ); - if ( chunk ) addString( chunk ); - - const match = pattern.exec( str ); - - const snippet = generator.code.snip( +match[1], +match[2] ); - - compiled.addSource({ - filename, - content: snippet - }); - }); - - addString( finalChunk ); - addString( '\n\n' + getOutro( format, constructorName, options, imports ) ); - - return { - code: compiled.toString(), - map: compiled.generateMap({ includeContent: true }) - }; + return generator.generate( builders.main.toString(), options, { constructorName, format } ); } diff --git a/src/generators/dom/utils/getBuilders.js b/src/generators/dom/utils/getBuilders.js new file mode 100644 index 0000000000..ca66c1bec5 --- /dev/null +++ b/src/generators/dom/utils/getBuilders.js @@ -0,0 +1,12 @@ +import CodeBuilder from '../../../utils/CodeBuilder.js'; + +export default function getBuilders () { + return { + init: new CodeBuilder(), + mount: new CodeBuilder(), + update: new CodeBuilder(), + detach: new CodeBuilder(), + detachRaw: new CodeBuilder(), + teardown: new CodeBuilder() + }; +} diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js index 31d52c777e..ede9332c6b 100644 --- a/src/generators/dom/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -33,7 +33,10 @@ export default { if ( hasChildren ) { const yieldName = generator.current.getUniqueName( `render${name}YieldFragment` ); - generator.generateBlock( node, yieldName ); + generator.fire( 'generateBlock', { + node, + name: yieldName + }); generator.current.builders.init.addLine( `var ${name}_yieldFragment = ${yieldName}( root, component );` diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 0c7b28072b..372f3bc10a 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -1,4 +1,5 @@ import deindent from '../../../utils/deindent.js'; +import getBuilders from '../utils/getBuilders.js'; export default { enter ( generator, node ) { @@ -105,7 +106,10 @@ export default { } if ( node.else ) { - generator.generateBlock( node.else, renderElse ); + generator.fire( 'generateBlock', { + node: node.else, + name: renderElse + }); } const indexNames = Object.assign( {}, generator.current.indexNames ); @@ -140,7 +144,7 @@ export default { listNames, params: blockParams, - builders: generator.getBuilders(), + builders: getBuilders(), getUniqueName: generator.getUniqueNameMaker() }); diff --git a/src/generators/dom/visitors/Element.js b/src/generators/dom/visitors/Element.js index 39ca4b297e..4b908fc01b 100644 --- a/src/generators/dom/visitors/Element.js +++ b/src/generators/dom/visitors/Element.js @@ -82,7 +82,7 @@ export default { generator.current.builders.init.addBlock( local.init ); if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update ); - generator.createMountStatement( name ); + generator.fire( 'createMountStatement', name ); generator.push({ namespace: local.namespace, diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 4099113183..9920548463 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -8,7 +8,11 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { condition: generator.contextualise( node.expression ).snippet, block: name }]; - generator.generateBlock( node, name ); + + generator.fire( 'generateBlock', { + node, + name + }); if ( node.else && node.else.children.length === 1 && node.else.children[0].type === 'IfBlock' ) { @@ -21,7 +25,10 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { block: node.else ? name : null, }); if (node.else) { - generator.generateBlock( node.else, name ); + generator.fire( 'generateBlock', { + node: node.else, + name + }); } } return conditionsAndBlocks; From 4df7cfa0f8f0f6f4f1102516cdc4ee0763f062c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 16:17:31 -0500 Subject: [PATCH 09/14] use shared generator for SSR compiler --- src/generators/Generator.js | 10 + src/generators/server-side-rendering/index.js | 386 +++--------------- .../server-side-rendering/visitors/Comment.js | 3 + .../visitors/Component.js | 46 +++ .../visitors/EachBlock.js | 32 ++ .../server-side-rendering/visitors/Element.js | 51 +++ .../server-side-rendering/visitors/IfBlock.js | 12 + .../visitors/MustacheTag.js | 6 + .../visitors/RawMustacheTag.js | 6 + .../server-side-rendering/visitors/Text.js | 9 + .../visitors/YieldTag.js | 9 + .../server-side-rendering/visitors/index.js | 19 + .../comment/_actual.html | 4 +- .../component-data-dynamic/_actual.html | 6 +- .../component-data-static/_actual.html | 2 +- .../computed/_actual.html | 2 +- .../empty-elements-closed/_actual.html | 2 +- .../import-non-component/_actual.html | 2 +- .../styles-nested/_actual.html | 8 +- test/ssr.js | 4 +- 20 files changed, 273 insertions(+), 346 deletions(-) create mode 100644 src/generators/server-side-rendering/visitors/Comment.js create mode 100644 src/generators/server-side-rendering/visitors/Component.js create mode 100644 src/generators/server-side-rendering/visitors/EachBlock.js create mode 100644 src/generators/server-side-rendering/visitors/Element.js create mode 100644 src/generators/server-side-rendering/visitors/IfBlock.js create mode 100644 src/generators/server-side-rendering/visitors/MustacheTag.js create mode 100644 src/generators/server-side-rendering/visitors/RawMustacheTag.js create mode 100644 src/generators/server-side-rendering/visitors/Text.js create mode 100644 src/generators/server-side-rendering/visitors/YieldTag.js create mode 100644 src/generators/server-side-rendering/visitors/index.js diff --git a/src/generators/Generator.js b/src/generators/Generator.js index fc9decf262..192782e7a7 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -19,6 +19,8 @@ export default class Generator { this.components = {}; this.events = {}; + this.elementDepth = 0; + this.code = new MagicString( source ); this.getUniqueName = counter( names ); this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; @@ -271,12 +273,20 @@ export default class Generator { if ( visitor.enter ) visitor.enter( this, node ); + if ( visitor.type === 'Element' ) { + this.elementDepth += 1; + } + if ( node.children ) { node.children.forEach( child => { this.visit( child ); }); } + if ( visitor.type === 'Element' ) { + this.elementDepth -= 1; + } + if ( visitor.leave ) visitor.leave( this, node ); } } diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index 28b7873a92..cdb096cdba 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -1,263 +1,35 @@ -import MagicString, { Bundle } from 'magic-string'; -import { walk } from 'estree-walker'; import deindent from '../../utils/deindent.js'; -import isReference from '../../utils/isReference.js'; -import flattenReference from '../../utils/flattenReference.js'; -import voidElementNames from '../../utils/voidElementNames.js'; +import CodeBuilder from '../../utils/CodeBuilder.js'; import processCss from '../shared/css/process.js'; +import visitors from './visitors/index.js'; +import Generator from '../Generator.js'; -export default function compile ( parsed, source, { filename }) { - const code = new MagicString( source ); +export default function ssr ( parsed, source, options, names ) { + const format = options.format || 'cjs'; + const constructorName = options.name || 'SvelteComponent'; - const templateProperties = {}; - const components = {}; - const helpers = {}; + const generator = new Generator( parsed, source, names, visitors ); - const imports = []; + const { computations, imports, templateProperties } = generator.parseJs(); - if ( parsed.js ) { - walk( parsed.js.content, { - enter ( node ) { - code.addSourcemapLocation( node.start ); - code.addSourcemapLocation( node.end ); - } - }); - - // imports need to be hoisted out of the IIFE - for ( let i = 0; i < parsed.js.content.body.length; i += 1 ) { - const node = parsed.js.content.body[i]; - if ( node.type === 'ImportDeclaration' ) { - let a = node.start; - let b = node.end; - while ( /[ \t]/.test( source[ a - 1 ] ) ) a -= 1; - while ( source[b] === '\n' ) b += 1; - - //imports.push( source.slice( a, b ).replace( /^\s/, '' ) ); - imports.push( node ); - code.remove( a, b ); - } - } - - const defaultExport = parsed.js.content.body.find( node => node.type === 'ExportDefaultDeclaration' ); - - if ( defaultExport ) { - const finalNode = parsed.js.content.body[ parsed.js.content.body.length - 1 ]; - if ( defaultExport === finalNode ) { - // export is last property, we can just return it - code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` ); - } else { - // TODO ensure `template` isn't already declared - code.overwrite( defaultExport.start, defaultExport.declaration.start, `var template = ` ); - - let i = defaultExport.start; - while ( /\s/.test( source[ i - 1 ] ) ) i--; - - const indentation = source.slice( i, defaultExport.start ); - code.appendLeft( finalNode.end, `\n\n${indentation}return template;` ); - } - - defaultExport.declaration.properties.forEach( prop => { - templateProperties[ prop.key.name ] = prop.value; - }); - - code.prependRight( parsed.js.content.start, 'var template = (function () {' ); - } else { - code.prependRight( parsed.js.content.start, '(function () {' ); - } - - code.appendLeft( parsed.js.content.end, '}());' ); - - if ( templateProperties.helpers ) { - templateProperties.helpers.properties.forEach( prop => { - helpers[ prop.key.name ] = prop.value; - }); - } - - if ( templateProperties.components ) { - templateProperties.components.properties.forEach( prop => { - components[ prop.key.name ] = prop.value; - }); - } - } - - let scope = new Set(); - const scopes = [ scope ]; - - function contextualise ( expression ) { - 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.` ); - return; - } - - if ( !scope.has( name ) ) { - code.prependRight( node.start, `data.` ); - } - - this.skip(); - } - } - }); - - return { - snippet: `[✂${expression.start}-${expression.end}✂]`, - string: code.slice( expression.start, expression.end ) - }; - } - - let elementDepth = 0; - - const stringifiers = { - Comment () { - return ''; - }, - - Component ( node ) { - const props = node.attributes.map( attribute => { - let value; - - if ( attribute.value === true ) { - value = `true`; - } else if ( attribute.value.length === 0 ) { - value = `''`; - } else if ( attribute.value.length === 1 ) { - const chunk = attribute.value[0]; - if ( chunk.type === 'Text' ) { - value = isNaN( parseFloat( chunk.data ) ) ? JSON.stringify( chunk.data ) : chunk.data; - } else { - const { snippet } = contextualise( chunk.expression ); - value = snippet; - } - } else { - value = '`' + attribute.value.map( stringify ).join( '' ) + '`'; - } - - return `${attribute.name}: ${value}`; - }).join( ', ' ); - - let params = `{${props}}`; - - if ( node.children.length ) { - params += `, { yield: () => \`${node.children.map( stringify ).join( '' )}\` }`; - } - - return `\${template.components.${node.name}.render(${params})}`; - }, - - EachBlock ( node ) { - const { snippet } = contextualise( node.expression ); - - scope = new Set(); - scope.add( node.context ); - if ( node.index ) scope.add( node.index ); - - scopes.push( scope ); - - const block = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \`${ node.children.map( stringify ).join( '' )}\` ).join( '' )}`; - - scopes.pop(); - scope = scopes[ scopes.length - 1 ]; - - return block; - }, - - Element ( node ) { - if ( node.name in components ) { - return stringifiers.Component( node ); - } - - let element = `<${node.name}`; - - node.attributes.forEach( attribute => { - if ( attribute.type !== 'Attribute' ) return; - - let str = ` ${attribute.name}`; - - if ( attribute.value !== true ) { - str += `="` + attribute.value.map( chunk => { - if ( chunk.type === 'Text' ) { - return chunk.data; - } - - const { snippet } = contextualise( chunk.expression ); - return '${' + snippet + '}'; - }).join( '' ) + `"`; - } - - element += str; - }); - - if ( parsed.css && elementDepth === 0 ) { - element += ` svelte-${parsed.hash}`; - } - - if ( voidElementNames.test( node.name ) ) { - element += '>'; - } else { - elementDepth += 1; - element += '>' + node.children.map( stringify ).join( '' ) + ``; - elementDepth -= 1; - } - - return element; - }, - - IfBlock ( node ) { - const { snippet } = contextualise( node.expression ); // TODO use snippet, for sourcemap support - - const consequent = node.children.map( stringify ).join( '' ); - const alternate = node.else ? node.else.children.map( stringify ).join( '' ) : ''; - - return '${ ' + snippet + ' ? `' + consequent + '` : `' + alternate + '` }'; - }, - - MustacheTag ( node ) { - const { snippet } = contextualise( node.expression ); // TODO use snippet, for sourcemap support - return '${__escape( String( ' + snippet + ') )}'; - }, + generator.push({ + contexts: {}, + indexes: {} + }); - RawMustacheTag ( node ) { - const { snippet } = contextualise( node.expression ); // TODO use snippet, for sourcemap support - return '${' + snippet + '}'; - }, + let renderCode = ''; + generator.on( 'append', str => { + renderCode += str; + }); - Text ( node ) { - return node.data.replace( /\${/g, '\\${' ); - }, + parsed.html.children.forEach( node => generator.visit( node ) ); - YieldTag () { - return `\${options.yield()}`; - } + const builders = { + main: new CodeBuilder(), + render: new CodeBuilder(), + renderCss: new CodeBuilder() }; - function stringify ( node ) { - const stringifier = stringifiers[ node.type ]; - - if ( !stringifier ) { - throw new Error( `Not implemented: ${node.type}` ); - } - - return stringifier( node ); - } - - function createBlock ( node ) { - const str = stringify( node ); - if ( str.slice( 0, 2 ) === '${' ) return str.slice( 2, -1 ); - return '`' + str + '`'; - } - - const blocks = parsed.html.children.map( node => { - return deindent` - rendered += ${createBlock( node )}; - `; - }); - - const topLevelStatements = []; - const importBlock = imports .map( ( declaration, i ) => { const defaultImport = declaration.specifiers.find( x => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' ); @@ -285,63 +57,38 @@ export default function compile ( parsed, source, { filename }) { if ( parsed.js ) { if ( imports.length ) { - topLevelStatements.push( importBlock ); + builders.main.addBlock( importBlock ); } - topLevelStatements.push( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); + builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); } - const renderStatements = [ - templateProperties.data ? `data = Object.assign( template.data(), data || {} );` : `data = data || {};` - ]; + builders.main.addBlock( `var ${constructorName} = {};` ); - if ( templateProperties.computed ) { - const statements = []; - const dependencies = new Map(); - - templateProperties.computed.properties.forEach( prop => { - const key = prop.key.name; - const value = prop.value; + builders.render.addLine( + templateProperties.data ? `root = Object.assign( template.data(), root || {} );` : `root = root || {};` + ); - const deps = value.params.map( param => param.name ); - dependencies.set( key, deps ); + if ( computations.length ) { + computations.forEach( ({ key, deps }) => { + builders.render.addLine( + `root.${key} = template.computed.${key}( ${deps.map( dep => `root.${dep}` ).join( ', ' )} );` + ); }); - - 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 ); - - statements.push( deindent` - data.${key} = template.computed.${key}( ${deps.map( dep => `data.${dep}` ).join( ', ' )} ); - ` ); - } - - templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) ); - - renderStatements.push( statements.join( '\n' ) ); } - renderStatements.push( - `var rendered = '';`, - blocks.join( '\n\n' ), - `return rendered;` + builders.render.addBlock( + `return \`${renderCode}\`;` ); - const renderCssStatements = [ + builders.renderCss.addBlock( `var components = [];` - ]; + ); if ( parsed.css ) { - renderCssStatements.push( deindent` + builders.renderCss.addBlock( deindent` components.push({ - filename: exports.filename, + filename: ${constructorName}.filename, css: ${JSON.stringify( processCss( parsed ) )}, map: null // TODO }); @@ -349,7 +96,7 @@ export default function compile ( parsed, source, { filename }) { } if ( templateProperties.components ) { - renderCssStatements.push( deindent` + builders.renderCss.addBlock( deindent` var seen = {}; function addComponent ( component ) { @@ -362,10 +109,12 @@ export default function compile ( parsed, source, { filename }) { } ` ); - renderCssStatements.push( templateProperties.components.properties.map( prop => `addComponent( template.components.${prop.key.name} );` ).join( '\n' ) ); + templateProperties.components.properties.forEach( prop => { + builders.renderCss.addLine( `addComponent( template.components.${prop.key.name} );` ); + }); } - renderCssStatements.push( deindent` + builders.renderCss.addBlock( deindent` return { css: components.map( x => x.css ).join( '\\n' ), map: null, @@ -373,15 +122,15 @@ export default function compile ( parsed, source, { filename }) { }; ` ); - topLevelStatements.push( deindent` - exports.filename = ${JSON.stringify( filename )}; + builders.main.addBlock( deindent` + ${constructorName}.filename = ${JSON.stringify( options.filename )}; - exports.render = function ( data, options ) { - ${renderStatements.join( '\n\n' )} + ${constructorName}.render = function ( root, options ) { + ${builders.render} }; - exports.renderCss = function () { - ${renderCssStatements.join( '\n\n' )} + ${constructorName}.renderCss = function () { + ${builders.renderCss} }; var escaped = { @@ -393,42 +142,15 @@ export default function compile ( parsed, source, { filename }) { }; function __escape ( html ) { - return html.replace( /["'&<>]/g, match => escaped[ match ] ); + return String( html ).replace( /["'&<>]/g, match => escaped[ match ] ); } ` ); - const rendered = topLevelStatements.join( '\n\n' ); + const result = builders.main.toString(); - const pattern = /\[✂(\d+)-(\d+)$/; + const generated = generator.generate( result, options, { constructorName, format } ); - const parts = rendered.split( '✂]' ); - const finalChunk = parts.pop(); + // console.log( generated.code ) - const compiled = new Bundle({ separator: '' }); - - function addString ( str ) { - compiled.addSource({ - content: new MagicString( str ) - }); - } - - parts.forEach( str => { - const chunk = str.replace( pattern, '' ); - if ( chunk ) addString( chunk ); - - const match = pattern.exec( str ); - - const snippet = code.snip( +match[1], +match[2] ); - - compiled.addSource({ - filename, - content: snippet - }); - }); - - addString( finalChunk ); - - return { - code: compiled.toString() - }; + return generated; } diff --git a/src/generators/server-side-rendering/visitors/Comment.js b/src/generators/server-side-rendering/visitors/Comment.js new file mode 100644 index 0000000000..f32a1e64a8 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/Comment.js @@ -0,0 +1,3 @@ +export default { + // do nothing +}; diff --git a/src/generators/server-side-rendering/visitors/Component.js b/src/generators/server-side-rendering/visitors/Component.js new file mode 100644 index 0000000000..0fe2237e1d --- /dev/null +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -0,0 +1,46 @@ +export default { + enter ( generator, node ) { + function stringify ( chunk ) { + if ( chunk.type === 'Text' ) return chunk.data; + if ( chunk.type === 'MustacheTag' ) { + const { snippet } = generator.contextualise( chunk.expression ); + return '${__escape( ' + snippet + ')}'; + } + } + + const props = node.attributes.map( attribute => { + let value; + + if ( attribute.value === true ) { + value = `true`; + } else if ( attribute.value.length === 0 ) { + value = `''`; + } else if ( attribute.value.length === 1 ) { + const chunk = attribute.value[0]; + if ( chunk.type === 'Text' ) { + value = isNaN( parseFloat( chunk.data ) ) ? JSON.stringify( chunk.data ) : chunk.data; + } else { + const { snippet } = generator.contextualise( chunk.expression ); + value = snippet; + } + } else { + value = '`' + attribute.value.map( stringify ).join( '' ) + '`'; + } + + return `${attribute.name}: ${value}`; + }).join( ', ' ); + + let open = `\${template.components.${node.name}.render({${props}}`; + + if ( node.children.length ) { + open += `, { yield: () => \``; + } + + generator.fire( 'append', open ); + }, + + leave ( generator, node ) { + const close = node.children.length ? `\` })}` : ')}'; + generator.fire( 'append', close ); + } +}; diff --git a/src/generators/server-side-rendering/visitors/EachBlock.js b/src/generators/server-side-rendering/visitors/EachBlock.js new file mode 100644 index 0000000000..5a7a8f80a8 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/EachBlock.js @@ -0,0 +1,32 @@ +export default { + enter ( generator, node ) { + const { dependencies, snippet } = generator.contextualise( node.expression ); + + const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``; + generator.fire( 'append', open ); + + // TODO should this be the generator's job? It's duplicated between + // here and the equivalent DOM compiler visitor + const contexts = Object.assign( {}, generator.current.contexts ); + contexts[ node.context ] = true; + + const indexes = Object.assign( {}, generator.current.indexes ); + if ( node.index ) indexes[ node.index ] = node.context; + + const contextDependencies = Object.assign( {}, generator.current.contextDependencies ); + contextDependencies[ node.context ] = dependencies; + + generator.push({ + contexts, + indexes, + contextDependencies + }); + }, + + leave ( generator ) { + const close = `\` ).join( '' )}`; + generator.fire( 'append', close ); + + generator.pop(); + } +}; diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js new file mode 100644 index 0000000000..fd36bf8f39 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -0,0 +1,51 @@ +import Component from './Component.js'; +import voidElementNames from '../../../utils/voidElementNames.js'; + +export default { + enter ( generator, node ) { + if ( node.name in generator.components ) { + Component.enter( generator, node ); + return; + } + + let openingTag = `<${node.name}`; + + node.attributes.forEach( attribute => { + if ( attribute.type !== 'Attribute' ) return; + + let str = ` ${attribute.name}`; + + if ( attribute.value !== true ) { + str += `="` + attribute.value.map( chunk => { + if ( chunk.type === 'Text' ) { + return chunk.data; + } + + const { snippet } = generator.contextualise( chunk.expression ); + return '${' + snippet + '}'; + }).join( '' ) + `"`; + } + + openingTag += str; + }); + + if ( generator.cssId && !generator.elementDepth ) { + openingTag += ` ${generator.cssId}`; + } + + openingTag += '>'; + + generator.fire( 'append', openingTag ); + }, + + leave ( generator, node ) { + if ( node.name in generator.components ) { + Component.leave( generator, node ); + return; + } + + if ( !voidElementNames.test( node.name ) ) { + generator.fire( 'append', `` ); + } + } +}; diff --git a/src/generators/server-side-rendering/visitors/IfBlock.js b/src/generators/server-side-rendering/visitors/IfBlock.js new file mode 100644 index 0000000000..892658b4f1 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/IfBlock.js @@ -0,0 +1,12 @@ +export default { + enter ( generator, node ) { + const { snippet } = generator.contextualise( node.expression ); + generator.fire( 'append', '${ ' + snippet + ' ? `' ); + }, + + leave ( generator, node ) { + generator.fire( 'append', '` : `' ); + if ( node.else ) node.else.children.forEach( child => generator.visit( child ) ); + generator.fire( 'append', '` }' ); + } +}; diff --git a/src/generators/server-side-rendering/visitors/MustacheTag.js b/src/generators/server-side-rendering/visitors/MustacheTag.js new file mode 100644 index 0000000000..d724cb8d54 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/MustacheTag.js @@ -0,0 +1,6 @@ +export default { + enter ( generator, node ) { + const { snippet } = generator.contextualise( node.expression ); + generator.fire( 'append', '${__escape( ' + snippet + ' )}' ); + } +}; diff --git a/src/generators/server-side-rendering/visitors/RawMustacheTag.js b/src/generators/server-side-rendering/visitors/RawMustacheTag.js new file mode 100644 index 0000000000..669d5c3455 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/RawMustacheTag.js @@ -0,0 +1,6 @@ +export default { + enter ( generator, node ) { + const { snippet } = generator.contextualise( node.expression ); + generator.fire( 'append', '${' + snippet + '}' ); + } +}; diff --git a/src/generators/server-side-rendering/visitors/Text.js b/src/generators/server-side-rendering/visitors/Text.js new file mode 100644 index 0000000000..6f2302c73b --- /dev/null +++ b/src/generators/server-side-rendering/visitors/Text.js @@ -0,0 +1,9 @@ +export default { + enter ( generator, node ) { + generator.fire( 'append', node.data.replace( /\${/g, '\\${' ) ); + }, + + leave ( generator ) { + + } +}; diff --git a/src/generators/server-side-rendering/visitors/YieldTag.js b/src/generators/server-side-rendering/visitors/YieldTag.js new file mode 100644 index 0000000000..d58468e9be --- /dev/null +++ b/src/generators/server-side-rendering/visitors/YieldTag.js @@ -0,0 +1,9 @@ +export default { + enter ( generator, node ) { + generator.fire( 'append', `\${options.yield()}` ); + }, + + leave ( generator ) { + + } +}; diff --git a/src/generators/server-side-rendering/visitors/index.js b/src/generators/server-side-rendering/visitors/index.js new file mode 100644 index 0000000000..8e125e5d8b --- /dev/null +++ b/src/generators/server-side-rendering/visitors/index.js @@ -0,0 +1,19 @@ +import Comment from './Comment.js'; +import EachBlock from './EachBlock.js'; +import Element from './Element.js'; +import IfBlock from './IfBlock.js'; +import MustacheTag from './MustacheTag.js'; +import RawMustacheTag from './RawMustacheTag.js'; +import Text from './Text.js'; +import YieldTag from './YieldTag.js'; + +export default { + Comment, + EachBlock, + Element, + IfBlock, + MustacheTag, + RawMustacheTag, + Text, + YieldTag +}; diff --git a/test/server-side-rendering/comment/_actual.html b/test/server-side-rendering/comment/_actual.html index bc1da94084..e2adc073dc 100644 --- a/test/server-side-rendering/comment/_actual.html +++ b/test/server-side-rendering/comment/_actual.html @@ -1,3 +1,3 @@

before

- -

after

\ No newline at end of file + +

after

\ No newline at end of file diff --git a/test/server-side-rendering/component-data-dynamic/_actual.html b/test/server-side-rendering/component-data-dynamic/_actual.html index 97800c17aa..6aedef46ee 100644 --- a/test/server-side-rendering/component-data-dynamic/_actual.html +++ b/test/server-side-rendering/component-data-dynamic/_actual.html @@ -1,4 +1,4 @@

foo: lol

-

baz: 42 (number)

-

qux: this is a piece of string

-

quux: core

\ No newline at end of file +

baz: 42 (number)

+

qux: this is a piece of string

+

quux: core

\ No newline at end of file diff --git a/test/server-side-rendering/component-data-static/_actual.html b/test/server-side-rendering/component-data-static/_actual.html index 9502567ec5..15a2100d4a 100644 --- a/test/server-side-rendering/component-data-static/_actual.html +++ b/test/server-side-rendering/component-data-static/_actual.html @@ -1,2 +1,2 @@

foo: bar

-

baz: 42 (number)

\ No newline at end of file +

baz: 42 (number)

\ No newline at end of file diff --git a/test/server-side-rendering/computed/_actual.html b/test/server-side-rendering/computed/_actual.html index f5a05c2882..099eebe604 100644 --- a/test/server-side-rendering/computed/_actual.html +++ b/test/server-side-rendering/computed/_actual.html @@ -1,2 +1,2 @@

1 + 2 = 3

-

3 * 3 = 9

\ No newline at end of file +

3 * 3 = 9

\ No newline at end of file diff --git a/test/server-side-rendering/empty-elements-closed/_actual.html b/test/server-side-rendering/empty-elements-closed/_actual.html index 76a9da4e6e..643f5b6b49 100644 --- a/test/server-side-rendering/empty-elements-closed/_actual.html +++ b/test/server-side-rendering/empty-elements-closed/_actual.html @@ -1,2 +1,2 @@ -

\ No newline at end of file +

\ No newline at end of file diff --git a/test/server-side-rendering/import-non-component/_actual.html b/test/server-side-rendering/import-non-component/_actual.html index e3d27c8e31..893a7b890b 100644 --- a/test/server-side-rendering/import-non-component/_actual.html +++ b/test/server-side-rendering/import-non-component/_actual.html @@ -1,2 +1,2 @@
i got 99 problems
-
the answer is 42
\ No newline at end of file +
the answer is 42
\ No newline at end of file diff --git a/test/server-side-rendering/styles-nested/_actual.html b/test/server-side-rendering/styles-nested/_actual.html index 91c42ef47f..1962e54454 100644 --- a/test/server-side-rendering/styles-nested/_actual.html +++ b/test/server-side-rendering/styles-nested/_actual.html @@ -1,5 +1,5 @@
red
-
green: foo
-
blue: foo
-
green: bar
-
blue: bar
\ No newline at end of file +
green: foo
+
blue: foo
+
green: bar
+
blue: bar
\ No newline at end of file diff --git a/test/ssr.js b/test/ssr.js index b985625922..828b10c797 100644 --- a/test/ssr.js +++ b/test/ssr.js @@ -1,7 +1,7 @@ import assert from 'assert'; import * as fs from 'fs'; -import { exists, tryToLoadJson } from './helpers.js'; +import { exists, setupHtmlEqual, tryToLoadJson } from './helpers.js'; function tryToReadFile ( file ) { try { @@ -17,6 +17,8 @@ describe( 'ssr', () => { require( process.env.COVERAGE ? '../src/server-side-rendering/register.js' : '../ssr/register' ); + + return setupHtmlEqual(); }); fs.readdirSync( 'test/server-side-rendering' ).forEach( dir => { From 32317a07a04e612c6e99f9f14b1a5c4f8ebef781 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 16:39:50 -0500 Subject: [PATCH 10/14] centralise import handling --- src/generators/Generator.js | 29 ++++++++++++++- src/generators/dom/index.js | 25 +------------ src/generators/dom/visitors/Component.js | 1 - src/generators/dom/visitors/Element.js | 3 +- src/generators/server-side-rendering/index.js | 37 +------------------ 5 files changed, 32 insertions(+), 63 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 192782e7a7..8653a1ad89 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -103,6 +103,34 @@ export default class Generator { } generate ( result, options, { constructorName, format } ) { + if ( this.imports.length ) { + const statements = []; + + this.imports.forEach( ( declaration, i ) => { + if ( format === 'es' ) { + statements.push( this.source.slice( declaration.start, declaration.end ) ); + return; + } + + 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 + + namedImports.forEach( specifier => { + statements.push( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` ); + }); + + if ( defaultImport ) { + statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); + } + }); + + result = `${statements.join( '\n' )}\n\n${result}`; + } + const pattern = /\[✂(\d+)-(\d+)$/; const parts = result.split( '✂]' ); @@ -242,7 +270,6 @@ export default class Generator { return { computations, - imports, templateProperties }; } diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index e9ac7b613a..f5c8ac19b4 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -11,7 +11,7 @@ export default function dom ( parsed, source, options, names ) { const generator = new Generator( parsed, source, names, visitors ); - const { computations, imports, templateProperties } = generator.parseJs(); + const { computations, templateProperties } = generator.parseJs(); const renderers = []; function addRenderer ( fragment ) { @@ -126,7 +126,6 @@ export default function dom ( parsed, source, options, names ) { name: 'renderMainFragment', namespace, target: 'target', - elementDepth: 0, localElementDepth: 0, contexts: {}, @@ -179,28 +178,6 @@ export default function dom ( parsed, source, options, names ) { dispatchObservers( observers.deferred, newState, oldState ); ` ); - imports.forEach( ( declaration, i ) => { - if ( format === 'es' ) { - builders.main.addLine( source.slice( declaration.start, declaration.end ) ); - return; - } - - 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 - - namedImports.forEach( specifier => { - builders.main.addLine( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` ); - }); - - if ( defaultImport ) { - builders.main.addLine( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); - } - }); - if ( parsed.js ) { builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); } diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js index ede9332c6b..81e682a233 100644 --- a/src/generators/dom/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -120,7 +120,6 @@ export default { namespace: local.namespace, target: name, parent: generator.current, - elementDepth: generator.current.elementDepth + 1, localElementDepth: generator.current.localElementDepth + 1 }); }, diff --git a/src/generators/dom/visitors/Element.js b/src/generators/dom/visitors/Element.js index 4b908fc01b..50fa0e3f32 100644 --- a/src/generators/dom/visitors/Element.js +++ b/src/generators/dom/visitors/Element.js @@ -61,7 +61,7 @@ export default { `var ${name} = document.createElementNS( '${local.namespace}', '${node.name}' );` : `var ${name} = document.createElement( '${node.name}' );`; - if ( generator.cssId && !generator.current.elementDepth ) { + if ( generator.cssId && !generator.elementDepth ) { render += `\n${name}.setAttribute( '${generator.cssId}', '' );`; } @@ -88,7 +88,6 @@ export default { namespace: local.namespace, target: name, parent: generator.current, - elementDepth: generator.current.elementDepth + 1, localElementDepth: generator.current.localElementDepth + 1 }); }, diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index cdb096cdba..f79961712f 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -10,7 +10,7 @@ export default function ssr ( parsed, source, options, names ) { const generator = new Generator( parsed, source, names, visitors ); - const { computations, imports, templateProperties } = generator.parseJs(); + const { computations, templateProperties } = generator.parseJs(); generator.push({ contexts: {}, @@ -30,36 +30,7 @@ export default function ssr ( parsed, source, options, names ) { renderCss: new CodeBuilder() }; - const importBlock = imports - .map( ( declaration, i ) => { - 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}`; - - const statements = [ - `var ${name} = require( '${declaration.source.value}' );` - ]; - - namedImports.forEach( specifier => { - statements.push( `var ${specifier.local.name} = ${name}.${specifier.imported.name};` ); - }); - - if ( defaultImport ) { - statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); - } - - return statements.join( '\n' ); - }) - .filter( Boolean ) - .join( '\n' ); - if ( parsed.js ) { - if ( imports.length ) { - builders.main.addBlock( importBlock ); - } - builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); } @@ -148,9 +119,5 @@ export default function ssr ( parsed, source, options, names ) { const result = builders.main.toString(); - const generated = generator.generate( result, options, { constructorName, format } ); - - // console.log( generated.code ) - - return generated; + return generator.generate( result, options, { constructorName, format } ); } From fd655f8c5bc2508660031104013188c83fb72a1c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 16:54:53 -0500 Subject: [PATCH 11/14] tidy up --- src/generators/Generator.js | 4 +- src/generators/dom/index.js | 9 ++-- src/generators/server-side-rendering/index.js | 50 +++++++++---------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/generators/Generator.js b/src/generators/Generator.js index 8653a1ad89..84bd2be367 100644 --- a/src/generators/Generator.js +++ b/src/generators/Generator.js @@ -102,7 +102,7 @@ export default class Generator { } } - generate ( result, options, { constructorName, format } ) { + generate ( result, options, { name, format } ) { if ( this.imports.length ) { const statements = []; @@ -164,7 +164,7 @@ export default class Generator { }); addString( finalChunk ); - addString( '\n\n' + getOutro( format, constructorName, options, this.imports ) ); + addString( '\n\n' + getOutro( format, name, options, this.imports ) ); return { code: compiled.toString(), diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index f5c8ac19b4..58cb848a5b 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -8,6 +8,7 @@ import Generator from '../Generator.js'; export default function dom ( parsed, source, options, names ) { const format = options.format || 'es'; + const name = options.name || 'SvelteComponent'; const generator = new Generator( parsed, source, names, visitors ); @@ -198,8 +199,6 @@ export default function dom ( parsed, source, options, names ) { let i = renderers.length; while ( i-- ) builders.main.addBlock( renderers[i] ); - const constructorName = options.name || 'SvelteComponent'; - if ( parsed.css && options.css !== false ) { builders.init.addLine( `if ( !addedCss ) addCss();` ); } @@ -249,7 +248,7 @@ export default function dom ( parsed, source, options, names ) { const initialState = templateProperties.data ? `Object.assign( template.data(), options.data )` : `options.data || {}`; builders.main.addBlock( deindent` - function ${constructorName} ( options ) { + function ${name} ( options ) { options = options || {}; var component = this;${generator.usesRefs ? `\nthis.refs = {}` : ``} @@ -354,8 +353,8 @@ export default function dom ( parsed, source, options, names ) { ` ); if ( templateProperties.methods ) { - builders.main.addBlock( `${constructorName}.prototype = template.methods;` ); + builders.main.addBlock( `${name}.prototype = template.methods;` ); } - return generator.generate( builders.main.toString(), options, { constructorName, format } ); + return generator.generate( builders.main.toString(), options, { name, format } ); } diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index f79961712f..b8e9c23f48 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -6,12 +6,19 @@ import Generator from '../Generator.js'; export default function ssr ( parsed, source, options, names ) { const format = options.format || 'cjs'; - const constructorName = options.name || 'SvelteComponent'; + const name = options.name || 'SvelteComponent'; const generator = new Generator( parsed, source, names, visitors ); const { computations, templateProperties } = generator.parseJs(); + const builders = { + main: new CodeBuilder(), + render: new CodeBuilder(), + renderCss: new CodeBuilder() + }; + + // create main render() function generator.push({ contexts: {}, indexes: {} @@ -24,34 +31,21 @@ export default function ssr ( parsed, source, options, names ) { parsed.html.children.forEach( node => generator.visit( node ) ); - const builders = { - main: new CodeBuilder(), - render: new CodeBuilder(), - renderCss: new CodeBuilder() - }; - - if ( parsed.js ) { - builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); - } - - builders.main.addBlock( `var ${constructorName} = {};` ); - builders.render.addLine( templateProperties.data ? `root = Object.assign( template.data(), root || {} );` : `root = root || {};` ); - if ( computations.length ) { - computations.forEach( ({ key, deps }) => { - builders.render.addLine( - `root.${key} = template.computed.${key}( ${deps.map( dep => `root.${dep}` ).join( ', ' )} );` - ); - }); - } + computations.forEach( ({ key, deps }) => { + builders.render.addLine( + `root.${key} = template.computed.${key}( ${deps.map( dep => `root.${dep}` ).join( ', ' )} );` + ); + }); builders.render.addBlock( `return \`${renderCode}\`;` ); + // create renderCss() function builders.renderCss.addBlock( `var components = [];` ); @@ -59,7 +53,7 @@ export default function ssr ( parsed, source, options, names ) { if ( parsed.css ) { builders.renderCss.addBlock( deindent` components.push({ - filename: ${constructorName}.filename, + filename: ${name}.filename, css: ${JSON.stringify( processCss( parsed ) )}, map: null // TODO }); @@ -93,14 +87,20 @@ export default function ssr ( parsed, source, options, names ) { }; ` ); + if ( parsed.js ) { + builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); + } + builders.main.addBlock( deindent` - ${constructorName}.filename = ${JSON.stringify( options.filename )}; + var ${name} = {}; + + ${name}.filename = ${JSON.stringify( options.filename )}; - ${constructorName}.render = function ( root, options ) { + ${name}.render = function ( root, options ) { ${builders.render} }; - ${constructorName}.renderCss = function () { + ${name}.renderCss = function () { ${builders.renderCss} }; @@ -119,5 +119,5 @@ export default function ssr ( parsed, source, options, names ) { const result = builders.main.toString(); - return generator.generate( result, options, { constructorName, format } ); + return generator.generate( result, options, { name, format } ); } From 8a99178070b0f188598762b5bd5c18545fa7990c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Dec 2016 17:02:15 -0500 Subject: [PATCH 12/14] lint --- src/generators/server-side-rendering/visitors/Text.js | 4 ---- src/generators/server-side-rendering/visitors/YieldTag.js | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/generators/server-side-rendering/visitors/Text.js b/src/generators/server-side-rendering/visitors/Text.js index 6f2302c73b..52e612e774 100644 --- a/src/generators/server-side-rendering/visitors/Text.js +++ b/src/generators/server-side-rendering/visitors/Text.js @@ -1,9 +1,5 @@ export default { enter ( generator, node ) { generator.fire( 'append', node.data.replace( /\${/g, '\\${' ) ); - }, - - leave ( generator ) { - } }; diff --git a/src/generators/server-side-rendering/visitors/YieldTag.js b/src/generators/server-side-rendering/visitors/YieldTag.js index d58468e9be..bc73a727e6 100644 --- a/src/generators/server-side-rendering/visitors/YieldTag.js +++ b/src/generators/server-side-rendering/visitors/YieldTag.js @@ -1,9 +1,5 @@ export default { - enter ( generator, node ) { + enter ( generator ) { generator.fire( 'append', `\${options.yield()}` ); - }, - - leave ( generator ) { - } }; From 443d7d825a1824b6d88682ead13b65a20a398450 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 17 Dec 2016 11:52:36 -0500 Subject: [PATCH 13/14] use DomGenerator subclass, instead of events --- src/generators/dom/index.js | 109 +++++++++--------- src/generators/dom/visitors/Component.js | 5 +- src/generators/dom/visitors/EachBlock.js | 12 +- src/generators/dom/visitors/Element.js | 2 +- src/generators/dom/visitors/IfBlock.js | 21 ++-- src/generators/dom/visitors/MustacheTag.js | 6 +- src/generators/dom/visitors/RawMustacheTag.js | 14 +-- src/generators/dom/visitors/Text.js | 6 +- src/generators/dom/visitors/YieldTag.js | 5 +- 9 files changed, 72 insertions(+), 108 deletions(-) diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 58cb848a5b..6c4e40dbd9 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -6,16 +6,34 @@ import processCss from '../shared/css/process.js'; import visitors from './visitors/index.js'; import Generator from '../Generator.js'; -export default function dom ( parsed, source, options, names ) { - const format = options.format || 'es'; - const name = options.name || 'SvelteComponent'; +class DomGenerator extends Generator { + constructor ( parsed, source, names, visitors ) { + super( parsed, source, names, visitors ); + this.renderers = []; + } - const generator = new Generator( parsed, source, names, visitors ); + addElement ( name, renderStatement, needsIdentifier = false ) { + const isToplevel = this.current.localElementDepth === 0; + if ( needsIdentifier || isToplevel ) { + this.current.builders.init.addLine( + `var ${name} = ${renderStatement};` + ); - const { computations, templateProperties } = generator.parseJs(); + this.createMountStatement( name ); + } else { + this.current.builders.init.addLine( + `${this.current.target}.appendChild( ${renderStatement} );` + ); + } - const renderers = []; - function addRenderer ( fragment ) { + if ( isToplevel ) { + this.current.builders.detach.addLine( + `${name}.parentNode.removeChild( ${name} );` + ); + } + } + + addRenderer ( fragment ) { if ( fragment.autofocus ) { fragment.builders.init.addLine( `${fragment.autofocus}.focus();` ); } @@ -32,7 +50,7 @@ export default function dom ( parsed, source, options, names ) { ` ); } - renderers.push( deindent` + this.renderers.push( deindent` function ${fragment.name} ( ${fragment.params}, component ) { ${fragment.builders.init} @@ -53,67 +71,48 @@ export default function dom ( parsed, source, options, names ) { ` ); } - generator.on( 'addRenderer', addRenderer ); - - generator.on( 'addElement', ({ name, renderStatement, needsIdentifier }) => { - const isToplevel = generator.current.localElementDepth === 0; - if ( needsIdentifier || isToplevel ) { - generator.current.builders.init.addLine( - `var ${name} = ${renderStatement};` - ); - - generator.fire( 'createMountStatement', name ); - } else { - generator.current.builders.init.addLine( - `${generator.current.target}.appendChild( ${renderStatement} );` - ); - } - - if ( isToplevel ) { - generator.current.builders.detach.addLine( - `${name}.parentNode.removeChild( ${name} );` - ); - } - }); - - generator.on( 'createAnchor', ({ name, description = '' }) => { + createAnchor ( name, description = '' ) { const renderStatement = `document.createComment( ${JSON.stringify( description )} )`; + this.addElement( name, renderStatement, true ); + } - generator.fire( 'addElement', { - name, - renderStatement, - needsIdentifier: true - }); - }); - - generator.on( 'createMountStatement', name => { - if ( generator.current.target === 'target' ) { - generator.current.builders.mount.addLine( + createMountStatement ( name ) { + if ( this.current.target === 'target' ) { + this.current.builders.mount.addLine( `target.insertBefore( ${name}, anchor );` ); } else { - generator.current.builders.init.addLine( - `${generator.current.target}.appendChild( ${name} );` ); + this.current.builders.init.addLine( + `${this.current.target}.appendChild( ${name} );` ); } - }); + } - generator.on( 'generateBlock', ({ node, name }) => { - generator.push({ + generateBlock ( node, name ) { + this.push({ name, target: 'target', localElementDepth: 0, builders: getBuilders(), - getUniqueName: generator.getUniqueNameMaker() + getUniqueName: this.getUniqueNameMaker() }); // walk the children here - node.children.forEach( node => generator.visit( node ) ); - generator.fire( 'addRenderer', generator.current ); - generator.pop(); + node.children.forEach( node => this.visit( node ) ); + this.addRenderer( this.current ); + this.pop(); // unset the children, to avoid them being visited again node.children = []; - }); + } +} + +export default function dom ( parsed, source, options, names ) { + const format = options.format || 'es'; + const name = options.name || 'SvelteComponent'; + + const generator = new DomGenerator( parsed, source, names, visitors ); + + const { computations, templateProperties } = generator.parseJs(); let namespace = null; if ( templateProperties.namespace ) { @@ -142,7 +141,7 @@ export default function dom ( parsed, source, options, names ) { parsed.html.children.forEach( node => generator.visit( node ) ); - addRenderer( generator.pop() ); + generator.addRenderer( generator.pop() ); const builders = { main: new CodeBuilder(), @@ -196,8 +195,8 @@ export default function dom ( parsed, source, options, names ) { ` ); } - let i = renderers.length; - while ( i-- ) builders.main.addBlock( renderers[i] ); + let i = generator.renderers.length; + while ( i-- ) builders.main.addBlock( generator.renderers[i] ); if ( parsed.css && options.css !== false ) { builders.init.addLine( `if ( !addedCss ) addCss();` ); diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js index 81e682a233..113417c2fe 100644 --- a/src/generators/dom/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -33,10 +33,7 @@ export default { if ( hasChildren ) { const yieldName = generator.current.getUniqueName( `render${name}YieldFragment` ); - generator.fire( 'generateBlock', { - node, - name: yieldName - }); + generator.generateBlock( node, yieldName ); generator.current.builders.init.addLine( `var ${name}_yieldFragment = ${yieldName}( root, component );` diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js index 372f3bc10a..b0ec2113e0 100644 --- a/src/generators/dom/visitors/EachBlock.js +++ b/src/generators/dom/visitors/EachBlock.js @@ -20,10 +20,7 @@ export default { const { dependencies, snippet } = generator.contextualise( node.expression ); const anchor = `${name}_anchor`; - generator.fire( 'createAnchor', { - name: anchor, - description: `#each ${generator.source.slice( node.expression.start, node.expression.end )}` - }); + generator.createAnchor( anchor, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` ); generator.current.builders.init.addBlock( deindent` var ${name}_value = ${snippet}; @@ -106,10 +103,7 @@ export default { } if ( node.else ) { - generator.fire( 'generateBlock', { - node: node.else, - name: renderElse - }); + generator.generateBlock( node.else, renderElse ); } const indexNames = Object.assign( {}, generator.current.indexNames ); @@ -159,7 +153,7 @@ export default { }, leave ( generator ) { - generator.fire( 'addRenderer', generator.current ); + generator.addRenderer( generator.current ); generator.pop(); } }; diff --git a/src/generators/dom/visitors/Element.js b/src/generators/dom/visitors/Element.js index 50fa0e3f32..67759ad60a 100644 --- a/src/generators/dom/visitors/Element.js +++ b/src/generators/dom/visitors/Element.js @@ -82,7 +82,7 @@ export default { generator.current.builders.init.addBlock( local.init ); if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update ); - generator.fire( 'createMountStatement', name ); + generator.createMountStatement( name ); generator.push({ namespace: local.namespace, diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js index 9920548463..02133e539d 100644 --- a/src/generators/dom/visitors/IfBlock.js +++ b/src/generators/dom/visitors/IfBlock.js @@ -9,26 +9,22 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { block: name }]; - generator.fire( 'generateBlock', { - node, - name - }); + generator.generateBlock( node, name ); if ( node.else && node.else.children.length === 1 && node.else.children[0].type === 'IfBlock' ) { conditionsAndBlocks.push( - ...getConditionsAndBlocks( generator, node.else.children[0], _name, i + 1 ) ); + ...getConditionsAndBlocks( generator, node.else.children[0], _name, i + 1 ) + ); } else { const name = `${_name}_${i + 1}`; conditionsAndBlocks.push({ condition: null, block: node.else ? name : null, }); - if (node.else) { - generator.fire( 'generateBlock', { - node: node.else, - name - }); + + if ( node.else ) { + generator.generateBlock( node.else, name ); } } return conditionsAndBlocks; @@ -45,10 +41,7 @@ export default { const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `renderIfBlock` ) ); const anchor = `${name}_anchor`; - generator.fire( 'createAnchor', { - name: anchor, - description: `#if ${generator.source.slice( node.expression.start, node.expression.end )}` - }); + generator.createAnchor( anchor, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` ); generator.current.builders.init.addBlock( deindent` function ${getBlock} ( ${params} ) { diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js index 231ba26557..f956a7be08 100644 --- a/src/generators/dom/visitors/MustacheTag.js +++ b/src/generators/dom/visitors/MustacheTag.js @@ -7,11 +7,7 @@ export default { generator.addSourcemapLocations( node.expression ); const { snippet } = generator.contextualise( node.expression ); - generator.fire( 'addElement', { - name, - renderStatement: `document.createTextNode( ${snippet} )`, - needsIdentifier: true - }); + generator.addElement( name, `document.createTextNode( ${snippet} )`, 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 3a2e4429bc..191b3c8a9d 100644 --- a/src/generators/dom/visitors/RawMustacheTag.js +++ b/src/generators/dom/visitors/RawMustacheTag.js @@ -10,18 +10,10 @@ export default { // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. const before = `${name}_before`; - generator.fire( 'addElement', { - name: before, - renderStatement: `document.createElement( 'noscript' )`, - needsIdentifier: true - }); - + generator.addElement( before, `document.createElement( 'noscript' )`, true ); + const after = `${name}_after`; - generator.fire( 'addElement', { - name: after, - renderStatement: `document.createElement( 'noscript' )`, - needsIdentifier: true - }); + generator.addElement( after, `document.createElement( 'noscript' )`, true ); const isToplevel = generator.current.localElementDepth === 0; diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js index 58090872bd..fcc9f9b447 100644 --- a/src/generators/dom/visitors/Text.js +++ b/src/generators/dom/visitors/Text.js @@ -5,10 +5,6 @@ export default { } const name = generator.current.getUniqueName( `text` ); - generator.fire( 'addElement', { - name, - renderStatement: `document.createTextNode( ${JSON.stringify( node.data )} )`, - needsIdentifier: false - }); + generator.addElement( name, `document.createTextNode( ${JSON.stringify( node.data )} )`, false ); } }; diff --git a/src/generators/dom/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js index 583964d129..a2ac7f57d7 100644 --- a/src/generators/dom/visitors/YieldTag.js +++ b/src/generators/dom/visitors/YieldTag.js @@ -1,10 +1,7 @@ export default { enter ( generator ) { const anchor = `yield_anchor`; - generator.fire( 'createAnchor', { - name: anchor, - description: 'yield' - }); + generator.createAnchor( anchor, 'yield' ); generator.current.builders.mount.addLine( `component.yield && component.yield.mount( ${generator.current.target}, ${anchor} );` From 603d61729efc698415eec7eea61edf56a5e06d13 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Sat, 17 Dec 2016 12:01:34 -0500 Subject: [PATCH 14/14] use SsrGenerator subclass --- src/generators/server-side-rendering/index.js | 20 ++++++++++++------- .../visitors/Component.js | 4 ++-- .../visitors/EachBlock.js | 4 ++-- .../server-side-rendering/visitors/Element.js | 4 ++-- .../server-side-rendering/visitors/IfBlock.js | 6 +++--- .../visitors/MustacheTag.js | 2 +- .../visitors/RawMustacheTag.js | 2 +- .../server-side-rendering/visitors/Text.js | 2 +- .../visitors/YieldTag.js | 2 +- 9 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index b8e9c23f48..d74c720036 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -4,11 +4,22 @@ import processCss from '../shared/css/process.js'; import visitors from './visitors/index.js'; import Generator from '../Generator.js'; +class SsrGenerator extends Generator { + constructor ( parsed, source, names, visitors ) { + super( parsed, source, names, visitors ); + this.renderCode = ''; + } + + append ( code ) { + this.renderCode += code; + } +} + export default function ssr ( parsed, source, options, names ) { const format = options.format || 'cjs'; const name = options.name || 'SvelteComponent'; - const generator = new Generator( parsed, source, names, visitors ); + const generator = new SsrGenerator( parsed, source, names, visitors ); const { computations, templateProperties } = generator.parseJs(); @@ -24,11 +35,6 @@ export default function ssr ( parsed, source, options, names ) { indexes: {} }); - let renderCode = ''; - generator.on( 'append', str => { - renderCode += str; - }); - parsed.html.children.forEach( node => generator.visit( node ) ); builders.render.addLine( @@ -42,7 +48,7 @@ export default function ssr ( parsed, source, options, names ) { }); builders.render.addBlock( - `return \`${renderCode}\`;` + `return \`${generator.renderCode}\`;` ); // create renderCss() function diff --git a/src/generators/server-side-rendering/visitors/Component.js b/src/generators/server-side-rendering/visitors/Component.js index 0fe2237e1d..e97ecaa5dd 100644 --- a/src/generators/server-side-rendering/visitors/Component.js +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -36,11 +36,11 @@ export default { open += `, { yield: () => \``; } - generator.fire( 'append', open ); + generator.append( open ); }, leave ( generator, node ) { const close = node.children.length ? `\` })}` : ')}'; - generator.fire( 'append', close ); + generator.append( close ); } }; diff --git a/src/generators/server-side-rendering/visitors/EachBlock.js b/src/generators/server-side-rendering/visitors/EachBlock.js index 5a7a8f80a8..de4bfffe6d 100644 --- a/src/generators/server-side-rendering/visitors/EachBlock.js +++ b/src/generators/server-side-rendering/visitors/EachBlock.js @@ -3,7 +3,7 @@ export default { const { dependencies, snippet } = generator.contextualise( node.expression ); const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``; - generator.fire( 'append', open ); + generator.append( open ); // TODO should this be the generator's job? It's duplicated between // here and the equivalent DOM compiler visitor @@ -25,7 +25,7 @@ export default { leave ( generator ) { const close = `\` ).join( '' )}`; - generator.fire( 'append', close ); + generator.append( close ); generator.pop(); } diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js index fd36bf8f39..863abf27db 100644 --- a/src/generators/server-side-rendering/visitors/Element.js +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -35,7 +35,7 @@ export default { openingTag += '>'; - generator.fire( 'append', openingTag ); + generator.append( openingTag ); }, leave ( generator, node ) { @@ -45,7 +45,7 @@ export default { } if ( !voidElementNames.test( node.name ) ) { - generator.fire( 'append', `` ); + generator.append( `` ); } } }; diff --git a/src/generators/server-side-rendering/visitors/IfBlock.js b/src/generators/server-side-rendering/visitors/IfBlock.js index 892658b4f1..a473965afd 100644 --- a/src/generators/server-side-rendering/visitors/IfBlock.js +++ b/src/generators/server-side-rendering/visitors/IfBlock.js @@ -1,12 +1,12 @@ export default { enter ( generator, node ) { const { snippet } = generator.contextualise( node.expression ); - generator.fire( 'append', '${ ' + snippet + ' ? `' ); + generator.append( '${ ' + snippet + ' ? `' ); }, leave ( generator, node ) { - generator.fire( 'append', '` : `' ); + generator.append( '` : `' ); if ( node.else ) node.else.children.forEach( child => generator.visit( child ) ); - generator.fire( 'append', '` }' ); + generator.append( '` }' ); } }; diff --git a/src/generators/server-side-rendering/visitors/MustacheTag.js b/src/generators/server-side-rendering/visitors/MustacheTag.js index d724cb8d54..9052e2627f 100644 --- a/src/generators/server-side-rendering/visitors/MustacheTag.js +++ b/src/generators/server-side-rendering/visitors/MustacheTag.js @@ -1,6 +1,6 @@ export default { enter ( generator, node ) { const { snippet } = generator.contextualise( node.expression ); - generator.fire( 'append', '${__escape( ' + snippet + ' )}' ); + generator.append( '${__escape( ' + snippet + ' )}' ); } }; diff --git a/src/generators/server-side-rendering/visitors/RawMustacheTag.js b/src/generators/server-side-rendering/visitors/RawMustacheTag.js index 669d5c3455..c84de16e2a 100644 --- a/src/generators/server-side-rendering/visitors/RawMustacheTag.js +++ b/src/generators/server-side-rendering/visitors/RawMustacheTag.js @@ -1,6 +1,6 @@ export default { enter ( generator, node ) { const { snippet } = generator.contextualise( node.expression ); - generator.fire( 'append', '${' + snippet + '}' ); + generator.append( '${' + snippet + '}' ); } }; diff --git a/src/generators/server-side-rendering/visitors/Text.js b/src/generators/server-side-rendering/visitors/Text.js index 52e612e774..16630a709d 100644 --- a/src/generators/server-side-rendering/visitors/Text.js +++ b/src/generators/server-side-rendering/visitors/Text.js @@ -1,5 +1,5 @@ export default { enter ( generator, node ) { - generator.fire( 'append', node.data.replace( /\${/g, '\\${' ) ); + generator.append( node.data.replace( /\${/g, '\\${' ) ); } }; diff --git a/src/generators/server-side-rendering/visitors/YieldTag.js b/src/generators/server-side-rendering/visitors/YieldTag.js index bc73a727e6..732f96301b 100644 --- a/src/generators/server-side-rendering/visitors/YieldTag.js +++ b/src/generators/server-side-rendering/visitors/YieldTag.js @@ -1,5 +1,5 @@ export default { enter ( generator ) { - generator.fire( 'append', `\${options.yield()}` ); + generator.append( `\${options.yield()}` ); } };