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
TemplateResult
class has all arguments passed byhtml
function as readonly properties. - In addition,
TemplateResult
class has two prototype methods by which to get HTML string and HTML template element whoseinnerHTML
is the HTML string. -
TemplateResult
class extendsTemplateResult
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.
Top comments (0)