Merge remote-tracking branch 'sveltejs/master'

* sveltejs/master:
  keep target and anchor inside mount method
  rename parent to root
  Fix typo in generator's contextualise; solves #46
  fix onrender hook for nested widgets
  add a createAnchor helper
  various refactorings & fixes
  separate create from mount
pull/112/head
Fabrice Weinberg 8 years ago
commit 52815b3313

@ -15,28 +15,41 @@ export default function generate ( parsed, source, options ) {
const generator = { const generator = {
addElement ( name, renderStatement, needsIdentifier = false ) { addElement ( name, renderStatement, needsIdentifier = false ) {
const needsTeardown = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
if ( needsIdentifier || needsTeardown ) { if ( needsIdentifier || isToplevel ) {
generator.current.initStatements.push( deindent` generator.current.initStatements.push( deindent`
var ${name} = ${renderStatement}; var ${name} = ${renderStatement};
${generator.appendToTarget( name )};
` ); ` );
generator.createMountStatement( name );
} else { } else {
generator.current.initStatements.push( deindent` generator.current.initStatements.push( deindent`
${generator.current.target}.appendChild( ${renderStatement} ); ${generator.current.target}.appendChild( ${renderStatement} );
` ); ` );
} }
if ( needsTeardown ) { if ( isToplevel ) {
generator.current.teardownStatements.push( deindent` generator.current.teardownStatements.push( deindent`
if ( detach ) ${name}.parentNode.removeChild( ${name} ); if ( detach ) ${name}.parentNode.removeChild( ${name} );
` ); ` );
} }
}, },
appendToTarget ( name ) {
if ( generator.current.useAnchor && generator.current.target === 'target' ) { createMountStatement ( name ) {
return `anchor.parentNode.insertBefore( ${name}, anchor )`; if ( generator.current.target === 'target' ) {
generator.current.mountStatements.push( deindent`
target.insertBefore( ${name}, anchor );
` );
} else {
generator.current.initStatements.push( deindent`
${generator.current.target}.appendChild( ${name} );
` );
} }
return `${generator.current.target}.appendChild( ${name} )`; },
createAnchor ( _name, description = '' ) {
const name = `${_name}_anchor`;
const statement = `document.createComment( ${JSON.stringify( description )} )`;
generator.addElement( name, statement, true );
return name;
}, },
addRenderer ( fragment ) { addRenderer ( fragment ) {
@ -45,10 +58,14 @@ export default function generate ( parsed, source, options ) {
} }
renderers.push( deindent` renderers.push( deindent`
function ${fragment.name} ( ${fragment.params}, component, target${fragment.useAnchor ? ', anchor' : ''} ) { function ${fragment.name} ( ${fragment.params}, component ) {
${fragment.initStatements.join( '\n\n' )} ${fragment.initStatements.join( '\n\n' )}
return { return {
mount: function ( target, anchor ) {
${fragment.mountStatements.join( '\n\n' )}
},
update: function ( changed, ${fragment.params} ) { update: function ( changed, ${fragment.params} ) {
${fragment.updateStatements.join( '\n\n' )} ${fragment.updateStatements.join( '\n\n' )}
}, },
@ -101,7 +118,7 @@ export default function generate ( parsed, source, options ) {
const context = indexes[ name ]; const context = indexes[ name ];
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
} else { } else {
dependencies.push( node.name ); dependencies.push( name );
generator.code.prependRight( node.start, `root.` ); generator.code.prependRight( node.start, `root.` );
if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' );
} }
@ -236,6 +253,7 @@ export default function generate ( parsed, source, options ) {
localElementDepth: 0, localElementDepth: 0,
initStatements: [], initStatements: [],
mountStatements: [],
updateStatements: [], updateStatements: [],
teardownStatements: [], teardownStatements: [],
@ -364,13 +382,17 @@ export default function generate ( parsed, source, options ) {
if ( generator.hasComplexBindings ) { if ( generator.hasComplexBindings ) {
initStatements.push( deindent` initStatements.push( deindent`
this.__bindings = []; this.__bindings = [];
var mainFragment = renderMainFragment( state, this, options.target ); var mainFragment = renderMainFragment( state, this );
if ( options.target ) this.mount( options.target );
while ( this.__bindings.length ) this.__bindings.pop()(); while ( this.__bindings.length ) this.__bindings.pop()();
` ); ` );
setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` ); setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` );
} else { } else {
initStatements.push( `var mainFragment = renderMainFragment( state, this, options.target );` ); initStatements.push( deindent`
var mainFragment = renderMainFragment( state, this );
if ( options.target ) this.mount( options.target );
` );
} }
if ( generator.hasComponents ) { if ( generator.hasComponents ) {
@ -387,8 +409,8 @@ export default function generate ( parsed, source, options ) {
if ( templateProperties.onrender ) { if ( templateProperties.onrender ) {
initStatements.push( deindent` initStatements.push( deindent`
if ( options.parent ) { if ( options.root ) {
options.parent.__renderHooks.push({ fn: template.onrender, context: this }); options.root.__renderHooks.push({ fn: template.onrender, context: this });
} else { } else {
template.onrender.call( this ); template.onrender.call( this );
} }
@ -449,6 +471,10 @@ export default function generate ( parsed, source, options ) {
${setStatements.join( '\n\n' )} ${setStatements.join( '\n\n' )}
}; };
this.mount = function mount ( target, anchor ) {
mainFragment.mount( target, anchor );
}
this.observe = function ( key, callback, options ) { this.observe = function ( key, callback, options ) {
var group = ( options && options.defer ) ? observers.deferred : observers.immediate; var group = ( options && options.defer ) ? observers.deferred : observers.immediate;
@ -489,6 +515,8 @@ export default function generate ( parsed, source, options ) {
state = {}; state = {};
}; };
this.root = options.root;
${initStatements.join( '\n\n' )} ${initStatements.join( '\n\n' )}
} }
` ); ` );

@ -5,52 +5,59 @@ export default {
enter ( generator, node ) { enter ( generator, node ) {
const i = generator.counters.each++; const i = generator.counters.each++;
const name = `eachBlock_${i}`; const name = `eachBlock_${i}`;
const anchor = `${name}_anchor`; const iterations = `${name}_iterations`;
const renderer = `renderEachBlock_${i}`; const renderer = `renderEachBlock_${i}`;
const listName = `${name}_value`; const listName = `${name}_value`;
const isToplevel = generator.current.localElementDepth === 0;
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
const { dependencies, snippet } = generator.contextualise( node.expression ); const { dependencies, snippet } = generator.contextualise( node.expression );
generator.addElement( anchor, `document.createComment( ${JSON.stringify( `#each ${generator.source.slice( node.expression.start, node.expression.end )}` )} )`, true ); const anchor = generator.createAnchor( name, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` );
generator.current.initStatements.push( deindent` generator.current.initStatements.push( deindent`
var ${name}_value = ${snippet}; var ${name}_value = ${snippet};
var ${name}_fragment = document.createDocumentFragment(); var ${iterations} = [];
var ${name}_iterations = [];
for ( var i = 0; i < ${name}_value.length; i += 1 ) { for ( var i = 0; i < ${name}_value.length; i += 1 ) {
${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component );
${!isToplevel ? `${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );` : ''}
} }
` );
${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); if ( isToplevel ) {
generator.current.mountStatements.push( deindent`
for ( var i = 0; i < ${iterations}.length; i += 1 ) {
${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );
}
` ); ` );
}
generator.current.updateStatements.push( deindent` generator.current.updateStatements.push( deindent`
var ${name}_value = ${snippet}; var ${name}_value = ${snippet};
for ( var i = 0; i < ${name}_value.length; i += 1 ) { for ( var i = 0; i < ${name}_value.length; i += 1 ) {
if ( !${name}_iterations[i] ) { if ( !${iterations}[i] ) {
${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component );
${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );
} else { } else {
${name}_iterations[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); ${iterations}[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i );
} }
} }
for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { for ( var i = ${name}_value.length; i < ${iterations}.length; i += 1 ) {
${name}_iterations[i].teardown( true ); ${iterations}[i].teardown( true );
} }
${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); ${iterations}.length = ${listName}.length;
${name}_iterations.length = ${listName}.length;
` ); ` );
const needsTeardown = generator.current.localElementDepth === 0;
generator.current.teardownStatements.push( deindent` generator.current.teardownStatements.push( deindent`
for ( var i = 0; i < ${name}_iterations.length; i += 1 ) { for ( var i = 0; i < ${iterations}.length; i += 1 ) {
${name}_iterations[i].teardown( ${needsTeardown ? 'detach' : 'false'} ); ${iterations}[i].teardown( ${isToplevel ? 'detach' : 'false'} );
} }
` ); ` );
@ -88,6 +95,7 @@ export default {
params, params,
initStatements: [], initStatements: [],
mountStatements: [],
updateStatements: [ Object.keys( contexts ).map( contextName => { updateStatements: [ Object.keys( contexts ).map( contextName => {
const listName = listNames[ contextName ]; const listName = listNames[ contextName ];
const indexName = indexNames[ contextName ]; const indexName = indexNames[ contextName ];

@ -15,11 +15,12 @@ export default {
allUsedContexts: new Set(), allUsedContexts: new Set(),
init: [], init: [],
mount: [],
update: [], update: [],
teardown: [] teardown: []
}; };
const shouldDetach = generator.current.localElementDepth === 0; const isToplevel = generator.current.localElementDepth === 0;
if ( isComponent ) { if ( isComponent ) {
generator.hasComponents = true; generator.hasComponents = true;
@ -57,20 +58,24 @@ export default {
${statements.join( '\n\n' )} ${statements.join( '\n\n' )}
var ${name} = new template.components.${node.name}({ var ${name} = new template.components.${node.name}({
target: ${generator.current.target}, target: ${!isToplevel ? generator.current.target: 'null'},
parent: component, root: component.root || component,
data: ${name}_initialData data: ${name}_initialData
}); });
` ); ` );
} else { } else {
local.init.unshift( deindent` local.init.unshift( deindent`
var ${name} = new template.components.${node.name}({ var ${name} = new template.components.${node.name}({
target: ${generator.current.target}, target: ${!isToplevel ? generator.current.target: 'null'},
parent: component root: component.root || component
}); });
` ); ` );
} }
if ( isToplevel ) {
local.mount.unshift( `${name}.mount( target, anchor );` );
}
if ( local.dynamicAttributes.length ) { if ( local.dynamicAttributes.length ) {
const updates = local.dynamicAttributes.map( attribute => { const updates = local.dynamicAttributes.map( attribute => {
return deindent` return deindent`
@ -87,7 +92,7 @@ export default {
` ); ` );
} }
local.teardown.push( `${name}.teardown( ${shouldDetach} );` ); local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` );
} }
else { else {
@ -132,7 +137,7 @@ export default {
} }
local.init.unshift( render ); local.init.unshift( render );
if ( shouldDetach ) { if ( isToplevel ) {
local.teardown.push( `if ( detach ) ${name}.parentNode.removeChild( ${name} );` ); local.teardown.push( `if ( detach ) ${name}.parentNode.removeChild( ${name} );` );
} }
} }
@ -146,6 +151,7 @@ export default {
generator.current.initStatements.push( local.init.join( '\n' ) ); generator.current.initStatements.push( local.init.join( '\n' ) );
if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) ); if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) );
if ( local.mount.length ) generator.current.mountStatements.push( local.mount.join( '\n' ) );
generator.current.teardownStatements.push( local.teardown.join( '\n' ) ); generator.current.teardownStatements.push( local.teardown.join( '\n' ) );
generator.push({ generator.push({
@ -166,7 +172,6 @@ export default {
if ( isComponent ) return; if ( isComponent ) return;
generator.current.initStatements.push( generator.createMountStatement( name );
generator.appendToTarget( name ) );
} }
}; };

@ -11,18 +11,19 @@ function generateBlock ( generator, node, name ) {
localElementDepth: 0, localElementDepth: 0,
initStatements: [], initStatements: [],
mountStatements: [],
updateStatements: [], updateStatements: [],
teardownStatements: [], teardownStatements: [],
counter: counter() counter: counter()
}); });
node.children.forEach( generator.visit ); node.children.forEach( generator.visit );
//generator.visit( node.children );
generator.addRenderer( generator.current ); generator.addRenderer( generator.current );
generator.pop(); generator.pop();
// unset the children, to avoid them being visited again // unset the children, to avoid them being visited again
node.children = []; node.children = [];
} }
function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { function getConditionsAndBlocks ( generator, node, _name, i = 0 ) {
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
const name = `${_name}_${i}`; const name = `${_name}_${i}`;
@ -54,15 +55,15 @@ export default {
enter ( generator, node ) { enter ( generator, node ) {
const i = generator.counters.if++; const i = generator.counters.if++;
const { params, target } = generator.current; const { params } = generator.current;
const name = `ifBlock_${i}`; const name = `ifBlock_${i}`;
const anchor = `${name}_anchor`;
const getBlock = `getBlock_${i}`; const getBlock = `getBlock_${i}`;
const currentBlock = `currentBlock_${i}`; const currentBlock = `currentBlock_${i}`;
const isToplevel = generator.current.localElementDepth === 0;
const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` );
generator.addElement( anchor, `document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} )`, true ); const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` );
generator.current.initStatements.push( deindent` generator.current.initStatements.push( deindent`
function ${getBlock} ( ${params} ) { function ${getBlock} ( ${params} ) {
@ -72,9 +73,16 @@ export default {
} }
var ${currentBlock} = ${getBlock}( ${params} ); var ${currentBlock} = ${getBlock}( ${params} );
var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component );
` ); ` );
const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`;
if ( isToplevel ) {
generator.current.mountStatements.push( mountStatement );
} else {
generator.current.initStatements.push( mountStatement );
}
generator.current.updateStatements.push( deindent` generator.current.updateStatements.push( deindent`
var _${currentBlock} = ${currentBlock}; var _${currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} ); ${currentBlock} = ${getBlock}( ${params} );
@ -82,10 +90,13 @@ export default {
${name}.update( changed, ${params} ); ${name}.update( changed, ${params} );
} else { } else {
if ( ${name} ) ${name}.teardown( true ); if ( ${name} ) ${name}.teardown( true );
${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); ${name} = ${currentBlock} && ${currentBlock}( ${params}, component );
if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
} }
` ); ` );
generator.current.teardownStatements.push( `if ( ${name} ) ${name}.teardown( detach );` ); generator.current.teardownStatements.push( deindent`
if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} );
` );
} }
}; };

@ -1,3 +1,4 @@
<p>foo: {{foo}}</p> <p>foo: {{foo}}</p>
<p>baz: {{baz}} ({{typeof baz}})</p> <p>baz: {{baz}} ({{typeof baz}})</p>
<p>qux: {{qux}}</p> <p>qux: {{qux}}</p>
<p>quux: {{quux}}</p>

@ -2,16 +2,18 @@ export default {
data: { data: {
bar: 'lol', bar: 'lol',
x: 2, x: 2,
compound: 'piece of' compound: 'piece of',
go: { deeper: 'core' }
}, },
html: `<div><p>foo: lol</p>\n<p>baz: 42 (number)</p>\n<p>qux: this is a piece of string</p></div>`, html: `<div><p>foo: lol</p>\n<p>baz: 42 (number)</p>\n<p>qux: this is a piece of string</p>\n<p>quux: core</p></div>`,
test ( assert, component, target ) { test ( assert, component, target ) {
component.set({ component.set({
bar: 'wut', bar: 'wut',
x: 3, x: 3,
compound: 'rather boring' compound: 'rather boring',
go: { deeper: 'heart' }
}); });
assert.equal( target.innerHTML, `<div><p>foo: wut</p>\n<p>baz: 43 (number)</p>\n<p>qux: this is a rather boring string</p></div>` ); assert.equal( target.innerHTML, `<div><p>foo: wut</p>\n<p>baz: 43 (number)</p>\n<p>qux: this is a rather boring string</p>\n<p>quux: heart</p></div>` );
} }
}; };

@ -1,5 +1,5 @@
<div> <div>
<Widget foo='{{bar}}' baz='{{40 + x}}' qux='this is a {{compound}} string'/> <Widget foo='{{bar}}' baz='{{40 + x}}' qux='this is a {{compound}} string' quux='{{go.deeper}}'/>
</div> </div>
<script> <script>

@ -0,0 +1,33 @@
const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' );
function permute () {
const values = VALUES.slice();
const number = Math.floor(Math.random() * VALUES.length);
const permuted = [];
for (let i = 0; i < number; i++) {
permuted.push( ...values.splice( Math.floor( Math.random() * ( number - i ) ), 1 ) );
}
return {
data: permuted,
expected: permuted.length ? `(${permuted.join(')(')})` : ''
};
}
let step = permute();
export default {
data: {
values: step.data
},
html: step.expected,
test ( assert, component, target ) {
for (let i = 0; i < 100; i++) {
step = permute();
component.set({ values: step.data });
assert.htmlEqual( target.innerHTML, step.expected );
}
}
};

@ -0,0 +1,3 @@
{{#each values as value}}
({{value}})
{{/each}}

@ -0,0 +1,13 @@
export default {
skip: true,
data: {
visible: true
},
html: 'before\n<p>Widget</p><!--#if visible-->\nafter',
test ( assert, component, target ) {
component.set({ visible: false });
assert.equal( target.innerHTML, 'before\n<!--#if visible-->\nafter' );
component.set({ visible: true });
assert.equal( target.innerHTML, 'before\n<p>Widget</p><!--#if visible-->\nafter' );
}
};

@ -0,0 +1,14 @@
before
{{#if visible}}
<Widget />
{{/if}}
after
<script>
import Widget from './Widget.html';
export default {
components: {
Widget
}
};
</script>

@ -0,0 +1,11 @@
<Widget/>{{#if foo}}<Widget/>{{/if}}
<script>
import Widget from './Widget.html';
export default {
components: {
Widget
}
};
</script>

@ -0,0 +1,9 @@
<p ref:x>{{inDocument}}</p>
<script>
export default {
onrender () {
this.set({ inDocument: document.contains( this.refs.x ) })
}
};
</script>

@ -0,0 +1,3 @@
export default {
html: `<div><p>true</p>\n<p>true</p></div>`
};

@ -0,0 +1,16 @@
<div>
<Widget/>
<ParentWidget />
</div>
<script>
import Widget from './Widget.html';
import ParentWidget from './ParentWidget.html';
export default {
components: {
Widget,
ParentWidget
}
};
</script>
Loading…
Cancel
Save