UNPKG

@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
// 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 */