Exploring How lit-html Works: TemplateResult and SVGTemplateResult constructors
Tags: javascript, typescript, webcomponents, devjournal
Published: 2020-01-05 14:58:27

In this series, How lit-html works, I will explore (not explain) internal implementation of [lit-html](https://lit-html.polymer-project.org/).

In [the previous post](https://dev.to/nozomuikuta/exploring-how-lit-html-works-html-and-svg-function-1lpe), we saw that what `html` and `svg` do. They receive template literals and pass the strings and values to the corresponding constructors, `TemplateResult` and `SVGTemplateResult`.

In this post, I will explore what instances that the constructors create looks like.

# `TemplateResult` class

Here is definitions of properties and constructor method of the `TemplateResult` class.

```typescript
export class TemplateResult {
  readonly strings: TemplateStringsArray;
  readonly values: readonly unknown[];
  readonly type: string;
  readonly processor: TemplateProcessor;


  constructor(
      strings: TemplateStringsArray, values: readonly unknown[], type: string,
      processor: TemplateProcessor) {
    this.strings = strings;
    this.values = values;
    this.type = type;
    this.processor = processor;
  }

  ...
}
```

All the arguments are simply assigned to readonly properties whose name is the same.

`TemplateResult` class has two prototype methods as well: `getHTML()` and `getTemplateElement()`.

## `getHTML()` method

```typescript
getHTML(): string {
  const l = this.strings.length - 1;
  let html = '';
  let isCommentBinding = false;

  for (let i = 0; i < l; i++) {
    const s = this.strings[i];

    const commentOpen = s.lastIndexOf('<!--');
    isCommentBinding = (commentOpen > -1 || isCommentBinding) &&
        s.indexOf('-->', commentOpen + 1) === -1;

    const attributeMatch = lastAttributeNameRegex.exec(s);
    if (attributeMatch === null) {
      html += s + (isCommentBinding ? commentMarker : nodeMarker);
    } else {
      html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
          attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
          marker;
    }
  }
  html += this.strings[l];
  return html;
}
```

`getHTML()` method returns full HTML string, where bindings are modified with suffixes based on their [binding types](https://lit-html.polymer-project.org/guide/template-reference#binding-types).

The bound values are replaced by the _marker_ `{{lit-RANDOM_NUMBER}}`. 

Note that, although _RANDOM_NUMBER_ in the examples below differs every time, it is actually determined only once in runtime and is shared.

```typescript
export const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
```

### Text Binding

```typescript
const name = 'Nozomu Ikuta';
const templateResult = html`<div>Hello, ${name}</div>`;

console.log(templateResult.getHTML());
// => `<div>Hello, <!--{{lit-6732669937008782}}-->!</div>`
```

### Attribute Binding

The names of bound attributes are prepended by the suffix `$lit$`.

```typescript
// Text Attribute Binding
const className = 'my-class';
const templateResult = html`<div class="${className}">Hello, Nozomu Ikuta</div>`;

console.log(templateResult.getHTML());
// => `<div class$lit$="{{lit-37406895828981424}}">Hello, Nozomu Ikuta</div>`
```

```typescript
// Boolean Attribute Binding
const bool = true;
const templateResult = html`<button type="button" ?disabled="${bool}">Click!</button>`
console.log(templateResult.getHTML())
// => `<button type="button" ?disabled$lit$="{{lit-407422159769641}}">Click!</button>`
```

```typescript
// Property Binding
const value = 'My Value';
const templateResult = html`<input .value=${value}>`
console.log(templateResult.getHTML())
// => `<input .value$lit$={{lit-36790084947651724}}>`
```

```typescript
// Event Listener Binding
const templateResult = html`<button @click=${(e) => console.log('clicked')}>Click!</button>`
console.log(templateResult.getHTML())
// => `<button @click$lit$={{lit-14297238026644}}>Click!</button>`
```

If-statements are to handle comment-like strings correctly, but I will check in more depth later.

## `getTemplateElement()` method

This method returns HTML `template` element whose `innerHTML` is the returned string by `getHTML()` method.

`convertConstantTemplateStringToTrustedHTML` function does nothing unless [Trusted Types](https://w3c.github.io/webappsec-trusted-types/dist/spec/) is available so I will skip in this time.


```typescript
getTemplateElement(): HTMLTemplateElement {
  const template = document.createElement('template');
  template.innerHTML =
      convertConstantTemplateStringToTrustedHTML(this.getHTML()) as string;
  return template;
}
```

`getTemplateElement()` method is used by `render` function, which is used when we want to insert elements based on the `TemplateResult` or `TemplateResult` into the real DOM tree.

# `SVGTemplateResult` class

`SVGTemplateResult` class extends `TemplateResult` class and overrides the two methods.

## `getHTML()` method

`getHTML()` method of `SVGTemplateResult` class wraps the HTML string with `svg` tags, so that it can be parsed in the SVG namespace.

```typescript
getHTML(): string {
  return `<svg>${super.getHTML()}</svg>`;
}
```

## `getTemplateElement()` method

`getHTML()` method of `SVGTemplateResult` class remove the extra `svg` element that is added by `getHTML()` method, and returns the HTML `template` element.

```typescript
getTemplateElement(): HTMLTemplateElement {
  const template = super.getTemplateElement();
  const content = template.content;
  const svgElement = content.firstChild!;
  content.removeChild(svgElement);
  reparentNodes(content, svgElement.firstChild);
  return template;
}
```

# Summary

So far, I learned the following points:

- An instance of `TemplateResult` class has all arguments passed by `html` function as readonly properties.
- In addition, `TemplateResult` class has two prototype methods by which to get HTML string and HTML template element whose `innerHTML` is the HTML string.
- `TemplateResult` class extends `TemplateResult` class and overrides the two methods.

Since I skipped some lines of code which are not important to grasp the  main logic, I will look into those in the next post.