2026-09-08 19:55:20 +02:00
|
|
|
# buuh (choo v8) — branch notes
|
|
|
|
|
|
|
|
|
|
> Naming note: this work now ships publicly as **buuh**, a friendly fork
|
|
|
|
|
> under the `uhhm` org 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 in `docs/upstream-rfc-draft.md` if this
|
|
|
|
|
> ever goes home.
|
2026-09-08 15:59:26 +02:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-09-08 19:55:20 +02:00
|
|
|
- `packages/core` — `@uhhm/buuh`: the Choo class, same API as choo v7
|
2026-09-08 15:59:26 +02:00
|
|
|
(`use`/`route`/`start`/`mount`/`toString`/`emit`), ported to ESM. The
|
|
|
|
|
nano* internals are consolidated into `lib/` as attributed ports:
|
|
|
|
|
- `lib/bus.js` ← nanobus 4.5.0
|
|
|
|
|
- `lib/router.js` ← nanorouter 4.0.0 + wayfarer 7.0.1 (+ trie)
|
|
|
|
|
- `lib/cache.js` ← choo component/cache.js + nanolru 1.0.0
|
|
|
|
|
- `lib/raf.js` ← nanoraf 3.1.0, `lib/href.js` ← nanohref 3.1.0
|
|
|
|
|
- `lib/timing.js` ← nanotiming 7.3.1 (unified on global `performance`)
|
|
|
|
|
- `lib/dom.js` ← document-ready 2.0.1 + scroll-to-anchor 1.0.0
|
|
|
|
|
- `lib/query.js` — nanoquery replaced by `URLSearchParams`
|
2026-09-08 19:55:20 +02:00
|
|
|
- `packages/html` — `@uhhm/buuh-html`: the rendering package.
|
2026-09-08 15:59:26 +02:00
|
|
|
- `server.js` — server-side tagged template (← nanohtml 1.10.0 server,
|
|
|
|
|
transform branches removed; pure runtime)
|
2026-09-08 16:56:54 +02:00
|
|
|
- `browser.js` — runtime-only cached template tag: each template literal
|
|
|
|
|
is parsed once (WeakMap keyed on its strings array) into a <template>
|
|
|
|
|
plus hole instructions; renders clone and fill. Values never pass
|
|
|
|
|
through innerHTML. Document-level roots (<body> etc.) parse via
|
|
|
|
|
DOMParser since <template> drops them. Known limits: no dynamic tag
|
|
|
|
|
names, no holes in raw-text elements, SVG fragments need their <svg>
|
|
|
|
|
root, and (unlike v7) whitespace is preserved as authored — which is
|
|
|
|
|
what makes server and browser output byte-identical.
|
2026-09-08 15:59:26 +02:00
|
|
|
- `morph.js` — ← nanomorph 5.4.3, consolidated to one module
|
2026-09-08 16:56:54 +02:00
|
|
|
- `raw.js` — mark pre-encoded strings (works with both renderers)
|
2026-09-08 19:55:20 +02:00
|
|
|
- `packages/component` — `@uhhm/buuh-component`: ← nanocomponent 6.6.0 +
|
2026-09-08 16:56:54 +02:00
|
|
|
on-load 3.4.1 as ES classes; the future island/hydration boundary.
|
2026-09-08 19:55:20 +02:00
|
|
|
- `packages/devtools` — `@uhhm/buuh-devtools`: window.choo with live state,
|
2026-09-08 18:57:51 +02:00
|
|
|
event log, timings via PerformanceObserver, copy(); no-op on the server.
|
2026-09-08 19:55:20 +02:00
|
|
|
- `packages/migrate` — `@uhhm/buuh-migrate`: the `choo-migrate` codemod.
|
2026-09-08 18:57:51 +02:00
|
|
|
Regex-based on purpose: converts simple top-level CJS to ESM, remaps
|
2026-09-08 19:55:20 +02:00
|
|
|
specifiers (choo → @uhhm/buuh, nanohtml → @uhhm/buuh-html, …), points
|
2026-09-08 18:57:51 +02:00
|
|
|
retired packages at their replacements, and reports everything it
|
|
|
|
|
refuses to guess at.
|
2026-09-08 16:56:54 +02:00
|
|
|
- `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`).
|
2026-09-08 18:57:51 +02:00
|
|
|
- `examples/streaming` — streaming SSR demo server
|
|
|
|
|
(`node examples/streaming/server.js`).
|
2026-09-08 15:59:26 +02:00
|
|
|
|
|
|
|
|
## Status vs the modernization plan
|
|
|
|
|
|
|
|
|
|
- [x] Phase 1: monorepo scaffold, ESM ports, choo v7 node test suite green
|
|
|
|
|
on `node --test` (Node ≥ 24), CI on GitHub Actions
|
2026-09-08 16:56:54 +02:00
|
|
|
- [x] URL normalization fix: WHATWG URL parsing, single decode with raw
|
|
|
|
|
fallback (no more URIError on '%'), NFC matching, per-segment
|
|
|
|
|
wildcard decode, decoded state.href
|
2026-09-08 19:55:20 +02:00
|
|
|
- [x] Phase 2 (core): browser renderer rewrite, `@uhhm/buuh-component`,
|
2026-09-08 16:56:54 +02:00
|
|
|
zero-build counter example, full-app integration test in happy-dom
|
2026-09-08 17:59:53 +02:00
|
|
|
- [x] Phase 2 (tail): adoption-style hydration with mismatch warnings
|
2026-09-08 19:55:20 +02:00
|
|
|
(`@uhhm/buuh-html/hydrate`, wired into `mount()`), real-browser
|
2026-09-08 17:59:53 +02:00
|
|
|
Playwright e2e (zero-build page, SSR-then-hydrate page, adoption
|
|
|
|
|
proof; CI job included), benchmarks vs nanohtml v1 / µhtml
|
|
|
|
|
|
|
|
|
|
## Benchmarks (2026-09, 100-row table, `npm run bench` / `bench:browser`)
|
|
|
|
|
|
2026-09-08 19:55:20 +02:00
|
|
|
Real Chromium: @uhhm/buuh-html creates fresh trees ~12% faster than µhtml v5
|
2026-09-08 17:59:53 +02:00
|
|
|
(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
|
2026-09-08 19:55:20 +02:00
|
|
|
apps by @uhhm/buuh-component caching (proxy nodes skip unchanged subtrees),
|
2026-09-08 17:59:53 +02:00
|
|
|
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.
|
2026-09-08 18:57:51 +02:00
|
|
|
- [x] 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.
|
2026-09-08 19:55:20 +02:00
|
|
|
- `@uhhm/buuh-devtools` and the `choo-migrate` codemod (validated by
|
2026-09-08 18:57:51 +02:00
|
|
|
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).
|
2026-09-08 19:32:38 +02:00
|
|
|
- [x] 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.js` exists, 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 a `window.initialState` tail
|
|
|
|
|
(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.
|
2026-09-08 19:41:22 +02:00
|
|
|
- [x] 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), the `style.css` convention (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).
|
|
|
|
|
- [x] 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), and `docs/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.
|
2026-09-08 15:59:26 +02:00
|
|
|
- [ ] Phase 4: bankai v10 (Vite 8/Rolldown shell, SSR middleware,
|
|
|
|
|
103 Early Hints, service worker, precompression)
|
|
|
|
|
- [ ] Phase 5: docs, examples, launch
|
|
|
|
|
|
|
|
|
|
## Deliberate changes from v7
|
|
|
|
|
|
|
|
|
|
- `choo()` still works without `new`; `Choo` is also a named export.
|
|
|
|
|
- nanoquery → `URLSearchParams` (same output shape, repeated keys → arrays).
|
|
|
|
|
- Assertions live in `lib/assert.js` so production builds can strip them.
|
|
|
|
|
- Everything targets Node ≥ 24 and Baseline Widely Available browsers; there
|
|
|
|
|
is no compile step and no transpilation anywhere.
|
|
|
|
|
|
|
|
|
|
## 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.
|