In this series, How lit-html works, I will explore (not explain) internal implementation of lit-html.
In the previous post, 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.
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
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.
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.
export const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
Text Binding
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$.
// 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>`
// 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>`
// Property Binding
const value = 'My Value';
const templateResult = html`<input .value=${value}>`
console.log(templateResult.getHTML())
// => `<input .value$lit$={{lit-36790084947651724}}>`
// 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 is available so I will skip in this time.
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.
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.
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
TemplateResultclass has all arguments passed byhtmlfunction as readonly properties. - In addition,
TemplateResultclass has two prototype methods by which to get HTML string and HTML template element whoseinnerHTMLis the HTML string. -
TemplateResultclass extendsTemplateResultclass 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.
Top comments (0)