rebrand: buuh — a friendly public fork under the uhhm org
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
This commit is contained in:
co-authored by
Claude Fable 5
parent
b957b02410
commit
ce1ec9e4a9
+11
-11
@@ -8,15 +8,15 @@ a platform baseline of Node ≥ 24 + Baseline Widely Available browsers.
|
||||
## The fast path
|
||||
|
||||
```console
|
||||
$ npx @choojs/migrate . # rewrite in place
|
||||
$ npx @choojs/migrate --dry . # or report only
|
||||
$ npx @uhhm/buuh-migrate . # rewrite in place
|
||||
$ npx @uhhm/buuh-migrate --dry . # or report only
|
||||
```
|
||||
|
||||
The codemod converts simple top-level CJS to ESM, remaps specifiers, and
|
||||
prints a note for everything it won't guess at. Then:
|
||||
|
||||
1. Add `"type": "module"` to package.json.
|
||||
2. Swap dependencies for `@choojs/core`, `@choojs/html`, and friends.
|
||||
2. Swap dependencies for `@uhhm/buuh`, `@uhhm/buuh-html`, and friends.
|
||||
3. Run your app. Read the console: hydration now *tells you* when server
|
||||
and client markup disagree.
|
||||
|
||||
@@ -24,14 +24,14 @@ prints a note for everything it won't guess at. Then:
|
||||
|
||||
| v7 | v8 |
|
||||
|---|---|
|
||||
| `choo` | `@choojs/core` |
|
||||
| `choo/html`, `nanohtml` | `@choojs/html` |
|
||||
| `nanohtml/raw` | `@choojs/html/raw` |
|
||||
| `nanomorph` | `@choojs/html/morph` |
|
||||
| `nanocomponent`, `choo/component` | `@choojs/component` |
|
||||
| `choo-devtools` | `@choojs/devtools` |
|
||||
| `choo-lazy-route` | `lazy()` from `@choojs/core` |
|
||||
| `nanobus`, `nanorouter`, `nanohref`, `nanotiming` | built into `@choojs/core` |
|
||||
| `choo` | `@uhhm/buuh` |
|
||||
| `choo/html`, `nanohtml` | `@uhhm/buuh-html` |
|
||||
| `nanohtml/raw` | `@uhhm/buuh-html/raw` |
|
||||
| `nanomorph` | `@uhhm/buuh-html/morph` |
|
||||
| `nanocomponent`, `choo/component` | `@uhhm/buuh-component` |
|
||||
| `choo-devtools` | `@uhhm/buuh-devtools` |
|
||||
| `choo-lazy-route` | `lazy()` from `@uhhm/buuh` |
|
||||
| `nanobus`, `nanorouter`, `nanohref`, `nanotiming` | built into `@uhhm/buuh` |
|
||||
| `nanoquery` | built in (`state.query`); use `URLSearchParams` elsewhere |
|
||||
| `nanoraf`, `nanoassert`, `nanolru` | retired — platform/built-in |
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Publishing buuh on project.uhhm.no
|
||||
|
||||
Two different distribution problems, two different mechanisms. Gitea
|
||||
handles one of them natively; the other needs a static host.
|
||||
|
||||
## 1. `npm install` — Gitea Packages (yes, it does what you hope)
|
||||
|
||||
Gitea ships a real npm registry per user/org. For the `uhhm` org the
|
||||
endpoint is:
|
||||
|
||||
```
|
||||
https://project.uhhm.no/api/packages/uhhm/npm/
|
||||
```
|
||||
|
||||
**Publishing** happens from the release workflow
|
||||
(`.gitea/workflows/release.yml`): tag `v8.0.0`, push the tag, and every
|
||||
workspace package is published with a `package:write` token stored as
|
||||
the `PACKAGES_TOKEN` repo secret. Bump the workspace versions before
|
||||
tagging.
|
||||
|
||||
**Consuming** needs one line of `.npmrc` in a project (or `~/.npmrc`):
|
||||
|
||||
```ini
|
||||
@uhhm:registry=https://project.uhhm.no/api/packages/uhhm/npm/
|
||||
```
|
||||
|
||||
Then plain npm works, and this is why the packages are scoped: npm
|
||||
routes *by scope*, so `@uhhm/*` resolves against your Gitea while
|
||||
everything else still comes from npmjs.org. No token is needed to
|
||||
install if the packages are public (they inherit visibility from the
|
||||
owner). Starting a project is:
|
||||
|
||||
```console
|
||||
$ echo "@uhhm:registry=https://project.uhhm.no/api/packages/uhhm/npm/" >> .npmrc
|
||||
$ npm i @uhhm/buuh @uhhm/buuh-html
|
||||
$ npm i -D @uhhm/bankai
|
||||
$ npx bankai start app.js
|
||||
```
|
||||
|
||||
## 2. "CDN require" — the part Gitea does NOT do
|
||||
|
||||
This is the misunderstanding worth clearing up: **Gitea's npm registry
|
||||
serves tarballs to package managers, not individual JavaScript files to
|
||||
browsers.** There is no unpkg-style `https://…/@uhhm/buuh/index.js`
|
||||
endpoint, and the generic-package download URLs serve
|
||||
`application/octet-stream` — browsers refuse that for ES modules (strict
|
||||
MIME checking). So an import map cannot point at Gitea directly.
|
||||
|
||||
What works instead, in order of effort:
|
||||
|
||||
**a. The single-file bundle + any static host (recommended).**
|
||||
`npm run bundle` produces `dist-cdn/buuh.js` — the whole framework as
|
||||
one minified ES module with named exports. The release workflow uploads
|
||||
it to Gitea's generic package store as the archive of record; to make it
|
||||
importable, serve a copy from any host that sends
|
||||
`text/javascript` + CORS. Since you run uhhm.no, that's a few lines of
|
||||
Caddy:
|
||||
|
||||
```
|
||||
cdn.uhhm.no {
|
||||
root * /srv/cdn
|
||||
file_server
|
||||
header Access-Control-Allow-Origin *
|
||||
header /buuh@* Cache-Control "public, max-age=31536000, immutable"
|
||||
}
|
||||
```
|
||||
|
||||
Drop each release in as `buuh@8.0.0.js` (a `curl` from the generic
|
||||
package URL, or an extra `scp` step in the workflow), and every project
|
||||
on earth can do:
|
||||
|
||||
```html
|
||||
<script type="importmap">
|
||||
{ "imports": { "buuh": "https://cdn.uhhm.no/buuh@8.0.0.js" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import { choo, html } from 'buuh'
|
||||
</script>
|
||||
```
|
||||
|
||||
That is the zero-build story with your own domain on it.
|
||||
|
||||
**b. Self-hosted esm.sh.** esm.sh is open source and can be pointed at
|
||||
a custom npm registry — run it against the Gitea endpoint and you get
|
||||
real CDN semantics (per-package URLs, versioning, bundling) for
|
||||
everything you publish. More moving parts; worth it only if you want
|
||||
per-package URLs rather than the one-file bundle.
|
||||
|
||||
**c. Raw Gitea file URLs — don't.** Gitea serves raw `.js` as
|
||||
`text/plain` for safety, which module loading rejects. Fronting raw
|
||||
URLs with a MIME-rewriting proxy works but is a hack with none of the
|
||||
caching benefits of (a).
|
||||
|
||||
## Version hygiene
|
||||
|
||||
- Workspace versions are currently `8.0.0-dev`; set real versions
|
||||
before the first tag (`npm version 8.0.0 --workspaces --no-git-tag-version`).
|
||||
- The tag drives the generic-package version in the workflow, so keep
|
||||
tags and package.json versions in step.
|
||||
- `bankai` the bin name survives; the package is `@uhhm/bankai` so scope
|
||||
routing works.
|
||||
+20
-12
@@ -1,4 +1,12 @@
|
||||
# choo v8 — branch notes
|
||||
# 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.
|
||||
|
||||
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;
|
||||
@@ -6,7 +14,7 @@ everything new lives under `packages/`.
|
||||
|
||||
## Layout
|
||||
|
||||
- `packages/core` — `@choojs/core`: the Choo class, same API as choo v7
|
||||
- `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 into `lib/` as attributed ports:
|
||||
- `lib/bus.js` ← nanobus 4.5.0
|
||||
@@ -16,7 +24,7 @@ everything new lives under `packages/`.
|
||||
- `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`
|
||||
- `packages/html` — `@choojs/html`: the rendering package.
|
||||
- `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
|
||||
@@ -29,13 +37,13 @@ everything new lives under `packages/`.
|
||||
what makes server and browser output byte-identical.
|
||||
- `morph.js` — ← nanomorph 5.4.3, consolidated to one module
|
||||
- `raw.js` — mark pre-encoded strings (works with both renderers)
|
||||
- `packages/component` — `@choojs/component`: ← nanocomponent 6.6.0 +
|
||||
- `packages/component` — `@uhhm/buuh-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,
|
||||
- `packages/devtools` — `@uhhm/buuh-devtools`: window.choo with live state,
|
||||
event log, timings via PerformanceObserver, copy(); no-op on the server.
|
||||
- `packages/migrate` — `@choojs/migrate`: the `choo-migrate` codemod.
|
||||
- `packages/migrate` — `@uhhm/buuh-migrate`: the `choo-migrate` codemod.
|
||||
Regex-based on purpose: converts simple top-level CJS to ESM, remaps
|
||||
specifiers (choo → @choojs/core, nanohtml → @choojs/html, …), points
|
||||
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
|
||||
@@ -51,20 +59,20 @@ everything new lives under `packages/`.
|
||||
- [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
|
||||
- [x] Phase 2 (core): browser renderer rewrite, `@choojs/component`,
|
||||
- [x] Phase 2 (core): browser renderer rewrite, `@uhhm/buuh-component`,
|
||||
zero-build counter example, full-app integration test in happy-dom
|
||||
- [x] Phase 2 (tail): adoption-style hydration with mismatch warnings
|
||||
(`@choojs/html/hydrate`, wired into `mount()`), real-browser
|
||||
(`@uhhm/buuh-html/hydrate`, wired into `mount()`), real-browser
|
||||
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`)
|
||||
|
||||
Real Chromium: @choojs/html creates fresh trees ~12% faster than µhtml v5
|
||||
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 @choojs/component caching (proxy nodes skip unchanged subtrees),
|
||||
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.
|
||||
@@ -82,7 +90,7 @@ Server string rendering is on par with nanohtml v1 (~13k ops/s, within
|
||||
the current tree) until the dynamic import lands, server awaits
|
||||
it in toStream; toString fails with guidance. Views cache after
|
||||
first load.
|
||||
- `@choojs/devtools` and the `choo-migrate` codemod (validated by
|
||||
- `@uhhm/buuh-devtools` and the `choo-migrate` codemod (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
|
||||
|
||||
Reference in New Issue
Block a user