|
|
|
@ -0,0 +1,228 @@
|
|
|
|
|
import MagicString from 'magic-string';
|
|
|
|
|
import CodeBuilder from '../utils/CodeBuilder.js';
|
|
|
|
|
import { walk } from 'estree-walker';
|
|
|
|
|
import deindent from '../utils/deindent.js';
|
|
|
|
|
import isReference from '../utils/isReference.js';
|
|
|
|
|
import counter from './shared/utils/counter.js';
|
|
|
|
|
import flattenReference from '../utils/flattenReference.js';
|
|
|
|
|
import globalWhitelist from '../utils/globalWhitelist.js';
|
|
|
|
|
|
|
|
|
|
export default class Generator {
|
|
|
|
|
constructor ( parsed, source, names, visitors ) {
|
|
|
|
|
this.parsed = parsed;
|
|
|
|
|
this.source = source;
|
|
|
|
|
this.names = names;
|
|
|
|
|
this.visitors = visitors;
|
|
|
|
|
|
|
|
|
|
this.renderers = [];
|
|
|
|
|
this.code = new MagicString( source );
|
|
|
|
|
this.components = {};
|
|
|
|
|
this.events = {};
|
|
|
|
|
this.helpers = {};
|
|
|
|
|
this.getUniqueName = counter( names );
|
|
|
|
|
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
|
|
|
|
|
this.usesRefs = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addElement ( name, renderStatement, needsIdentifier = false ) {
|
|
|
|
|
const isToplevel = this.current.localElementDepth === 0;
|
|
|
|
|
if ( needsIdentifier || isToplevel ) {
|
|
|
|
|
this.current.builders.init.addLine(
|
|
|
|
|
`var ${name} = ${renderStatement};`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.createMountStatement( name );
|
|
|
|
|
} else {
|
|
|
|
|
this.current.builders.init.addLine(
|
|
|
|
|
`${this.current.target}.appendChild( ${renderStatement} );`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( isToplevel ) {
|
|
|
|
|
this.current.builders.detach.addLine(
|
|
|
|
|
`${name}.parentNode.removeChild( ${name} );`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createMountStatement ( name ) {
|
|
|
|
|
if ( this.current.target === 'target' ) {
|
|
|
|
|
this.current.builders.mount.addLine(
|
|
|
|
|
`target.insertBefore( ${name}, anchor );`
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
this.current.builders.init.addLine(
|
|
|
|
|
`${this.current.target}.appendChild( ${name} );` );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createAnchor ( _name, description = '' ) {
|
|
|
|
|
const name = `${_name}_anchor`;
|
|
|
|
|
const statement = `document.createComment( ${JSON.stringify( description )} )`;
|
|
|
|
|
this.addElement( name, statement, true );
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generateBlock ( node, name ) {
|
|
|
|
|
this.push({
|
|
|
|
|
name,
|
|
|
|
|
target: 'target',
|
|
|
|
|
localElementDepth: 0,
|
|
|
|
|
builders: this.getBuilders(),
|
|
|
|
|
getUniqueName: this.getUniqueNameMaker()
|
|
|
|
|
});
|
|
|
|
|
// walk the children here
|
|
|
|
|
node.children.forEach( node => this.visit( node ) );
|
|
|
|
|
this.addRenderer( this.current );
|
|
|
|
|
this.pop();
|
|
|
|
|
// unset the children, to avoid them being visited again
|
|
|
|
|
node.children = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addRenderer ( fragment ) {
|
|
|
|
|
if ( fragment.autofocus ) {
|
|
|
|
|
fragment.builders.init.addLine( `${fragment.autofocus}.focus();` );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// minor hack – we need to ensure that any {{{triples}}} are detached
|
|
|
|
|
// first, so we append normal detach statements to detachRaw
|
|
|
|
|
fragment.builders.detachRaw.addBlock( fragment.builders.detach );
|
|
|
|
|
|
|
|
|
|
if ( !fragment.builders.detachRaw.isEmpty() ) {
|
|
|
|
|
fragment.builders.teardown.addBlock( deindent`
|
|
|
|
|
if ( detach ) {
|
|
|
|
|
${fragment.builders.detachRaw}
|
|
|
|
|
}
|
|
|
|
|
` );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.renderers.push( deindent`
|
|
|
|
|
function ${fragment.name} ( ${fragment.params}, component ) {
|
|
|
|
|
${fragment.builders.init}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
mount: function ( target, anchor ) {
|
|
|
|
|
${fragment.builders.mount}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
update: function ( changed, ${fragment.params} ) {
|
|
|
|
|
${fragment.builders.update}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
teardown: function ( detach ) {
|
|
|
|
|
${fragment.builders.teardown}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
` );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addSourcemapLocations ( node ) {
|
|
|
|
|
walk( node, {
|
|
|
|
|
enter: node => {
|
|
|
|
|
this.code.addSourcemapLocation( node.start );
|
|
|
|
|
this.code.addSourcemapLocation( node.end );
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contextualise ( expression, isEventHandler ) {
|
|
|
|
|
const usedContexts = [];
|
|
|
|
|
const dependencies = [];
|
|
|
|
|
|
|
|
|
|
const { code, helpers } = this;
|
|
|
|
|
const { contextDependencies, contexts, indexes } = this.current;
|
|
|
|
|
|
|
|
|
|
walk( expression, {
|
|
|
|
|
enter ( node, parent ) {
|
|
|
|
|
if ( isReference( node, parent ) ) {
|
|
|
|
|
const { name } = flattenReference( node );
|
|
|
|
|
|
|
|
|
|
if ( parent && parent.type === 'CallExpression' && node === parent.callee && helpers[ name ] ) {
|
|
|
|
|
code.prependRight( node.start, `template.helpers.` );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( name === 'event' && isEventHandler ) {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( contexts[ name ] ) {
|
|
|
|
|
dependencies.push( ...contextDependencies[ name ] );
|
|
|
|
|
if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( indexes[ name ] ) {
|
|
|
|
|
const context = indexes[ name ];
|
|
|
|
|
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
if ( globalWhitelist[ name ] ) {
|
|
|
|
|
code.prependRight( node.start, `( '${name}' in root ? root.` );
|
|
|
|
|
code.appendLeft( node.object.end, ` : ${name} )` );
|
|
|
|
|
} else {
|
|
|
|
|
code.prependRight( node.start, `root.` );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dependencies.push( name );
|
|
|
|
|
if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.skip();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
dependencies,
|
|
|
|
|
contexts: usedContexts,
|
|
|
|
|
snippet: `[✂${expression.start}-${expression.end}✂]`,
|
|
|
|
|
string: this.code.slice( expression.start, expression.end )
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getBuilders () {
|
|
|
|
|
return {
|
|
|
|
|
init: new CodeBuilder(),
|
|
|
|
|
mount: new CodeBuilder(),
|
|
|
|
|
update: new CodeBuilder(),
|
|
|
|
|
detach: new CodeBuilder(),
|
|
|
|
|
detachRaw: new CodeBuilder(),
|
|
|
|
|
teardown: new CodeBuilder()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getUniqueNameMaker () {
|
|
|
|
|
return counter( this.names );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pop () {
|
|
|
|
|
const tail = this.current;
|
|
|
|
|
this.current = tail.parent;
|
|
|
|
|
|
|
|
|
|
return tail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
push ( fragment ) {
|
|
|
|
|
const newFragment = Object.assign( {}, this.current, fragment, {
|
|
|
|
|
parent: this.current
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.current = newFragment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visit ( node ) {
|
|
|
|
|
const visitor = this.visitors[ node.type ];
|
|
|
|
|
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
|
|
|
|
|
|
|
|
|
|
if ( visitor.enter ) visitor.enter( this, node );
|
|
|
|
|
|
|
|
|
|
if ( node.children ) {
|
|
|
|
|
node.children.forEach( child => {
|
|
|
|
|
this.visit( child );
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( visitor.leave ) visitor.leave( this, node );
|
|
|
|
|
}
|
|
|
|
|
}
|