Packages renamed to the @uhhm scope (@uhhm/buuh, @uhhm/buuh-html, @uhhm/buuh-component, @uhhm/buuh-devtools, @uhhm/buuh-migrate, @uhhm/bankai). Scoping is load-bearing twice over: npm routes registries per scope so @uhhm/* resolves against project.uhhm.no while everything else stays on npmjs, and it means this fork never squats upstream's names anywhere. The codemod now migrates choo v7 apps to the @uhhm names. README rewritten with the fork framing and full upstream credit; the choojs RFC moves to docs/upstream-rfc-draft.md, in the drawer for if this work ever goes home. API unchanged — choo() is still choo(). Also: Gitea Actions CI + release workflows (npm publish to the uhhm registry on tag push, CDN bundle uploaded as a generic package), npm run bundle producing dist-cdn/buuh.js (the whole framework as one minified ES module for import-map use), docs/publishing.md explaining what Gitea Packages is (a real npm registry) and is not (a CDN — serve the bundle from a static host with module-safe MIME instead), and onload.js constructing window.MutationObserver to match its own guard (surfaced by smoke-testing the bundle outside a full browser). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
8.9 KiB
buuh (choo v8) — branch notes
Naming note: this work now ships publicly as buuh, a friendly fork under the
uhhmorg on project.uhhm.no, with packages scoped@uhhm/*(scoped so npm's per-scope registry routing works — and so we never squat upstream's names on any registry). The docs below use both names; "v8" refers to this modernization effort either way. The upstream RFC stays drafted indocs/upstream-rfc-draft.mdif this ever goes home.
This branch is the working tree for the v8 modernization effort. The v7 code
at the repo root is untouched and stays authoritative until 8.0.0 ships;
everything new lives under packages/.
Layout
packages/core—@uhhm/buuh: the Choo class, same API as choo v7 (use/route/start/mount/toString/emit), ported to ESM. The nano* internals are consolidated intolib/as attributed ports:lib/bus.js← nanobus 4.5.0lib/router.js← nanorouter 4.0.0 + wayfarer 7.0.1 (+ trie)lib/cache.js← choo component/cache.js + nanolru 1.0.0lib/raf.js← nanoraf 3.1.0,lib/href.js← nanohref 3.1.0lib/timing.js← nanotiming 7.3.1 (unified on globalperformance)lib/dom.js← document-ready 2.0.1 + scroll-to-anchor 1.0.0lib/query.js— nanoquery replaced byURLSearchParams
packages/html—@uhhm/buuh-html: the rendering package.server.js— server-side tagged template (← nanohtml 1.10.0 server, transform branches removed; pure runtime)browser.js— runtime-only cached template tag: each template literal is parsed once (WeakMap keyed on its strings array) into a plus hole instructions; renders clone and fill. Values never pass through innerHTML. Document-level roots ( etc.) parse via DOMParser since drops them. Known limits: no dynamic tag names, no holes in raw-text elements, SVG fragments need theirmorph.js— ← nanomorph 5.4.3, consolidated to one moduleraw.js— mark pre-encoded strings (works with both renderers)packages/component—@uhhm/buuh-component: ← nanocomponent 6.6.0 + on-load 3.4.1 as ES classes; the future island/hydration boundary.packages/devtools—@uhhm/buuh-devtools: window.choo with live state, event log, timings via PerformanceObserver, copy(); no-op on the server.packages/migrate—@uhhm/buuh-migrate: thechoo-migratecodemod. Regex-based on purpose: converts simple top-level CJS to ESM, remaps specifiers (choo → @uhhm/buuh, nanohtml → @uhhm/buuh-html, …), points retired packages at their replacements, and reports everything it refuses to guess at.examples/counter— the isomorphic proof: one app module, mounted zero-build in the browser via import map (index.html), string-rendered in Node (render.js).examples/streaming— streaming SSR demo server (node examples/streaming/server.js).- Phase 1: monorepo scaffold, ESM ports, choo v7 node test suite green
on
node --test(Node ≥ 24), CI on GitHub Actions - URL normalization fix: WHATWG URL parsing, single decode with raw fallback (no more URIError on '%'), NFC matching, per-segment wildcard decode, decoded state.href
- Phase 2 (core): browser renderer rewrite,
@uhhm/buuh-component, zero-build counter example, full-app integration test in happy-dom - Phase 2 (tail): adoption-style hydration with mismatch warnings
(
@uhhm/buuh-html/hydrate, wired intomount()), real-browser Playwright e2e (zero-build page, SSR-then-hydrate page, adoption proof; CI job included), benchmarks vs nanohtml v1 / µhtml - Phase 3: v8 wiring, all shipped together:
-
toStream(location, state)— web-standard ReadableStream of UTF-8;new Response(stream)on web servers,Readable.fromWeb(stream).pipe(res)on Node. Progressive: the server tag now builds a parts list, so promises/async iterables in child position flush the shell first and stream the rest in document order (proven byte-level and in real Chromium). -state.prefetch— stores push promises during init; toStream awaits them before rendering; toString refuses them loudly. -lazy(loader, loadingView?)— the answer to choojs/choo#653, both halves at once: browser renders the loading view (or holds the current tree) until the dynamic import lands, server awaits it in toStream; toString fails with guidance. Views cache after first load. -@uhhm/buuh-devtoolsand thechoo-migratecodemod (validated by migrating choo's own v7 example and running the result on v8). - Phase 3 follow-ups for the RFC: API shape feedback on lazy() (thenable handlers vs wrapper), serializing streamed state for hydration of async holes (bankai v10 territory).
- Phase 4: bankai v10 —
packages/bankai, a thin shell over Vite 8 (Rolldown). The server side needs no build at all: v8 apps are plain ESM that Node runs as-authored, so only the client bundles. -bankai start <entry>— dev: Vite middleware mode + HMR, with per-request streaming SSR through vite.ssrLoadModule and a virtual client entry (the user writes one isomorphic module; bankai generates the two lines of browser glue). -bankai build <entry>— client bundle + Vite manifest + dist/bankai.json route/asset metadata + service worker (if<entry dir>/sw.jsexists, built with the precache list injected) + brotli/gzip precompression of every text asset. -bankai serve— production: static assets with immutable caching and precompressed-variant negotiation, 103 Early Hints (res.writeEarlyHints) carrying the route's assets before every SSR page, streaming render, and awindow.initialStatetail (script-safe serialization, choo internals filtered; hydration ignores server-only scripts). -bankai inspect— raw/gzip/brotli size table from the manifest. All proven end-to-end in real Chromium: both dev and prod pages hydrate with zero console errors and zero mismatch warnings, and the 103 interim response is asserted at the HTTP level. - Phase 4 follow-ups:
bankai serve --h2(local certs via openssl, 103 verified with a real h2 client),bankai build --prerender, SSR<title>from state (the server reads the first stream chunk before writing the head), thestyle.cssconvention (client-only CSS — the sheetify answer), and the CI wire-size budget (npm run size: 7.97 kB min+gzip for the whole framework, budget 8.5 kB). - Phase 5: README rewritten for v8 (honest size claim, credits),
docs/migrating-v7-to-v8.md,docs/deploy.md(Node, Docker, proxy/CDN with the HTTP/3 story — h3 is infrastructure's job, bankai's contract is the 103 + Link headers any hints-aware edge propagates — and web-standard runtimes), anddocs/rfc.md: the draft announcement to post on choojs/choo, with the pings, the asks, and the three-week comment window. - Post-RFC: publish pre-releases under a next tag, refresh choo.io/handbook (separate repos), deprecation pointers on retired packages only after 8.0.0, first-class edge adapter for bankai.
- Phase 4: bankai v10 (Vite 8/Rolldown shell, SSR middleware, 103 Early Hints, service worker, precompression)
- Phase 5: docs, examples, launch
choo()still works withoutnew;Choois also a named export.- nanoquery →
URLSearchParams(same output shape, repeated keys → arrays). - Assertions live in
lib/assert.jsso production builds can strip them. - Everything targets Node ≥ 24 and Baseline Widely Available browsers; there is no compile step and no transpilation anywhere.
Status vs the modernization plan
Benchmarks (2026-09, 100-row table,
npm run bench/bench:browser)Real Chromium: @uhhm/buuh-html creates fresh trees ~12% faster than µhtml v5 (3.5k vs 3.1k ops/s) — the parse-once/clone design pays off in native DOM. µhtml updates in place ~5x faster than our fresh-tree + nanomorph loop (3.0k vs 0.6k ops/s): that is choo's architectural cost, mitigated in real apps by @uhhm/buuh-component caching (proxy nodes skip unchanged subtrees), and the number to beat if Phase 3 explores keyed-hole optimizations. Server string rendering is on par with nanohtml v1 (~13k ops/s, within 6%). happy-dom numbers in bench/render.js are indicative only.
Deliberate changes from v7
Attribution
All ported modules are MIT-licensed work by the original choojs/nano* authors and contributors; per-file headers note the source package and version. This branch exists to carry that work forward, not to replace it.