@popeindustries/lit-html
Version:
Seamlessly and efficiently use @popeindustries/lit-html-server rendered HTML to hydrate lit-html templates in the browser
376 lines (373 loc) • 12.3 kB
JavaScript
// src/index.js
import { isPrimitive, isSingleExpression, isTemplateResult } from "./vendor/directive-helpers.js";
import { noChange, render as litRender } from "./vendor/lit-html.js";
import { PartType } from "./vendor/directive.js";
// src/private-ssr-support.js
import { _$LH as p } from "./vendor/lit-html.js";
var ChildPart = p._ChildPart;
var ElementPart = p._ElementPart;
var resolveDirective = p._resolveDirective;
var TemplateInstance = p._TemplateInstance;
var _$LH = {
ChildPart,
ElementPart,
resolveDirective,
TemplateInstance,
/**
* @param { Part } part
*/
getPartCommittedValue(part) {
return part._$committedValue;
},
/**
* @param { Part } part
* @param { unknown } value
*/
setPartCommittedValue(part, value) {
part._$committedValue = value;
},
/**
* @param { InstanceType<TemplateInstance> } instance
* @param { Part } part
*/
templateInstanceAddPart(instance, part) {
instance._parts.push(part);
},
/**
* @param { InstanceType<TemplateInstance> } instance
* @param { number } index
* @returns { Part | undefined }
*/
getTemplateInstanceTemplatePart(instance, index) {
return instance._$template.parts[index];
},
/**
* @param { AttributePart } part
* @param { unknown } value
* @param { number } index
* @param { boolean } noCommit
*/
setAttributePartValue(part, value, index, noCommit) {
part._$setValue(value, part, index, noCommit);
},
/**
* @param { TemplateResult } value
*/
getChildPartTemplate(value) {
return ChildPart.prototype._$getTemplate(value);
},
/**
* @param { InstanceType<ChildPart> } part
* @param { Comment } marker
*/
setChildPartEndNode(part, marker) {
part._$endNode = marker;
}
};
// src/index.js
import { html, noChange as noChange2, nothing, svg } from "./vendor/lit-html.js";
var {
ChildPart: ChildPart2,
ElementPart: ElementPart2,
resolveDirective: resolveDirective2,
TemplateInstance: TemplateInstance2,
getPartCommittedValue,
setPartCommittedValue,
templateInstanceAddPart,
getTemplateInstanceTemplatePart,
setAttributePartValue,
setChildPartEndNode,
getChildPartTemplate
} = _$LH;
var RE_CHILD_MARKER = /^lit |^lit-child/;
var RE_ATTR_LENGTH = /^lit-attr (\d+)/;
var NO_META_ERROR = "NIL";
function render(value, container, options = {}) {
const partOwnerNode = options.renderBefore ?? container;
if (partOwnerNode["_$litPart$"] !== void 0) {
litRender(value, container, options);
return partOwnerNode["_$litPart$"];
}
let openingComment = null;
let closingComment = null;
try {
const startNode = (
/** @type { Node } */
options.renderBefore ?? container.lastChild
);
[openingComment, closingComment] = findEnclosingCommentNodes(startNode);
let active = false;
let nestedTreeParent = null;
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT, (node) => {
const markerText = (
/** @type { Comment } */
node.data
);
if (node === openingComment) {
active = true;
return NodeFilter.FILTER_ACCEPT;
} else if (active && markerText.startsWith("lit ")) {
active = false;
nestedTreeParent = node.parentElement;
return NodeFilter.FILTER_SKIP;
} else if (!active && markerText === "/lit" && node.parentElement === nestedTreeParent) {
active = true;
nestedTreeParent = null;
return NodeFilter.FILTER_SKIP;
} else if (node === closingComment) {
active = false;
return NodeFilter.FILTER_ACCEPT;
}
return active ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
});
let rootPart = void 0;
let currentChildPart = void 0;
let marker;
const stack = [];
while ((marker = walker.nextNode()) !== null) {
const markerText = marker.data;
if (RE_CHILD_MARKER.test(markerText)) {
if (stack.length === 0 && rootPart !== void 0) {
throw Error("must be only one root part per container");
}
currentChildPart = openChildPart(value, marker, stack, options);
rootPart ?? (rootPart = /** @type { RootPart } */
currentChildPart);
} else if (markerText.startsWith("lit-attr")) {
createAttributeParts(marker, stack, options);
} else if (markerText.startsWith("/lit-child")) {
if (stack.length === 1 && currentChildPart !== rootPart) {
throw Error("internal error");
}
currentChildPart = closeChildPart(marker, currentChildPart, stack);
}
}
if (rootPart === void 0) {
throw Error("there should be exactly one root part in a render container");
}
partOwnerNode["_$litPart$"] = rootPart;
return rootPart;
} catch (err) {
if (err && /** @type { Error } */
err.message !== NO_META_ERROR) {
console.error(err);
}
if (openingComment !== null && closingComment !== null) {
console.error(`Hydration failed. Clearing nodes and performing clean render`);
let node = closingComment;
while (node && node !== openingComment) {
const previousSibling = node.previousSibling;
partOwnerNode.removeChild(node);
node = previousSibling;
}
partOwnerNode.removeChild(openingComment);
}
return litRender(value, container, options);
}
}
render.setSanitizer = litRender.setSanitizer;
render.createSanitizer = litRender.createSanitizer;
function findEnclosingCommentNodes(startNode) {
let closingComment = null;
let openingComment = null;
let node = startNode;
while (node != null) {
if (node.nodeType === 8) {
const comment = (
/** @type { Comment } */
node
);
if (closingComment === null && comment.data === "/lit") {
closingComment = comment;
} else if (comment.data.startsWith("lit ")) {
openingComment = comment;
break;
}
}
node = node.previousSibling;
}
if (openingComment === null || closingComment === null) {
throw Error(NO_META_ERROR);
}
return [openingComment, closingComment];
}
function openChildPart(value, marker, stack, options) {
let part;
if (stack.length === 0) {
part = new ChildPart2(marker, null, void 0, options);
} else {
const state = stack[stack.length - 1];
if (state.type === "template-instance") {
part = new ChildPart2(marker, null, state.instance, options);
templateInstanceAddPart(state.instance, part);
value = state.result.values[state.instancePartIndex++];
state.templatePartIndex++;
} else if (state.type === "iterable") {
part = new ChildPart2(marker, null, state.part, options);
const result = state.iterator.next();
if (result.done) {
value = void 0;
state.done = true;
throw Error("shorter than expected iterable");
} else {
value = result.value;
}
getPartCommittedValue(state.part).push(part);
} else {
consoleUnexpectedPrimitiveError(value, marker, options);
throw Error("unexpected primitive rendered to part");
}
}
value = resolveDirective2(part, value);
if (value === noChange) {
stack.push({ part, type: "leaf" });
} else if (isPrimitive(value)) {
setPartCommittedValue(part, value);
stack.push({ part, type: "leaf" });
} else if (isTemplateResult(value)) {
if (!marker.data.includes(digestForTemplateStrings(value.strings))) {
consoleUnexpectedTemplateResultError(value);
throw Error("unexpected TemplateResult rendered to part");
}
const template = getChildPartTemplate(value);
const instance = new TemplateInstance2(template, part);
setPartCommittedValue(part, instance);
stack.push({
instance,
instancePartIndex: 0,
part,
result: value,
templatePartIndex: 0,
type: "template-instance"
});
} else if (isIterable(value)) {
setPartCommittedValue(part, []);
stack.push({
done: false,
iterator: value[Symbol.iterator](),
part,
type: "iterable",
value
});
} else {
setPartCommittedValue(part, value == null ? "" : value);
stack.push({ part, type: "leaf" });
}
return part;
}
function createAttributeParts(comment, stack, options) {
const node = comment.previousElementSibling ?? comment.parentElement;
if (node === null) {
throw Error("could not find node for attribute parts");
}
const state = stack[stack.length - 1];
if (state.type === "template-instance") {
const { instance } = state;
const n = parseInt(RE_ATTR_LENGTH.exec(comment.data)?.[1] ?? "0");
for (let i = 0; i < n; i++) {
const templatePart = getTemplateInstanceTemplatePart(instance, state.templatePartIndex);
if (templatePart === void 0 || templatePart.type !== PartType.ATTRIBUTE && templatePart.type !== PartType.ELEMENT) {
break;
}
if (templatePart.type === PartType.ATTRIBUTE) {
const instancePart = new templatePart.ctor(
node,
templatePart.name,
templatePart.strings,
state.instance,
options
);
const value = isSingleExpression(instancePart) ? state.result.values[state.instancePartIndex] : state.result.values;
const noCommit = !(instancePart.type === PartType.EVENT || instancePart.type === PartType.PROPERTY);
setAttributePartValue(instancePart, value, state.instancePartIndex, noCommit);
state.instancePartIndex += templatePart.strings.length - 1;
templateInstanceAddPart(instance, instancePart);
} else {
const instancePart = new ElementPart2(node, state.instance, options);
resolveDirective2(instancePart, state.result.values[state.instancePartIndex++]);
templateInstanceAddPart(instance, instancePart);
}
state.templatePartIndex++;
}
node.removeAttribute("hydrate:defer");
} else {
throw Error("internal error");
}
}
function closeChildPart(marker, part, stack) {
if (part === void 0) {
throw Error("unbalanced part marker");
}
setChildPartEndNode(part, marker);
const currentState = (
/** @type { HydrationChildPartState } */
stack.pop()
);
if (currentState.type === "iterable") {
if (!currentState.iterator.next().done) {
throw Error("longer than expected iterable");
}
}
if (stack.length > 0) {
return stack[stack.length - 1].part;
} else {
return void 0;
}
}
function digestForTemplateStrings(strings) {
const digestSize = 2;
const hashes = new Uint32Array(digestSize).fill(5381);
for (const s of strings) {
for (let i = 0; i < s.length; i++) {
hashes[i % digestSize] = hashes[i % digestSize] * 33 ^ s.charCodeAt(i);
}
}
return btoa(String.fromCharCode(...new Uint8Array(hashes.buffer)));
}
function isIterable(iterator) {
return iterator != null && (Array.isArray(iterator) || typeof /** @type { Iterable<unknown> } */
iterator[Symbol.iterator] === "function");
}
function consoleUnexpectedPrimitiveError(value, marker, options) {
try {
console.error(
"The following element was an unexpected primitive rendered to part on the client:\n\nParent element:",
options?.host,
"\n\nElement rendered on server:",
marker.parentElement,
"\n\nElement rendered on client:",
value + "\n\n"
);
} catch (_e) {
console.error("Had trouble logging unexpected primitive error message");
}
}
function consoleUnexpectedTemplateResultError(value) {
try {
const values = value.values;
const formattedElements = values.map((elementStrings, index) => {
const elementString = elementStrings.strings[0].trim();
return `${index + 1}) %c${elementString}%c`;
}).join("\n");
const styles = values.flatMap(() => ["color: red;", ""]);
console.error(
"The following elements got unexpected TemplateResult rendered to part: \n" + formattedElements,
...styles
);
} catch (_e) {
console.error("Had trouble logging unexpected TemplateResult error message");
}
}
export {
html,
noChange2 as noChange,
nothing,
render,
svg
};
/**
* @license
* Some of this code is copied and modified from `lit-html/experimental-hydrate.js`
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/