a post-component library for building user-interfaces on the web.

migrate from exported class Root to function createRoot (#32)

just to trim a few bytes.
also one other driveby byte shave.

authored by tombl.dev and committed by

GitHub 17a523b1 8026b7ba

+42 -48
+2 -2
examples/todomvc/main.js
··· 1 - import { Root, html, invalidate } from 'dhtml' 1 + import { createRoot, html, invalidate } from 'dhtml' 2 2 3 3 function classes(node, value) { 4 4 let prev = new Set() ··· 193 193 194 194 const rootEl = document.getElementById('root') 195 195 if (!rootEl) throw new Error('Root element not found') 196 - Root.appendInto(rootEl).render(app) 196 + createRoot(rootEl).render(app)
+6 -6
index.html
··· 7 7 <title>dhtml</title> 8 8 <script type="module"> 9 9 globalThis.DHTML_PROD = !new URLSearchParams(location.search).has('dev') 10 - const { Root, html, invalidate } = await import('./src/html.js') 11 - const r = Root.appendInto(document.body) 12 - Object.assign(globalThis, { Root, html, r }) 13 - r.render({ 10 + const { createRoot, html, invalidate } = await import('./src/html.js') 11 + const root = createRoot(document.body) 12 + Object.assign(globalThis, { createRoot, html, root }) 13 + root.render({ 14 14 i: 0, 15 15 render() { 16 - const codePre = `import { Root, html, invalidate } from '` 16 + const codePre = `import { createRoot, html, invalidate } from '` 17 17 const url = new URL('src/html.js', location.href).toString() 18 18 const codePost = `' 19 19 ··· 33 33 }, 34 34 } 35 35 36 - Root.appendInto(document.body).render(app)` 36 + createRoot(document.body).render(app)` 37 37 38 38 return html` 39 39 <main style="padding: 1rem; display: flex; flex-direction: column; gap: 1rem">
+1 -1
package.json
··· 7 7 "format": "prettier --write .", 8 8 "check": "tsc", 9 9 "test": "vitest run", 10 - "test:watch": "vitest dev", 10 + "test:watch": "vitest dev", 11 11 "test:prod": "npm run build && NODE_ENV=production vitest run" 12 12 }, 13 13 "devDependencies": {
+2 -2
readme.md
··· 3 3 a post-component library for building user interfaces on the web. 4 4 5 5 ```javascript 6 - import { Root, html, invalidate } from 'https://tombl.github.io/dhtml/src/html.js' 6 + import { createRoot, html, invalidate } from 'https://tombl.github.io/dhtml/src/html.js' 7 7 8 8 const app = { 9 9 i: 0, ··· 21 21 }, 22 22 } 23 23 24 - Root.appendInto(document.body).render(app) 24 + createRoot(document.body).render(app) 25 25 ```
+3 -4
src/html.d.ts
··· 8 8 export function onUnmount(renderable: Renderable, callback: () => void): void 9 9 export function getParentNode(renderable: Renderable): Node 10 10 11 - export class Root { 12 - static appendInto(parent: Node): Root 13 - static replace(node: Node): Root 14 - 11 + export interface Root { 15 12 render(value: Displayable): void 16 13 detach(): void 17 14 } 15 + 16 + export function createRoot(node: Node): Root
+23 -28
src/html.js
··· 125 125 } 126 126 } 127 127 128 - export class Root { 128 + /** @param {ParentNode} parent */ 129 + export function createRoot(parent) { 130 + const marker = new Text() 131 + parent.appendChild(marker) 132 + return new Root(new Span(marker)) 133 + } 134 + 135 + /** @param {Node} node */ 136 + function insertRootAfter(node) { 137 + DEV: assert(node.parentNode, 'expected a parent node') 138 + const marker = new Text() 139 + node.parentNode.insertBefore(marker, node.nextSibling) 140 + return new Root(new Span(marker)) 141 + } 142 + 143 + class Root { 129 144 /** @type {Key | undefined} */ _key 130 145 131 146 /** @param {Span} span */ ··· 133 148 this._span = span 134 149 } 135 150 136 - /** @param {ParentNode} parent */ 137 - static appendInto(parent) { 138 - const marker = new Text() 139 - parent.appendChild(marker) 140 - return new Root(new Span(marker)) 141 - } 142 - 143 - /** @param {Node} node */ 144 - static insertAfter(node) { 145 - DEV: assert(node.parentNode, 'expected a parent node') 146 - const marker = new Text() 147 - node.parentNode.insertBefore(marker, node.nextSibling) 148 - return new Root(new Span(marker)) 149 - } 150 - 151 - /** @param {Node} node */ 152 - static replace(node) { 153 - return new Root(new Span(node)) 154 - } 155 - 156 151 render(value) { 157 152 const t = value instanceof BoundTemplateInstance ? value : singlePartTemplate(value) 158 153 ··· 298 293 toRemove.push(name) 299 294 match = DYNAMIC_WHOLE.exec(value) 300 295 if (match) { 301 - name = name.slice(1).replace(/-([a-z])/g, (_, c) => c.toUpperCase()) 296 + name = name.slice(1).replace(/-./g, match => match[1].toUpperCase()) 302 297 patch(node, parseInt(match[1]), () => new PropertyPart(name)) 303 - } else { 304 - DEV: assert(!DYNAMIC_GLOBAL.test(value), `expected a whole dynamic value for ${name}, got a partial one`) 305 - DEV: throw new Error(`static properties are not supported, please wrap the value of ${name} in \${...}`) 298 + } else if (DEV) { 299 + assert(!DYNAMIC_GLOBAL.test(value), `expected a whole dynamic value for ${name}, got a partial one`) 300 + throw new Error(`static properties are not supported, please wrap the value of ${name} in \${...}`) 306 301 } 307 302 } else { 308 303 // attribute: 309 304 match = DYNAMIC_WHOLE.exec(value) 310 305 if (match) { 311 306 patch(node, parseInt(match[1]), () => new AttributePart(name)) 312 - } else { 313 - DEV: assert(!DYNAMIC_GLOBAL.test(value), `expected a whole dynamic value for ${name}, got a partial one`) 307 + } else if (DEV) { 308 + assert(!DYNAMIC_GLOBAL.test(value), `expected a whole dynamic value for ${name}, got a partial one`) 314 309 } 315 310 } 316 311 } ··· 475 470 for (const item of value) { 476 471 // @ts-expect-error -- WeakMap lookups of non-objects always return undefined, which is fine 477 472 const key = keys.get(item) ?? item 478 - let root = (this.#roots[i] ??= Root.insertAfter(end)) 473 + let root = (this.#roots[i] ??= insertRootAfter(end)) 479 474 480 475 if (key !== undefined && root._key !== key) { 481 476 const j = this.#roots.findIndex(r => r._key === key)
+3 -3
test/custom-elements.test.ts
··· 1 - import { html, Root } from 'dhtml' 2 - import { expect, describe, it } from 'vitest' 1 + import { createRoot, html } from 'dhtml' 2 + import { describe, expect, it } from 'vitest' 3 3 import { setup } from './setup' 4 4 5 5 class CustomElement extends HTMLElement { ··· 35 35 const { el } = setup() 36 36 const shadowRoot = el.attachShadow({ mode: 'open' }) 37 37 38 - const root = Root.appendInto(shadowRoot) 38 + const root = createRoot(shadowRoot) 39 39 root.render(html`<p>hello</p>`) 40 40 41 41 expect(el.innerHTML).toBe(``)
+2 -2
test/setup.ts
··· 3 3 4 4 import '../reset.css' 5 5 6 - import { Root } from 'dhtml' 6 + import { createRoot, type Root } from 'dhtml' 7 7 import { afterEach, expect } from 'vitest' 8 8 9 9 const roots: Root[] = [] ··· 22 22 el.innerHTML = initialHtml 23 23 document.body.appendChild(parentEl).appendChild(el) 24 24 25 - const root = Root.appendInto(el) 25 + const root = createRoot(el) 26 26 roots.push(root) 27 27 28 28 return { root, el }