import { assert, is_html, is_iterable, is_keyed, is_renderable, single_part_template, type Displayable, type Key, type Renderable, } from '../shared.ts' import { compile_template, DYNAMIC_WHOLE, PART_ATTRIBUTE, PART_CHILD, PART_DIRECTIVE, PART_PROPERTY, type CompiledTemplate, } from './compiler.ts' import { create_attribute_part, create_child_part, create_directive_part, create_property_part, type Part, } from './parts.ts' import { create_span_into, type Span } from './span.ts' import { is_comment, is_element } from './util.ts' export interface Root { render(value: Displayable): void } export function createRoot(parent: Node): Root { const span = create_span_into(parent) return { render: create_child_part(span) } } function find_end(start: Comment): Comment | null { assert(start.data === '?[') let depth = 1 let node: ChildNode | null = start while ((node = node.nextSibling)) { if (is_comment(node)) { if (node.data === '?[') depth++ else if (node.data === '?]') { depth-- if (depth === 0) return node } } } return null } export function hydrate(parent: Node, value: Displayable): Root { let start for (start of parent.childNodes) { if (is_comment(start) && start.data === '?[') break } assert( start && is_comment(start), `Could not find hydration start comment. Please ensure the element contains server-side rendered output.`, ) const end = find_end(start) assert(end, `Could not find hydration end comment. Please ensure the element contains server-side rendered output.`) const render = hydrate_child_part({ _start: start, _end: end }, value) render(value) return { render } } function hydrate_child_part(span: Span, value: unknown) { let current_renderable: Renderable | undefined let template: CompiledTemplate | undefined let template_parts: [number, Part][] | undefined let entries: Array<{ _span: Span; _part: Part; _key: Key }> | undefined if (is_renderable(value)) { try { value = (current_renderable = value).render() } catch (thrown) { if (is_html(thrown)) { value = thrown } else { throw thrown } } if (is_renderable(value)) value = single_part_template(value) } if (is_iterable(value)) { entries = [] let end = span._start for (const item of value) { const key = is_keyed(item) ? item._key : (item as Key) const start = end.nextSibling assert(start && is_comment(start) && start.data === '?[') end = find_end(start)! assert(end) const span = { _start: start, _end: end } entries.push({ _span: span, _part: hydrate_child_part(span, item), _key: key }) } assert(end.nextSibling === span._end) } if (is_html(value)) { template = compile_template(value._statics) const node_by_part: Array = [] const walker = document.createTreeWalker(span._start.parentNode!, 129) const template_walker = document.createTreeWalker(template._content, 129) assert((NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT) === 129) walker.currentNode = span._start while (walker.nextNode() && template_walker.nextNode()) { const node = walker.currentNode const template_node = template_walker.currentNode if (node === span._end) break if (is_comment(node) && is_comment(template_node)) { if (node.data === '?[') { assert(DYNAMIC_WHOLE.test(template_node.data)) const end = find_end(node) assert(end) walker.currentNode = end } continue } assert(is_element(node)) assert( node.nodeType === template_node.nodeType, `Node type mismatch: ${node.nodeType} != ${template_node.nodeType}`, ) assert(template_node instanceof HTMLElement || template_node instanceof SVGElement) assert(node.tagName === template_node.tagName, `Tag name mismatch: ${node.tagName} !== ${template_node.tagName}`) if (template_node.dataset.dynparts) for (const part of template_node.dataset.dynparts.split(' ')) node_by_part[+part] = node } for (const part of template._root_parts) node_by_part[part] = span template_parts = template._parts.map(([dynamic_index, [type, data]], element_index): [number, Part] => { const node = node_by_part[element_index] switch (type) { case PART_CHILD: let child: ChildNode | null if (node instanceof Node) { child = node.childNodes[data] assert(child) } else { child = node._start.nextSibling assert(child) for (let i = 0; i < data; i++) { child = child.nextSibling assert(child !== null, 'expected more siblings') assert(child !== node._end, 'ran out of siblings before the end') } } assert(child.parentNode) assert(child.previousSibling && is_comment(child.previousSibling) && child.previousSibling.data === '?[') const end = find_end(child.previousSibling) assert(end) return [ dynamic_index, hydrate_child_part( { _start: child.previousSibling, _end: end, }, value._dynamics[dynamic_index], ), ] case PART_DIRECTIVE: assert(node instanceof Node) return [dynamic_index, create_directive_part(node)] case PART_ATTRIBUTE: assert(node instanceof Element) return [dynamic_index, create_attribute_part(node, data)] case PART_PROPERTY: assert(node instanceof Node) return [dynamic_index, create_property_part(node, data)] } }) } return create_child_part(span, true, current_renderable, template, template_parts, entries, value) }