import MagicString from 'magic-string'; import { walk } from 'estree-walker'; import deindent from './utils/deindent.js'; import walkHtml from './utils/walkHtml.js'; import isReference from './utils/isReference.js'; import contextualise from './utils/contextualise.js'; import counter from './utils/counter.js'; import attributeLookup from './attributes/lookup.js'; function createRenderer ( fragment ) { return deindent` function ${fragment.name} ( component, target${fragment.useAnchor ? ', anchor' : ''} ) { ${fragment.initStatements.join( '\n\n' )} return { update: function ( ${fragment.contextChain.join( ', ' )} ) { ${fragment.updateStatements.join( '\n\n' )} }, teardown: function () { ${fragment.teardownStatements.join( '\n\n' )} } }; } `; } export default function generate ( parsed, template ) { const code = new MagicString( template ); function addSourcemapLocations ( node ) { walk( node, { enter ( node ) { code.addSourcemapLocation( node.start ); code.addSourcemapLocation( node.end ); } }); } const templateProperties = {}; if ( parsed.js ) { addSourcemapLocations( parsed.js.content ); const defaultExport = parsed.js.content.body.find( node => node.type === 'ExportDefaultDeclaration' ); if ( defaultExport ) { code.overwrite( defaultExport.start, defaultExport.declaration.start, `const template = ` ); defaultExport.declaration.properties.forEach( prop => { templateProperties[ prop.key.name ] = prop.value; }); } } const helpers = {}; if ( templateProperties.helpers ) { templateProperties.helpers.properties.forEach( prop => { helpers[ prop.key.name ] = prop.value; }); } const renderers = []; const getName = counter(); // TODO use getName instead of counters const counters = { if: 0, each: 0 }; // TODO (scoped) css let current = { useAnchor: false, name: 'renderMainFragment', target: 'target', initStatements: [], updateStatements: [], teardownStatements: [], contexts: {}, contextChain: [ 'root' ], counter: counter(), parent: null }; parsed.html.children.forEach( child => { walkHtml( child, { Comment: { // do nothing }, Element: { enter ( node ) { const name = current.counter( node.name ); const initStatements = [ `var ${name} = document.createElement( '${node.name}' );` ]; const updateStatements = []; const teardownStatements = [ `${name}.parentNode.removeChild( ${name} );` ]; const allUsedContexts = new Set(); node.attributes.forEach( attribute => { if ( attribute.type === 'Attribute' ) { let metadata = attributeLookup[ attribute.name ]; if ( metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf( node.name ) ) metadata = null; if ( attribute.value === true ) { // attributes without values, e.g.