Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
5.9 KiB
choo v8 — branch notes
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—@choojs/core: 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—@choojs/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—@choojs/component: ← nanocomponent 6.6.0 + on-load 3.4.1 as ES classes; the future island/hydration boundary.packages/devtools—@choojs/devtools: window.choo with live state, event log, timings via PerformanceObserver, copy(); no-op on the server.packages/migrate—@choojs/migrate: thechoo-migratecodemod. Regex-based on purpose: converts simple top-level CJS to ESM, remaps specifiers (choo → @choojs/core, nanohtml → @choojs/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,
@choojs/component, zero-build counter example, full-app integration test in happy-dom - Phase 2 (tail): adoption-style hydration with mismatch warnings
(
@choojs/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. -@choojs/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 (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: @choojs/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 @choojs/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.