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
@@ -0,0 +1,32 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master, v8]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [24, 26]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm test
|
||||||
|
- run: npm run size
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
- run: npm install
|
||||||
|
- run: npx playwright install --with-deps chromium-headless-shell
|
||||||
|
- run: npm run test:e2e
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
# Tag a version (git tag v8.0.0 && git push --tags) and this publishes
|
||||||
|
# every workspace package to the uhhm npm registry on this Gitea, plus
|
||||||
|
# the single-file CDN bundle as a generic package.
|
||||||
|
#
|
||||||
|
# Repo secrets needed (Settings → Actions → Secrets):
|
||||||
|
# PACKAGES_TOKEN — a Gitea access token with package:write scope
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
- run: npm install
|
||||||
|
- run: npm test
|
||||||
|
|
||||||
|
- name: point npm at the uhhm registry
|
||||||
|
run: |
|
||||||
|
echo "@uhhm:registry=https://project.uhhm.no/api/packages/uhhm/npm/" >> "$HOME/.npmrc"
|
||||||
|
echo "//project.uhhm.no/api/packages/uhhm/npm/:_authToken=${{ secrets.PACKAGES_TOKEN }}" >> "$HOME/.npmrc"
|
||||||
|
|
||||||
|
- name: publish workspaces
|
||||||
|
run: npm publish --workspaces
|
||||||
|
|
||||||
|
- name: build + upload the CDN bundle (generic package)
|
||||||
|
run: |
|
||||||
|
npm run bundle
|
||||||
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
curl --fail -X PUT \
|
||||||
|
-H "Authorization: token ${{ secrets.PACKAGES_TOKEN }}" \
|
||||||
|
--upload-file dist-cdn/buuh.js \
|
||||||
|
"https://project.uhhm.no/api/packages/uhhm/generic/buuh-cdn/${VERSION}/buuh.js"
|
||||||
|
curl --fail -X PUT \
|
||||||
|
-H "Authorization: token ${{ secrets.PACKAGES_TOKEN }}" \
|
||||||
|
--upload-file dist-cdn/buuh.js.map \
|
||||||
|
"https://project.uhhm.no/api/packages/uhhm/generic/buuh-cdn/${VERSION}/buuh.js.map"
|
||||||
@@ -12,3 +12,4 @@ coverage.json
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.idea
|
.idea
|
||||||
|
dist-cdn/
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
<h1 align="center">choo</h1>
|
<h1 align="center">buuh</h1>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<strong>🚂🚋🚋🚋🚋🚋</strong>
|
<strong>🚂🚋🚋🚋🚋👻</strong>
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
The sturdy frontend framework — now with a modern engine under the same hood.
|
The sturdy little frontend framework — a friendly fork of choo,
|
||||||
|
rebuilt for the modern platform.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
> **This is the v8 branch.** choo v7 is on `master` and stays untouched
|
> **buuh is a fork of [choo](https://github.com/choojs/choo)** — same
|
||||||
> until 8.0.0 ships. Everything below describes v8: same API, same
|
> API, same philosophy, modern engine. It exists because we love choo
|
||||||
> philosophy, rebuilt for the platform of 2026. See
|
> and wanted it alive on today's platform; all credit for the design
|
||||||
> [`docs/v8.md`](docs/v8.md) for status and the full plan.
|
> belongs upstream (see [Credits](#credits)). Everything is MIT, and if
|
||||||
|
> upstream ever wants this work home, the door is open
|
||||||
|
> ([`docs/upstream-rfc-draft.md`](docs/upstream-rfc-draft.md)).
|
||||||
|
|
||||||
## What is this
|
## What is this
|
||||||
|
|
||||||
Choo is a small framework for building frontend applications with plain
|
buuh is a small framework for building frontend applications with plain
|
||||||
JavaScript and HTML. State lives in stores, changes flow through an event
|
JavaScript and HTML. State lives in stores, changes flow through an event
|
||||||
emitter, views are tagged template literals, and re-renders morph the real
|
emitter, views are tagged template literals, and re-renders morph the real
|
||||||
DOM. The whole framework — core, html engine, morphing, hydration, router,
|
DOM. The whole framework — core, html engine, morphing, hydration, router,
|
||||||
event bus — is **7.97 kB min+gzip** (7.15 kB brotli), enforced by CI.
|
event bus — is **7.97 kB min+gzip** (7.15 kB brotli), enforced by CI.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import choo from '@choojs/core'
|
import choo from '@uhhm/buuh'
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
|
|
||||||
const app = choo()
|
const app = choo()
|
||||||
|
|
||||||
@@ -46,12 +49,37 @@ app.route('/', (state, emit) => html`
|
|||||||
app.mount('body')
|
app.mount('body')
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why v8
|
## Getting it
|
||||||
|
|
||||||
|
From the uhhm registry (one `.npmrc` line routes the scope; everything
|
||||||
|
else still resolves from npmjs):
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with no package manager at all — one file, one import map:
|
||||||
|
|
||||||
|
```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>
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`docs/publishing.md`](docs/publishing.md) for how the registry and
|
||||||
|
the CDN bundle fit together.
|
||||||
|
|
||||||
|
## Why this fork
|
||||||
|
|
||||||
- **No build step, anywhere.** Templates parse at runtime (cached per
|
- **No build step, anywhere.** Templates parse at runtime (cached per
|
||||||
call site in a WeakMap, clone-based instantiation — as fast as the old
|
call site in a WeakMap, clone-based instantiation — as fast as choo's
|
||||||
compile-time transform, without the toolchain). An import map and a
|
old compile-time transform, without the toolchain). An import map and
|
||||||
`<script type="module">` is a complete development setup:
|
a `<script type="module">` is a complete development setup:
|
||||||
see [`examples/counter/index.html`](examples/counter/index.html).
|
see [`examples/counter/index.html`](examples/counter/index.html).
|
||||||
- **Isomorphic by contract.** One app module. The browser mounts it, the
|
- **Isomorphic by contract.** One app module. The browser mounts it, the
|
||||||
server imports the same file and calls `toString(route)` or streams it
|
server imports the same file and calls `toString(route)` or streams it
|
||||||
@@ -62,17 +90,17 @@ app.mount('body')
|
|||||||
`state.prefetch` promises. Hydration *adopts* the server DOM (same
|
`state.prefetch` promises. Hydration *adopts* the server DOM (same
|
||||||
element references, form state kept) and warns precisely when server
|
element references, form state kept) and warns precisely when server
|
||||||
and client markup disagree.
|
and client markup disagree.
|
||||||
- **Async routes, finally.** `app.route('/big', lazy(() => import('./big.js')))`
|
- **Async routes.** `app.route('/big', lazy(() => import('./big.js')))`
|
||||||
— loading view or held tree while in flight, awaited on the server.
|
— loading view or held tree while in flight, awaited on the server
|
||||||
([choojs/choo#653](https://github.com/choojs/choo/pull/653), answered.)
|
(the answer to [choojs/choo#653](https://github.com/choojs/choo/pull/653)).
|
||||||
- **Modern platform, no shims.** ESM only, Node ≥ 24, Baseline
|
- **Modern platform, no shims.** ESM only, Node ≥ 24, Baseline
|
||||||
Widely Available browsers. The router speaks WHATWG URL and survives
|
Widely Available browsers. The router speaks WHATWG URL and survives
|
||||||
emoji, literal `%`, and Unicode normalization differences.
|
emoji, literal `%`, and Unicode normalization differences.
|
||||||
|
|
||||||
## bankai v10
|
## bankai
|
||||||
|
|
||||||
The isomorphic compiler & server, rebuilt on Vite 8 / Rolldown. Only the
|
The isomorphic compiler & server, rebuilt on Vite 8 / Rolldown. Only the
|
||||||
client ever bundles — v8 server code is plain ESM Node runs as-authored.
|
client ever bundles — server code is plain ESM Node runs as-authored.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ bankai start app.js # dev: HMR + streaming SSR on every request
|
$ bankai start app.js # dev: HMR + streaming SSR on every request
|
||||||
@@ -92,17 +120,17 @@ with the precache manifest injected, `<title>` follows your
|
|||||||
|
|
||||||
| package | what it is |
|
| package | what it is |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `@choojs/core` | the Choo class: stores, router, emitter, `toString`/`toStream`, `lazy()` |
|
| `@uhhm/buuh` | the app class: stores, router, emitter, `toString`/`toStream`, `lazy()` |
|
||||||
| `@choojs/html` | tagged templates: DOM in the browser, strings/streams on the server, `morph`, `hydrate`, `raw` |
|
| `@uhhm/buuh-html` | tagged templates: DOM in the browser, strings/streams on the server, `morph`, `hydrate`, `raw` |
|
||||||
| `@choojs/component` | stateful components with lifecycle hooks; the hydration boundary |
|
| `@uhhm/buuh-component` | stateful components with lifecycle hooks; the hydration boundary |
|
||||||
| `@choojs/devtools` | `window.choo` console tooling |
|
| `@uhhm/buuh-devtools` | `window.choo` console tooling |
|
||||||
| `@choojs/migrate` | `choo-migrate` — the v7 → v8 codemod |
|
| `@uhhm/buuh-migrate` | `buuh-migrate` — the choo v7 → buuh codemod |
|
||||||
| `bankai` | the compiler & server above |
|
| `@uhhm/bankai` | the compiler & server above (bin: `bankai`) |
|
||||||
|
|
||||||
## Migrating from v7
|
## Migrating from choo v7
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ npx @choojs/migrate .
|
$ npx @uhhm/buuh-migrate .
|
||||||
```
|
```
|
||||||
|
|
||||||
converts simple CJS, remaps every specifier, and tells you honestly what
|
converts simple CJS, remaps every specifier, and tells you honestly what
|
||||||
@@ -117,13 +145,19 @@ $ npm test # 109 tests on node:test, zero framework deps
|
|||||||
$ npm run test:e2e # real Chromium: hydration, streaming, bankai
|
$ npm run test:e2e # real Chromium: hydration, streaming, bankai
|
||||||
$ npm run size # the wire-size budget
|
$ npm run size # the wire-size budget
|
||||||
$ npm run bench # vs nanohtml v1 and µhtml
|
$ npm run bench # vs nanohtml v1 and µhtml
|
||||||
|
$ npm run bundle # the single-file CDN build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Releases: bump versions, `git tag vX.Y.Z`, push the tag — Gitea Actions
|
||||||
|
tests, publishes every package to the uhhm registry, and uploads the CDN
|
||||||
|
bundle ([`.gitea/workflows/release.yml`](.gitea/workflows/release.yml)).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Choo v8 carries forward a decade of work by
|
buuh is choo. The design, the API, and most of the ideas are
|
||||||
[Yoshua Wuyts](https://github.com/yoshuawuyts) and the
|
[Yoshua Wuyts](https://github.com/yoshuawuyts)'s and the
|
||||||
[choojs contributors](https://github.com/choojs), and builds on the
|
[choojs contributors](https://github.com/choojs)'; the ESM groundwork
|
||||||
ESM groundwork of the [@pirxpilot](https://github.com/pirxpilot) fork
|
came from the [@pirxpilot](https://github.com/pirxpilot) fork line; this
|
||||||
line. Per-file attribution headers name the module and version each port
|
fork modernized the engine and the toolchain around them. Per-file
|
||||||
came from. MIT, as always.
|
attribution headers name the module and version each port came from.
|
||||||
|
MIT, as always.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Real-browser benchmark (Playwright Chromium): @choojs/html vs µhtml v5,
|
// Real-browser benchmark (Playwright Chromium): @uhhm/buuh-html vs µhtml v5,
|
||||||
// same create/update scenarios as bench/render.js but with native DOM.
|
// same create/update scenarios as bench/render.js but with native DOM.
|
||||||
// nanohtml v1 is CJS-only and can't be import-mapped, so it only appears
|
// nanohtml v1 is CJS-only and can't be import-mapped, so it only appears
|
||||||
// in the happy-dom bench.
|
// in the happy-dom bench.
|
||||||
@@ -16,8 +16,8 @@ const PAGE = `<!doctype html>
|
|||||||
<html><head><meta charset="utf-8">
|
<html><head><meta charset="utf-8">
|
||||||
<script type="importmap">${JSON.stringify({
|
<script type="importmap">${JSON.stringify({
|
||||||
imports: {
|
imports: {
|
||||||
'@choojs/html': '/packages/html/browser.js',
|
'@uhhm/buuh-html': '/packages/html/browser.js',
|
||||||
'@choojs/html/morph': '/packages/html/morph.js',
|
'@uhhm/buuh-html/morph': '/packages/html/morph.js',
|
||||||
uhtml: '/node_modules/uhtml/dist/prod/dom.js'
|
uhtml: '/node_modules/uhtml/dist/prod/dom.js'
|
||||||
}
|
}
|
||||||
})}</script>
|
})}</script>
|
||||||
@@ -27,8 +27,8 @@ await page.route('**/bench-page', (route) => route.fulfill({ contentType: 'text/
|
|||||||
await page.goto(srv.origin + '/bench-page')
|
await page.goto(srv.origin + '/bench-page')
|
||||||
|
|
||||||
const results = await page.evaluate(async () => {
|
const results = await page.evaluate(async () => {
|
||||||
const { default: html } = await import('@choojs/html')
|
const { default: html } = await import('@uhhm/buuh-html')
|
||||||
const { default: morph } = await import('@choojs/html/morph')
|
const { default: morph } = await import('@uhhm/buuh-html/morph')
|
||||||
const { html: uhtml, render: urender } = await import('uhtml')
|
const { html: uhtml, render: urender } = await import('uhtml')
|
||||||
|
|
||||||
const ROWS = 100
|
const ROWS = 100
|
||||||
@@ -51,7 +51,7 @@ const results = await page.evaluate(async () => {
|
|||||||
const uTable = (rows) => uhtml`<table><tbody>${rows.map((r) => uhtml`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
const uTable = (rows) => uhtml`<table><tbody>${rows.map((r) => uhtml`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
||||||
|
|
||||||
const out = {}
|
const out = {}
|
||||||
out['@choojs/html create'] = bench((t) => ourTable(data(t)))
|
out['@uhhm/buuh-html create'] = bench((t) => ourTable(data(t)))
|
||||||
out['uhtml v5 create (fresh container)'] = bench((t) => {
|
out['uhtml v5 create (fresh container)'] = bench((t) => {
|
||||||
const c = document.createElement('div')
|
const c = document.createElement('div')
|
||||||
urender(c, uTable(data(t)))
|
urender(c, uTable(data(t)))
|
||||||
@@ -59,7 +59,7 @@ const results = await page.evaluate(async () => {
|
|||||||
|
|
||||||
const live = ourTable(data(0))
|
const live = ourTable(data(0))
|
||||||
document.body.appendChild(live)
|
document.body.appendChild(live)
|
||||||
out['@choojs/html + nanomorph update'] = bench((t) => morph(live, ourTable(data(t))))
|
out['@uhhm/buuh-html + nanomorph update'] = bench((t) => morph(live, ourTable(data(t))))
|
||||||
live.remove()
|
live.remove()
|
||||||
|
|
||||||
const c = document.createElement('div')
|
const c = document.createElement('div')
|
||||||
|
|||||||
+7
-7
@@ -1,4 +1,4 @@
|
|||||||
// Rendering benchmarks: @choojs/html vs nanohtml v1 vs µhtml.
|
// Rendering benchmarks: @uhhm/buuh-html vs nanohtml v1 vs µhtml.
|
||||||
//
|
//
|
||||||
// npm run bench
|
// npm run bench
|
||||||
//
|
//
|
||||||
@@ -51,12 +51,12 @@ function bench (name, fn) {
|
|||||||
|
|
||||||
console.log('\nserver rendering — %d-row table to string:', ROWS)
|
console.log('\nserver rendering — %d-row table to string:', ROWS)
|
||||||
{
|
{
|
||||||
const { default: ourHtml } = await import('@choojs/html/server')
|
const { default: ourHtml } = await import('@uhhm/buuh-html/server')
|
||||||
const nano = require('nanohtml') // main entry is the server renderer
|
const nano = require('nanohtml') // main entry is the server renderer
|
||||||
|
|
||||||
const table = (html, rows) => html`<table><tbody>${rows.map((r) => html`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
const table = (html, rows) => html`<table><tbody>${rows.map((r) => html`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
||||||
|
|
||||||
bench('@choojs/html (server)', (t) => String(table(ourHtml, data(t))))
|
bench('@uhhm/buuh-html (server)', (t) => String(table(ourHtml, data(t))))
|
||||||
bench('nanohtml v1 (server)', (t) => String(table(nano, data(t))))
|
bench('nanohtml v1 (server)', (t) => String(table(nano, data(t))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,15 +75,15 @@ for (const name of [
|
|||||||
if (!(name in globalThis) && name in win) globalThis[name] = win[name]
|
if (!(name in globalThis) && name in win) globalThis[name] = win[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { default: ourBrowserHtml } = await import('@choojs/html/browser')
|
const { default: ourBrowserHtml } = await import('@uhhm/buuh-html/browser')
|
||||||
const { default: morph } = await import('@choojs/html/morph')
|
const { default: morph } = await import('@uhhm/buuh-html/morph')
|
||||||
const nanoBrowser = require('nanohtml/lib/browser.js')
|
const nanoBrowser = require('nanohtml/lib/browser.js')
|
||||||
const { html: uhtml, render: urender } = await import('uhtml')
|
const { html: uhtml, render: urender } = await import('uhtml')
|
||||||
|
|
||||||
const table = (html, rows) => html`<table><tbody>${rows.map((r) => html`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
const table = (html, rows) => html`<table><tbody>${rows.map((r) => html`<tr class=${r.selected ? 'selected' : ''}><td>${r.id}</td><td>${r.label}</td></tr>`)}</tbody></table>`
|
||||||
|
|
||||||
console.log('\nbrowser-path create — fresh %d-row table per iteration (happy-dom):', ROWS)
|
console.log('\nbrowser-path create — fresh %d-row table per iteration (happy-dom):', ROWS)
|
||||||
bench('@choojs/html (cached templates)', (t) => table(ourBrowserHtml, data(t)))
|
bench('@uhhm/buuh-html (cached templates)', (t) => table(ourBrowserHtml, data(t)))
|
||||||
bench('nanohtml v1 (hyperx runtime)', (t) => table(nanoBrowser, data(t)))
|
bench('nanohtml v1 (hyperx runtime)', (t) => table(nanoBrowser, data(t)))
|
||||||
bench('uhtml v5 (fresh container)', (t) => {
|
bench('uhtml v5 (fresh container)', (t) => {
|
||||||
const c = document.createElement('div')
|
const c = document.createElement('div')
|
||||||
@@ -94,7 +94,7 @@ console.log('\nbrowser-path update — re-render same live tree (happy-dom):')
|
|||||||
{
|
{
|
||||||
const live = table(ourBrowserHtml, data(0))
|
const live = table(ourBrowserHtml, data(0))
|
||||||
document.body.appendChild(live)
|
document.body.appendChild(live)
|
||||||
bench('@choojs/html + nanomorph', (t) => morph(live, table(ourBrowserHtml, data(t))))
|
bench('@uhhm/buuh-html + nanomorph', (t) => morph(live, table(ourBrowserHtml, data(t))))
|
||||||
document.body.removeChild(live)
|
document.body.removeChild(live)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
+11
-11
@@ -8,15 +8,15 @@ a platform baseline of Node ≥ 24 + Baseline Widely Available browsers.
|
|||||||
## The fast path
|
## The fast path
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ npx @choojs/migrate . # rewrite in place
|
$ npx @uhhm/buuh-migrate . # rewrite in place
|
||||||
$ npx @choojs/migrate --dry . # or report only
|
$ npx @uhhm/buuh-migrate --dry . # or report only
|
||||||
```
|
```
|
||||||
|
|
||||||
The codemod converts simple top-level CJS to ESM, remaps specifiers, and
|
The codemod converts simple top-level CJS to ESM, remaps specifiers, and
|
||||||
prints a note for everything it won't guess at. Then:
|
prints a note for everything it won't guess at. Then:
|
||||||
|
|
||||||
1. Add `"type": "module"` to package.json.
|
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
|
3. Run your app. Read the console: hydration now *tells you* when server
|
||||||
and client markup disagree.
|
and client markup disagree.
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ prints a note for everything it won't guess at. Then:
|
|||||||
|
|
||||||
| v7 | v8 |
|
| v7 | v8 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `choo` | `@choojs/core` |
|
| `choo` | `@uhhm/buuh` |
|
||||||
| `choo/html`, `nanohtml` | `@choojs/html` |
|
| `choo/html`, `nanohtml` | `@uhhm/buuh-html` |
|
||||||
| `nanohtml/raw` | `@choojs/html/raw` |
|
| `nanohtml/raw` | `@uhhm/buuh-html/raw` |
|
||||||
| `nanomorph` | `@choojs/html/morph` |
|
| `nanomorph` | `@uhhm/buuh-html/morph` |
|
||||||
| `nanocomponent`, `choo/component` | `@choojs/component` |
|
| `nanocomponent`, `choo/component` | `@uhhm/buuh-component` |
|
||||||
| `choo-devtools` | `@choojs/devtools` |
|
| `choo-devtools` | `@uhhm/buuh-devtools` |
|
||||||
| `choo-lazy-route` | `lazy()` from `@choojs/core` |
|
| `choo-lazy-route` | `lazy()` from `@uhhm/buuh` |
|
||||||
| `nanobus`, `nanorouter`, `nanohref`, `nanotiming` | built into `@choojs/core` |
|
| `nanobus`, `nanorouter`, `nanohref`, `nanotiming` | built into `@uhhm/buuh` |
|
||||||
| `nanoquery` | built in (`state.query`); use `URLSearchParams` elsewhere |
|
| `nanoquery` | built in (`state.query`); use `URLSearchParams` elsewhere |
|
||||||
| `nanoraf`, `nanoassert`, `nanolru` | retired — platform/built-in |
|
| `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
|
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;
|
at the repo root is untouched and stays authoritative until 8.0.0 ships;
|
||||||
@@ -6,7 +14,7 @@ everything new lives under `packages/`.
|
|||||||
|
|
||||||
## Layout
|
## 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
|
(`use`/`route`/`start`/`mount`/`toString`/`emit`), ported to ESM. The
|
||||||
nano* internals are consolidated into `lib/` as attributed ports:
|
nano* internals are consolidated into `lib/` as attributed ports:
|
||||||
- `lib/bus.js` ← nanobus 4.5.0
|
- `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/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/dom.js` ← document-ready 2.0.1 + scroll-to-anchor 1.0.0
|
||||||
- `lib/query.js` — nanoquery replaced by `URLSearchParams`
|
- `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,
|
- `server.js` — server-side tagged template (← nanohtml 1.10.0 server,
|
||||||
transform branches removed; pure runtime)
|
transform branches removed; pure runtime)
|
||||||
- `browser.js` — runtime-only cached template tag: each template literal
|
- `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.
|
what makes server and browser output byte-identical.
|
||||||
- `morph.js` — ← nanomorph 5.4.3, consolidated to one module
|
- `morph.js` — ← nanomorph 5.4.3, consolidated to one module
|
||||||
- `raw.js` — mark pre-encoded strings (works with both renderers)
|
- `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.
|
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.
|
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
|
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
|
retired packages at their replacements, and reports everything it
|
||||||
refuses to guess at.
|
refuses to guess at.
|
||||||
- `examples/counter` — the isomorphic proof: one app module, mounted
|
- `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
|
- [x] URL normalization fix: WHATWG URL parsing, single decode with raw
|
||||||
fallback (no more URIError on '%'), NFC matching, per-segment
|
fallback (no more URIError on '%'), NFC matching, per-segment
|
||||||
wildcard decode, decoded state.href
|
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
|
zero-build counter example, full-app integration test in happy-dom
|
||||||
- [x] Phase 2 (tail): adoption-style hydration with mismatch warnings
|
- [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
|
Playwright e2e (zero-build page, SSR-then-hydrate page, adoption
|
||||||
proof; CI job included), benchmarks vs nanohtml v1 / µhtml
|
proof; CI job included), benchmarks vs nanohtml v1 / µhtml
|
||||||
|
|
||||||
## Benchmarks (2026-09, 100-row table, `npm run bench` / `bench:browser`)
|
## 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.
|
(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
|
µ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
|
(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.
|
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
|
Server string rendering is on par with nanohtml v1 (~13k ops/s, within
|
||||||
6%). happy-dom numbers in bench/render.js are indicative only.
|
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
|
the current tree) until the dynamic import lands, server awaits
|
||||||
it in toStream; toString fails with guidance. Views cache after
|
it in toStream; toString fails with guidance. Views cache after
|
||||||
first load.
|
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).
|
migrating choo's own v7 example and running the result on v8).
|
||||||
- [ ] Phase 3 follow-ups for the RFC: API shape feedback on lazy()
|
- [ ] Phase 3 follow-ups for the RFC: API shape feedback on lazy()
|
||||||
(thenable handlers vs wrapper), serializing streamed state for
|
(thenable handlers vs wrapper), serializing streamed state for
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// The classic choo counter. This one module is the whole app, and both
|
// The classic choo counter. This one module is the whole app, and both
|
||||||
// sides consume it: the browser mounts it (import map resolves
|
// sides consume it: the browser mounts it (import map resolves
|
||||||
// @choojs/html to the DOM renderer), Node stringifies it (same specifier
|
// @uhhm/buuh-html to the DOM renderer), Node stringifies it (same specifier
|
||||||
// resolves to the string renderer). That's the isomorphic contract.
|
// resolves to the string renderer). That's the isomorphic contract.
|
||||||
|
|
||||||
import choo from '@choojs/core'
|
import choo from '@uhhm/buuh'
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
|
|
||||||
export default function createApp () {
|
export default function createApp () {
|
||||||
const app = choo()
|
const app = choo()
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"@choojs/core": "../../packages/core/index.js",
|
"@uhhm/buuh": "../../packages/core/index.js",
|
||||||
"@choojs/core/timing": "../../packages/core/lib/timing.js",
|
"@uhhm/buuh/timing": "../../packages/core/lib/timing.js",
|
||||||
"@choojs/html": "../../packages/html/browser.js",
|
"@uhhm/buuh-html": "../../packages/html/browser.js",
|
||||||
"@choojs/html/raw": "../../packages/html/raw.js",
|
"@uhhm/buuh-html/raw": "../../packages/html/raw.js",
|
||||||
"@choojs/html/morph": "../../packages/html/morph.js",
|
"@uhhm/buuh-html/morph": "../../packages/html/morph.js",
|
||||||
"@choojs/html/hydrate": "../../packages/html/hydrate.js"
|
"@uhhm/buuh-html/hydrate": "../../packages/html/hydrate.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
// stream on the server, while client-side views must be synchronous.
|
// stream on the server, while client-side views must be synchronous.
|
||||||
// Serializing streamed state for hydration is bankai v10 territory.
|
// Serializing streamed state for hydration is bankai v10 territory.
|
||||||
|
|
||||||
import choo from '@choojs/core'
|
import choo from '@uhhm/buuh'
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
|
|
||||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/monorepo",
|
"name": "@uhhm/buuh-monorepo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "Monorepo for choo v8 — a 4kb framework for creating sturdy frontend applications",
|
"description": "Monorepo for buuh \u2014 a friendly fork of choo, rebuilt for the modern platform",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -15,9 +15,10 @@
|
|||||||
"test:e2e": "node --test test/e2e/",
|
"test:e2e": "node --test test/e2e/",
|
||||||
"bench": "node bench/render.js",
|
"bench": "node bench/render.js",
|
||||||
"bench:browser": "node bench/real-browser.js",
|
"bench:browser": "node bench/real-browser.js",
|
||||||
"size": "node scripts/size.js"
|
"size": "node scripts/size.js",
|
||||||
|
"bundle": "node scripts/bundle.js"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"happy-dom": "^20.14.0",
|
"happy-dom": "^20.14.0",
|
||||||
|
|||||||
Regular → Executable
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "bankai",
|
"name": "@uhhm/bankai",
|
||||||
"version": "10.0.0-dev",
|
"version": "10.0.0-dev",
|
||||||
"description": "The isomorphic choo compiler & server: one command, streaming SSR, zero config",
|
"description": "The isomorphic choo compiler & server: one command, streaming SSR, zero config",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vite": "^8.0.0"
|
"vite": "^8.0.0"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"choo",
|
"choo",
|
||||||
"bankai",
|
"bankai",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Native DOM components: render once, morph on update, lifecycle hooks via
|
// Native DOM components: render once, morph on update, lifecycle hooks via
|
||||||
// onload. In v8 this class is also the intended island/hydration boundary.
|
// onload. In v8 this class is also the intended island/hydration boundary.
|
||||||
|
|
||||||
import morph from '@choojs/html/morph'
|
import morph from '@uhhm/buuh-html/morph'
|
||||||
import nanotiming from '@choojs/core/timing'
|
import nanotiming from '@uhhm/buuh/timing'
|
||||||
import onload, { KEY_ATTR } from './onload.js'
|
import onload, { KEY_ATTR } from './onload.js'
|
||||||
|
|
||||||
function makeID () {
|
function makeID () {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const KEY_ATTR = 'data-' + KEY_ID
|
|||||||
let INDEX = 0
|
let INDEX = 0
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && window.MutationObserver) {
|
if (typeof window !== 'undefined' && window.MutationObserver) {
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new window.MutationObserver(function (mutations) {
|
||||||
if (Object.keys(watch).length < 1) return
|
if (Object.keys(watch).length < 1) return
|
||||||
for (let i = 0; i < mutations.length; i++) {
|
for (let i = 0; i < mutations.length; i++) {
|
||||||
if (mutations[i].attributeName === KEY_ATTR) {
|
if (mutations[i].attributeName === KEY_ATTR) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/component",
|
"name": "@uhhm/buuh-component",
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "Native DOM components with lifecycle hooks — the choo island boundary",
|
"description": "Native DOM components with lifecycle hooks — the choo island boundary",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
"node": ">=24"
|
"node": ">=24"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@choojs/core": "*",
|
"@uhhm/buuh": "*",
|
||||||
"@choojs/html": "*"
|
"@uhhm/buuh-html": "*"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"component",
|
"component",
|
||||||
"dom",
|
"dom",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { test, before } from 'node:test'
|
|||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
import { Window } from 'happy-dom'
|
import { Window } from 'happy-dom'
|
||||||
|
|
||||||
import serverHtml from '@choojs/html/server'
|
import serverHtml from '@uhhm/buuh-html/server'
|
||||||
|
|
||||||
test('renders via createElement on the server (no window)', async () => {
|
test('renders via createElement on the server (no window)', async () => {
|
||||||
const { default: Component } = await import('../index.js')
|
const { default: Component } = await import('../index.js')
|
||||||
@@ -29,8 +29,8 @@ test('browser: render, mount, update morphs in place, proxy stands in', async ()
|
|||||||
// module registry caches per specifier; component/index.js re-evaluates
|
// module registry caches per specifier; component/index.js re-evaluates
|
||||||
// _hasWindow per instance, so importing after globals are set is enough
|
// _hasWindow per instance, so importing after globals are set is enough
|
||||||
const { default: Component } = await import('../index.js?browser')
|
const { default: Component } = await import('../index.js?browser')
|
||||||
const { default: html } = await import('@choojs/html/browser')
|
const { default: html } = await import('@uhhm/buuh-html/browser')
|
||||||
const { default: morph } = await import('@choojs/html/morph')
|
const { default: morph } = await import('@uhhm/buuh-html/morph')
|
||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
createElement (n) {
|
createElement (n) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Ported from choo 7.1.0 index.js (MIT) — https://github.com/choojs/choo
|
// Ported from choo 7.1.0 index.js (MIT) — https://github.com/choojs/choo
|
||||||
// Same API, same event flow; ESM, consolidated nano* internals in ./lib.
|
// Same API, same event flow; ESM, consolidated nano* internals in ./lib.
|
||||||
|
|
||||||
import morph from '@choojs/html/morph'
|
import morph from '@uhhm/buuh-html/morph'
|
||||||
import hydrate from '@choojs/html/hydrate'
|
import hydrate from '@uhhm/buuh-html/hydrate'
|
||||||
import nanotiming from './lib/timing.js'
|
import nanotiming from './lib/timing.js'
|
||||||
import Nanorouter from './lib/router.js'
|
import Nanorouter from './lib/router.js'
|
||||||
import Nanobus from './lib/bus.js'
|
import Nanobus from './lib/bus.js'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/core",
|
"name": "@uhhm/buuh",
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "A 4kb framework for creating sturdy frontend applications",
|
"description": "A 4kb framework for creating sturdy frontend applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
"node": ">=24"
|
"node": ">=24"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@choojs/html": "*"
|
"@uhhm/buuh-html": "*"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"client",
|
"client",
|
||||||
"frontend",
|
"frontend",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ before(async () => {
|
|||||||
globalThis.window = win
|
globalThis.window = win
|
||||||
globalThis.document = win.document
|
globalThis.document = win.document
|
||||||
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
||||||
;({ default: choo } = await import('@choojs/core'))
|
;({ default: choo } = await import('@uhhm/buuh'))
|
||||||
;({ default: html } = await import('@choojs/html/browser'))
|
;({ default: html } = await import('@uhhm/buuh-html/browser'))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
|
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
import raw from '@choojs/html/raw'
|
import raw from '@uhhm/buuh-html/raw'
|
||||||
import choo, { Choo } from '@choojs/core'
|
import choo, { Choo } from '@uhhm/buuh'
|
||||||
|
|
||||||
test('should render on the server with @choojs/html', () => {
|
test('should render on the server with @uhhm/buuh-html', () => {
|
||||||
const app = choo()
|
const app = choo()
|
||||||
app.route('/', function (state, emit) {
|
app.route('/', function (state, emit) {
|
||||||
const strong = '<strong>Hello filthy planet</strong>'
|
const strong = '<strong>Hello filthy planet</strong>'
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ let choo, browserHtml, serverString
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
// Server side first, before any window exists — exactly like production,
|
// Server side first, before any window exists — exactly like production,
|
||||||
// and toString() enforces it.
|
// and toString() enforces it.
|
||||||
;({ default: choo } = await import('@choojs/core'))
|
;({ default: choo } = await import('@uhhm/buuh'))
|
||||||
const { default: serverHtml } = await import('@choojs/html/server')
|
const { default: serverHtml } = await import('@uhhm/buuh-html/server')
|
||||||
serverString = makeApp(serverHtml).toString('/')
|
serverString = makeApp(serverHtml).toString('/')
|
||||||
|
|
||||||
const win = new Window({ url: 'http://localhost/' })
|
const win = new Window({ url: 'http://localhost/' })
|
||||||
globalThis.window = win
|
globalThis.window = win
|
||||||
globalThis.document = win.document
|
globalThis.document = win.document
|
||||||
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
||||||
;({ default: browserHtml } = await import('@choojs/html/browser'))
|
;({ default: browserHtml } = await import('@uhhm/buuh-html/browser'))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ before(async () => {
|
|||||||
globalThis.window = win
|
globalThis.window = win
|
||||||
globalThis.document = win.document
|
globalThis.document = win.document
|
||||||
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
||||||
;({ default: choo, lazy } = await import('@choojs/core'))
|
;({ default: choo, lazy } = await import('@uhhm/buuh'))
|
||||||
;({ default: html } = await import('@choojs/html/browser'))
|
;({ default: html } = await import('@uhhm/buuh-html/browser'))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
|
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
import choo, { lazy } from '@choojs/core'
|
import choo, { lazy } from '@uhhm/buuh'
|
||||||
|
|
||||||
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
|
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
|
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
import choo from '@choojs/core'
|
import choo from '@uhhm/buuh'
|
||||||
|
|
||||||
function view (state) {
|
function view (state) {
|
||||||
return html`<div></div>`
|
return html`<div></div>`
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// event log, emit-from-the-console, and render timings via the
|
// event log, emit-from-the-console, and render timings via the
|
||||||
// PerformanceObserver watching nanotiming's measures. Usage:
|
// PerformanceObserver watching nanotiming's measures. Usage:
|
||||||
//
|
//
|
||||||
// import devtools from '@choojs/devtools'
|
// import devtools from '@uhhm/buuh-devtools'
|
||||||
// app.use(devtools())
|
// app.use(devtools())
|
||||||
//
|
//
|
||||||
// The store is a no-op on the server and (by default) stays quiet in the
|
// The store is a no-op on the server and (by default) stays quiet in the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/devtools",
|
"name": "@uhhm/buuh-devtools",
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "Console devtools for choo: window.choo, event log, perf timings",
|
"description": "Console devtools for choo: window.choo, event log, perf timings",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24"
|
"node": ">=24"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"choo",
|
"choo",
|
||||||
"devtools",
|
"devtools",
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ before(async () => {
|
|||||||
globalThis.window = win
|
globalThis.window = win
|
||||||
globalThis.document = win.document
|
globalThis.document = win.document
|
||||||
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
globalThis.requestAnimationFrame = win.requestAnimationFrame.bind(win)
|
||||||
;({ default: choo } = await import('@choojs/core'))
|
;({ default: choo } = await import('@uhhm/buuh'))
|
||||||
;({ default: html } = await import('@choojs/html/browser'))
|
;({ default: html } = await import('@uhhm/buuh-html/browser'))
|
||||||
;({ default: devtools } = await import('../index.js'))
|
;({ default: devtools } = await import('../index.js'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/html",
|
"name": "@uhhm/buuh-html",
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "HTML template literals that render to DOM in the browser and strings on the server",
|
"description": "HTML template literals that render to DOM in the browser and strings on the server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24"
|
"node": ">=24"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"html",
|
"html",
|
||||||
"template",
|
"template",
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export class HtmlChunks {
|
|||||||
for (const part of this.parts) {
|
for (const part of this.parts) {
|
||||||
if (typeof part !== 'string') {
|
if (typeof part !== 'string') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'@choojs/html: this template has async content and cannot render synchronously — stream it (choo: use toStream() instead of toString())'
|
'@uhhm/buuh-html: this template has async content and cannot render synchronously — stream it (choo: use toStream() instead of toString())'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
out += part
|
out += part
|
||||||
@@ -168,7 +168,7 @@ export default function html (pieces, ...values) {
|
|||||||
|
|
||||||
// tag position: attributes are synchronous, always
|
// tag position: attributes are synchronous, always
|
||||||
if (isThenable(value) || isAsyncIterable(value)) {
|
if (isThenable(value) || isAsyncIterable(value)) {
|
||||||
throw new Error('@choojs/html: async values are only allowed in child position, not in attributes')
|
throw new Error('@uhhm/buuh-html: async values are only allowed in child position, not in attributes')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers are behavior, not markup: `onclick=${fn}` renders
|
// Event handlers are behavior, not markup: `onclick=${fn}` renders
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
|
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
import raw from '@choojs/html/raw'
|
import raw from '@uhhm/buuh-html/raw'
|
||||||
|
|
||||||
test('renders a template to a string', () => {
|
test('renders a template to a string', () => {
|
||||||
const res = html`<p>hello</p>`
|
const res = html`<p>hello</p>`
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { test } from 'node:test'
|
import { test } from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
|
|
||||||
import html from '@choojs/html'
|
import html from '@uhhm/buuh-html'
|
||||||
import raw from '@choojs/html/raw'
|
import raw from '@uhhm/buuh-html/raw'
|
||||||
|
|
||||||
async function collect (chunks) {
|
async function collect (chunks) {
|
||||||
const out = []
|
const out = []
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ for (const target of targets) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n${dry ? 'dry run — no files written' : changedCount + ' file(s) rewritten'}. Remember: package.json needs "type": "module" and deps on @choojs/*.`)
|
console.log(`\n${dry ? 'dry run — no files written' : changedCount + ' file(s) rewritten'}. Remember: package.json needs "type": "module" and deps on @uhhm/*.`)
|
||||||
|
|||||||
@@ -5,28 +5,28 @@
|
|||||||
|
|
||||||
// old specifier → new specifier
|
// old specifier → new specifier
|
||||||
export const SPECIFIERS = {
|
export const SPECIFIERS = {
|
||||||
choo: '@choojs/core',
|
choo: '@uhhm/buuh',
|
||||||
'choo/html': '@choojs/html',
|
'choo/html': '@uhhm/buuh-html',
|
||||||
'choo/html/raw': '@choojs/html/raw',
|
'choo/html/raw': '@uhhm/buuh-html/raw',
|
||||||
'choo/component': '@choojs/component',
|
'choo/component': '@uhhm/buuh-component',
|
||||||
nanohtml: '@choojs/html',
|
nanohtml: '@uhhm/buuh-html',
|
||||||
'nanohtml/raw': '@choojs/html/raw',
|
'nanohtml/raw': '@uhhm/buuh-html/raw',
|
||||||
nanomorph: '@choojs/html/morph',
|
nanomorph: '@uhhm/buuh-html/morph',
|
||||||
nanocomponent: '@choojs/component',
|
nanocomponent: '@uhhm/buuh-component',
|
||||||
'choo-devtools': '@choojs/devtools'
|
'choo-devtools': '@uhhm/buuh-devtools'
|
||||||
}
|
}
|
||||||
|
|
||||||
// old specifier → guidance (no direct replacement package)
|
// old specifier → guidance (no direct replacement package)
|
||||||
export const RETIRED = {
|
export const RETIRED = {
|
||||||
nanobus: "built into @choojs/core — use app.emitter, or import Nanobus from '@choojs/core' internals is no longer needed",
|
nanobus: "built into @uhhm/buuh — use app.emitter, or import Nanobus from '@uhhm/buuh' internals is no longer needed",
|
||||||
nanorouter: 'built into @choojs/core — app.route covers it',
|
nanorouter: 'built into @uhhm/buuh — app.route covers it',
|
||||||
nanohref: 'built into @choojs/core — link handling is automatic',
|
nanohref: 'built into @uhhm/buuh — link handling is automatic',
|
||||||
nanotiming: "built into @choojs/core — import from '@choojs/core/timing' if you need it directly",
|
nanotiming: "built into @uhhm/buuh — import from '@uhhm/buuh/timing' if you need it directly",
|
||||||
nanoraf: 'retired — use requestAnimationFrame, or rely on choo render batching',
|
nanoraf: 'retired — use requestAnimationFrame, or rely on choo render batching',
|
||||||
nanoquery: 'retired — state.query is built in; use URLSearchParams directly elsewhere',
|
nanoquery: 'retired — state.query is built in; use URLSearchParams directly elsewhere',
|
||||||
nanoassert: 'retired — use plain checks or node:assert',
|
nanoassert: 'retired — use plain checks or node:assert',
|
||||||
nanolru: 'retired — the component cache is built into @choojs/core',
|
nanolru: 'retired — the component cache is built into @uhhm/buuh',
|
||||||
'choo-lazy-route': "replaced by lazy() from '@choojs/core': app.route('/x', lazy(() => import('./x.js')))",
|
'choo-lazy-route': "replaced by lazy() from '@uhhm/buuh': app.route('/x', lazy(() => import('./x.js')))",
|
||||||
'choo-service-worker': 'kept as a concept; bankai v10 will inject the asset manifest — check docs before migrating this one'
|
'choo-service-worker': 'kept as a concept; bankai v10 will inject the asset manifest — check docs before migrating this one'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@choojs/migrate",
|
"name": "@uhhm/buuh-migrate",
|
||||||
"version": "8.0.0-dev",
|
"version": "8.0.0-dev",
|
||||||
"description": "Codemod: migrate choo v7 apps to @choojs v8 (ESM, new package names)",
|
"description": "Codemod: migrate choo v7 apps to buuh (choo v8) (ESM, new package names)",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"choo-migrate": "./cli.js"
|
"buuh-migrate": "./cli.js"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./lib/transform.js"
|
".": "./lib/transform.js"
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24"
|
"node": ">=24"
|
||||||
},
|
},
|
||||||
"repository": "choojs/choo",
|
"repository": "https://project.uhhm.no/uhhm/buuh",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"choo",
|
"choo",
|
||||||
"codemod",
|
"codemod",
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ test('converts a classic choo v7 header to v8 ESM', () => {
|
|||||||
|
|
||||||
const { code, changed, notes } = transform(src)
|
const { code, changed, notes } = transform(src)
|
||||||
assert.ok(changed)
|
assert.ok(changed)
|
||||||
assert.match(code, /import choo from '@choojs\/core'/)
|
assert.match(code, /import choo from '@uhhm\/buuh'/)
|
||||||
assert.match(code, /import html from '@choojs\/html'/)
|
assert.match(code, /import html from '@uhhm\/buuh-html'/)
|
||||||
assert.match(code, /import devtools from '@choojs\/devtools'/)
|
assert.match(code, /import devtools from '@uhhm\/buuh-devtools'/)
|
||||||
assert.match(code, /export default app/)
|
assert.match(code, /export default app/)
|
||||||
assert.deepStrictEqual(notes, [])
|
assert.deepStrictEqual(notes, [])
|
||||||
})
|
})
|
||||||
@@ -48,11 +48,11 @@ test('already-ESM sources get specifiers remapped', () => {
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const { code } = transform(src)
|
const { code } = transform(src)
|
||||||
assert.match(code, /from '@choojs\/core'/)
|
assert.match(code, /from '@uhhm\/buuh'/)
|
||||||
assert.match(code, /from '@choojs\/html'\n/)
|
assert.match(code, /from '@uhhm\/buuh-html'\n/)
|
||||||
assert.match(code, /from '@choojs\/html\/raw'/)
|
assert.match(code, /from '@uhhm\/buuh-html\/raw'/)
|
||||||
assert.match(code, /from '@choojs\/html\/morph'/)
|
assert.match(code, /from '@uhhm\/buuh-html\/morph'/)
|
||||||
assert.match(code, /from '@choojs\/component'/)
|
assert.match(code, /from '@uhhm\/buuh-component'/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('retired packages produce notes, not rewrites', () => {
|
test('retired packages produce notes, not rewrites', () => {
|
||||||
@@ -69,7 +69,7 @@ test('retired packages produce notes, not rewrites', () => {
|
|||||||
|
|
||||||
test('dynamic import() specifiers are remapped too', () => {
|
test('dynamic import() specifiers are remapped too', () => {
|
||||||
const { code } = transform("const mod = await import('choo/html')")
|
const { code } = transform("const mod = await import('choo/html')")
|
||||||
assert.match(code, /import\('@choojs\/html'\)/)
|
assert.match(code, /import\('@uhhm\/buuh-html'\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('unconvertible CJS is reported honestly', () => {
|
test('unconvertible CJS is reported honestly', () => {
|
||||||
@@ -79,7 +79,7 @@ test('unconvertible CJS is reported honestly', () => {
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const { code, notes } = transform(src)
|
const { code, notes } = transform(src)
|
||||||
assert.match(code, /require\('@choojs\/core'\)/, 'specifier remapped even inside functions')
|
assert.match(code, /require\('@uhhm\/buuh'\)/, 'specifier remapped even inside functions')
|
||||||
assert.ok(notes.some((n) => n.includes('module.exports')))
|
assert.ok(notes.some((n) => n.includes('module.exports')))
|
||||||
assert.ok(notes.some((n) => n.includes('require() calls remain')))
|
assert.ok(notes.some((n) => n.includes('require() calls remain')))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Build the single-file CDN bundle: everything a browser needs as one
|
||||||
|
// minified ES module, for import-map use against any static host that
|
||||||
|
// serves JavaScript with the right MIME type.
|
||||||
|
//
|
||||||
|
// npm run bundle → dist-cdn/buuh.js (+ sourcemap)
|
||||||
|
//
|
||||||
|
// <script type="importmap">
|
||||||
|
// { "imports": { "buuh": "https://cdn.uhhm.no/buuh@8.0.0.js" } }
|
||||||
|
// </script>
|
||||||
|
// <script type="module">
|
||||||
|
// import { choo, html } from 'buuh'
|
||||||
|
// </script>
|
||||||
|
|
||||||
|
import { build } from 'vite'
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { join, dirname, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||||
|
const { version } = JSON.parse(await readFile(join(root, 'packages/core/package.json'), 'utf8'))
|
||||||
|
|
||||||
|
const VIRTUAL = '\0bundle:entry'
|
||||||
|
|
||||||
|
await build({
|
||||||
|
appType: 'custom',
|
||||||
|
logLevel: 'warn',
|
||||||
|
plugins: [{
|
||||||
|
name: 'bundle-entry',
|
||||||
|
resolveId: (id) => id === 'bundle:entry' ? VIRTUAL : undefined,
|
||||||
|
load: (id) => id === VIRTUAL
|
||||||
|
? [
|
||||||
|
"export { default as choo, Choo, lazy } from '@uhhm/buuh'",
|
||||||
|
"export { default as html } from '@uhhm/buuh-html'",
|
||||||
|
"export { default as raw } from '@uhhm/buuh-html/raw'",
|
||||||
|
"export { default as morph } from '@uhhm/buuh-html/morph'",
|
||||||
|
"export { default as hydrate } from '@uhhm/buuh-html/hydrate'",
|
||||||
|
"export { default as Component } from '@uhhm/buuh-component'",
|
||||||
|
"export { default as devtools } from '@uhhm/buuh-devtools'"
|
||||||
|
].join('\n')
|
||||||
|
: undefined
|
||||||
|
}],
|
||||||
|
build: {
|
||||||
|
outDir: join(root, 'dist-cdn'),
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
lib: false,
|
||||||
|
rollupOptions: {
|
||||||
|
input: { buuh: 'bundle:entry' },
|
||||||
|
output: { entryFileNames: 'buuh.js' },
|
||||||
|
preserveEntrySignatures: 'strict'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`bundled buuh ${version} → dist-cdn/buuh.js`)
|
||||||
+5
-5
@@ -1,5 +1,5 @@
|
|||||||
// The size budget: what does the framework itself cost on the wire?
|
// The size budget: what does the framework itself cost on the wire?
|
||||||
// Bundles @choojs/core + @choojs/html (browser condition) minified via
|
// Bundles @uhhm/buuh + @uhhm/buuh-html (browser condition) minified via
|
||||||
// the same Vite/Rolldown pipeline apps use, then reports min+gzip and
|
// the same Vite/Rolldown pipeline apps use, then reports min+gzip and
|
||||||
// min+brotli. CI fails if min+gzip exceeds the budget.
|
// min+brotli. CI fails if min+gzip exceeds the budget.
|
||||||
//
|
//
|
||||||
@@ -28,9 +28,9 @@ await build({
|
|||||||
name: 'size-entry',
|
name: 'size-entry',
|
||||||
resolveId: (id) => id === 'size:entry' ? VIRTUAL : undefined,
|
resolveId: (id) => id === 'size:entry' ? VIRTUAL : undefined,
|
||||||
load: (id) => id === VIRTUAL
|
load: (id) => id === VIRTUAL
|
||||||
? "import choo, { Choo, lazy } from '@choojs/core'\n" +
|
? "import choo, { Choo, lazy } from '@uhhm/buuh'\n" +
|
||||||
"import html from '@choojs/html'\n" +
|
"import html from '@uhhm/buuh-html'\n" +
|
||||||
"import raw from '@choojs/html/raw'\n" +
|
"import raw from '@uhhm/buuh-html/raw'\n" +
|
||||||
'window.__keep = { choo, Choo, lazy, html, raw }\n'
|
'window.__keep = { choo, Choo, lazy, html, raw }\n'
|
||||||
: undefined
|
: undefined
|
||||||
}],
|
}],
|
||||||
@@ -52,7 +52,7 @@ const br = brotliCompressSync(code, {
|
|||||||
}).length
|
}).length
|
||||||
|
|
||||||
const kb = (n) => (n / 1024).toFixed(2) + ' kB'
|
const kb = (n) => (n / 1024).toFixed(2) + ' kB'
|
||||||
console.log(`@choojs/core + @choojs/html (browser, minified)`)
|
console.log(`@uhhm/buuh + @uhhm/buuh-html (browser, minified)`)
|
||||||
console.log(` raw: ${kb(code.length)}`)
|
console.log(` raw: ${kb(code.length)}`)
|
||||||
console.log(` gzip: ${kb(gz)} (budget ${kb(BUDGET_GZIP)})`)
|
console.log(` gzip: ${kb(gz)} (budget ${kb(BUDGET_GZIP)})`)
|
||||||
console.log(` brotli: ${kb(br)}`)
|
console.log(` brotli: ${kb(br)}`)
|
||||||
|
|||||||
+6
-6
@@ -24,12 +24,12 @@ const MIME = {
|
|||||||
|
|
||||||
const IMPORT_MAP = JSON.stringify({
|
const IMPORT_MAP = JSON.stringify({
|
||||||
imports: {
|
imports: {
|
||||||
'@choojs/core': '/packages/core/index.js',
|
'@uhhm/buuh': '/packages/core/index.js',
|
||||||
'@choojs/core/timing': '/packages/core/lib/timing.js',
|
'@uhhm/buuh/timing': '/packages/core/lib/timing.js',
|
||||||
'@choojs/html': '/packages/html/browser.js',
|
'@uhhm/buuh-html': '/packages/html/browser.js',
|
||||||
'@choojs/html/raw': '/packages/html/raw.js',
|
'@uhhm/buuh-html/raw': '/packages/html/raw.js',
|
||||||
'@choojs/html/morph': '/packages/html/morph.js',
|
'@uhhm/buuh-html/morph': '/packages/html/morph.js',
|
||||||
'@choojs/html/hydrate': '/packages/html/hydrate.js'
|
'@uhhm/buuh-html/hydrate': '/packages/html/hydrate.js'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user