more refactoring – sort computations, move addElement and addRenderer out of generator

pull/204/head
Rich Harris 8 years ago
parent 7493b6c49b
commit 529d3a4a0e

@ -14,13 +14,14 @@ export default class Generator {
this.names = names;
this.visitors = visitors;
this.imports = [];
this.templateProperties = {};
this.helpers = {};
this.components = {};
this.events = {};
this.renderers = [];
this.imports = [];
this.computations = [];
this.code = new MagicString( source );
this.getUniqueName = counter( names );
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
@ -31,65 +32,6 @@ export default class Generator {
this.init();
}
addElement ( name, renderStatement, needsIdentifier = false ) {
const isToplevel = this.current.localElementDepth === 0;
if ( needsIdentifier || isToplevel ) {
this.current.builders.init.addLine(
`var ${name} = ${renderStatement};`
);
this.createMountStatement( name );
} else {
this.current.builders.init.addLine(
`${this.current.target}.appendChild( ${renderStatement} );`
);
}
if ( isToplevel ) {
this.current.builders.detach.addLine(
`${name}.parentNode.removeChild( ${name} );`
);
}
}
addRenderer ( fragment ) {
if ( fragment.autofocus ) {
fragment.builders.init.addLine( `${fragment.autofocus}.focus();` );
}
// minor hack we need to ensure that any {{{triples}}} are detached
// first, so we append normal detach statements to detachRaw
fragment.builders.detachRaw.addBlock( fragment.builders.detach );
if ( !fragment.builders.detachRaw.isEmpty() ) {
fragment.builders.teardown.addBlock( deindent`
if ( detach ) {
${fragment.builders.detachRaw}
}
` );
}
this.renderers.push( deindent`
function ${fragment.name} ( ${fragment.params}, component ) {
${fragment.builders.init}
return {
mount: function ( target, anchor ) {
${fragment.builders.mount}
},
update: function ( changed, ${fragment.params} ) {
${fragment.builders.update}
},
teardown: function ( detach ) {
${fragment.builders.teardown}
}
};
}
` );
}
addSourcemapLocations ( node ) {
walk( node, {
enter: node => {
@ -156,8 +98,14 @@ export default class Generator {
createAnchor ( _name, description = '' ) {
const name = `${_name}_anchor`;
const statement = `document.createComment( ${JSON.stringify( description )} )`;
this.addElement( name, statement, true );
const renderStatement = `document.createComment( ${JSON.stringify( description )} )`;
this.fire( 'addElement', {
name,
renderStatement,
needsIdentifier: true
});
return name;
}
@ -191,7 +139,7 @@ export default class Generator {
});
// walk the children here
node.children.forEach( node => this.visit( node ) );
this.addRenderer( this.current );
this.fire( 'addRenderer', this.current );
this.pop();
// unset the children, to avoid them being visited again
node.children = [];
@ -213,8 +161,9 @@ export default class Generator {
}
init () {
const { imports, source } = this;
const { computations, imports, source } = this;
const { js } = this.parsed;
if ( js ) {
this.addSourcemapLocations( js.content );
@ -269,6 +218,34 @@ export default class Generator {
});
}
});
if ( this.templateProperties.computed ) {
const dependencies = new Map();
this.templateProperties.computed.properties.forEach( prop => {
const key = prop.key.name;
const value = prop.value;
const deps = value.params.map( param => param.name );
dependencies.set( key, deps );
});
const visited = new Set();
function visit ( key ) {
if ( !dependencies.has( key ) ) return; // not a computation
if ( visited.has( key ) ) return;
visited.add( key );
const deps = dependencies.get( key );
deps.forEach( visit );
computations.push({ key, deps });
}
this.templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) );
}
}
}

