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

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

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

@ -11,18 +11,19 @@ function generateBlock ( generator, node, name ) {
localElementDepth: 0,
initStatements: [],
mountStatements: [],
updateStatements: [],
teardownStatements: [],
counter: counter()
});
node.children.forEach( generator.visit );
//generator.visit( node.children );
generator.addRenderer( generator.current );
generator.pop();
// unset the children, to avoid them being visited again
node.children = [];
}
function getConditionsAndBlocks ( generator, node, _name, i = 0 ) {
generator.addSourcemapLocations( node.expression );
const name = `${_name}_${i}`;
@ -54,15 +55,15 @@ export default {
enter ( generator, node ) {
const i = generator.counters.if++;
const { params, target } = generator.current;
const { params } = generator.current;
const name = `ifBlock_${i}`;
const anchor = `${name}_anchor`;
const getBlock = `getBlock_${i}`;
const currentBlock = `currentBlock_${i}`;
const isToplevel = generator.current.localElementDepth === 0;
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`
function ${getBlock} ( ${params} ) {
@ -72,9 +73,16 @@ export default {
}
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`
var _${currentBlock} = ${currentBlock};
${currentBlock} = ${getBlock}( ${params} );
@ -82,10 +90,13 @@ export default {
${name}.update( changed, ${params} );
} else {
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>baz: {{baz}} ({{typeof baz}})</p>
<p>qux: {{qux}}</p>
<p>quux: {{quux}}</p>

@ -2,16 +2,18 @@ export default {
data: {
bar: 'lol',
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 ) {
component.set({
bar: 'wut',
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>
<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>
<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