Template Instantiation Prollyfill
Began prollyfill as a close approximation of Apple’s proposal
Template Instantiation Prollyfill
Implemented lit-html and Polymer in terms of Template Instantiation, also sketched out Angular
https://github.com/PolymerLabs/template-instantiation-experiments
Template Instantiation Prollyfill
Template Instantiation Prollyfill
General Philosophy
There are two ways to look at template instantiation:
Thinking as a template processor author, there are 3 stages where work happens
UPDATE: whenever template instance values are changed
Elementary use case for parameterized templates
CREATE: once when template instance content is created
Example: adding declared event listeners
PREPARE: once per template element per template processor
Example: parsing sophisticated expressions
Template parts
Constructable template parts?
Author can construct TemplatePart s that refer to selected tree locations or attributes
Constructable template parts?
Enables recursive application of template processing by assigning sub-values of complex objects to “sub-parts” of a part
Constructable template parts?
contrivedPartFork(rootPart, value) { // Author method on some custom template processor� let { forkedPart } = rootPart;�� if (forkedPart == null) {� // Create a new NodeTemplatePart positioned next to rootPart:� forkedPart = rootPart.fork();� // Keep a reference to the sub-part for later updating:� rootPart.forkedPart = forkedPart;� }�� // Update the root node here:� rootNode.value = value.left;� // Constructed parts are passed back through the processor flow:� this.processSinglePart(forkedPart, value.right);�}
Constructable template parts?
Required to reproduce how lit-html handles iterables, which can yield item values that require specialty handling by a TemplateProcessor .
Inner Template Part
Why treat directive and expression attributes specially?
InnerTemplatePart has a reference to the template element. Can we let the template processor decide which attributes of the template are important?
Not all inner templates should be removed
<template>� <cool-list>� <template>� <cool-item></cool-item>� </template>� </cool-list>�</template>
What makes an inner template special?
What if we could designate arbitrary elements for removal at “preparation” time, creating corresponding parts with singleton references to those elements?
Regardless, there should be an explicit signal that a template should create a InnerTemplatePart .
Continued prollyfill explorations
Blue sky API ideas based on our explorations
Continued prollyfill explorations
Imperatively define expressions for a template
/**� * Describes an expression and the place where it occurs in a� * tree. Includes specialized classes for attribute, node and inner� * template cases.� */�class TemplateExpressionRule {� constructor(expression, locationInTree) {}�}
Possible API Refactoring
Note: Sorry this didn't make it into an issue yet!
Possible API Refactoring
<template>. �⬇� PreparedTemplate.�⬇� TemplateInstance.�⬇� DocumentFragment.
HTMLTemplateElement
#prepare(processor)
=> PreparedTemplate
PreparedTemplate
#createInstance(state)
=> TemplateInstance
TemplateInstance
#createContent()
=> DocumentFragment
TemplateInstance
#update(state)
TemplateInstance
#update(state)
const childPreparedTemplate = childTemplateEl.prepare(processor);�const childInstance = childPreparedTemplate.createInstance(inputValues);��const parentPreparedTemplate = parentTemplateEl.prepare(processor);�const parentInstance = � parentPreparedTemplate.createInstance([childInstance]);��const content = parentInstance.createContent();�document.body.appendChild(content);��// later�parentInstance.update(newValues);
Why Lazy?
Specialized callbacks for template processor
class TemplateProcessor {� /** returns “prepared” template **/� prepare(templateElement) { ... }�� /** returns state in terms of expressions */� evaluate(expressionRules, inputValues) { ... }�� /** updates parts w/ evaluated state */� update(templateInstance, evaluatedState) { ... }�}
Specialized callbacks for template processor: prepare .
/**� * Runs one time per template element per template processor.� * Returns a PreparedTemplate that holds a list of expression rules and� * a copy of the template content with expressions / inner templates� * extracted.� */�prepare(templateElement) {� // Template expression parsing happens here, and produces a list of� // expression rules (expression + location in template)
// NOTE: Later, parts are produced from expression rules on a 1-to-1 basis.�� return new PreparedTemplate(this, parsedTemplate, expressionRules);�}
Specialized callbacks for template processor: evaluate .
/**� * Invoked every time input values are passed to the template instance.
* - in PreparedTemplate.createInstance()
* - in TemplateInstance.update()� * Return the state that should be assigned to the instance's parts.� */�evaluate(expressionRules, inputValues) {� return inputValues; // Default implementation echoes input values�}
Specialized callbacks for template processor: update .
/**� * Invoked every time there are new input values. Note that parts are available� * via the template instance. � */�update(templateInstance, evaluatedState) {� const { parts } = templateInstance;� for (const part of parts) { ... }�}
Continued prollyfill explorations
Partial implementation available on a branch of the prollyfill
https://github.com/PolymerLabs/template-instantiation/tree/refactored-api
TemplateInstance Trees
TemplateInstance
'a'
values
1
<x-foo>
TemplateInstance
'a'
values
1
<x-foo>
TemplateInstance
Dynamic Template Composition
// JSX
let list = (data) => <ul>{data.map(item => <li>{item.name}</li>)}</ul>;
// lit-html
let list = (data) => html`<ul>${data.map(item => html`<li>{item.name}</li>`)}</ul>`;