Exploring How lit-html Works: TemplateResult and SVGTemplateResult constructors (3)
Tags: javascript, typescript, webcomponents, devjournal
Published: 2020-01-07 13:53:15

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-templateresult-and-svgtemplateresult-constructors-2-5bbc), we saw how how lit-html uses Trusted Types API to convert template strings to trusted HTML string.

In this post, I will dive into `getHTML()` method of `TemplateResult` more deeply.

# Handling Comment-Like Expressions in the Template Literals

In [the past post](https://dev.to/nozomuikuta/exploring-how-lit-html-works-templateresult-and-svgtemplateresult-constructors-g5h), I said that if-statements and related lines in `getHTML()` method of `TemplateResult` class are to handle comment-like strings.

Let's look into this point.

```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 does conditional string concatenation based on the result of regular expression matching.

The definition of the regular expression (`lastAttributeNameRegex`) is like below.

```typescript
export const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
```

Since [comment in the code explains this regular expression well](https://github.com/Polymer/lit-html/blob/f69936e1b80d3ac0c34cd599f611846cdfc9d898/src/lib/template.ts#L242-L270), you can check it to understand what the regular expression represents.

In short, the following patterns are all valid attribute expressions.

```txt
 attr=value

    attr    =    value

    attr    =    
value

 attr="value"

 attr    =   'value'

 attr="value >>>

 attr='value `<>=>>>'

 attr='<!--
```

It will be difficult for most people to distinguish which part of the regular expression corresponds to which part of the actual string. To make it easier, I will show the matched parts with [Named capture groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges), which is a new feature of ES2018.

```typescript
var string = `<div attr="<!--`
var regex = /(?<spaceBeforeName>[ \x09\x0a\x0c\x0d])(?<name>[^\0-\x1F\x7F-\x9F "'>=/]+)(?<equalSignAndValue>[ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/g
var result = regex.exec(string)

console.dir(result)
// =>
// [ 
//    ' attr="<!--',
//    ' ',
//    'attr',
//    '="<!--',
//    index: 4,
//    input: '<div attr="<!--',
//    groups: { 
//      spaceBeforeName: ' ',
//      name: 'attr', 
//      equalSignAndValue: '="<!--'
//    }
// ]

```

Now they became easier to distinguish, I think.

If the string matches the pattern, then lit-html combines the expressions with all the space characters before equal sign removed and .

```typescript
html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
          attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
          marker;

// is actually...

html += '<div' + ' ' + 'attr' + '="<!--' + '{{lit-3958644673182541}}'

// and is finally...

html += '<div attr="<!--{{lit-3958644673182541}}'
```

## What's Wrong

Now It is the problem that all the part after ` attr="<!--{{lit-3958644673182541}}` could be parsed as comment.

When this is `getTemplateElement()` is called, the `content` of the HTML template element becomes empty because there seems no elements which have both of opening and closing tags.

This kind of string will finally throw an error when `render` function of the library is called (Of course I will explore this function later).

```typescript
const value = 'value'
const templateResult = html`<div attr="<!--${value}>Error!</div>`

render(templateResult, document.body)
// => Uncaught TypeError: Failed to set the 'currentNode' property on 'TreeWalker': The provided value is not of type 'Node'.
```

# Summary

So far, I learned the following points:

- lit-html sometimes can't create a HTML string 100% accurately.
- It is always good to wrap attribute values with quotations to avoid the cases.

In the next post, I will explore the `reparentNodes` function which is used by `getTemplateElement()` of `SVGTemplateResult` class.