@ -13,7 +13,69 @@ export default function dom ( parsed, source, options, names ) {
const generator = new Generator( parsed, source, names, visitors );
const { templateProperties, imports } = generator;
const { computations, imports, templateProperties } = generator; // TODO make this generator.parseJs() or similar?
const renderers = [];
function addRenderer ( fragment ) {
if ( fragment.autofocus ) {
fragment.builders.init.addLine( `${fragment.autofocus}.focus();` );
}
// minor hack we need to ensure that any {{{triples}}} are detached
// first, so we append normal detach statements to detachRaw
fragment.builders.detachRaw.addBlock( fragment.builders.detach );
if ( !fragment.builders.detachRaw.isEmpty() ) {
fragment.builders.teardown.addBlock( deindent`
if ( detach ) {
${fragment.builders.detachRaw}
}
` );
}
renderers.push( deindent`
function ${fragment.name} ( ${fragment.params}, component ) {
${fragment.builders.init}
return {
mount: function ( target, anchor ) {
${fragment.builders.mount}
},
update: function ( changed, ${fragment.params} ) {
${fragment.builders.update}
},
teardown: function ( detach ) {
${fragment.builders.teardown}
}
};
}
` );
}
generator.on( 'addRenderer', addRenderer );
generator.on( 'addElement', function addElement ({ name, renderStatement, needsIdentifier }) {
const isToplevel = this.current.localElementDepth === 0;
if ( needsIdentifier || isToplevel ) {
generator.current.builders.init.addLine(
`var ${name} = ${renderStatement};`
);
generator.createMountStatement( name );
} else {
generator.current.builders.init.addLine(
`${generator.current.target}.appendChild( ${renderStatement} );`
);
}
if ( isToplevel ) {
generator.current.builders.detach.addLine(
`${name}.parentNode.removeChild( ${name} );`
);
}
});
let namespace = null;
if ( templateProperties.namespace ) {
@ -43,7 +105,7 @@ export default function dom ( parsed, source, options, names ) {
parsed.html.children.forEach( node => generator.visit( node ) );
generator.addRenderer( generator.pop() );
addRenderer( generator.pop() );
const builders = {
main: new CodeBuilder(),
@ -54,37 +116,16 @@ export default function dom ( parsed, source, options, names ) {
builders.set.addLine( 'var oldState = state;' );
builders.set.addLine( 'state = Object.assign( {}, oldState, newState );' );
if ( templateProperties.computed ) {
if ( computations.length ) {
const builder = new CodeBuilder();
const dependencies = new Map();
templateProperties.computed.properties.forEach( prop => {
const key = prop.key.name;
const value = prop.value;
const deps = value.params.map( param => param.name );
dependencies.set( key, deps );
});
const visited = new Set();
function visit ( key ) {
if ( !dependencies.has( key ) ) return; // not a computation
if ( visited.has( key ) ) return;
visited.add( key );
const deps = dependencies.get( key );
deps.forEach( visit );
computations.forEach( ({ key, deps }) => {
builder.addBlock( deindent`
if ( ${deps.map( dep => `( '${dep}' in newState && typeof state.${dep} === 'object' || state.${dep} !== oldState.${dep} )` ).join( ' || ' )} ) {
state.${key} = newState.${key} = template.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );
}
` );
}
templateProperties.computed.properties.forEach( prop => visit( prop.key.name ) );
});
builders.main.addBlock( deindent`
function applyComputations ( state, newState, oldState ) {
@ -140,8 +181,8 @@ export default function dom ( parsed, source, options, names ) {
` );
}
let i = generator.renderers.length;
while ( i-- ) builders.main.addBlock( generator.renderers[i] );
let i = renderers.length;
while ( i-- ) builders.main.addBlock( renderers[i] );
const constructorName = options.name || 'SvelteComponent';

@ -151,7 +151,7 @@ export default {
},
leave ( generator ) {
generator.addRenderer( generator.current );
generator.fire( 'addRenderer', generator.current );
generator.pop();
}
};

@ -7,7 +7,11 @@ export default {
generator.addSourcemapLocations( node.expression );
const { snippet } = generator.contextualise( node.expression );
generator.addElement( name, `document.createTextNode( ${snippet} )`, true );
generator.fire( 'addElement', {
name,
renderStatement: `document.createTextNode( ${snippet} )`,
needsIdentifier: true
});
generator.current.builders.update.addBlock( deindent`
${name}.data = ${snippet};

@ -10,9 +10,18 @@ export default {
// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
const before = `${name}_before`;
generator.addElement( before, `document.createElement( 'noscript' )`, true );
generator.fire( 'addElement', {
name: before,
renderStatement: `document.createElement( 'noscript' )`,
needsIdentifier: true
});
const after = `${name}_after`;
generator.addElement( after, `document.createElement( 'noscript' )`, true );
generator.fire( 'addElement', {
name: after,
renderStatement: `document.createElement( 'noscript' )`,
needsIdentifier: true
});
const isToplevel = generator.current.localElementDepth === 0;

@ -5,6 +5,10 @@ export default {
}
const name = generator.current.getUniqueName( `text` );
generator.addElement( name, `document.createTextNode( ${JSON.stringify( node.data )} )` );
generator.fire( 'addElement', {
name,
renderStatement: `document.createTextNode( ${JSON.stringify( node.data )} )`,
needsIdentifier: false
});
}
};

Loading…
Cancel
Save