1 of 38

Template parts

Web Components F2F 2018-03-06�by Chris, Domenic, & Justin

2 of 38

Template Instantiation Prollyfill

Began prollyfill as a close approximation of Apple’s proposal

https://github.com/PolymerLabs/template-instantiation

3 of 38

Template Instantiation Prollyfill

Implemented lit-html and Polymer in terms of Template Instantiation, also sketched out Angular

https://github.com/PolymerLabs/template-instantiation-experiments

4 of 38

Template Instantiation Prollyfill

  • Factoring of primitives is great
  • A trivial template processor can do a lot of useful things
  • Inner template parts are very flexible

5 of 38

Template Instantiation Prollyfill

  • We improvised a little bit in some areas.
  • What did we learn?
  • What did we change?

6 of 38

General Philosophy

There are two ways to look at template instantiation:

  • High-level: declarative templating
  • Low-level: a mechanism for managing easily-updated DOM parts

7 of 38

Thinking as a template processor author, there are 3 stages where work happens

  • Update: whenever template instance values are changed
  • Create: once when template instance content is created
  • Prepare: once per template element per template processor

8 of 38

UPDATE: whenever template instance values are changed

Elementary use case for parameterized templates

9 of 38

CREATE: once when template instance content is created

Example: adding declared event listeners

10 of 38

PREPARE: once per template element per template processor

Example: parsing sophisticated expressions

11 of 38

Template parts

  • Pointer to a spot in the tree
  • List of nodes / attribute value currently filling that spot
  • Nodes at spot / attribute value can be “replaced” with new list

12 of 38

Constructable template parts?

Author can construct TemplatePart s that refer to selected tree locations or attributes

13 of 38

Constructable template parts?

Enables recursive application of template processing by assigning sub-values of complex objects to “sub-parts” of a part

14 of 38

Constructable template parts?

contrivedPartFork(rootPart, value) { // Author method on some custom template processorlet { 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);�}

15 of 38

Constructable template parts?

Required to reproduce how lit-html handles iterables, which can yield item values that require specialty handling by a TemplateProcessor .

16 of 38

Inner Template Part

  • Why treat directive and expression attributes specially?
  • Not all inner templates should be removed
  • What makes an inner template special?

17 of 38

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?

18 of 38

Not all inner templates should be removed

<template>� <cool-list>� <template>� <cool-item></cool-item>� </template>� </cool-list>�</template>

19 of 38

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 .

20 of 38

Continued prollyfill explorations

Blue sky API ideas based on our explorations

21 of 38

Continued prollyfill explorations

  • Imperatively define expressions for a template
  • TemplateInstance method to support lazy expansion of template instance content
  • Alternative callback API for TemplateProcessor .

22 of 38

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) {}�}

23 of 38

Possible API Refactoring

Note: Sorry this didn't make it into an issue yet!

  • Answer questions about mutable <template>s
  • Add explicit prepare phase
  • Allow for "lazy" DOM creation
  • Remove empty DocumentFragment (TemplateInstance) with update() API

24 of 38

Possible API Refactoring

<template>. �⬇� PreparedTemplate.�⬇� TemplateInstance.�⬇� DocumentFragment.

25 of 38

HTMLTemplateElement

#prepare(processor)

=> PreparedTemplate

  • Creates a PreparedTemplate from a <template>.
  • Captures the state of the <template>.
  • Performs prepare step/callback
  • Sets TemplateProcessor for further operations
  • Useful to pass to higher-order components, ie infinite list

26 of 38

PreparedTemplate

#createInstance(state)

=> TemplateInstance

  • Creates a TemplateInstance from a .PreparedTemplate.
  • Creates TemplateExpressionRule objects
  • Evaluates expressions
  • Does not create DOM
  • Does not create Parts

27 of 38

TemplateInstance

#createContent()

=> DocumentFragment

  • Creates DOM
  • Creates Parts
  • Interpolates values into DOM
  • Can create entire tree of templates and values in order
  • Only called on outermost template.

28 of 38

TemplateInstance

#update(state)

  • Updates Part values
  • If a value is a TemplateInstance runs internal createContent() steps to create new DOM

29 of 38

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);

30 of 38

Why Lazy?

  • Allow Browser to create DOM in optimal order�Especially important for dynamic composition (nested templates)
  • Defer Custom Element reactions until entire tree is created
  • Make TemplateInstances cheap, good for conditional directives: when(cond, templ1, templ2)

31 of 38

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) { ... }�}

32 of 38

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);�}

33 of 38

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�}

34 of 38

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) { ... }�}

35 of 38

Continued prollyfill explorations

Partial implementation available on a branch of the prollyfill

https://github.com/PolymerLabs/template-instantiation/tree/refactored-api

36 of 38

37 of 38

TemplateInstance Trees

TemplateInstance

'a'

values

1

<x-foo>

TemplateInstance

'a'

values

1

<x-foo>

TemplateInstance

38 of 38

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>`;