hydration working with elements, text nodes, tags and if blocks

pull/7738/head
Rich Harris 8 years ago
parent 451ea0068c
commit 1cc36884d4

@ -40,7 +40,9 @@ export default class Block {
listName: string; listName: string;
builders: { builders: {
init: CodeBuilder;
create: CodeBuilder; create: CodeBuilder;
hydrate: CodeBuilder;
mount: CodeBuilder; mount: CodeBuilder;
intro: CodeBuilder; intro: CodeBuilder;
update: CodeBuilder; update: CodeBuilder;
@ -86,7 +88,9 @@ export default class Block {
this.listName = options.listName; this.listName = options.listName;
this.builders = { this.builders = {
init: new CodeBuilder(),
create: new CodeBuilder(), create: new CodeBuilder(),
hydrate: new CodeBuilder(),
mount: new CodeBuilder(), mount: new CodeBuilder(),
intro: new CodeBuilder(), intro: new CodeBuilder(),
update: new CodeBuilder(), update: new CodeBuilder(),
@ -111,7 +115,7 @@ export default class Block {
this.hasUpdateMethod = false; // determined later this.hasUpdateMethod = false; // determined later
} }
addDependencies(dependencies) { addDependencies(dependencies: string[]) {
dependencies.forEach(dependency => { dependencies.forEach(dependency => {
this.dependencies.add(dependency); this.dependencies.add(dependency);
}); });
@ -120,21 +124,17 @@ export default class Block {
addElement( addElement(
name: string, name: string,
renderStatement: string, renderStatement: string,
hydrateStatement: string,
parentNode: string, parentNode: string,
needsIdentifier = false needsIdentifier = false
) { ) {
const isToplevel = !parentNode; const isToplevel = !parentNode;
if (needsIdentifier || isToplevel) {
this.builders.create.addLine(`var ${name} = ${renderStatement};`);
this.mount(name, parentNode); this.addVariable(name);
} else { this.builders.create.addLine(`${name} = ${renderStatement};`);
this.builders.create.addLine( this.builders.hydrate.addLine(`${name} = ${hydrateStatement};`)
`${this.generator.helper(
'appendNode' this.mount(name, parentNode);
)}( ${renderStatement}, ${parentNode} );`
);
}
if (isToplevel) { if (isToplevel) {
this.builders.unmount.addLine( this.builders.unmount.addLine(
@ -184,7 +184,7 @@ export default class Block {
mount(name: string, parentNode: string) { mount(name: string, parentNode: string) {
if (parentNode) { if (parentNode) {
this.builders.create.addLine( this.builders.mount.addLine(
`${this.generator.helper('appendNode')}( ${name}, ${parentNode} );` `${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
); );
} else { } else {
@ -210,19 +210,8 @@ export default class Block {
this.addVariable(outroing); this.addVariable(outroing);
} }
if (this.variables.size) {
const variables = Array.from(this.variables.keys())
.map(key => {
const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
})
.join(', ');
this.builders.create.addBlockAtStart(`var ${variables};`);
}
if (this.autofocus) { if (this.autofocus) {
this.builders.create.addLine(`${this.autofocus}.focus();`); this.builders.mount.addLine(`${this.autofocus}.focus();`);
} }
// minor hack we need to ensure that any {{{triples}}} are detached first // minor hack we need to ensure that any {{{triples}}} are detached first
@ -240,6 +229,26 @@ export default class Block {
properties.addBlock(`first: ${this.first},`); properties.addBlock(`first: ${this.first},`);
} }
if (this.builders.create.isEmpty()) {
properties.addBlock(`create: ${this.generator.helper('noop')},`);
} else {
properties.addBlock(deindent`
create: function () {
${this.builders.create}
},
`);
}
if (this.builders.hydrate.isEmpty()) {
properties.addBlock(`hydrate: ${this.generator.helper('noop')},`);
} else {
properties.addBlock(deindent`
hydrate: function ( nodes ) {
${this.builders.hydrate}
},
`);
}
if (this.builders.mount.isEmpty()) { if (this.builders.mount.isEmpty()) {
properties.addBlock(`mount: ${this.generator.helper('noop')},`); properties.addBlock(`mount: ${this.generator.helper('noop')},`);
} else { } else {
@ -331,7 +340,13 @@ export default class Block {
.key .key
? `, ${localKey}` ? `, ${localKey}`
: ''} ) { : ''} ) {
${this.builders.create} ${this.variables.size > 0 && (
`var ${Array.from(this.variables.keys()).map(key => {
const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
}).join(', ')};`)}
${!this.builders.init.isEmpty() && this.builders.init}
return { return {
${properties} ${properties}

@ -233,7 +233,9 @@ export default function dom(
this._fragment = ${generator.alias( this._fragment = ${generator.alias(
'create_main_fragment' 'create_main_fragment'
)}( options.target, 0, this._state, this ); )}( this._state, this );
this._fragment.hydrate( ${generator.helper('children')}( options.target ) );
this._fragment.mount( options.target, null );
${generator.hasComplexBindings && ${generator.hasComplexBindings &&
`while ( this._bindings.length ) this._bindings.pop()();`} `while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) && ${(generator.hasComponents || generator.hasIntroTransitions) &&

@ -2,6 +2,7 @@ export interface State {
name: string; name: string;
namespace: string; namespace: string;
parentNode: string; parentNode: string;
parentNodes: string;
isTopLevel: boolean; isTopLevel: boolean;
parentNodeName?: string; parentNodeName?: string;
basename?: string; basename?: string;

@ -12,7 +12,7 @@ function isElseIf(node: Node) {
} }
function getChildState(parent: State, child = {}) { function getChildState(parent: State, child = {}) {
return assign({}, parent, { name: null, parentNode: null }, child || {}); return assign({}, parent, { name: null, parentNode: null, parentNodes: 'nodes' }, child || {});
} }
// Whitespace inside one of these elements will not result in // Whitespace inside one of these elements will not result in
@ -285,6 +285,7 @@ const preprocessors = {
isTopLevel: false, isTopLevel: false,
name, name,
parentNode: name, parentNode: name,
parentNodes: block.getUniqueName(`${name}_nodes`),
parentNodeName: node.name, parentNodeName: node.name,
namespace: node.name === 'svg' namespace: node.name === 'svg'
? 'http://www.w3.org/2000/svg' ? 'http://www.w3.org/2000/svg'
@ -388,7 +389,7 @@ export default function preprocess(
indexes: new Map(), indexes: new Map(),
contextDependencies: new Map(), contextDependencies: new Map(),
params: ['target', 'h', 'state'], params: ['state'],
indexNames: new Map(), indexNames: new Map(),
listNames: new Map(), listNames: new Map(),
@ -398,6 +399,7 @@ export default function preprocess(
const state: State = { const state: State = {
namespace, namespace,
parentNode: null, parentNode: null,
parentNodes: 'nodes',
isTopLevel: true, isTopLevel: true,
}; };

@ -47,14 +47,44 @@ export default function visitElement(
const childState = node._state; const childState = node._state;
const name = childState.parentNode; const name = childState.parentNode;
block.builders.create.addLine( const isToplevel = !state.parentNode;
`var ${name} = ${getRenderStatement(
generator, block.addVariable(name);
childState.namespace, block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`);
node.name block.builders.hydrate.addBlock(deindent`
)};` ${name} = ${getHydrateStatement(generator, childState.namespace, state.parentNodes, node.name)};
); var ${childState.parentNodes} = ${generator.helper('children')}( ${name} )
block.mount(name, state.parentNode); `);
if (state.parentNode) {
block.builders.mount.addLine(`${block.generator.helper('appendNode')}( ${name}, ${state.parentNode} );`);
} else {
block.builders.mount.addLine(`${block.generator.helper('insertNode')}( ${name}, ${block.target}, anchor );`);
}
if (isToplevel) {
block.builders.unmount.addLine(
`${block.generator.helper('detachNode')}( ${name} );`
);
}
// block.addVariable(name);
// block.builders.create.addLine(
// `${name} = ${getRenderStatement(
// generator,
// childState.namespace,
// node.name
// )};`
// );
// block.builders.hydrate.addLine(
// `${name} = ${getHydrateStatement(
// generator,
// childState.namespace,
// node.name
// )};`
// );
// add CSS encapsulation attribute // add CSS encapsulation attribute
if (generator.cssId && (!generator.cascade || state.isTopLevel)) { if (generator.cssId && (!generator.cascade || state.isTopLevel)) {
@ -178,34 +208,35 @@ export default function visitElement(
} }
} }
// function getRenderStatement( function getRenderStatement(
// generator: DomGenerator, generator: DomGenerator,
// namespace: string, namespace: string,
// name: string name: string
// ) { ) {
// if (namespace === 'http://www.w3.org/2000/svg') { if (namespace === 'http://www.w3.org/2000/svg') {
// return `${generator.helper('createSvgElement')}( '${name}' )`; return `${generator.helper('createSvgElement')}( '${name}' )`;
// } }
// if (namespace) { if (namespace) {
// return `document.createElementNS( '${namespace}', '${name}' )`; return `document.createElementNS( '${namespace}', '${name}' )`;
// } }
// return `${generator.helper('createElement')}( '${name}' )`; return `${generator.helper('createElement')}( '${name}' )`;
// } }
function getRenderStatement( function getHydrateStatement(
generator: DomGenerator, generator: DomGenerator,
namespace: string, namespace: string,
nodes: string,
name: string name: string
) { ) {
// if (namespace === 'http://www.w3.org/2000/svg') { if (namespace === 'http://www.w3.org/2000/svg') {
// return `${generator.helper('createSvgElement')}( '${name}' )`; return `${generator.helper('claimSvgElement')}( '${name}' )`;
// } }
// if (namespace) { if (namespace) {
// return `document.createElementNS( '${namespace}', '${name}' )`; throw new Error('TODO hydrate exotic namespaces');
// } }
return `${generator.helper('hydrateElement')}( target, h, '${name.toUpperCase()}' )`; return `${generator.helper('claimElement')}( ${nodes}, '${name.toUpperCase()}' )`;
} }

@ -13,11 +13,11 @@ export default function visitRef(
) { ) {
const name = attribute.name; const name = attribute.name;
block.builders.create.addLine( block.builders.mount.addLine(
`${block.component}.refs.${name} = ${state.parentNode};` `${block.component}.refs.${name} = ${state.parentNode};`
); );
block.builders.destroy.addLine(deindent` block.builders.unmount.addLine(deindent`
if ( ${block.component}.refs.${name} === ${state.parentNode} ) ${block.component}.refs.${name} = null; if ( ${block.component}.refs.${name} === ${state.parentNode} ) ${block.component}.refs.${name} = null;
`); `);

@ -105,10 +105,19 @@ export default function visitIfBlock(
simple(generator, block, state, node, branches[0], dynamic, vars); simple(generator, block, state, node, branches[0], dynamic, vars);
} }
block.builders.create.addLine(
`${name}.create();`
);
block.builders.hydrate.addLine(
`${name}.hydrate( ${state.parentNodes} );`
);
if (node.needsAnchor) { if (node.needsAnchor) {
block.addElement( block.addElement(
anchor, anchor,
`${generator.helper('createComment')}()`, `${generator.helper('createComment')}()`,
`${generator.helper('createComment')}()`,
state.parentNode, state.parentNode,
true true
); );
@ -126,22 +135,18 @@ function simple(
dynamic, dynamic,
{ name, anchor, params, if_name } { name, anchor, params, if_name }
) { ) {
block.builders.create.addBlock(deindent` block.builders.init.addBlock(deindent`
var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} ); var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} );
`); `);
const isTopLevel = !state.parentNode; const isTopLevel = !state.parentNode;
const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount';
const targetNode = state.parentNode || block.target;
const anchorNode = state.parentNode ? 'null' : 'anchor';
if (isTopLevel) { block.builders.mount.addLine(
block.builders.mount.addLine( `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );`
`if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` );
);
} else {
block.builders.create.addLine(
`if ( ${name} ) ${name}.${mountOrIntro}( ${state.parentNode}, null );`
);
}
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
@ -218,7 +223,7 @@ function compound(
const current_block = block.getUniqueName(`current_block`); const current_block = block.getUniqueName(`current_block`);
const current_block_and = hasElse ? '' : `${current_block} && `; const current_block_and = hasElse ? '' : `${current_block} && `;
block.builders.create.addBlock(deindent` block.builders.init.addBlock(deindent`
function ${get_block} ( ${params} ) { function ${get_block} ( ${params} ) {
${branches ${branches
.map(({ condition, block }) => { .map(({ condition, block }) => {
@ -234,15 +239,11 @@ function compound(
const isTopLevel = !state.parentNode; const isTopLevel = !state.parentNode;
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
if (isTopLevel) { const targetNode = state.parentNode || block.target;
block.builders.mount.addLine( const anchorNode = state.parentNode ? 'null' : 'anchor';
`${if_name}${name}.${mountOrIntro}( ${block.target}, anchor );` block.builders.mount.addLine(
); `${if_name}${name}.${mountOrIntro}( ${targetNode}, ${anchorNode} );`
} else { );
block.builders.create.addLine(
`${if_name}${name}.${mountOrIntro}( ${state.parentNode}, null );`
);
}
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
@ -304,7 +305,7 @@ function compoundWithOutros(
block.addVariable(current_block_index); block.addVariable(current_block_index);
block.addVariable(name); block.addVariable(name);
block.builders.create.addBlock(deindent` block.builders.init.addBlock(deindent`
var ${if_block_creators} = [ var ${if_block_creators} = [
${branches.map(branch => branch.block).join(',\n')} ${branches.map(branch => branch.block).join(',\n')}
]; ];
@ -323,12 +324,12 @@ function compoundWithOutros(
`); `);
if (hasElse) { if (hasElse) {
block.builders.create.addBlock(deindent` block.builders.init.addBlock(deindent`
${current_block_index} = ${get_block}( ${params} ); ${current_block_index} = ${get_block}( ${params} );
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
`); `);
} else { } else {
block.builders.create.addBlock(deindent` block.builders.init.addBlock(deindent`
if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) { if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) {
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
} }
@ -337,16 +338,12 @@ function compoundWithOutros(
const isTopLevel = !state.parentNode; const isTopLevel = !state.parentNode;
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
const targetNode = state.parentNode || block.target;
const anchorNode = state.parentNode ? 'null' : 'anchor';
if (isTopLevel) { block.builders.mount.addLine(
block.builders.mount.addLine( `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${targetNode}, ${anchorNode} );`
`${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${block.target}, anchor );` );
);
} else {
block.builders.create.addLine(
`${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${state.parentNode}, null );`
);
}
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;

@ -19,6 +19,7 @@ export default function visitMustacheTag(
block.addElement( block.addElement(
name, name,
`${generator.helper('createText')}( ${value} = ${snippet} )`, `${generator.helper('createText')}( ${value} = ${snippet} )`,
`${generator.helper('claimText')}( ${state.parentNode}_nodes, ${value} = ${snippet} )`,
state.parentNode, state.parentNode,
true true
); );

@ -10,32 +10,11 @@ export default function visitText(
node: Node node: Node
) { ) {
if (!node._state.shouldCreate) return; if (!node._state.shouldCreate) return;
block.addElement(
const isTopLevel = !state.parentNode; node._state.name,
let h; `${generator.helper('createText')}( ${JSON.stringify(node.data)} )`,
if (!isTopLevel) { `${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )`,
h = block.getUniqueName(`${state.parentNode}_i`) state.parentNode,
block.addVariable(h, 0); node.usedAsAnchor
} else {
h = block.alias('h');
}
const prefix = state.parentNode && !node.usedAsAnchor ? '' : `var ${node._state.name} = `;
block.builders.create.addLine(
`${prefix}${generator.helper('hydrateText')}( ${state.parentNode || 'target'}, ${h}++, ${JSON.stringify(node.data)} )`
); );
if (!state.parentNode) {
this.builders.unmount.addLine(
`${this.generator.helper('detachNode')}( ${name} );`
);
}
// block.addElement(
// node._state.name,
// `${generator.helper('hydrateText')}( ${state.parentNode}, 0, ${JSON.stringify(node.data)} )`,
// state.parentNode,
// node.usedAsAnchor
// );
} }

@ -67,30 +67,30 @@ export function toNumber(value) {
return value === '' ? undefined : +value; return value === '' ? undefined : +value;
} }
export function hydrateElement(target, i, type) { // TODO attrs export function children ( element ) {
var child; return Array.from(element.childNodes);
while (child = target.childNodes[i]) { }
if (child.nodeName === type) {
return child; export function claimElement ( nodes, name ) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeName === name) {
return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes
} }
target.removeChild(child);
} }
child = createElement(type); console.trace('creating', name);
target.appendChild(child); return createElement(name);
return child;
} }
export function hydrateText(target, i, data) { export function claimText ( nodes, data ) {
var child; for (var i = 0; i < nodes.length; i += 1) {
while (child = target.childNodes[i]) { var node = nodes[i];
if (child.nodeType === 3) { if (node.nodeType === 3) {
return (child.data = data, child); node.data = data;
return nodes.splice(i, 1)[0];
} }
target.removeChild(child);
} }
child = createText(data); return createText(data);
target.appendChild(child);
return child;
} }

@ -0,0 +1,17 @@
export default {
snapshot(target) {
const div = target.querySelector('div');
return {
div,
p: div.querySelector('p')
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.querySelector('p'), snapshot.p);
}
};

@ -0,0 +1,3 @@
<div>
<p>nested</p>
</div>

@ -0,0 +1,19 @@
export default {
data: {
foo: true
},
snapshot(target) {
const p = target.querySelector('p');
return {
p
};
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
}
};

@ -0,0 +1,3 @@
{{#if foo}}
<p>foo!</p>
{{/if}}

@ -0,0 +1,13 @@
export default {
snapshot(target) {
return {
text: target.childNodes[0]
};
},
test(assert, target, snapshot) {
const text = target.childNodes[0];
assert.equal(text, snapshot.text);
}
};
Loading…
Cancel
Save