|
|
@ -1,31 +1,39 @@
|
|
|
|
|
|
|
|
import { getLocator } from 'locate-character';
|
|
|
|
import deindent from './utils/deindent.js';
|
|
|
|
import deindent from './utils/deindent.js';
|
|
|
|
import walkHtml from './utils/walkHtml.js';
|
|
|
|
import walkHtml from './utils/walkHtml.js';
|
|
|
|
|
|
|
|
|
|
|
|
const ROOT = 'options.target';
|
|
|
|
const ROOT = 'options.target';
|
|
|
|
|
|
|
|
|
|
|
|
export default function generate ( parsed ) {
|
|
|
|
export default function generate ( parsed, template ) {
|
|
|
|
const counters = {
|
|
|
|
const counters = {
|
|
|
|
element: 0,
|
|
|
|
element: 0,
|
|
|
|
text: 0,
|
|
|
|
text: 0,
|
|
|
|
anchor: 0
|
|
|
|
anchor: 0,
|
|
|
|
|
|
|
|
if: 0
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderBlocks = [];
|
|
|
|
const initStatements = [];
|
|
|
|
const updateBlocks = [];
|
|
|
|
const setStatements = [ deindent`
|
|
|
|
const teardownBlocks = [];
|
|
|
|
const oldState = state;
|
|
|
|
|
|
|
|
state = Object.assign( {}, oldState, newState );
|
|
|
|
const codeBlocks = [];
|
|
|
|
` ];
|
|
|
|
|
|
|
|
const teardownStatements = [];
|
|
|
|
|
|
|
|
|
|
|
|
// TODO add contents of <script> tag, with `export default` replaced with `var template =`
|
|
|
|
// TODO add contents of <script> tag, with `export default` replaced with `var template =`
|
|
|
|
// TODO css
|
|
|
|
// TODO css
|
|
|
|
|
|
|
|
|
|
|
|
// create component
|
|
|
|
const locator = getLocator( template );
|
|
|
|
|
|
|
|
|
|
|
|
parsed.html.children.forEach( child => {
|
|
|
|
parsed.html.children.forEach( child => {
|
|
|
|
|
|
|
|
const declarations = [];
|
|
|
|
|
|
|
|
|
|
|
|
let current = {
|
|
|
|
let current = {
|
|
|
|
target: ROOT,
|
|
|
|
target: ROOT,
|
|
|
|
indentation: 0,
|
|
|
|
conditions: [],
|
|
|
|
block: []
|
|
|
|
children: [],
|
|
|
|
|
|
|
|
renderBlocks: [],
|
|
|
|
|
|
|
|
removeBlocks: [],
|
|
|
|
|
|
|
|
anchor: null
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const stack = [ current ];
|
|
|
|
const stack = [ current ];
|
|
|
@ -35,24 +43,30 @@ export default function generate ( parsed ) {
|
|
|
|
if ( node.type === 'Element' ) {
|
|
|
|
if ( node.type === 'Element' ) {
|
|
|
|
current = {
|
|
|
|
current = {
|
|
|
|
target: `element_${counters.element++}`,
|
|
|
|
target: `element_${counters.element++}`,
|
|
|
|
indentation: current.indentation,
|
|
|
|
conditions: current.conditions,
|
|
|
|
block: current.block
|
|
|
|
children: current.children,
|
|
|
|
|
|
|
|
renderBlocks: current.renderBlocks,
|
|
|
|
|
|
|
|
removeBlocks: current.removeBlocks,
|
|
|
|
|
|
|
|
anchor: current.anchor
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
stack.push( current );
|
|
|
|
stack.push( current );
|
|
|
|
|
|
|
|
|
|
|
|
const renderBlock = deindent`
|
|
|
|
declarations.push( `var ${current.target};` );
|
|
|
|
var ${current.target} = document.createElement( '${node.name}' );
|
|
|
|
|
|
|
|
options.target.appendChild( ${current.target} );
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
renderBlocks.push( renderBlock );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// updateBlocks.push( deindent`
|
|
|
|
if ( current.anchor ) {
|
|
|
|
//
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
// ` );
|
|
|
|
${current.target} = document.createElement( '${node.name}' );
|
|
|
|
|
|
|
|
${current.anchor}.parentNode.insertBefore( ${current.target}, ${current.anchor} );
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
|
|
|
|
${current.target} = document.createElement( '${node.name}' );
|
|
|
|
|
|
|
|
options.target.appendChild( ${current.target} );
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
teardownBlocks.push( deindent`
|
|
|
|
current.removeBlocks.push( deindent`
|
|
|
|
${current.target}.parentNode.removeChild( ${current.target} );
|
|
|
|
${current.target}.parentNode.removeChild( ${current.target} );
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -61,18 +75,21 @@ export default function generate ( parsed ) {
|
|
|
|
if ( current.target === ROOT ) {
|
|
|
|
if ( current.target === ROOT ) {
|
|
|
|
const identifier = `text_${counters.text++}`;
|
|
|
|
const identifier = `text_${counters.text++}`;
|
|
|
|
|
|
|
|
|
|
|
|
renderBlocks.push( deindent`
|
|
|
|
declarations.push( `var ${identifier};` );
|
|
|
|
var ${identifier} = document.createTextNode( ${JSON.stringify( node.data )} );
|
|
|
|
|
|
|
|
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
|
|
|
|
${identifier} = document.createTextNode( ${JSON.stringify( node.data )} );
|
|
|
|
${current.target}.appendChild( ${identifier} );
|
|
|
|
${current.target}.appendChild( ${identifier} );
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
teardownBlocks.push( deindent`
|
|
|
|
current.removeBlocks.push( deindent`
|
|
|
|
${identifier}.parentNode.removeChild( ${identifier} );
|
|
|
|
${identifier}.parentNode.removeChild( ${identifier} );
|
|
|
|
|
|
|
|
${identifier} = null;
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
renderBlocks.push( deindent`
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) );
|
|
|
|
${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) );
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -82,22 +99,65 @@ export default function generate ( parsed ) {
|
|
|
|
const identifier = `text_${counters.text++}`;
|
|
|
|
const identifier = `text_${counters.text++}`;
|
|
|
|
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state
|
|
|
|
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state
|
|
|
|
|
|
|
|
|
|
|
|
renderBlocks.push( deindent`
|
|
|
|
declarations.push( `var ${identifier};` );
|
|
|
|
var ${identifier} = document.createTextNode( '' );
|
|
|
|
|
|
|
|
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
|
|
|
|
${identifier} = document.createTextNode( '' );
|
|
|
|
${current.target}.appendChild( ${identifier} );
|
|
|
|
${current.target}.appendChild( ${identifier} );
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
updateBlocks.push( deindent`
|
|
|
|
setStatements.push( deindent`
|
|
|
|
if ( state.${expression} !== oldState.${expression} ) {
|
|
|
|
if ( state.${expression} !== oldState.${expression} ) { // TODO and conditions
|
|
|
|
${identifier}.data = state.${expression};
|
|
|
|
${identifier}.data = state.${expression};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
if ( current.target === ROOT ) {
|
|
|
|
current.removeBlocks.push( deindent`
|
|
|
|
teardownBlocks.push( deindent`
|
|
|
|
|
|
|
|
${identifier}.parentNode.removeChild( ${identifier} );
|
|
|
|
${identifier}.parentNode.removeChild( ${identifier} );
|
|
|
|
|
|
|
|
${identifier} = null;
|
|
|
|
` );
|
|
|
|
` );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else if ( node.type === 'IfBlock' ) {
|
|
|
|
|
|
|
|
const anchor = `anchor_${counters.anchor++}`;
|
|
|
|
|
|
|
|
const suffix = `if_${counters.if++}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
declarations.push( `var ${anchor};` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current.renderBlocks.push( deindent`
|
|
|
|
|
|
|
|
${anchor} = document.createComment( '#if ${template.slice( node.expression.start, node.expression.end)}' );
|
|
|
|
|
|
|
|
${current.target}.appendChild( ${anchor} );
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current.removeBlocks.push( deindent`
|
|
|
|
|
|
|
|
${anchor}.parentNode.removeChild( ${anchor} );
|
|
|
|
|
|
|
|
${anchor} = null;
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current = {
|
|
|
|
|
|
|
|
renderName: `render_${suffix}`,
|
|
|
|
|
|
|
|
removeName: `remove_${suffix}`,
|
|
|
|
|
|
|
|
target: current.target,
|
|
|
|
|
|
|
|
conditions: current.conditions.concat( expression ),
|
|
|
|
|
|
|
|
renderBlocks: [],
|
|
|
|
|
|
|
|
removeBlocks: [],
|
|
|
|
|
|
|
|
anchor
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setStatements.push( deindent`
|
|
|
|
|
|
|
|
// TODO account for conditions (nested ifs)
|
|
|
|
|
|
|
|
if ( state.${expression} && !oldState.${expression} ) ${current.renderName}();
|
|
|
|
|
|
|
|
else if ( !state.${expression} && oldState.${expression} ) ${current.removeName}();
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
teardownStatements.push( deindent`
|
|
|
|
|
|
|
|
// TODO account for conditions (nested ifs)
|
|
|
|
|
|
|
|
if ( state.${expression} ) ${current.removeName}();
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stack.push( current );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
else {
|
|
|
@ -106,14 +166,39 @@ export default function generate ( parsed ) {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
leave ( node ) {
|
|
|
|
leave ( node ) {
|
|
|
|
if ( node.type === 'Element' ) {
|
|
|
|
if ( node.type === 'IfBlock' ) {
|
|
|
|
|
|
|
|
const { line, column } = locator( node.start );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initStatements.push( deindent`
|
|
|
|
|
|
|
|
// (${line}:${column}) {{#if ${template.slice( node.expression.start, node.expression.end )}}}...{{/if}}
|
|
|
|
|
|
|
|
function ${current.renderName} () {
|
|
|
|
|
|
|
|
${current.renderBlocks.join( '\n\n' )}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function ${current.removeName} () {
|
|
|
|
|
|
|
|
${current.removeBlocks.join( '\n\n' )}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stack.pop();
|
|
|
|
|
|
|
|
current = stack[ stack.length - 1 ];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else if ( node.type === 'Element' ) {
|
|
|
|
stack.pop();
|
|
|
|
stack.pop();
|
|
|
|
current = stack[ stack.length - 1 ];
|
|
|
|
current = stack[ stack.length - 1 ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initStatements.push( ...current.renderBlocks );
|
|
|
|
|
|
|
|
teardownStatements.push( ...current.removeBlocks );
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
teardownStatements.push( deindent`
|
|
|
|
|
|
|
|
state = {};
|
|
|
|
|
|
|
|
` );
|
|
|
|
|
|
|
|
|
|
|
|
const code = deindent`
|
|
|
|
const code = deindent`
|
|
|
|
export default function createComponent ( options ) {
|
|
|
|
export default function createComponent ( options ) {
|
|
|
|
var component = {};
|
|
|
|
var component = {};
|
|
|
@ -145,13 +230,6 @@ export default function generate ( parsed ) {
|
|
|
|
return state[ key ];
|
|
|
|
return state[ key ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
component.set = function set ( newState ) {
|
|
|
|
|
|
|
|
const oldState = state;
|
|
|
|
|
|
|
|
state = Object.assign( {}, oldState, newState );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
${updateBlocks.join( '\n\n' )}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
component.observe = function ( key, callback, options = {} ) {
|
|
|
|
component.observe = function ( key, callback, options = {} ) {
|
|
|
|
const group = options.defer ? observers.deferred : observers.immediate;
|
|
|
|
const group = options.defer ? observers.deferred : observers.immediate;
|
|
|
|
|
|
|
|
|
|
|
@ -166,12 +244,16 @@ export default function generate ( parsed ) {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// component-specific methods
|
|
|
|
|
|
|
|
component.set = function set ( newState ) {
|
|
|
|
|
|
|
|
${setStatements.join( '\n\n' )}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
component.teardown = function teardown () {
|
|
|
|
component.teardown = function teardown () {
|
|
|
|
${teardownBlocks.join( '\n\n' )}
|
|
|
|
${teardownStatements.join( '\n\n' )}
|
|
|
|
state = {};
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
${renderBlocks.join( '\n\n' )}
|
|
|
|
${initStatements.join( '\n\n' )}
|
|
|
|
|
|
|
|
|
|
|
|
component.set( options.data );
|
|
|
|
component.set( options.data );
|
|
|
|
|
|
|
|
|
|
|
|