a post-component library for building user-interfaces on the web.
at main 200 lines 5.5 kB view raw
1import { 2 assert, 3 is_html, 4 is_iterable, 5 is_keyed, 6 is_renderable, 7 single_part_template, 8 type Displayable, 9 type Key, 10 type Renderable, 11} from '../shared.ts' 12import { 13 compile_template, 14 DYNAMIC_WHOLE, 15 PART_ATTRIBUTE, 16 PART_CHILD, 17 PART_DIRECTIVE, 18 PART_PROPERTY, 19 type CompiledTemplate, 20} from './compiler.ts' 21import { 22 create_attribute_part, 23 create_child_part, 24 create_directive_part, 25 create_property_part, 26 type Part, 27} from './parts.ts' 28import { create_span_into, type Span } from './span.ts' 29import { is_comment, is_element } from './util.ts' 30 31export interface Root { 32 render(value: Displayable): void 33} 34 35export function createRoot(parent: Node): Root { 36 const span = create_span_into(parent) 37 return { render: create_child_part(span) } 38} 39 40function find_end(start: Comment): Comment | null { 41 assert(start.data === '?[') 42 let depth = 1 43 let node: ChildNode | null = start 44 while ((node = node.nextSibling)) { 45 if (is_comment(node)) { 46 if (node.data === '?[') depth++ 47 else if (node.data === '?]') { 48 depth-- 49 if (depth === 0) return node 50 } 51 } 52 } 53 return null 54} 55 56export function hydrate(parent: Node, value: Displayable): Root { 57 let start 58 for (start of parent.childNodes) { 59 if (is_comment(start) && start.data === '?[') break 60 } 61 assert( 62 start && is_comment(start), 63 `Could not find hydration start comment. Please ensure the element contains server-side rendered output.`, 64 ) 65 66 const end = find_end(start) 67 assert(end, `Could not find hydration end comment. Please ensure the element contains server-side rendered output.`) 68 69 const render = hydrate_child_part({ _start: start, _end: end }, value) 70 render(value) 71 return { render } 72} 73 74function hydrate_child_part(span: Span, value: unknown) { 75 let current_renderable: Renderable | undefined 76 let template: CompiledTemplate | undefined 77 let template_parts: [number, Part][] | undefined 78 let entries: Array<{ _span: Span; _part: Part; _key: Key }> | undefined 79 80 if (is_renderable(value)) { 81 try { 82 value = (current_renderable = value).render() 83 } catch (thrown) { 84 if (is_html(thrown)) { 85 value = thrown 86 } else { 87 throw thrown 88 } 89 } 90 91 if (is_renderable(value)) value = single_part_template(value) 92 } 93 94 if (is_iterable(value)) { 95 entries = [] 96 let end = span._start 97 98 for (const item of value) { 99 const key = is_keyed(item) ? item._key : (item as Key) 100 101 const start = end.nextSibling 102 assert(start && is_comment(start) && start.data === '?[') 103 104 end = find_end(start)! 105 assert(end) 106 107 const span = { _start: start, _end: end } 108 entries.push({ _span: span, _part: hydrate_child_part(span, item), _key: key }) 109 } 110 assert(end.nextSibling === span._end) 111 } 112 113 if (is_html(value)) { 114 template = compile_template(value._statics) 115 116 const node_by_part: Array<Node | Span> = [] 117 118 const walker = document.createTreeWalker(span._start.parentNode!, 129) 119 const template_walker = document.createTreeWalker(template._content, 129) 120 assert((NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT) === 129) 121 walker.currentNode = span._start 122 123 while (walker.nextNode() && template_walker.nextNode()) { 124 const node = walker.currentNode 125 const template_node = template_walker.currentNode 126 if (node === span._end) break 127 128 if (is_comment(node) && is_comment(template_node)) { 129 if (node.data === '?[') { 130 assert(DYNAMIC_WHOLE.test(template_node.data)) 131 const end = find_end(node) 132 assert(end) 133 walker.currentNode = end 134 } 135 continue 136 } 137 138 assert(is_element(node)) 139 assert( 140 node.nodeType === template_node.nodeType, 141 `Node type mismatch: ${node.nodeType} != ${template_node.nodeType}`, 142 ) 143 assert(template_node instanceof HTMLElement || template_node instanceof SVGElement) 144 assert(node.tagName === template_node.tagName, `Tag name mismatch: ${node.tagName} !== ${template_node.tagName}`) 145 146 if (template_node.dataset.dynparts) 147 for (const part of template_node.dataset.dynparts.split(' ')) node_by_part[+part] = node 148 } 149 150 for (const part of template._root_parts) node_by_part[part] = span 151 152 template_parts = template._parts.map(([dynamic_index, [type, data]], element_index): [number, Part] => { 153 const node = node_by_part[element_index] 154 switch (type) { 155 case PART_CHILD: 156 let child: ChildNode | null 157 158 if (node instanceof Node) { 159 child = node.childNodes[data] 160 assert(child) 161 } else { 162 child = node._start.nextSibling 163 assert(child) 164 for (let i = 0; i < data; i++) { 165 child = child.nextSibling 166 assert(child !== null, 'expected more siblings') 167 assert(child !== node._end, 'ran out of siblings before the end') 168 } 169 } 170 171 assert(child.parentNode) 172 assert(child.previousSibling && is_comment(child.previousSibling) && child.previousSibling.data === '?[') 173 const end = find_end(child.previousSibling) 174 assert(end) 175 176 return [ 177 dynamic_index, 178 hydrate_child_part( 179 { 180 _start: child.previousSibling, 181 _end: end, 182 }, 183 value._dynamics[dynamic_index], 184 ), 185 ] 186 case PART_DIRECTIVE: 187 assert(node instanceof Node) 188 return [dynamic_index, create_directive_part(node)] 189 case PART_ATTRIBUTE: 190 assert(node instanceof Element) 191 return [dynamic_index, create_attribute_part(node, data)] 192 case PART_PROPERTY: 193 assert(node instanceof Node) 194 return [dynamic_index, create_property_part(node, data)] 195 } 196 }) 197 } 198 199 return create_child_part(span, true, current_renderable, template, template_parts, entries, value) 200}