In this series, How lit-html works, I will explore (not explain) internal implementation of lit-html.
In the previous post, 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, 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.
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.
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, you can check it to understand what the regular expression represents.
In short, the following patterns are all valid attribute expressions.
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, which is a new feature of ES2018.
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 .
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).
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.
Top comments (0)