Merge pull request #453 from sveltejs/refactor

Refactor
pull/457/head
Rich Harris 8 years ago committed by GitHub
commit ec709cb490

@ -1,3 +1,5 @@
src/shared
shared.js
test/test.js
**/_actual.js
**/expected.js

@ -12,11 +12,10 @@ import processCss from './shared/processCss.js';
import annotateWithScopes from './annotateWithScopes.js';
export default class Generator {
constructor ( parsed, source, name, visitors, options ) {
constructor ( parsed, source, name, options ) {
this.parsed = parsed;
this.source = source;
this.name = name;
this.visitors = visitors;
this.options = options;
this.imports = [];
@ -31,8 +30,6 @@ export default class Generator {
// in dev mode
this.expectedProperties = new Set();
this.elementDepth = 0;
this.code = new MagicString( source );
this.css = parsed.css ? processCss( parsed, this.code ) : null;
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
@ -43,8 +40,6 @@ export default class Generator {
this.importedNames = new Set();
this._aliases = new Map();
this._usedNames = new Set();
this._callbacks = new Map();
}
addSourcemapLocations ( node ) {
@ -65,14 +60,14 @@ export default class Generator {
return alias;
}
contextualise ( expression, isEventHandler ) {
contextualise ( fragment, expression, isEventHandler ) {
this.addSourcemapLocations( expression );
const usedContexts = [];
const dependencies = [];
const { code, helpers } = this;
const { contextDependencies, contexts, indexes } = this.current;
const { contextDependencies, contexts, indexes } = fragment;
let scope = annotateWithScopes( expression );
@ -154,15 +149,6 @@ export default class Generator {
};
}
fire ( eventName, data ) {
const handlers = this._callbacks.has( eventName ) && this._callbacks.get( eventName ).slice();
if ( !handlers ) return;
for ( let i = 0; i < handlers.length; i += 1 ) {
handlers[i].call( this, data );
}
}
generate ( result, options, { name, format } ) {
if ( this.imports.length ) {
const statements = [];
@ -430,50 +416,4 @@ export default class Generator {
templateProperties
};
}
on ( eventName, handler ) {
if ( this._callbacks.has( eventName ) ) {
this._callbacks.get( eventName ).push( handler );
} else {
this._callbacks.set( eventName, [ handler ] );
}
}
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 ( 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 );
}
}

@ -0,0 +1,140 @@
import CodeBuilder from '../../utils/CodeBuilder.js';
import deindent from '../../utils/deindent.js';
export default class Block {
constructor ({ generator, name, key, expression, context, contextDependencies, contexts, indexes, params, indexNames, listNames }) {
this.generator = generator;
this.name = name;
this.key = key;
this.expression = expression;
this.context = context;
this.contexts = contexts;
this.indexes = indexes;
this.contextDependencies = contextDependencies;
this.params = params;
this.indexNames = indexNames;
this.listNames = listNames;
this.builders = {
create: new CodeBuilder(),
mount: new CodeBuilder(),
update: new CodeBuilder(),
detach: new CodeBuilder(),
detachRaw: new CodeBuilder(),
destroy: new CodeBuilder()
};
this.getUniqueName = generator.getUniqueNameMaker( params );
// unique names
this.component = this.getUniqueName( 'component' );
}
addElement ( name, renderStatement, parentNode, needsIdentifier = false ) {
const isToplevel = !parentNode;
if ( needsIdentifier || isToplevel ) {
this.builders.create.addLine(
`var ${name} = ${renderStatement};`
);
this.createMountStatement( name, parentNode );
} else {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${parentNode} );` );
}
if ( isToplevel ) {
this.builders.detach.addLine( `${this.generator.helper( 'detachNode' )}( ${name} );` );
}
}
child ( options ) {
return new Block( Object.assign( {}, this, options, { parent: this } ) );
}
createAnchor ( name, parentNode ) {
const renderStatement = `${this.generator.helper( 'createComment' )}()`;
this.addElement( name, renderStatement, parentNode, true );
}
createMountStatement ( name, parentNode ) {
if ( parentNode ) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
} else {
this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, target, anchor );` );
}
}
render () {
if ( this.autofocus ) {
this.builders.create.addLine( `${this.autofocus}.focus();` );
}
// minor hack we need to ensure that any {{{triples}}} are detached
// first, so we append normal detach statements to detachRaw
this.builders.detachRaw.addBlock( this.builders.detach );
if ( !this.builders.detachRaw.isEmpty() ) {
this.builders.destroy.addBlock( deindent`
if ( detach ) {
${this.builders.detachRaw}
}
` );
}
const properties = new CodeBuilder();
let localKey;
if ( this.key ) {
localKey = this.getUniqueName( 'key' );
properties.addBlock( `key: ${localKey},` );
}
if ( this.builders.mount.isEmpty() ) {
properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
mount: function ( target, anchor ) {
${this.builders.mount}
},
` );
}
if ( this.builders.update.isEmpty() ) {
properties.addBlock( `update: ${this.generator.helper( 'noop' )},` );
} else {
if ( this._tmp ) this.builders.update.addBlockAtStart( `var ${this._tmp};` );
properties.addBlock( deindent`
update: function ( changed, ${this.params.join( ', ' )} ) {
${this.builders.update}
},
` );
}
if ( this.builders.destroy.isEmpty() ) {
properties.addBlock( `destroy: ${this.generator.helper( 'noop' )}` );
} else {
properties.addBlock( deindent`
destroy: function ( detach ) {
${this.builders.destroy}
}
` );
}
return deindent`
function ${this.name} ( ${this.params.join( ', ' )}, ${this.component}${this.key ? `, ${localKey}` : ''} ) {
${this.builders.create}
return {
${properties}
};
}
`;
}
tmp () {
if ( !this._tmp ) this._tmp = this.getUniqueName( 'tmp' );
return this._tmp;
}
}

@ -1,14 +1,14 @@
import deindent from '../../utils/deindent.js';
import getBuilders from './utils/getBuilders.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import visitors from './visitors/index.js';
import visit from './visit.js';
import Generator from '../Generator.js';
import Block from './Block.js';
import * as shared from '../../shared/index.js';
class DomGenerator extends Generator {
constructor ( parsed, source, name, visitors, options ) {
super( parsed, source, name, visitors, options );
this.renderers = [];
constructor ( parsed, source, name, options ) {
super( parsed, source, name, options );
this.blocks = [];
this.uses = new Set();
// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
@ -17,121 +17,8 @@ class DomGenerator extends Generator {
};
}
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.helper( 'appendNode' )}( ${renderStatement}, ${this.current.target} );` );
}
if ( isToplevel ) {
this.current.builders.detach.addLine( `${this.helper( 'detachNode' )}( ${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}
}
` );
}
const properties = new CodeBuilder();
let localKey;
if ( fragment.key ) {
localKey = fragment.getUniqueName( 'key' );
properties.addBlock( `key: ${localKey},` );
}
if ( fragment.builders.mount.isEmpty() ) {
properties.addBlock( `mount: ${this.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
mount: function ( target, anchor ) {
${fragment.builders.mount}
},
` );
}
if ( fragment.builders.update.isEmpty() ) {
properties.addBlock( `update: ${this.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
update: function ( changed, ${fragment.params.join( ', ' )} ) {
${fragment.tmp ? `var ${fragment.tmp};` : ''}
${fragment.builders.update}
},
` );
}
if ( fragment.builders.teardown.isEmpty() ) {
properties.addBlock( `teardown: ${this.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
teardown: function ( detach ) {
${fragment.builders.teardown}
}
` );
}
this.renderers.push( deindent`
function ${fragment.name} ( ${fragment.params.join( ', ' )}, ${fragment.component}${fragment.key ? `, ${localKey}` : ''} ) {
${fragment.builders.init}
return {
${properties}
};
}
` );
}
createAnchor ( name ) {
const renderStatement = `${this.helper( 'createComment' )}()`;
this.addElement( name, renderStatement, true );
}
createMountStatement ( name ) {
if ( this.current.target === 'target' ) {
this.current.builders.mount.addLine( `${this.helper( 'insertNode' )}( ${name}, target, anchor );` );
} else {
this.current.builders.init.addLine( `${this.helper( 'appendNode' )}( ${name}, ${this.current.target} );` );
}
}
generateBlock ( node, name, type ) {
this.push({
type,
name,
target: 'target',
localElementDepth: 0,
builders: getBuilders(),
getUniqueName: this.getUniqueNameMaker( this.current.params )
});
// 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 = [];
addBlock ( block ) {
this.blocks.push( block );
}
helper ( name ) {
@ -149,19 +36,16 @@ export default function dom ( parsed, source, options ) {
const format = options.format || 'es';
const name = options.name || 'SvelteComponent';
const generator = new DomGenerator( parsed, source, name, visitors, options );
const generator = new DomGenerator( parsed, source, name, options );
const { computations, hasJs, templateProperties, namespace } = generator.parseJs();
const getUniqueName = generator.getUniqueNameMaker( [ 'root' ] );
const component = getUniqueName( 'component' );
generator.push({
type: 'block',
name: generator.alias( 'render_main_fragment' ),
namespace,
target: 'target',
localElementDepth: 0,
const mainBlock = new Block({
generator,
name: generator.alias( 'create_main_fragment' ),
key: null,
component,
@ -173,13 +57,20 @@ export default function dom ( parsed, source, options ) {
indexNames: new Map(),
listNames: new Map(),
builders: getBuilders(),
getUniqueName,
getUniqueName
});
parsed.html.children.forEach( node => generator.visit( node ) );
const state = {
namespace,
parentNode: null,
isTopLevel: true
};
parsed.html.children.forEach( node => {
visit( generator, mainBlock, state, node );
});
generator.addRenderer( generator.pop() );
generator.addBlock( mainBlock );
const builders = {
main: new CodeBuilder(),
@ -243,8 +134,8 @@ export default function dom ( parsed, source, options ) {
` );
}
let i = generator.renderers.length;
while ( i-- ) builders.main.addBlock( generator.renderers[i] );
let i = generator.blocks.length;
while ( i-- ) builders.main.addBlock( generator.blocks[i].render() );
builders.init.addLine( `this._torndown = false;` );
@ -259,7 +150,7 @@ export default function dom ( parsed, source, options ) {
if ( generator.hasComplexBindings ) {
builders.init.addBlock( deindent`
this._bindings = [];
this._fragment = ${generator.alias( 'render_main_fragment' )}( this._state, this );
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
while ( this._bindings.length ) this._bindings.pop()();
` );
@ -267,7 +158,7 @@ export default function dom ( parsed, source, options ) {
builders._set.addLine( `while ( this._bindings.length ) this._bindings.pop()();` );
} else {
builders.init.addBlock( deindent`
this._fragment = ${generator.alias( 'render_main_fragment' )}( this._state, this );
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
` );
}
@ -367,7 +258,7 @@ export default function dom ( parsed, source, options ) {
${name}.prototype.teardown = ${name}.prototype.destroy = function destroy ( detach ) {
this.fire( 'destroy' );${templateProperties.ondestroy ? `\n${generator.alias( 'template' )}.ondestroy.call( this );` : ``}
this._fragment.teardown( detach !== false );
this._fragment.destroy( detach !== false );
this._fragment = null;
this._state = {};

@ -1,4 +0,0 @@
export default function findBlock ( fragment ) {
while ( fragment.type !== 'block' ) fragment = fragment.parent;
return fragment;
}

@ -1,12 +0,0 @@
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()
};
}

@ -0,0 +1,6 @@
import visitors from './visitors/index.js';
export default function visit ( generator, block, state, node ) {
const visitor = visitors[ node.type ];
visitor( generator, block, state, node );
}

@ -1,3 +1,3 @@
export default {
export default function visitComment () {
// do nothing
};
}

@ -1,5 +1,6 @@
import deindent from '../../../utils/deindent.js';
import CodeBuilder from '../../../utils/CodeBuilder.js';
import visit from '../visit.js';
import addComponentAttributes from './attributes/addComponentAttributes.js';
function capDown ( name ) {
@ -18,35 +19,33 @@ function stringifyProps ( props ) {
return `{ ${joined} }`;
}
export default {
enter ( generator, node ) {
export default function visitComponent ( generator, block, state, node ) {
const hasChildren = node.children.length > 0;
const { current } = generator;
const name = current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const name = block.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const local = {
name,
namespace: current.namespace,
namespace: state.namespace,
isComponent: true,
allUsedContexts: [],
init: new CodeBuilder(),
create: new CodeBuilder(),
update: new CodeBuilder()
};
const isToplevel = current.localElementDepth === 0;
const isToplevel = !state.parentNode;
generator.hasComponents = true;
addComponentAttributes( generator, node, local );
addComponentAttributes( generator, block, node, local );
if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
@ -54,13 +53,13 @@ export default {
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}._context.root = root;`;
const listName = current.listNames.get( contextName );
const indexName = current.indexNames.get( contextName );
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' );
local.init.addBlock( deindent`
local.create.addBlock( deindent`
${name}._context = {
${initialProps}
};
@ -70,28 +69,39 @@ export default {
}
const componentInitProperties = [
`target: ${!isToplevel ? current.target: 'null'}`,
`_root: ${current.component}._root || ${current.component}`
`target: ${!isToplevel ? state.parentNode: 'null'}`,
`_root: ${block.component}._root || ${block.component}`
];
// Component has children, put them in a separate {{yield}} block
if ( hasChildren ) {
const yieldName = generator.getUniqueName( `render_${name}_yield_fragment` );
const params = current.params.join( ', ' );
const params = block.params.join( ', ' );
generator.generateBlock( node, yieldName, 'block' );
const childBlock = block.child({
name: generator.getUniqueName( `create_${name}_yield_fragment` ) // TODO should getUniqueName happen inside Fragment? probably
});
const childState = Object.assign( {}, state, {
parentNode: null
});
node.children.forEach( child => {
visit( generator, childBlock, childState, child );
});
const yieldFragment = current.getUniqueName( `${name}_yield_fragment` );
const yieldFragment = block.getUniqueName( `${name}_yield_fragment` );
current.builders.init.addLine(
`var ${yieldFragment} = ${yieldName}( ${params}, ${current.component} );`
block.builders.create.addLine(
`var ${yieldFragment} = ${childBlock.name}( ${params}, ${block.component} );`
);
current.builders.update.addLine(
block.builders.update.addLine(
`${yieldFragment}.update( changed, ${params} );`
);
componentInitProperties.push( `_yield: ${yieldFragment}`);
generator.addBlock( childBlock );
}
const statements = [];
@ -104,7 +114,7 @@ export default {
const initialPropString = stringifyProps( initialProps );
if ( local.bindings.length ) {
const initialData = current.getUniqueName( `${name}_initial_data` );
const initialData = block.getUniqueName( `${name}_initial_data` );
statements.push( `var ${name}_initial_data = ${initialPropString};` );
@ -120,7 +130,7 @@ export default {
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`;
local.init.addBlockAtStart( deindent`
local.create.addBlockAtStart( deindent`
${statements.join( '\n' )}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
@ -128,7 +138,7 @@ export default {
` );
if ( isToplevel ) {
current.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
block.builders.mount.addLine( `${name}._fragment.mount( target, anchor );` );
}
if ( local.dynamicAttributes.length ) {
@ -153,22 +163,8 @@ export default {
` );
}
current.builders.teardown.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` );
current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) current.builders.update.addBlock( local.update );
block.builders.destroy.addLine( `${name}.destroy( ${isToplevel ? 'detach' : 'false'} );` );
generator.push({
type: 'component',
namespace: local.namespace,
target: name,
parent: current,
localElementDepth: current.localElementDepth + 1,
key: null
});
},
leave ( generator ) {
generator.pop();
block.builders.create.addBlock( local.create );
if ( !local.update.isEmpty() ) block.builders.update.addBlock( local.update );
}
};

@ -1,54 +1,51 @@
import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js';
import visit from '../visit.js';
export default {
enter ( generator, node ) {
export default function visitEachBlock ( generator, block, state, node ) {
const name = generator.getUniqueName( `each_block` );
const renderer = generator.getUniqueName( `render_each_block` );
const renderer = generator.getUniqueName( `create_each_block` );
const elseName = generator.getUniqueName( `${name}_else` );
const renderElse = generator.getUniqueName( `${renderer}_else` );
const i = generator.current.getUniqueName( `i` );
const params = generator.current.params.join( ', ' );
const i = block.getUniqueName( `i` );
const params = block.params.join( ', ' );
const listName = generator.current.getUniqueName( `${name}_value` );
const listName = block.getUniqueName( `${name}_value` );
const isToplevel = generator.current.localElementDepth === 0;
const isToplevel = !state.parentNode;
generator.addSourcemapLocations( node.expression );
const { dependencies, snippet } = generator.contextualise( block, node.expression );
const { dependencies, snippet } = generator.contextualise( node.expression );
const anchor = generator.current.getUniqueName( `${name}_anchor` );
generator.createAnchor( anchor );
const anchor = block.getUniqueName( `${name}_anchor` );
block.createAnchor( anchor, state.parentNode );
const localVars = {};
localVars.iteration = generator.current.getUniqueName( `${name}_iteration` );
localVars.iterations = generator.current.getUniqueName( `${name}_iterations` );
localVars._iterations = generator.current.getUniqueName( `_${name}_iterations` );
localVars.lookup = generator.current.getUniqueName( `${name}_lookup` );
localVars._lookup = generator.current.getUniqueName( `_${name}_lookup` );
localVars.iteration = block.getUniqueName( `${name}_iteration` );
localVars.iterations = block.getUniqueName( `${name}_iterations` );
localVars._iterations = block.getUniqueName( `_${name}_iterations` );
localVars.lookup = block.getUniqueName( `${name}_lookup` );
localVars._lookup = block.getUniqueName( `_${name}_lookup` );
generator.current.builders.init.addLine( `var ${listName} = ${snippet};` );
generator.current.builders.init.addLine( `var ${localVars.iterations} = [];` );
if ( node.key ) generator.current.builders.init.addLine( `var ${localVars.lookup} = Object.create( null );` );
if ( node.else ) generator.current.builders.init.addLine( `var ${elseName} = null;` );
block.builders.create.addLine( `var ${listName} = ${snippet};` );
block.builders.create.addLine( `var ${localVars.iterations} = [];` );
if ( node.key ) block.builders.create.addLine( `var ${localVars.lookup} = Object.create( null );` );
if ( node.else ) block.builders.create.addLine( `var ${elseName} = null;` );
const initialRender = new CodeBuilder();
if ( node.key ) {
localVars.fragment = generator.current.getUniqueName( 'fragment' );
localVars.value = generator.current.getUniqueName( 'value' );
localVars.key = generator.current.getUniqueName( 'key' );
localVars.fragment = block.getUniqueName( 'fragment' );
localVars.value = block.getUniqueName( 'value' );
localVars.key = block.getUniqueName( 'key' );
initialRender.addBlock( deindent`
var ${localVars.key} = ${listName}[${i}].${node.key};
${localVars.iterations}[${i}] = ${localVars.lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
${localVars.iterations}[${i}] = ${localVars.lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${localVars.key}` : `` } );
` );
} else {
initialRender.addLine(
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );`
`${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} );`
);
}
@ -58,29 +55,29 @@ export default {
);
}
generator.current.builders.init.addBlock( deindent`
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
${initialRender}
}
` );
if ( node.else ) {
generator.current.builders.init.addBlock( deindent`
block.builders.create.addBlock( deindent`
if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${elseName} = ${renderElse}( ${params}, ${block.component} );
${!isToplevel ? `${elseName}.mount( ${anchor}.parentNode, ${anchor} );` : ''}
}
` );
}
if ( isToplevel ) {
generator.current.builders.mount.addBlock( deindent`
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
}
` );
if ( node.else ) {
generator.current.builders.mount.addBlock( deindent`
block.builders.mount.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
}
@ -89,7 +86,7 @@ export default {
}
if ( node.key ) {
generator.current.builders.update.addBlock( deindent`
block.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
var ${localVars._iterations} = [];
var ${localVars._lookup} = Object.create( null );
@ -105,7 +102,7 @@ export default {
${localVars._iterations}[${i}] = ${localVars._lookup}[ ${localVars.key} ] = ${localVars.lookup}[ ${localVars.key} ];
${localVars._lookup}[ ${localVars.key} ].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
} else {
${localVars._iterations}[${i}] = ${localVars._lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component}${node.key ? `, ${localVars.key}` : `` } );
${localVars._iterations}[${i}] = ${localVars._lookup}[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component}${node.key ? `, ${localVars.key}` : `` } );
}
${localVars._iterations}[${i}].mount( ${localVars.fragment}, null );
@ -115,7 +112,7 @@ export default {
for ( var ${i} = 0; ${i} < ${localVars.iterations}.length; ${i} += 1 ) {
var ${localVars.iteration} = ${localVars.iterations}[${i}];
if ( !${localVars._lookup}[ ${localVars.iteration}.key ] ) {
${localVars.iteration}.teardown( true );
${localVars.iteration}.destroy( true );
}
}
@ -125,83 +122,70 @@ export default {
${localVars.lookup} = ${localVars._lookup};
` );
} else {
generator.current.builders.update.addBlock( deindent`
block.builders.update.addBlock( deindent`
var ${listName} = ${snippet};
for ( var ${i} = 0; ${i} < ${listName}.length; ${i} += 1 ) {
if ( !${localVars.iterations}[${i}] ) {
${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${generator.current.component} );
${localVars.iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, ${block.component} );
${localVars.iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${localVars.iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
}
}
teardownEach( ${localVars.iterations}, true, ${listName}.length );
destroyEach( ${localVars.iterations}, true, ${listName}.length );
${localVars.iterations}.length = ${listName}.length;
` );
}
if ( node.else ) {
generator.current.builders.update.addBlock( deindent`
block.builders.update.addBlock( deindent`
if ( !${listName}.length && ${elseName} ) {
${elseName}.update( changed, ${params} );
} else if ( !${listName}.length ) {
${elseName} = ${renderElse}( ${params}, ${generator.current.component} );
${elseName} = ${renderElse}( ${params}, ${block.component} );
${elseName}.mount( ${anchor}.parentNode, ${anchor} );
} else if ( ${elseName} ) {
${elseName}.teardown( true );
${elseName}.destroy( true );
}
` );
}
generator.current.builders.teardown.addBlock(
`${generator.helper( 'teardownEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` );
block.builders.destroy.addBlock(
`${generator.helper( 'destroyEach' )}( ${localVars.iterations}, ${isToplevel ? 'detach' : 'false'} );` );
if ( node.else ) {
generator.current.builders.teardown.addBlock( deindent`
block.builders.destroy.addBlock( deindent`
if ( ${elseName} ) {
${elseName}.teardown( ${isToplevel ? 'detach' : 'false'} );
${elseName}.destroy( ${isToplevel ? 'detach' : 'false'} );
}
` );
}
if ( node.else ) {
generator.generateBlock( node.else, renderElse, 'block' );
}
const indexNames = new Map( generator.current.indexNames );
const indexName = node.index || generator.current.getUniqueName( `${node.context}_index` );
const indexNames = new Map( block.indexNames );
const indexName = node.index || block.getUniqueName( `${node.context}_index` );
indexNames.set( node.context, indexName );
const listNames = new Map( generator.current.listNames );
const listNames = new Map( block.listNames );
listNames.set( node.context, listName );
const context = generator.getUniqueName( node.context );
const contexts = new Map( generator.current.contexts );
const contexts = new Map( block.contexts );
contexts.set( node.context, context );
const indexes = new Map( generator.current.indexes );
const indexes = new Map( block.indexes );
if ( node.index ) indexes.set( indexName, node.context );
const contextDependencies = new Map( generator.current.contextDependencies );
const contextDependencies = new Map( block.contextDependencies );
contextDependencies.set( node.context, dependencies );
const blockParams = generator.current.params.concat( listName, context, indexName );
const getUniqueName = generator.getUniqueNameMaker( blockParams );
generator.push({
type: 'block',
const childBlock = block.child({
name: renderer,
target: 'target',
expression: node.expression,
context: node.context,
key: node.key,
localElementDepth: 0,
component: getUniqueName( 'component' ),
contextDependencies,
contexts,
@ -209,15 +193,28 @@ export default {
indexNames,
listNames,
params: blockParams,
params: block.params.concat( listName, context, indexName )
});
builders: getBuilders(),
getUniqueName,
const childState = Object.assign( {}, state, {
parentNode: null
});
},
leave ( generator ) {
generator.addRenderer( generator.current );
generator.pop();
node.children.forEach( child => {
visit( generator, childBlock, childState, child );
});
generator.addBlock( childBlock );
if ( node.else ) {
const childBlock = block.child({
name: renderElse
});
node.else.children.forEach( child => {
visit( generator, childBlock, childState, child );
});
generator.addBlock( childBlock );
}
}
};

@ -1,49 +1,47 @@
import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
import addElementAttributes from './attributes/addElementAttributes.js';
import Component from './Component.js';
import Window from './meta/Window.js';
import visitComponent from './Component.js';
import visitWindow from './meta/Window.js';
const meta = {
':Window': Window
':Window': visitWindow
};
export default {
enter ( generator, node ) {
export default function visitElement ( generator, block, state, node ) {
if ( node.name in meta ) {
return meta[ node.name ].enter( generator, node );
return meta[ node.name ]( generator, block, node );
}
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) {
return Component.enter( generator, node );
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
return visitComponent( generator, block, state, node );
}
const name = generator.current.getUniqueName( node.name );
const name = block.getUniqueName( node.name );
const local = {
name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : generator.current.namespace,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace,
isComponent: false,
allUsedContexts: [],
init: new CodeBuilder(),
create: new CodeBuilder(),
update: new CodeBuilder(),
teardown: new CodeBuilder()
destroy: new CodeBuilder()
};
const isToplevel = generator.current.localElementDepth === 0;
const isToplevel = !state.parentNode;
addElementAttributes( generator, node, local );
addElementAttributes( generator, block, node, local );
if ( local.allUsedContexts.length ) {
const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`;
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' );
@ -51,13 +49,13 @@ export default {
const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames.get( contextName );
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' );
local.init.addBlock( deindent`
local.create.addBlock( deindent`
${name}.__svelte = {
${initialProps}
};
@ -78,13 +76,13 @@ export default {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`;
}
if ( generator.cssId && !generator.elementDepth ) {
if ( generator.cssId && state.isTopLevel ) {
render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`;
}
local.init.addLineAtStart( render );
local.create.addLineAtStart( render );
if ( isToplevel ) {
generator.current.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
block.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
}
// special case bound <option> without a value attribute
@ -94,38 +92,23 @@ export default {
node.initialUpdate = statement;
}
generator.current.builders.init.addBlock( local.init );
if ( !local.update.isEmpty() ) generator.current.builders.update.addBlock( local.update );
if ( !local.teardown.isEmpty() ) generator.current.builders.teardown.addBlock( local.teardown );
block.builders.create.addBlock( local.create );
if ( !local.update.isEmpty() ) block.builders.update.addBlock( local.update );
if ( !local.destroy.isEmpty() ) block.builders.destroy.addBlock( local.destroy );
generator.createMountStatement( name );
block.createMountStatement( name, state.parentNode );
generator.push({
type: 'element',
namespace: local.namespace,
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1,
key: null
const childState = Object.assign( {}, state, {
isTopLevel: false,
parentNode: name,
namespace: local.namespace
});
},
leave ( generator, node ) {
if ( node.name in meta ) {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node );
return;
}
const isComponent = generator.components.has( node.name );
if ( isComponent ) {
return Component.leave( generator, node );
}
node.children.forEach( child => {
visit( generator, block, childState, child );
});
if ( node.initialUpdate ) {
generator.current.builders.init.addBlock( node.initialUpdate );
block.builders.create.addBlock( node.initialUpdate );
}
generator.pop();
}
};

@ -1,20 +1,20 @@
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
function getConditionsAndBlocks ( generator, node, _name, i = 0 ) {
generator.addSourcemapLocations( node.expression );
function getConditionsAndBlocks ( generator, block, state, node, _name, i = 0 ) {
const name = generator.getUniqueName( `${_name}_${i}` );
const conditionsAndBlocks = [{
condition: generator.contextualise( node.expression ).snippet,
condition: generator.contextualise( block, node.expression ).snippet,
block: name
}];
generator.generateBlock( node, name, 'block' );
generateBlock( generator, block, state, 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, block, state, node.else.children[0], _name, i + 1 )
);
} else {
const name = generator.getUniqueName( `${_name}_${i + 1}` );
@ -24,27 +24,42 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) {
});
if ( node.else ) {
generator.generateBlock( node.else, name, 'block' );
generateBlock( generator, block, state, node.else, name );
}
}
return conditionsAndBlocks;
}
export default {
enter ( generator, node ) {
const params = generator.current.params.join( ', ' );
function generateBlock ( generator, block, state, node, name ) {
const childBlock = block.child({
name
});
const childState = Object.assign( {}, state, {
parentNode: null
});
node.children.forEach( node => {
visit( generator, childBlock, childState, node );
});
generator.addBlock( childBlock );
}
export default function visitIfBlock ( generator, block, state, node ) {
const params = block.params.join( ', ' );
const name = generator.getUniqueName( `if_block` );
const getBlock = generator.current.getUniqueName( `get_block` );
const currentBlock = generator.current.getUniqueName( `current_block` );
const _currentBlock = generator.current.getUniqueName( `_current_block` );
const getBlock = block.getUniqueName( `get_block` );
const currentBlock = block.getUniqueName( `current_block` );
const _currentBlock = block.getUniqueName( `_current_block` );
const isToplevel = generator.current.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `render_if_block` ) );
const isToplevel = !state.parentNode;
const conditionsAndBlocks = getConditionsAndBlocks( generator, block, state, node, generator.getUniqueName( `create_if_block` ) );
const anchor = `${name}_anchor`;
generator.createAnchor( anchor );
block.createAnchor( anchor, state.parentNode );
generator.current.builders.init.addBlock( deindent`
block.builders.create.addBlock( deindent`
function ${getBlock} ( ${params} ) {
${conditionsAndBlocks.map( ({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
@ -52,30 +67,29 @@ export default {
}
var ${currentBlock} = ${getBlock}( ${params} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} );
` );
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
if ( isToplevel ) {
generator.current.builders.mount.addLine( mountStatement );
block.builders.mount.addLine( mountStatement );
} else {
generator.current.builders.init.addLine( mountStatement );
block.builders.create.addLine( mountStatement );
}
generator.current.builders.update.addBlock( deindent`
block.builders.update.addBlock( deindent`
var ${_currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} );
if ( ${_currentBlock} === ${currentBlock} && ${name}) {
${name}.update( changed, ${params} );
} else {
if ( ${name} ) ${name}.teardown( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${generator.current.component} );
if ( ${name} ) ${name}.destroy( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, ${block.component} );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
}
` );
generator.current.builders.teardown.addLine(
`if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );`
block.builders.destroy.addLine(
`if ( ${name} ) ${name}.destroy( ${isToplevel ? 'detach' : 'false'} );`
);
}
};

@ -1,22 +1,16 @@
import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js';
export default {
enter ( generator, node ) {
const name = generator.current.getUniqueName( 'text' );
export default function visitMustacheTag ( generator, block, state, node ) {
const name = block.getUniqueName( 'text' );
const { snippet } = generator.contextualise( node.expression );
const { snippet } = generator.contextualise( block, node.expression );
generator.current.builders.init.addLine( `var last_${name} = ${snippet};` );
generator.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true );
block.builders.create.addLine( `var last_${name} = ${snippet};` );
block.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, state.parentNode, true );
const fragment = findBlock( generator.current );
if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' );
generator.current.builders.update.addBlock( deindent`
if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) {
${name}.data = last_${name} = ${fragment.tmp};
block.builders.update.addBlock( deindent`
if ( ( ${block.tmp()} = ${snippet} ) !== last_${name} ) {
${name}.data = last_${name} = ${block.tmp()};
}
` );
}
};

@ -1,43 +1,37 @@
import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js';
export default {
enter ( generator, node ) {
const name = generator.current.getUniqueName( 'raw' );
export default function visitRawMustacheTag ( generator, block, state, node ) {
const name = block.getUniqueName( 'raw' );
const { snippet } = generator.contextualise( node.expression );
const { snippet } = generator.contextualise( block, node.expression );
// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
const before = `${name}_before`;
generator.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, true );
block.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true );
const after = `${name}_after`;
generator.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, true );
block.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true );
const isToplevel = generator.current.localElementDepth === 0;
const isToplevel = !state.parentNode;
generator.current.builders.init.addLine( `var last_${name} = ${snippet};` );
block.builders.create.addLine( `var last_${name} = ${snippet};` );
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`;
const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`;
if ( isToplevel ) {
generator.current.builders.mount.addLine( mountStatement );
block.builders.mount.addLine( mountStatement );
} else {
generator.current.builders.init.addLine( mountStatement );
block.builders.create.addLine( mountStatement );
}
const fragment = findBlock( generator.current );
if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' );
generator.current.builders.update.addBlock( deindent`
if ( ( ${fragment.tmp} = ${snippet} ) !== last_${name} ) {
last_${name} = ${fragment.tmp};
block.builders.update.addBlock( deindent`
if ( ( ${block.tmp()} = ${snippet} ) !== last_${name} ) {
last_${name} = ${block.tmp()};
${detachStatement}
${mountStatement}
}
` );
generator.current.builders.detachRaw.addBlock( detachStatement );
block.builders.detachRaw.addBlock( detachStatement );
}
};

@ -1,10 +1,8 @@
export default {
enter ( generator, node ) {
if ( generator.current.namespace && !/\S/.test( node.data ) ) {
export default function visitText ( generator, block, state, node ) {
if ( state.namespace && !/\S/.test( node.data ) ) {
return;
}
const name = generator.current.getUniqueName( `text` );
generator.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, false );
const name = block.getUniqueName( `text` );
block.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false );
}
};

@ -1,14 +1,12 @@
export default {
enter ( generator ) {
export default function visitYieldTag ( generator, block, state ) {
const anchor = `yield_anchor`;
generator.createAnchor( anchor );
block.createAnchor( anchor, state.parentNode );
generator.current.builders.mount.addLine(
`${generator.current.component}._yield && ${generator.current.component}._yield.mount( ${generator.current.target}, ${anchor} );`
block.builders.mount.addLine(
`${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || 'target'}, ${anchor} );`
);
generator.current.builders.teardown.addLine(
`${generator.current.component}._yield && ${generator.current.component}._yield.teardown( detach );`
block.builders.destroy.addLine(
`${block.component}._yield && ${block.component}._yield.destroy( detach );`
);
}
};

@ -1,7 +1,7 @@
import addComponentBinding from './addComponentBinding.js';
import deindent from '../../../../utils/deindent.js';
export default function addComponentAttributes ( generator, node, local ) {
export default function addComponentAttributes ( generator, block, node, local ) {
local.staticAttributes = [];
local.dynamicAttributes = [];
local.bindings = [];
@ -37,7 +37,7 @@ export default function addComponentAttributes ( generator, node, local ) {
else {
// simple dynamic attributes
const { dependencies, string } = generator.contextualise( value.expression );
const { dependencies, string } = generator.contextualise( block, value.expression );
// TODO only update attributes that have changed
local.dynamicAttributes.push({
@ -57,7 +57,7 @@ export default function addComponentAttributes ( generator, node, local ) {
if ( chunk.type === 'Text' ) {
return JSON.stringify( chunk.data );
} else {
const { dependencies, string } = generator.contextualise( chunk.expression );
const { dependencies, string } = generator.contextualise( block, chunk.expression );
dependencies.forEach( dependency => {
if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency );
});
@ -78,11 +78,11 @@ export default function addComponentAttributes ( generator, node, local ) {
else if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression );
generator.code.prependRight( attribute.expression.start, `${generator.current.component}.` );
generator.code.prependRight( attribute.expression.start, `${block.component}.` );
const usedContexts = [];
attribute.expression.arguments.forEach( arg => {
const { contexts } = generator.contextualise( arg, true );
const { contexts } = generator.contextualise( block, arg, true );
contexts.forEach( context => {
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
@ -94,15 +94,15 @@ export default function addComponentAttributes ( generator, node, local ) {
const declarations = usedContexts.map( name => {
if ( name === 'root' ) return 'var root = this._context.root;';
const listName = generator.current.listNames.get( name );
const indexName = generator.current.indexNames.get( name );
const listName = block.listNames.get( name );
const indexName = block.indexNames.get( name );
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
local.init.addBlock( deindent`
local.create.addBlock( deindent`
${local.name}.on( '${attribute.name}', function ( event ) {
${handlerBody}
});
@ -110,18 +110,18 @@ export default function addComponentAttributes ( generator, node, local ) {
}
else if ( attribute.type === 'Binding' ) {
addComponentBinding( generator, node, attribute, generator.current, local );
addComponentBinding( generator, node, attribute, block, local );
}
else if ( attribute.type === 'Ref' ) {
generator.usesRefs = true;
local.init.addLine(
`${generator.current.component}.refs.${attribute.name} = ${local.name};`
local.create.addLine(
`${block.component}.refs.${attribute.name} = ${local.name};`
);
generator.current.builders.teardown.addLine( deindent`
if ( ${generator.current.component}.refs.${attribute.name} === ${local.name} ) ${generator.current.component}.refs.${attribute.name} = null;
block.builders.destroy.addLine( deindent`
if ( ${block.component}.refs.${attribute.name} === ${local.name} ) ${block.component}.refs.${attribute.name} = null;
` );
}

@ -2,9 +2,9 @@ import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from './binding/getSetter.js';
export default function createBinding ( generator, node, attribute, current, local ) {
export default function addComponentBinding ( generator, node, attribute, block, local ) {
const { name, keypath } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = generator.contextualise( attribute.value );
const { snippet, contexts, dependencies } = generator.contextualise( block, attribute.value );
if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' );
@ -12,14 +12,14 @@ export default function createBinding ( generator, node, attribute, current, loc
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context );
});
const contextual = current.contexts.has( name );
const contextual = block.contexts.has( name );
let obj;
let prop;
if ( contextual ) {
obj = current.listNames.get( name );
prop = current.indexNames.get( name );
obj = block.listNames.get( name );
prop = block.indexNames.get( name );
} else if ( attribute.value.type === 'MemberExpression' ) {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property.end}✂]'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
@ -35,16 +35,16 @@ export default function createBinding ( generator, node, attribute, current, loc
prop
});
const setter = getSetter({ current, name, keypath, context: '_context', attribute, dependencies, value: 'value' });
const setter = getSetter({ block, name, keypath, context: '_context', attribute, dependencies, value: 'value' });
generator.hasComplexBindings = true;
const updating = generator.current.getUniqueName( `${local.name}_updating` );
const updating = block.getUniqueName( `${local.name}_updating` );
local.init.addBlock( deindent`
local.create.addBlock( deindent`
var ${updating} = false;
${generator.current.component}._bindings.push( function () {
${block.component}._bindings.push( function () {
if ( ${local.name}._torndown ) return;
${local.name}.observe( '${attribute.name}', function ( value ) {
if ( ${updating} ) return;

@ -3,9 +3,8 @@ import addElementBinding from './addElementBinding';
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getStaticAttributeValue from './binding/getStaticAttributeValue.js';
import findBlock from '../../utils/findBlock.js';
export default function addElementAttributes ( generator, node, local ) {
export default function addElementAttributes ( generator, block, node, local ) {
node.attributes.forEach( attribute => {
const name = attribute.name;
@ -32,28 +31,28 @@ export default function addElementAttributes ( generator, node, local ) {
if ( attribute.value === true ) {
// attributes without values, e.g. <textarea readonly>
if ( propertyName ) {
local.init.addLine(
local.create.addLine(
`${local.name}.${propertyName} = true;`
);
} else {
local.init.addLine(
local.create.addLine(
`${generator.helper( method )}( ${local.name}, '${name}', true );`
);
}
// special case autofocus. has to be handled in a bit of a weird way
if ( name === 'autofocus' ) {
generator.current.autofocus = local.name;
block.autofocus = local.name;
}
}
else if ( attribute.value.length === 0 ) {
if ( propertyName ) {
local.init.addLine(
local.create.addLine(
`${local.name}.${propertyName} = '';`
);
} else {
local.init.addLine(
local.create.addLine(
`${generator.helper( method )}( ${local.name}, '${name}', '' );`
);
}
@ -75,7 +74,7 @@ export default function addElementAttributes ( generator, node, local ) {
local.namespace = value.data;
addAttribute = true;
} else if ( propertyName ) {
local.init.addLine(
local.create.addLine(
`${local.name}.${propertyName} = ${result};`
);
} else {
@ -83,7 +82,7 @@ export default function addElementAttributes ( generator, node, local ) {
}
if ( addAttribute ) {
local.init.addLine(
local.create.addLine(
`${generator.helper( method )}( ${local.name}, '${name}', ${result} );`
);
}
@ -93,10 +92,10 @@ export default function addElementAttributes ( generator, node, local ) {
dynamic = true;
// dynamic but potentially non-string attributes
const { snippet } = generator.contextualise( value.expression );
const { snippet } = generator.contextualise( block, value.expression );
const last = `last_${local.name}_${name.replace( /-/g, '_')}`;
local.init.addLine( `var ${last} = ${snippet};` );
local.create.addLine( `var ${last} = ${snippet};` );
let updater;
if ( propertyName ) {
@ -105,13 +104,11 @@ export default function addElementAttributes ( generator, node, local ) {
updater = `${generator.helper( method )}( ${local.name}, '${name}', ${last} );`;
}
local.init.addLine( updater );
const fragment = findBlock( generator.current );
if ( !fragment.tmp ) fragment.tmp = fragment.getUniqueName( 'tmp' );
local.create.addLine( updater );
local.update.addBlock( deindent`
if ( ( ${fragment.tmp} = ${snippet} ) !== ${last} ) {
${last} = ${fragment.tmp};
if ( ( ${block.tmp()} = ${snippet} ) !== ${last} ) {
${last} = ${block.tmp()};
${updater}
}
` );
@ -126,7 +123,7 @@ export default function addElementAttributes ( generator, node, local ) {
if ( chunk.type === 'Text' ) {
return JSON.stringify( chunk.data );
} else {
const { snippet } = generator.contextualise( chunk.expression );
const { snippet } = generator.contextualise( block, chunk.expression );
return `( ${snippet} )`;
}
}).join( ' + ' )
@ -139,14 +136,14 @@ export default function addElementAttributes ( generator, node, local ) {
updater = `${generator.helper( method )}( ${local.name}, '${name}', ${value} );`;
}
local.init.addLine( updater );
local.create.addLine( updater );
local.update.addLine( updater );
}
if ( isIndirectlyBoundValue ) {
const updateValue = `${local.name}.value = ${local.name}.__value;`;
local.init.addLine( updateValue );
local.create.addLine( updateValue );
if ( dynamic ) local.update.addLine( updateValue );
}
}
@ -158,12 +155,12 @@ export default function addElementAttributes ( generator, node, local ) {
const flattened = flattenReference( attribute.expression.callee );
if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
// allow event.stopPropagation(), this.select() etc
generator.code.prependRight( attribute.expression.start, `${generator.current.component}.` );
generator.code.prependRight( attribute.expression.start, `${block.component}.` );
}
const usedContexts = [];
attribute.expression.arguments.forEach( arg => {
const { contexts } = generator.contextualise( arg, true );
const { contexts } = generator.contextualise( block, arg, true );
contexts.forEach( context => {
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
@ -175,27 +172,27 @@ export default function addElementAttributes ( generator, node, local ) {
const declarations = usedContexts.map( name => {
if ( name === 'root' ) return 'var root = this.__svelte.root;';
const listName = generator.current.listNames.get( name );
const indexName = generator.current.indexNames.get( name );
const listName = block.listNames.get( name );
const indexName = block.indexNames.get( name );
return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerName = generator.current.getUniqueName( `${name}_handler` );
const handlerName = block.getUniqueName( `${name}_handler` );
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
if ( generator.events.has( name ) ) {
local.init.addBlock( deindent`
var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( ${generator.current.component}, ${local.name}, function ( event ) {
local.create.addBlock( deindent`
var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( ${block.component}, ${local.name}, function ( event ) {
${handlerBody}
}.bind( ${local.name} ) );
` );
generator.current.builders.teardown.addLine( deindent`
block.builders.destroy.addLine( deindent`
${handlerName}.teardown();
` );
} else {
local.init.addBlock( deindent`
local.create.addBlock( deindent`
function ${handlerName} ( event ) {
${handlerBody}
}
@ -203,25 +200,25 @@ export default function addElementAttributes ( generator, node, local ) {
${generator.helper( 'addEventListener' )}( ${local.name}, '${name}', ${handlerName} );
` );
generator.current.builders.teardown.addLine( deindent`
block.builders.destroy.addLine( deindent`
${generator.helper( 'removeEventListener' )}( ${local.name}, '${name}', ${handlerName} );
` );
}
}
else if ( attribute.type === 'Binding' ) {
addElementBinding( generator, node, attribute, generator.current, local );
addElementBinding( generator, node, attribute, block, local );
}
else if ( attribute.type === 'Ref' ) {
generator.usesRefs = true;
local.init.addLine(
`${generator.current.component}.refs.${name} = ${local.name};`
local.create.addLine(
`${block.component}.refs.${name} = ${local.name};`
);
generator.current.builders.teardown.addLine( deindent`
if ( ${generator.current.component}.refs.${name} === ${local.name} ) ${generator.current.component}.refs.${name} = null;
block.builders.destroy.addLine( deindent`
if ( ${block.component}.refs.${name} === ${local.name} ) ${block.component}.refs.${name} = null;
` );
}

@ -3,9 +3,9 @@ import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from './binding/getSetter.js';
import getStaticAttributeValue from './binding/getStaticAttributeValue.js';
export default function createBinding ( generator, node, attribute, current, local ) {
export default function addElementBinding ( generator, node, attribute, block, local ) {
const { name, keypath } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = generator.contextualise( attribute.value );
const { snippet, contexts, dependencies } = generator.contextualise( block, attribute.value );
if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' );
@ -13,15 +13,15 @@ export default function createBinding ( generator, node, attribute, current, loc
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context );
});
const handler = current.getUniqueName( `${local.name}_change_handler` );
const handler = block.getUniqueName( `${local.name}_change_handler` );
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue( node, 'type' );
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, current, attribute, keypath ) : null;
const value = getBindingValue( generator, local, node, attribute, isMultipleSelect, bindingGroup, type );
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, keypath ) : null;
const value = getBindingValue( generator, block, local, node, attribute, isMultipleSelect, bindingGroup, type );
const eventName = getBindingEventName( node );
let setter = getSetter({ current, name, keypath, context: '__svelte', attribute, dependencies, value });
let setter = getSetter({ block, name, keypath, context: '__svelte', attribute, dependencies, value });
let updateElement;
// <select> special case
@ -30,9 +30,9 @@ export default function createBinding ( generator, node, attribute, current, loc
setter = `var selectedOption = ${local.name}.selectedOptions[0] || ${local.name}.options[0];\n${setter}`;
}
const value = current.getUniqueName( 'value' );
const i = current.getUniqueName( 'i' );
const option = current.getUniqueName( 'option' );
const value = block.getUniqueName( 'value' );
const i = block.getUniqueName( 'i' );
const option = block.getUniqueName( 'option' );
const ifStatement = isMultipleSelect ?
deindent`
@ -66,12 +66,12 @@ export default function createBinding ( generator, node, attribute, current, loc
`~${snippet}.indexOf( ${local.name}.__value )` :
`${local.name}.__value === ${snippet}`;
local.init.addLine(
`${current.component}._bindingGroups[${bindingGroup}].push( ${local.name} );`
local.create.addLine(
`${block.component}._bindingGroups[${bindingGroup}].push( ${local.name} );`
);
local.teardown.addBlock(
`${current.component}._bindingGroups[${bindingGroup}].splice( ${current.component}._bindingGroups[${bindingGroup}].indexOf( ${local.name} ), 1 );`
local.destroy.addBlock(
`${block.component}._bindingGroups[${bindingGroup}].splice( ${block.component}._bindingGroups[${bindingGroup}].indexOf( ${local.name} ), 1 );`
);
updateElement = `${local.name}.checked = ${condition};`;
@ -82,9 +82,9 @@ export default function createBinding ( generator, node, attribute, current, loc
updateElement = `${local.name}.${attribute.name} = ${snippet};`;
}
const updating = generator.current.getUniqueName( `${local.name}_updating` );
const updating = block.getUniqueName( `${local.name}_updating` );
local.init.addBlock( deindent`
local.create.addBlock( deindent`
var ${updating} = false;
function ${handler} () {
@ -104,7 +104,7 @@ export default function createBinding ( generator, node, attribute, current, loc
}
` );
current.builders.teardown.addLine( deindent`
block.builders.destroy.addLine( deindent`
${generator.helper( 'removeEventListener' )}( ${local.name}, '${eventName}', ${handler} );
` );
}
@ -124,7 +124,7 @@ function getBindingEventName ( node ) {
return 'change';
}
function getBindingValue ( generator, local, node, attribute, isMultipleSelect, bindingGroup, type ) {
function getBindingValue ( generator, block, local, node, attribute, isMultipleSelect, bindingGroup, type ) {
// <select multiple bind:value='selected>
if ( isMultipleSelect ) {
return `[].map.call( ${local.name}.selectedOptions, function ( option ) { return option.__value; })`;
@ -138,7 +138,7 @@ function getBindingValue ( generator, local, node, attribute, isMultipleSelect,
// <input type='checkbox' bind:group='foo'>
if ( attribute.name === 'group' ) {
if ( type === 'checkbox' ) {
return `${generator.helper( 'getBindingGroupValue' )}( ${generator.current.component}._bindingGroups[${bindingGroup}] )`;
return `${generator.helper( 'getBindingGroupValue' )}( ${block.component}._bindingGroups[${bindingGroup}] )`;
}
return `${local.name}.__value`;
@ -153,7 +153,7 @@ function getBindingValue ( generator, local, node, attribute, isMultipleSelect,
return `${local.name}.${attribute.name}`;
}
function getBindingGroup ( generator, current, attribute, keypath ) {
function getBindingGroup ( generator, keypath ) {
// TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context
let index = generator.bindingGroups.indexOf( keypath );

@ -1,28 +1,28 @@
import deindent from '../../../../../utils/deindent.js';
export default function getSetter ({ current, name, keypath, context, attribute, dependencies, value }) {
if ( current.contexts.has( name ) ) {
export default function getSetter ({ block, name, keypath, context, attribute, dependencies, value }) {
if ( block.contexts.has( name ) ) {
const prop = dependencies[0];
const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : '';
return deindent`
var list = this.${context}.${current.listNames.get( name )};
var index = this.${context}.${current.indexNames.get( name )};
var list = this.${context}.${block.listNames.get( name )};
var index = this.${context}.${block.indexNames.get( name )};
list[index]${tail} = ${value};
${current.component}._set({ ${prop}: ${current.component}.get( '${prop}' ) });
${block.component}._set({ ${prop}: ${block.component}.get( '${prop}' ) });
`;
}
if ( attribute.value.type === 'MemberExpression' ) {
return deindent`
var ${name} = ${current.component}.get( '${name}' );
var ${name} = ${block.component}.get( '${name}' );
${keypath} = ${value};
${current.component}._set({ ${name}: ${name} });
${block.component}._set({ ${name}: ${name} });
`;
}
return `${current.component}._set({ ${name}: ${value} });`;
return `${block.component}._set({ ${name}: ${value} });`;
}
function getTailSnippet ( node ) {

@ -11,8 +11,7 @@ const associatedEvents = {
scrollY: 'scroll'
};
export default {
enter ( generator, node ) {
export default function visitWindow ( generator, block, node ) {
const events = {};
node.attributes.forEach( attribute => {
@ -26,16 +25,16 @@ export default {
generator.code.prependRight( attribute.expression.start, 'component.' );
}
const handlerName = generator.current.getUniqueName( `onwindow${attribute.name}` );
const handlerName = block.getUniqueName( `onwindow${attribute.name}` );
generator.current.builders.init.addBlock( deindent`
block.builders.create.addBlock( deindent`
var ${handlerName} = function ( event ) {
[${attribute.expression.start}-${attribute.expression.end}];
};
window.addEventListener( '${attribute.name}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
block.builders.destroy.addBlock( deindent`
window.removeEventListener( '${attribute.name}', ${handlerName} );
` );
}
@ -63,11 +62,11 @@ export default {
});
Object.keys( events ).forEach( event => {
const handlerName = generator.current.getUniqueName( `onwindow${event}` );
const handlerName = block.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' );
generator.current.builders.init.addBlock( deindent`
block.builders.create.addBlock( deindent`
var ${handlerName} = function ( event ) {
component.set({
${props}
@ -76,9 +75,8 @@ export default {
window.addEventListener( '${event}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
block.builders.destroy.addBlock( deindent`
window.removeEventListener( '${event}', ${handlerName} );
` );
});
}
};

@ -0,0 +1,30 @@
import deindent from '../../utils/deindent.js';
import flattenReference from '../../utils/flattenReference.js';
export default class Block {
constructor ( options ) {
Object.assign( this, options );
}
addBinding ( binding, name ) {
const conditions = [ `!( '${binding.name}' in root )`].concat( // TODO handle contextual bindings...
this.conditions.map( c => `(${c})` )
);
const { keypath } = flattenReference( binding.value );
this.generator.bindings.push( deindent`
if ( ${conditions.join( '&&' )} ) {
tmp = ${name}.data();
if ( '${keypath}' in tmp ) {
root.${binding.name} = tmp.${keypath};
settled = false;
}
}
` );
}
child ( options ) {
return new Block( Object.assign( {}, this, options, { parent: this } ) );
}
}

@ -1,34 +1,16 @@
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import flattenReference from '../../utils/flattenReference.js';
import visitors from './visitors/index.js';
import Generator from '../Generator.js';
import Block from './Block.js';
import visit from './visit.js';
class SsrGenerator extends Generator {
constructor ( parsed, source, name, visitors, options ) {
super( parsed, source, name, visitors, options );
constructor ( parsed, source, name, options ) {
super( parsed, source, name, options );
this.bindings = [];
this.renderCode = '';
}
addBinding ( binding, name ) {
const conditions = [ `!( '${binding.name}' in root )`].concat( // TODO handle contextual bindings...
this.current.conditions.map( c => `(${c})` )
);
const { keypath } = flattenReference( binding.value );
this.bindings.push( deindent`
if ( ${conditions.join( '&&' )} ) {
tmp = ${name}.data();
if ( '${keypath}' in tmp ) {
root.${binding.name} = tmp.${keypath};
settled = false;
}
}
` );
}
append ( code ) {
this.renderCode += code;
}
@ -38,7 +20,7 @@ export default function ssr ( parsed, source, options ) {
const format = options.format || 'cjs';
const name = options.name || 'SvelteComponent';
const generator = new SsrGenerator( parsed, source, name, visitors, options );
const generator = new SsrGenerator( parsed, source, name, options );
const { computations, hasJs, templateProperties } = generator.parseJs( true );
@ -50,13 +32,16 @@ export default function ssr ( parsed, source, options ) {
};
// create main render() function
generator.push({
const mainBlock = new Block({
generator,
contexts: new Map(),
indexes: new Map(),
conditions: []
});
parsed.html.children.forEach( node => generator.visit( node ) );
parsed.html.children.forEach( node => {
visit( generator, mainBlock, node );
});
builders.render.addLine(
templateProperties.data ? `root = Object.assign( ${generator.alias( 'template' )}.data(), root || {} );` : `root = root || {};`

@ -0,0 +1,6 @@
import visitors from './visitors/index.js';
export default function visit ( generator, fragment, node ) {
const visitor = visitors[ node.type ];
visitor( generator, fragment, node );
}

@ -1,3 +1,3 @@
export default {
export default function visitComment () {
// do nothing
};
}

@ -1,11 +1,11 @@
import flattenReference from '../../../utils/flattenReference.js';
import visit from '../visit.js';
export default {
enter ( generator, node ) {
export default function visitComponent ( generator, block, node ) {
function stringify ( chunk ) {
if ( chunk.type === 'Text' ) return chunk.data;
if ( chunk.type === 'MustacheTag' ) {
const { snippet } = generator.contextualise( chunk.expression );
const { snippet } = generator.contextualise( block, chunk.expression );
return '${__escape( ' + snippet + ')}';
}
}
@ -34,7 +34,7 @@ export default {
if ( chunk.type === 'Text' ) {
value = isNaN( chunk.data ) ? JSON.stringify( chunk.data ) : chunk.data;
} else {
const { snippet } = generator.contextualise( chunk.expression );
const { snippet } = generator.contextualise( block, chunk.expression );
value = snippet;
}
} else {
@ -45,7 +45,7 @@ export default {
})
.concat( bindings.map( binding => {
const { name, keypath } = flattenReference( binding.value );
const value = generator.current.contexts.has( name ) ? keypath : `root.${keypath}`;
const value = block.contexts.has( name ) ? keypath : `root.${keypath}`;
return `${binding.name}: ${value}`;
}))
.join( ', ' );
@ -53,7 +53,7 @@ export default {
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`;
bindings.forEach( binding => {
generator.addBinding( binding, expression );
block.addBinding( binding, expression );
});
let open = `\${${expression}.render({${props}}`;
@ -63,10 +63,15 @@ export default {
}
generator.append( open );
},
leave ( generator, node ) {
generator.elementDepth += 1;
node.children.forEach( child => {
visit( generator, block, child );
});
generator.elementDepth -= 1;
const close = node.children.length ? `\` })}` : ')}';
generator.append( close );
}
};

@ -1,32 +1,32 @@
export default {
enter ( generator, node ) {
const { dependencies, snippet } = generator.contextualise( node.expression );
import visit from '../visit.js';
export default function visitEachBlock ( generator, block, node ) {
const { dependencies, snippet } = generator.contextualise( block, node.expression );
const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``;
generator.append( open );
// TODO should this be the generator's job? It's duplicated between
// here and the equivalent DOM compiler visitor
const contexts = new Map( generator.current.contexts );
const contexts = new Map( block.contexts );
contexts.set( node.context, node.context );
const indexes = new Map( generator.current.indexes );
const indexes = new Map( block.indexes );
if ( node.index ) indexes.set( node.index, node.context );
const contextDependencies = new Map( generator.current.contextDependencies );
const contextDependencies = new Map( block.contextDependencies );
contextDependencies.set( node.context, dependencies );
generator.push({
const childBlock = block.child({
contexts,
indexes,
contextDependencies
});
},
leave ( generator ) {
node.children.forEach( child => {
visit( generator, childBlock, child );
});
const close = `\` ).join( '' )}`;
generator.append( close );
generator.pop();
}
};

@ -1,19 +1,19 @@
import Component from './Component.js';
import visitComponent from './Component.js';
import isVoidElementName from '../../../utils/isVoidElementName.js';
import Window from './meta/Window.js';
import visit from '../visit.js';
import visitWindow from './meta/Window.js';
const meta = {
':Window': Window
':Window': visitWindow
};
export default {
enter ( generator, node ) {
export default function visitElement ( generator, block, node ) {
if ( node.name in meta ) {
return meta[ node.name ].enter( generator, node );
return meta[ node.name ]( generator, block, node );
}
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.enter( generator, node );
visitComponent( generator, block, node );
return;
}
@ -30,7 +30,7 @@ export default {
return chunk.data;
}
const { snippet } = generator.contextualise( chunk.expression );
const { snippet } = generator.contextualise( block, chunk.expression );
return '${' + snippet + '}';
}).join( '' ) + `"`;
}
@ -45,21 +45,16 @@ export default {
openingTag += '>';
generator.append( openingTag );
},
leave ( generator, node ) {
if ( node.name in meta ) {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node );
return;
}
generator.elementDepth += 1;
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.leave( generator, node );
return;
}
node.children.forEach( child => {
visit( generator, block, child );
});
generator.elementDepth -= 1;
if ( !isVoidElementName( node.name ) ) {
generator.append( `</${node.name}>` );
}
}
};

@ -1,19 +1,25 @@
export default {
enter ( generator, node ) {
const { snippet } = generator.contextualise( node.expression );
import visit from '../visit.js';
export default function visitIfBlock ( generator, block, node ) {
const { snippet } = generator.contextualise( block, node.expression );
generator.append( '${ ' + snippet + ' ? `' );
generator.push({
conditions: generator.current.conditions.concat( snippet )
const childBlock = block.child({
conditions: block.conditions.concat( snippet )
});
node.children.forEach( child => {
visit( generator, childBlock, child );
});
},
leave ( generator, node ) {
generator.append( '` : `' );
if ( node.else ) node.else.children.forEach( child => generator.visit( child ) );
generator.append( '` }' );
generator.pop();
if ( node.else ) {
node.else.children.forEach( child => {
visit( generator, childBlock, child );
});
}
generator.append( '` }' );
}
};

@ -1,6 +1,4 @@
export default {
enter ( generator, node ) {
const { snippet } = generator.contextualise( node.expression );
export default function visitMustacheTag ( generator, block, node ) {
const { snippet } = generator.contextualise( block, node.expression );
generator.append( '${__escape( ' + snippet + ' )}' );
}
};

@ -1,6 +1,4 @@
export default {
enter ( generator, node ) {
const { snippet } = generator.contextualise( node.expression );
export default function visitRawMustacheTag ( generator, block, node ) {
const { snippet } = generator.contextualise( block, node.expression );
generator.append( '${' + snippet + '}' );
}
};

@ -1,5 +1,3 @@
export default {
enter ( generator, node ) {
export default function visitText ( generator, block, node ) {
generator.append( node.data.replace( /\${/g, '\\${' ) );
}
};

@ -1,5 +1,3 @@
export default {
enter ( generator ) {
export default function visitYieldTag ( generator ) {
generator.append( `\${options && options.yield ? options.yield() : ''}` );
}
};

@ -1,9 +1,3 @@
export default {
enter () {
// noop
},
leave () {
export default function visitWindow () {
// noop
}
};

@ -16,9 +16,9 @@ export function detachBetween ( before, after ) {
}
}
export function teardownEach ( iterations, detach, start ) {
export function destroyEach ( iterations, detach, start ) {
for ( var i = ( start || 0 ); i < iterations.length; i += 1 ) {
iterations[i].teardown( detach );
iterations[i].destroy( detach );
}
}

@ -2,7 +2,7 @@ import checkForDupes from '../utils/checkForDupes.js';
import checkForComputedKeys from '../utils/checkForComputedKeys.js';
import usesThisOrArguments from '../utils/usesThisOrArguments.js';
const builtin = new Set( [ 'set', 'get', 'on', 'fire', 'observe', 'teardown' ] );
const builtin = new Set( [ 'set', 'get', 'on', 'fire', 'observe', 'destroy' ] );
export default function methods ( validator, prop ) {
if ( prop.value.type !== 'ObjectExpression' ) {

@ -1,17 +0,0 @@
export default {
data: {
animals: [ 'alpaca', 'baboon', 'capybara' ],
foo: 'something else'
},
html: 'before\n<p>alpaca</p><p>baboon</p><p>capybara</p>\nafter',
test ( assert, component, target ) {
component.set({ animals: [] });
assert.htmlEqual( target.innerHTML, 'before\n<p>no animals, but rather something else</p>\nafter' );
component.set({ foo: 'something other' });
assert.htmlEqual( target.innerHTML, 'before\n<p>no animals, but rather something other</p>\nafter' );
component.set({ animals: ['wombat'] });
assert.htmlEqual( target.innerHTML, 'before\n<p>wombat</p>\nafter' );
}
};

@ -0,0 +1 @@
Use these tests sparingly, as they will need to be updated frequently as the code generation changes.

@ -0,0 +1,28 @@
import assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import { svelte } from '../helpers.js';
describe( 'js', () => {
fs.readdirSync( 'test/js/samples' ).forEach( dir => {
if ( dir[0] === '.' ) return;
// add .solo to a sample directory name to only run that test
const solo = /\.solo/.test( dir );
if ( solo && process.env.CI ) {
throw new Error( 'Forgot to remove `solo: true` from test' );
}
( solo ? it.only : it )( dir, () => {
dir = path.resolve( 'test/js/samples', dir );
const input = fs.readFileSync( `${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
const actual = svelte.compile( input ).code;
fs.writeFileSync( `${dir}/_actual.js`, actual );
const expected = fs.readFileSync( `${dir}/expected.js`, 'utf-8' );
assert.equal( actual.trim(), expected.trim() );
});
});
});

@ -32,13 +32,13 @@ require.extensions[ '.html' ] = function ( module, filename ) {
const Object_assign = Object.assign;
describe( 'generate', () => {
describe( 'runtime', () => {
before( setupHtmlEqual );
function runTest ( dir, shared ) {
if ( dir[0] === '.' ) return;
const config = loadConfig( `./generator/samples/${dir}/_config.js` );
const config = loadConfig( `./runtime/samples/${dir}/_config.js` );
if ( config.solo && process.env.CI ) {
throw new Error( 'Forgot to remove `solo: true` from test' );
@ -53,7 +53,7 @@ describe( 'generate', () => {
compileOptions.dev = config.dev;
try {
const source = fs.readFileSync( `test/generator/samples/${dir}/main.html`, 'utf-8' );
const source = fs.readFileSync( `test/runtime/samples/${dir}/main.html`, 'utf-8' );
compiled = svelte.compile( source, compileOptions );
} catch ( err ) {
if ( config.compileError ) {
@ -69,7 +69,8 @@ describe( 'generate', () => {
// check that no ES2015+ syntax slipped in
if ( !config.allowES2015 ) {
try {
const startIndex = code.indexOf( 'function render_main_fragment' ); // may change!
const startIndex = code.indexOf( 'function create_main_fragment' ); // may change!
if ( startIndex === -1 ) throw new Error( 'missing create_main_fragment' );
const es5 = spaces( startIndex ) + code.slice( startIndex ).replace( /export default .+/, '' );
acorn.parse( es5, { ecmaVersion: 5 });
} catch ( err ) {
@ -117,6 +118,8 @@ describe( 'generate', () => {
data: config.data
});
Object.assign = Object_assign;
console.warn = warn;
if ( config.error ) {
@ -160,13 +163,13 @@ describe( 'generate', () => {
}
describe( 'inline helpers', () => {
fs.readdirSync( 'test/generator/samples' ).forEach( dir => {
fs.readdirSync( 'test/runtime/samples' ).forEach( dir => {
runTest( dir, null );
});
});
describe( 'shared helpers', () => {
fs.readdirSync( 'test/generator/samples' ).forEach( dir => {
fs.readdirSync( 'test/runtime/samples' ).forEach( dir => {
runTest( dir, path.resolve( 'shared.js' ) );
});
});

@ -7,5 +7,9 @@ export default {
]
},
html: `<div class="foo ">1</div><div class=" bar">2</div><div class="foo bar">3</div><!---->`
html: `
<div class="foo ">1</div>
<div class=" bar">2</div>
<div class="foo bar">3</div>
`
};

@ -1,5 +1,6 @@
export default {
html: `<div style="color: red;">red</div>`,
test ( assert, component, target ) {
const div = target.querySelector( 'div' );

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save