separate current *fragment* from current *generator state*

pull/453/head
Rich-Harris 8 years ago
parent abf774bc57
commit 421f3d698f

@ -30,8 +30,6 @@ export default class Generator {
// in dev mode // in dev mode
this.expectedProperties = new Set(); this.expectedProperties = new Set();
this.elementDepth = 0;
this.code = new MagicString( source ); this.code = new MagicString( source );
this.css = parsed.css ? processCss( parsed, this.code ) : null; this.css = parsed.css ? processCss( parsed, this.code ) : null;
this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';

@ -3,16 +3,16 @@ export default class Fragment {
Object.assign( this, options ); Object.assign( this, options );
} }
addElement ( name, renderStatement, needsIdentifier = false ) { addElement ( name, renderStatement, target, localElementDepth, needsIdentifier = false ) {
const isToplevel = this.localElementDepth === 0; const isToplevel = localElementDepth === 0;
if ( needsIdentifier || isToplevel ) { if ( needsIdentifier || isToplevel ) {
this.builders.create.addLine( this.builders.create.addLine(
`var ${name} = ${renderStatement};` `var ${name} = ${renderStatement};`
); );
this.createMountStatement( name ); this.createMountStatement( name, target );
} else { } else {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${this.target} );` ); this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${target} );` );
} }
if ( isToplevel ) { if ( isToplevel ) {
@ -24,16 +24,16 @@ export default class Fragment {
return new Fragment( Object.assign( {}, this, options, { parent: this } ) ); return new Fragment( Object.assign( {}, this, options, { parent: this } ) );
} }
createAnchor ( name ) { createAnchor ( name, target, localElementDepth ) {
const renderStatement = `${this.generator.helper( 'createComment' )}()`; const renderStatement = `${this.generator.helper( 'createComment' )}()`;
this.addElement( name, renderStatement, true ); this.addElement( name, renderStatement, target, localElementDepth, true );
} }
createMountStatement ( name ) { createMountStatement ( name, target ) {
if ( this.target === 'target' ) { if ( target === 'target' ) {
this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, target, anchor );` ); this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, target, anchor );` );
} else { } else {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${this.target} );` ); this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${target} );` );
} }
} }
} }

@ -112,9 +112,6 @@ export default function dom ( parsed, source, options ) {
type: 'block', type: 'block',
generator, generator,
name: generator.alias( 'create_main_fragment' ), name: generator.alias( 'create_main_fragment' ),
namespace,
target: 'target',
localElementDepth: 0,
key: null, key: null,
component, component,
@ -130,8 +127,14 @@ export default function dom ( parsed, source, options ) {
getUniqueName getUniqueName
}); });
const state = {
namespace,
target: 'target',
localElementDepth: 0
};
parsed.html.children.forEach( node => { parsed.html.children.forEach( node => {
visit( generator, mainFragment, node ); visit( generator, mainFragment, state, node );
}); });
generator.addRenderer( mainFragment ); generator.addRenderer( mainFragment );

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

