diff --git a/compiler/generate/index.js b/compiler/generate/index.js index f2a70fc6a6..e7af0032d4 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -1,11 +1,13 @@ -import { getLocator } from 'locate-character'; +import MagicString from 'magic-string'; +import { walk } from 'estree-walker'; import deindent from './utils/deindent.js'; import walkHtml from './utils/walkHtml.js'; import flattenReference from './utils/flattenReference.js'; +import counter from './utils/counter.js'; function createRenderer ( fragment ) { return deindent` - function ${fragment.name} ( target${fragment.useAnchor ? ', anchor' : ''} ) { + function ${fragment.name} ( component, target${fragment.useAnchor ? ', anchor' : ''} ) { ${fragment.initStatements.join( '\n\n' )} return { @@ -16,13 +18,14 @@ function createRenderer ( fragment ) { teardown: function () { ${fragment.teardownStatements.join( '\n\n' )} } - } + }; } `; } export default function generate ( parsed, template ) { - const locator = getLocator( template ); + const code = new MagicString( template ); + const renderers = []; const counters = { @@ -45,11 +48,7 @@ export default function generate ( parsed, template ) { contexts: {}, contextChain: [ 'context' ], - counters: { - element: 0, - text: 0, - anchor: 0 - }, + counter: counter(), parent: null }; @@ -63,6 +62,8 @@ export default function generate ( parsed, template ) { return `context.${flattened.keypath}`; } + console.log( `node, contexts`, node, contexts ) + return 'TODO' throw new Error( 'TODO expressions' ); } @@ -70,15 +71,50 @@ export default function generate ( parsed, template ) { walkHtml( child, { Element: { enter ( node ) { - const name = `element_${current.counters.element++}`; + const name = current.counter( node.name ); - current.initStatements.push( deindent` - var ${name} = document.createElement( '${node.name}' ); - ` ); + const initStatements = [ + `var ${name} = document.createElement( '${node.name}' );` + ]; - current.teardownStatements.push( deindent` - ${name}.parentNode.removeChild( ${name} ); - ` ); + const teardownStatements = [ + `${name}.parentNode.removeChild( ${name} );` + ]; + + node.attributes.forEach( attribute => { + if ( attribute.type === 'EventHandler' ) { + // TODO use magic-string here, so that stack traces + // go through the template + + // TODO verify that it's a valid callee (i.e. built-in or declared method) + + const handler = current.counter( `${attribute.name}Handler` ); + + const callee = `component.${attribute.expression.callee.name}`; + const args = attribute.expression.arguments + .map( arg => flattenExpression( arg, current.contexts ) ) + .join( ', ' ); + + initStatements.push( deindent` + function ${handler} ( event ) { + ${callee}(${args}); + } + + ${name}.addEventListener( '${attribute.name}', ${handler}, false ); + ` ); + + teardownStatements.push( deindent` + ${name}.removeEventListener( '${attribute.name}', ${handler}, false ); + ` ); + } + + else { + throw new Error( `Not implemented: ${attribute.type}` ); + } + }); + + current.initStatements.push( initStatements.join( '\n' ) ); + current.teardownStatements.push( teardownStatements.join( '\n' ) ); current = Object.assign( {}, current, { target: name, @@ -106,14 +142,14 @@ export default function generate ( parsed, template ) { Text: { enter ( node ) { current.initStatements.push( deindent` - ${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data ) }) ); + ${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) ); ` ); } }, MustacheTag: { enter ( node ) { - const name = `text_${current.counters.text++}`; + const name = current.counter( 'text' ); const expression = flattenExpression( node.expression, current.contexts ); current.initStatements.push( deindent` @@ -147,7 +183,7 @@ export default function generate ( parsed, template ) { current.updateStatements.push( deindent` if ( ${expression} && !${name} ) { - ${name} = ${renderer}( ${current.target}, ${name}_anchor ); + ${name} = ${renderer}( component, ${current.target}, ${name}_anchor ); } else if ( !${expression} && ${name} ) { @@ -175,11 +211,7 @@ export default function generate ( parsed, template ) { updateStatements: [], teardownStatements: [], - counters: { - element: 0, - text: 0, - anchor: 0 - }, + counter: counter(), parent: current }; @@ -209,7 +241,7 @@ export default function generate ( parsed, template ) { current.updateStatements.push( deindent` for ( var i = 0; i < ${expression}.length; i += 1 ) { if ( !${name}_iterations[i] ) { - ${name}_iterations[i] = ${renderer}( ${name}_fragment ); + ${name}_iterations[i] = ${renderer}( component, ${name}_fragment ); } const iteration = ${name}_iterations[i]; @@ -245,11 +277,7 @@ export default function generate ( parsed, template ) { updateStatements: [], teardownStatements: [], - counters: { - element: 0, - text: 0, - anchor: 0 - }, + counter: counter(), parent: current }; @@ -266,7 +294,7 @@ export default function generate ( parsed, template ) { renderers.push( createRenderer( current ) ); - let js; + let js = ''; let hasDefaultData = false; // TODO wrap all this in magic-string @@ -280,7 +308,7 @@ export default function generate ( parsed, template ) { } } - const code = deindent` + const result = deindent` ${js} ${renderers.reverse().join( '\n\n' )} @@ -294,7 +322,6 @@ export default function generate ( parsed, template ) { deferred: Object.create( null ) }; - // universal methods function dispatchObservers ( group, state, oldState ) { for ( const key in group ) { const newValue = state[ key ]; @@ -315,6 +342,11 @@ export default function generate ( parsed, template ) { return state[ key ]; }; + component.set = function set ( newState ) { + Object.assign( state, newState ); + mainFragment.update( state ); + }; + component.observe = function ( key, callback, options = {} ) { const group = options.defer ? observers.deferred : observers.immediate; @@ -329,12 +361,6 @@ export default function generate ( parsed, template ) { }; }; - // component-specific methods - component.set = function set ( newState ) { - Object.assign( state, newState ); - mainFragment.update( state ); - }; - component.teardown = function teardown () { mainFragment.teardown(); mainFragment = null; @@ -342,12 +368,12 @@ export default function generate ( parsed, template ) { state = {}; }; - let mainFragment = renderMainFragment( options.target ); + let mainFragment = renderMainFragment( component, options.target ); component.set( ${hasDefaultData ? `Object.assign( template.data(), options.data )` : `options.data`} ); return component; } `; - return { code }; + return { code: result }; // TODO use magic-string } diff --git a/compiler/generate/utils/counter.js b/compiler/generate/utils/counter.js new file mode 100644 index 0000000000..799e72fb48 --- /dev/null +++ b/compiler/generate/utils/counter.js @@ -0,0 +1,12 @@ +export default function counter () { + const counts = {}; + + return function ( label ) { + if ( label in counts ) { + return `label_${counts[ label ]++}`; + } + + counts[ label ] = 0; + return label; + }; +} diff --git a/package.json b/package.json index 9270cdd527..4796b7afdb 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "acorn": "^4.0.3", + "estree-walker": "^0.3.0", "locate-character": "^2.0.0", "magic-string": "^0.16.0" } diff --git a/test/compiler/event-handler/_config.js b/test/compiler/event-handler/_config.js new file mode 100644 index 0000000000..61d3fd4505 --- /dev/null +++ b/test/compiler/event-handler/_config.js @@ -0,0 +1,10 @@ +import * as assert from 'assert'; + +export default { + solo: true, + description: 'attaches event handlers', + html: '', + test ( component, target ) { + assert.ok( false, 'TODO fire synthetic event to test' ); + } +}; diff --git a/test/compiler/event-handler/main.svelte b/test/compiler/event-handler/main.svelte new file mode 100644 index 0000000000..ba9bbc27fe --- /dev/null +++ b/test/compiler/event-handler/main.svelte @@ -0,0 +1,5 @@ + + +{{#if visible}} +

hello!

+{{/if}}