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

pull/649/head
Rich Harris 7 years ago
parent be7ddbac58
commit 14fe89eae8

@ -40,7 +40,9 @@ export default class Block {
listName: string;
builders: {
init: CodeBuilder;
create: CodeBuilder;
hydrate: CodeBuilder;
mount: CodeBuilder;
intro: CodeBuilder;
update: CodeBuilder;
@ -86,7 +88,9 @@ export default class Block {
this.listName = options.listName;
this.builders = {
init: new CodeBuilder(),
create: new CodeBuilder(),
hydrate: new CodeBuilder(),
mount: new CodeBuilder(),
intro: new CodeBuilder(),
update: new CodeBuilder(),
@ -111,7 +115,7 @@ export default class Block {
this.hasUpdateMethod = false; // determined later
}
addDependencies(dependencies) {
addDependencies(dependencies: string[]) {
dependencies.forEach(dependency => {
this.dependencies.add(dependency);
});
@ -120,21 +124,17 @@ export default class Block {
addElement(
name: string,
renderStatement: string,
hydrateStatement: string,
parentNode: string,
needsIdentifier = false
) {
const isToplevel = !parentNode;
if (needsIdentifier || isToplevel) {
this.builders.create.addLine(`var ${name} = ${renderStatement};`);
this.mount(name, parentNode);
} else {
this.builders.create.addLine(
`${this.generator.helper(
'appendNode'
)}( ${renderStatement}, ${parentNode} );`
);
}
this.addVariable(name);
this.builders.create.addLine(`${name} = ${renderStatement};`);
this.builders.hydrate.addLine(`${name} = ${hydrateStatement};`)
this.mount(name, parentNode);
if (isToplevel) {
this.builders.unmount.addLine(
@ -184,7 +184,7 @@ export default class Block {
mount(name: string, parentNode: string) {
if (parentNode) {
this.builders.create.addLine(
this.builders.mount.addLine(
`${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
);
} else {
@ -210,19 +210,8 @@ export default class Block {
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) {
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
@ -240,6 +229,26 @@ export default class Block {
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()) {
properties.addBlock(`mount: ${this.generator.helper('noop')},`);
} else {
@ -331,7 +340,13 @@ export default class Block {
.key
? `, ${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 {
${properties}

@ -233,7 +233,9 @@ export default function dom(
this._fragment = ${generator.alias(
'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 &&
`while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&

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

@ -12,7 +12,7 @@ function isElseIf(node: Node) {
}
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
@ -285,6 +285,7 @@ const preprocessors = {
isTopLevel: false,
name,
parentNode: name,
parentNodes: block.getUniqueName(`${name}_nodes`),
parentNodeName: node.name,
namespace: node.name === 'svg'
? 'http://www.w3.org/2000/svg'
@ -388,7 +389,7 @@ export default function preprocess(
indexes: new Map(),
contextDependencies: new Map(),
params: ['target', 'h', 'state'],
params: ['state'],
indexNames: new Map(),
listNames: new Map(),
@ -398,6 +399,7 @@ export default function preprocess(
const state: State = {
namespace,
parentNode: null,
parentNodes: 'nodes',
isTopLevel: true,
};

@ -47,14 +47,44 @@ export default function visitElement(
const childState = node._state;
const name = childState.parentNode;
block.builders.create.addLine(
`var ${name} = ${getRenderStatement(
generator,
childState.namespace,
node.name
)};`
);
block.mount(name, state.parentNode);
const isToplevel = !state.parentNode;
block.addVariable(name);
block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`);
block.builders.hydrate.addBlock(deindent`
${name} = ${getHydrateStatement(generator, childState.namespace, state.parentNodes, node.name)};
var ${childState.parentNodes} = ${generator.helper('children')}( ${name} )
`);
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
if (generator.cssId && (!generator.cascade || state.isTopLevel)) {
@ -178,34 +208,35 @@ export default function visitElement(
}
}
// function getRenderStatement(
// generator: DomGenerator,
// namespace: string,
// name: string
// ) {
// if (namespace === 'http://www.w3.org/2000/svg') {
// return `${generator.helper('createSvgElement')}( '${name}' )`;
// }
function getRenderStatement(
generator: DomGenerator,
namespace: string,
name: string
) {
if (namespace === 'http://www.w3.org/2000/svg') {
return `${generator.helper('createSvgElement')}( '${name}' )`;
}
// if (namespace) {
// return `document.createElementNS( '${namespace}', '${name}' )`;
// }
if (namespace) {
return `document.createElementNS( '${namespace}', '${name}' )`;
}
// return `${generator.helper('createElement')}( '${name}' )`;
// }
return `${generator.helper('createElement')}( '${name}' )`;
}
function getRenderStatement(
function getHydrateStatement(
generator: DomGenerator,
namespace: string,
nodes: string,
name: string
) {
// if (namespace === 'http://www.w3.org/2000/svg') {
// return `${generator.helper('createSvgElement')}( '${name}' )`;
// }
if (namespace === 'http://www.w3.org/2000/svg') {
return `${generator.helper('claimSvgElement')}( '${name}' )`;
}
// if (namespace) {
// return `document.createElementNS( '${namespace}', '${name}' )`;
// }
if (namespace) {
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;
block.builders.create.addLine(
block.builders.mount.addLine(
`${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;
`);

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

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

@ -10,32 +10,11 @@ export default function visitText(
node: Node
) {
if (!node._state.shouldCreate) return;
const isTopLevel = !state.parentNode;
let h;
if (!isTopLevel) {
h = block.getUniqueName(`${state.parentNode}_i`)
block.addVariable(h, 0);
} 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)} )`
block.addElement(
node._state.name,
`${generator.helper('createText')}( ${JSON.stringify(node.data)} )`,
`${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )`,
state.parentNode,
node.usedAsAnchor
);
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;
}
export function hydrateElement(target, i, type) { // TODO attrs
var child;
while (child = target.childNodes[i]) {
if (child.nodeName === type) {
return child;
export function children ( element ) {
return Array.from(element.childNodes);
}
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);
target.appendChild(child);
return child;
console.trace('creating', name);
return createElement(name);
}
export function hydrateText(target, i, data) {
var child;
while (child = target.childNodes[i]) {
if (child.nodeType === 3) {
return (child.data = data, child);
export function claimText ( nodes, data ) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeType === 3) {
node.data = data;
return nodes.splice(i, 1)[0];
}
target.removeChild(child);
}
child = createText(data);
target.appendChild(child);
return child;
return createText(data);
}

@ -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