@ -20,13 +20,13 @@ function stringifyProps ( props ) {
return `{ ${joined} }`; return `{ ${joined} }`;
} }
export default function visitComponent ( generator, fragment, node ) { export default function visitComponent ( generator, fragment, state, node ) {
const hasChildren = node.children.length > 0; const hasChildren = node.children.length > 0;
const name = fragment.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) ); const name = fragment.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const local = { const local = {
name, name,
namespace: fragment.namespace, namespace: state.namespace,
isComponent: true, isComponent: true,
allUsedContexts: [], allUsedContexts: [],
@ -35,7 +35,7 @@ export default function visitComponent ( generator, fragment, node ) {
update: new CodeBuilder() update: new CodeBuilder()
}; };
const isToplevel = fragment.localElementDepth === 0; const isToplevel = state.localElementDepth === 0;
generator.hasComponents = true; generator.hasComponents = true;
@ -70,7 +70,7 @@ export default function visitComponent ( generator, fragment, node ) {
} }
const componentInitProperties = [ const componentInitProperties = [
`target: ${!isToplevel ? fragment.target: 'null'}`, `target: ${!isToplevel ? state.target: 'null'}`,
`_root: ${fragment.component}._root || ${fragment.component}` `_root: ${fragment.component}._root || ${fragment.component}`
]; ];
@ -81,13 +81,16 @@ export default function visitComponent ( generator, fragment, node ) {
const childFragment = fragment.child({ const childFragment = fragment.child({
type: 'component', type: 'component',
name: generator.getUniqueName( `render_${name}_yield_fragment` ), // TODO should getUniqueName happen inside Fragment? probably name: generator.getUniqueName( `render_${name}_yield_fragment` ), // TODO should getUniqueName happen inside Fragment? probably
target: 'target',
localElementDepth: 0,
builders: getBuilders() builders: getBuilders()
}); });
const childState = Object.assign( {}, state, {
target: 'target',
localElementDepth: 0
});
node.children.forEach( child => { node.children.forEach( child => {
visit( generator, childFragment, child ); visit( generator, childFragment, childState, child );
}); });
const yieldFragment = fragment.getUniqueName( `${name}_yield_fragment` ); const yieldFragment = fragment.getUniqueName( `${name}_yield_fragment` );

@ -3,7 +3,7 @@ import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js'; import getBuilders from '../utils/getBuilders.js';
import visit from '../visit.js'; import visit from '../visit.js';
export default function visitEachBlock ( generator, fragment, node ) { export default function visitEachBlock ( generator, fragment, state, node ) {
const name = generator.getUniqueName( `each_block` ); const name = generator.getUniqueName( `each_block` );
const renderer = generator.getUniqueName( `render_each_block` ); const renderer = generator.getUniqueName( `render_each_block` );
const elseName = generator.getUniqueName( `${name}_else` ); const elseName = generator.getUniqueName( `${name}_else` );
@ -13,14 +13,14 @@ export default function visitEachBlock ( generator, fragment, node ) {
const listName = fragment.getUniqueName( `${name}_value` ); const listName = fragment.getUniqueName( `${name}_value` );
const isToplevel = fragment.localElementDepth === 0; const isToplevel = state.localElementDepth === 0;
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
const { dependencies, snippet } = generator.contextualise( fragment, node.expression ); const { dependencies, snippet } = generator.contextualise( fragment, node.expression );
const anchor = fragment.getUniqueName( `${name}_anchor` ); const anchor = fragment.getUniqueName( `${name}_anchor` );
fragment.createAnchor( anchor ); fragment.createAnchor( anchor, state.target, state.localElementDepth );
const localVars = {}; const localVars = {};
@ -191,11 +191,9 @@ export default function visitEachBlock ( generator, fragment, node ) {
const childFragment = fragment.child({ const childFragment = fragment.child({
type: 'block', type: 'block',
name: renderer, name: renderer,
target: 'target',
expression: node.expression, expression: node.expression,
context: node.context, context: node.context,
key: node.key, key: node.key,
localElementDepth: 0,
component: getUniqueName( 'component' ), component: getUniqueName( 'component' ),
@ -211,8 +209,13 @@ export default function visitEachBlock ( generator, fragment, node ) {
getUniqueName getUniqueName
}); });
const childState = Object.assign( {}, state, {
target: 'target',
localElementDepth: 0
});
node.children.forEach( child => { node.children.forEach( child => {
visit( generator, childFragment, child ); visit( generator, childFragment, childState, child );
}); });
generator.addRenderer( childFragment ); generator.addRenderer( childFragment );
@ -221,14 +224,12 @@ export default function visitEachBlock ( generator, fragment, node ) {
const childFragment = fragment.child({ const childFragment = fragment.child({
type: 'block', type: 'block',
name: renderElse, name: renderElse,
target: 'target',
localElementDepth: 0,
builders: getBuilders(), builders: getBuilders(),
getUniqueName: generator.getUniqueNameMaker( fragment.params ) getUniqueName: generator.getUniqueNameMaker( fragment.params )
}); });
node.else.children.forEach( child => { node.else.children.forEach( child => {
visit( generator, childFragment, child ); visit( generator, childFragment, childState, child );
}); });
generator.addRenderer( childFragment ); generator.addRenderer( childFragment );

@ -9,20 +9,20 @@ const meta = {
':Window': visitWindow ':Window': visitWindow
}; };
export default function visitElement ( generator, fragment, node ) { export default function visitElement ( generator, fragment, state, node ) {
if ( node.name in meta ) { if ( node.name in meta ) {
return meta[ node.name ]( generator, fragment, node ); return meta[ node.name ]( generator, fragment, node );
} }
if ( generator.components.has( node.name ) || node.name === ':Self' ) { if ( generator.components.has( node.name ) || node.name === ':Self' ) {
return visitComponent( generator, fragment, node ); return visitComponent( generator, fragment, state, node );
} }
const name = fragment.getUniqueName( node.name ); const name = fragment.getUniqueName( node.name );
const local = { const local = {
name, name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : fragment.namespace, namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace,
isComponent: false, isComponent: false,
allUsedContexts: [], allUsedContexts: [],
@ -32,7 +32,7 @@ export default function visitElement ( generator, fragment, node ) {
destroy: new CodeBuilder() destroy: new CodeBuilder()
}; };
const isToplevel = fragment.localElementDepth === 0; const isToplevel = state.localElementDepth === 0;
addElementAttributes( generator, fragment, node, local ); addElementAttributes( generator, fragment, node, local );
@ -76,7 +76,7 @@ export default function visitElement ( generator, fragment, node ) {
render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`; render = `var ${name} = ${generator.helper( 'createElement' )}( '${node.name}' );`;
} }
if ( generator.cssId && !generator.elementDepth ) { if ( generator.cssId && !state.elementDepth ) {
render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`; render += `\n${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );`;
} }
@ -96,25 +96,19 @@ export default function visitElement ( generator, fragment, node ) {
if ( !local.update.isEmpty() ) fragment.builders.update.addBlock( local.update ); if ( !local.update.isEmpty() ) fragment.builders.update.addBlock( local.update );
if ( !local.destroy.isEmpty() ) fragment.builders.destroy.addBlock( local.destroy ); if ( !local.destroy.isEmpty() ) fragment.builders.destroy.addBlock( local.destroy );
fragment.createMountStatement( name ); fragment.createMountStatement( name, state.target );
const childFragment = fragment.child({ const childState = Object.assign( {}, state, {
type: 'element', elementDepth: state.elementDepth + 1,
namespace: local.namespace, localElementDepth: state.localElementDepth + 1,
target: name, target: name,
parent: fragment, namespace: local.namespace
localElementDepth: fragment.localElementDepth + 1,
key: null
}); });
generator.elementDepth += 1;
node.children.forEach( child => { node.children.forEach( child => {
visit( generator, childFragment, child ); visit( generator, fragment, childState, child );
}); });
generator.elementDepth -= 1;
if ( node.initialUpdate ) { if ( node.initialUpdate ) {
fragment.builders.create.addBlock( node.initialUpdate ); fragment.builders.create.addBlock( node.initialUpdate );
} }

@ -2,7 +2,7 @@ import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js'; import getBuilders from '../utils/getBuilders.js';
import visit from '../visit.js'; import visit from '../visit.js';
function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) { function getConditionsAndBlocks ( generator, fragment, state, node, _name, i = 0 ) {
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
const name = generator.getUniqueName( `${_name}_${i}` ); const name = generator.getUniqueName( `${_name}_${i}` );
@ -11,12 +11,12 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) {
block: name block: name
}]; }];
generateBlock( generator, fragment, node, name, 'block' ); generateBlock( generator, fragment, state, node, name, 'block' );
if ( node.else && node.else.children.length === 1 && if ( node.else && node.else.children.length === 1 &&
node.else.children[0].type === 'IfBlock' ) { node.else.children[0].type === 'IfBlock' ) {
conditionsAndBlocks.push( conditionsAndBlocks.push(
...getConditionsAndBlocks( generator, fragment, node.else.children[0], _name, i + 1 ) ...getConditionsAndBlocks( generator, fragment, state, node.else.children[0], _name, i + 1 )
); );
} else { } else {
const name = generator.getUniqueName( `${_name}_${i + 1}` ); const name = generator.getUniqueName( `${_name}_${i + 1}` );
@ -26,42 +26,44 @@ function getConditionsAndBlocks ( generator, fragment, node, _name, i = 0 ) {
}); });
if ( node.else ) { if ( node.else ) {
generateBlock( generator, fragment, node.else, name, 'block' ); generateBlock( generator, fragment, state, node.else, name, 'block' );
} }
} }
return conditionsAndBlocks; return conditionsAndBlocks;
} }
function generateBlock ( generator, fragment, node, name, type ) { function generateBlock ( generator, fragment, state, node, name, type ) {
const childFragment = fragment.child({ const childFragment = fragment.child({
type, type,
isIfBlock: true,
name, name,
builders: getBuilders()
});
const childState = Object.assign( {}, state, {
target: 'target', target: 'target',
builders: getBuilders(),
localElementDepth: 0 localElementDepth: 0
}); });
// walk the children here // walk the children here
node.children.forEach( node => { node.children.forEach( node => {
visit( generator, childFragment, node ); visit( generator, childFragment, childState, node );
}); });
generator.addRenderer( childFragment ); generator.addRenderer( childFragment );
} }
export default function visitIfBlock ( generator, fragment, node ) { export default function visitIfBlock ( generator, fragment, state, node ) {
const params = fragment.params.join( ', ' ); const params = fragment.params.join( ', ' );
const name = generator.getUniqueName( `if_block` ); const name = generator.getUniqueName( `if_block` );
const getBlock = fragment.getUniqueName( `get_block` ); const getBlock = fragment.getUniqueName( `get_block` );
const currentBlock = fragment.getUniqueName( `current_block` ); const currentBlock = fragment.getUniqueName( `current_block` );
const _currentBlock = fragment.getUniqueName( `_current_block` ); const _currentBlock = fragment.getUniqueName( `_current_block` );
const isToplevel = fragment.localElementDepth === 0; const isToplevel = state.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, fragment, node, generator.getUniqueName( `render_if_block` ) ); const conditionsAndBlocks = getConditionsAndBlocks( generator, fragment, state, node, generator.getUniqueName( `render_if_block` ) );
const anchor = `${name}_anchor`; const anchor = `${name}_anchor`;
fragment.createAnchor( anchor ); fragment.createAnchor( anchor, state.target, state.localElementDepth );
fragment.builders.create.addBlock( deindent` fragment.builders.create.addBlock( deindent`
function ${getBlock} ( ${params} ) { function ${getBlock} ( ${params} ) {

@ -1,13 +1,13 @@
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js'; import findBlock from '../utils/findBlock.js';
export default function visitMustacheTag ( generator, fragment, node ) { export default function visitMustacheTag ( generator, fragment, state, node ) {
const name = fragment.getUniqueName( 'text' ); const name = fragment.getUniqueName( 'text' );
const { snippet } = generator.contextualise( fragment, node.expression ); const { snippet } = generator.contextualise( fragment, node.expression );
fragment.builders.create.addLine( `var last_${name} = ${snippet};` ); fragment.builders.create.addLine( `var last_${name} = ${snippet};` );
fragment.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, true ); fragment.addElement( name, `${generator.helper( 'createText' )}( last_${name} )`, state.target, state.localElementDepth, true );
// TODO this should be unnecessary once we separate fragments from state // TODO this should be unnecessary once we separate fragments from state
const parentFragment = findBlock( fragment ); const parentFragment = findBlock( fragment );

@ -1,7 +1,7 @@
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import findBlock from '../utils/findBlock.js'; import findBlock from '../utils/findBlock.js';
export default function visitRawMustacheTag ( generator, fragment, node ) { export default function visitRawMustacheTag ( generator, fragment, state, node ) {
const name = fragment.getUniqueName( 'raw' ); const name = fragment.getUniqueName( 'raw' );
const { snippet } = generator.contextualise( fragment, node.expression ); const { snippet } = generator.contextualise( fragment, node.expression );
@ -9,12 +9,12 @@ export default function visitRawMustacheTag ( generator, fragment, node ) {
// we would have used comments here, but the `insertAdjacentHTML` api only // we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s. // exists for `Element`s.
const before = `${name}_before`; const before = `${name}_before`;
fragment.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); fragment.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, state.target, state.localElementDepth, true );
const after = `${name}_after`; const after = `${name}_after`;
fragment.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, true ); fragment.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, state.target, state.localElementDepth, true );
const isToplevel = fragment.localElementDepth === 0; const isToplevel = state.localElementDepth === 0;
fragment.builders.create.addLine( `var last_${name} = ${snippet};` ); fragment.builders.create.addLine( `var last_${name} = ${snippet};` );
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`; const mountStatement = `${before}.insertAdjacentHTML( 'afterend', last_${name} );`;

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

@ -1,9 +1,9 @@
export default function visitYieldTag ( generator, fragment ) { export default function visitYieldTag ( generator, fragment, state ) {
const anchor = `yield_anchor`; const anchor = `yield_anchor`;
fragment.createAnchor( anchor ); fragment.createAnchor( anchor, state.target, state.localElementDepth );
fragment.builders.mount.addLine( fragment.builders.mount.addLine(
`${fragment.component}._yield && ${fragment.component}._yield.mount( ${fragment.target}, ${anchor} );` `${fragment.component}._yield && ${fragment.component}._yield.mount( ${state.target}, ${anchor} );`
); );
fragment.builders.destroy.addLine( fragment.builders.destroy.addLine(

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

Loading…
Cancel
Save