@ultimat3/render
Advanced tools
| /** | ||
| * The BUILD-TIME half of `@ultimat3/render` — the loaders and the route → bytes pipeline — split | ||
| * off because `css-modules.ts` imports `node:url`, whose browser polyfill exports neither name it | ||
| * asks for: one barrel carrying both halves could not be bundled for a browser at all (axiom 6). | ||
| * Disjoint from `@ultimat3/render` by construction, which `server.test.ts` checks. | ||
| */ | ||
| import { installRenderLoader } from './module-loader'; | ||
| // A side effect on import, deliberately, and the reason this barrel is named in `sideEffects`: a | ||
| // Bun plugin only transforms modules loaded AFTER it, so the install has to happen before any | ||
| // `.tsx` route or `.scss` module is imported. It lives on THIS barrel rather than on | ||
| // `@ultimat3/render` because the loader is build-time code — a browser bundle that reached it | ||
| // would carry `sass` and `node:fs`, which is the defect this split closes. | ||
| installRenderLoader(); | ||
| // ---- scss → css, and the scoped class map every `import styles from` receives ------------------- | ||
| export type { CompiledStylesheet } from './css-modules'; | ||
| export { compileStylesheet, isCssModule, isGlobalStylesheet, scopeClasses } from './css-modules'; | ||
| // ---- the two Bun loaders: `.tsx` → the server JSX factory, `.scss` → css + a class map ---------- | ||
| export type { Stylesheet } from './module-loader'; | ||
| export { | ||
| clearStylesheets, | ||
| installRenderLoader, | ||
| loadStylesheet, | ||
| registeredStylesheets, | ||
| stylesFor, | ||
| transformTsx, | ||
| } from './module-loader'; | ||
| // ---- the render pipeline: one entry point per mode ---------------------------------------------- | ||
| export type { RenderHtmlOptions } from './render-html'; | ||
| export { ROOT_ELEMENT_ID, renderComponent, renderToHtml } from './render-html'; | ||
| export type { | ||
| IsrController, | ||
| IsrControllerOptions, | ||
| IsrEntry, | ||
| IsrRenderFn, | ||
| IsrServeResult, | ||
| IsrState, | ||
| IsrStore, | ||
| MemoryIsrStoreOptions, | ||
| } from './render-isr'; | ||
| export { | ||
| createIsrController, | ||
| DEFAULT_ISR_MAX_ENTRIES, | ||
| invalidateAndRevalidate, | ||
| isrKey, | ||
| memoryIsrStore, | ||
| } from './render-isr'; | ||
| export type { SsrOptions, SsrRenderFn, SsrRenderInput } from './render-ssr'; | ||
| export { renderSsr, ssrHeaders } from './render-ssr'; | ||
| export type { StaticArtifact, StaticBuildOptions, StaticRenderFn } from './render-static'; | ||
| export { | ||
| assertNoPerRequestState, | ||
| contentHash, | ||
| enumeratePrerender, | ||
| fillPath, | ||
| renderStatic, | ||
| staticHeaders, | ||
| staticResult, | ||
| } from './render-static'; | ||
| export type { StreamHole, StreamOptions, StreamPlan } from './render-stream'; | ||
| export { | ||
| collectStream, | ||
| DEFAULT_HOLE_TIMEOUT_MS, | ||
| holeId, | ||
| holeMarker, | ||
| REVEAL_SCRIPT, | ||
| renderStreamHtml, | ||
| revealChunk, | ||
| streamResult, | ||
| } from './render-stream'; |
+15
-1
@@ -7,2 +7,16 @@ # @ultimat3/render — boundary | ||
| **Two entry points, disjoint, split 2026-08-22** — the same `"."` / `"./server"` shape | ||
| `@ultimat3/realtime` took, for the same reason. `"."` (`index.ts`) is the CLIENT half and BUNDLES | ||
| for the browser; `"./server"` (`server.ts`) is the build-time half — `css-modules`, | ||
| `module-loader`, `render-html`, `render-isr`, `render-ssr`, `render-static`, `render-stream` — and | ||
| does not. `css-modules.ts` imports `fileURLToPath`/`pathToFileURL` from `node:url`, which Bun's | ||
| browser polyfill exports NEITHER of, so the single barrel was not a fat bundle, it was a | ||
| `bun build --target=browser` that FAILED at link time on any app entry that reached this package. | ||
| Measured: no `sideEffects` value repairs it (`false`, `[]`, an array naming only `errors.ts` — all | ||
| fail identically), which is where this split differs from realtime's, where the array alone was | ||
| enough. Only not importing the module does. Never re-export a name from both barrels: disjointness | ||
| is what makes "which half does this live in" a mechanical fact, and `index.test.ts` asserts both | ||
| the empty name intersection AND that `index.ts`'s transitive runtime import graph reaches none of | ||
| the seven modules above. `scripts/browser-barrel.test.ts` holds the end property, both directions. | ||
| `island()` is a **factory over the route's own `hydrate`**, not a ninth primitive and not a | ||
@@ -72,3 +86,3 @@ second render mode — the same rule `llm()` and `backfill()` follow. It adds no key to | ||
| | Root element | `ROOT_ELEMENT_ID` (`render-html.ts`), the id every document's body wraps its component in. It was `SPA_ROOT_ID` in `render-spa.ts`, naming a mode that never used it and that no longer exists. | | ||
| | The loaders | `module-loader.ts` installs them at `index.ts` module scope, once. A plugin only affects modules loaded after it, so a second install point is a page that renders in one entry point and not another. | | ||
| | The loaders | `module-loader.ts` installs them at **`server.ts`** module scope, once — `index.ts` until the barrel split, and it cannot be there again: the client barrel would carry `sass` and `node:url`. A plugin only affects modules loaded after it, so a second install point is a page that renders in one entry point and not another. Anything that loads an app's `.tsx` reaches `@ultimat3/render/server` first, which is why `packages/cli/src/app-load.ts` imports it for the side effect and nothing else. | | ||
| | `<head>` baseline | `documentBaseline()` in `head.ts` — charset, viewport, `color-scheme` — merged FIRST so a route can still override any of them. Absent until `As of 2026-08`, and the missing `viewport` is why every deployed app rendered zoomed-out on a phone whatever its CSS said. | | ||
@@ -75,0 +89,0 @@ | Escaping | `html.ts` only, and that now includes `render-stream.ts` (`holeMarker`'s attribute, and `revealChunk`'s `$X(...)` argument via `JSON.stringify` — an id containing `")` closed the call and ran the rest) and `head.ts`'s `themeScript` (`storageKey`/`attribute` as JS string LITERALS). All author-controlled today — the identical status `emitIslandAttributes` had before the last sweep. `render-spa.ts` was the third entry here and went with the mode. A second escaper is how one of them ends up missing a character, and a missing character in an attribute is an injection. `escapeAttribute` itself is `@ultimat3/seo`'s (tier 1), re-exported by `html.ts` rather than reimplemented — the copy that lived here was the second escaper this row forbids, and `pwa/CLAUDE.md` already named seo's as the one. `head.ts` and `hydrate.ts` each had a private copy; both now import — `escapeAttribute` for every attribute value (`emitIslandAttributes` interpolated all five of its own raw until 2026-08, while this row already claimed otherwise) and `escapeJsonContent` for a JSON script body. | |
+8
-7
| { | ||
| "name": "@ultimat3/render", | ||
| "version": "8.0.0", | ||
| "version": "9.0.0", | ||
| "description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.", | ||
@@ -9,3 +9,3 @@ "license": "MIT", | ||
| "./src/errors.ts", | ||
| "./src/index.ts" | ||
| "./src/server.ts" | ||
| ], | ||
@@ -22,3 +22,4 @@ "repository": { | ||
| "exports": { | ||
| ".": "./src/index.ts" | ||
| ".": "./src/index.ts", | ||
| "./server": "./src/server.ts" | ||
| }, | ||
@@ -40,8 +41,8 @@ "files": [ | ||
| "dependencies": { | ||
| "@ultimat3/cache": "8.0.0", | ||
| "@ultimat3/core": "8.0.0", | ||
| "@ultimat3/i18n": "8.0.0", | ||
| "@ultimat3/seo": "8.0.0", | ||
| "@ultimat3/cache": "9.0.0", | ||
| "@ultimat3/core": "9.0.0", | ||
| "@ultimat3/i18n": "9.0.0", | ||
| "@ultimat3/seo": "9.0.0", | ||
| "sass": "1.102.0" | ||
| } | ||
| } |
+29
-3
@@ -324,4 +324,28 @@ # 🖼 @ultimat3/render | ||
| ## Two entry points | ||
| **Split 2026-08-22, and every claim in this section holds `As of 2026-08`.** | ||
| `@ultimat3/render` is the **client** half — the `route` primitive, the JSX factory, islands, | ||
| hydration, `<head>`, the route table. It bundles for the browser, and | ||
| `scripts/browser-barrel.test.ts` builds it that way and asserts it. | ||
| `@ultimat3/render/server` is the **build-time** half — the `.tsx`/`.scss` Bun loaders and the | ||
| render pipeline. It imports `sass` and `node:url`, so it never reaches a browser bundle. | ||
| The two are **disjoint**: no name is on both, and a file needing both imports both. That is the | ||
| price of the split and it is the point of it — a single barrel could not be bundled for the | ||
| browser at all, because `node:url`'s browser polyfill exports neither `fileURLToPath` nor | ||
| `pathToFileURL` and the build fails at link time. No `sideEffects` value fixes that (measured: | ||
| `false`, `[]` and an array naming only `errors.ts` all fail identically) — only not importing it | ||
| does. | ||
| **Importing `@ultimat3/render/server` installs the `.tsx`/`.scss` loaders**, once, as a module | ||
| side effect. Anything that loads an app's source — `x dev`, `x build`, `server.ts`, a test that | ||
| `await import()`s a `page.tsx` — reaches it before the module it loads. | ||
| ## Public API | ||
| `†` marks a name on `@ultimat3/render/server`. | ||
| | Export | Owns | | ||
@@ -334,5 +358,7 @@ |---|---| | ||
| | `checkSurfaceBoundary`, `assertSurfaceBoundary`, `surfaceOf` | the hard boundary | | ||
| | `renderStatic`, `enumeratePrerender` | build-time render, content hashing | | ||
| | `createIsrController`, `invalidateAndRevalidate` | SWR + single-flight + tag triggers | | ||
| | `renderSsr`, `streamResult` | the per-request modes | | ||
| | `renderStatic`†, `enumeratePrerender`† | build-time render, content hashing | | ||
| | `createIsrController`†, `invalidateAndRevalidate`† | SWR + single-flight + tag triggers | | ||
| | `renderSsr`†, `streamResult`† | the per-request modes | | ||
| | `renderToHtml`†, `renderComponent`†, `stylesFor`† | the server JSX writer and the surface's css | | ||
| | `installRenderLoader`†, `compileStylesheet`† | the `.tsx`/`.scss` loaders, installed on import | | ||
| | `emitIslandAttributes`, `hydrateRuntime` | the four hydration strategies | | ||
@@ -339,0 +365,0 @@ | `ISLAND_MOUNTED_ATTRIBUTE`, `ISLAND_FAILED_ATTRIBUTE`, `IDLE_HYDRATE_TIMEOUT_MS` | what hydration looks like from outside the page | |
+5
-63
@@ -1,12 +0,7 @@ | ||
| /** Public API of `@ultimat3/render`: the `route` primitive, the five modes, the table. */ | ||
| /** | ||
| * The CLIENT half of `@ultimat3/render` — the `route` primitive, the JSX factory, islands, and the | ||
| * tables describing them — kept disjoint from `@ultimat3/render/server` because everything here | ||
| * must bundle for a browser, which the loaders cannot (axiom 6). | ||
| */ | ||
| import { installRenderLoader } from './module-loader'; | ||
| // A side effect on import, deliberately: a Bun plugin only transforms modules loaded AFTER it, and | ||
| // every consumer that will ever load a `.tsx` route or a `.scss` module imports this package first | ||
| // (an app's route file imports `defineRoute` from here before it imports anything else it owns). | ||
| // Any later hook — `x dev`, `x build`, `server.ts` — would each have to remember, which is four | ||
| // places one fact can be wrong instead of none. | ||
| installRenderLoader(); | ||
| /** | ||
@@ -24,4 +19,2 @@ * The route vocabulary is declared once, at tier 0 (`@ultimat3/core`), and re-exported here | ||
| export { formatBytes, HYDRATE_STRATEGIES, OFFLINE_STRATEGIES, RENDER_MODES } from '@ultimat3/core'; | ||
| export type { CompiledStylesheet } from './css-modules'; | ||
| export { compileStylesheet, isCssModule, isGlobalStylesheet, scopeClasses } from './css-modules'; | ||
| export { parseTtlMs } from './duration'; | ||
@@ -108,11 +101,2 @@ export type { RenderErrorCode } from './errors'; | ||
| } from './modes'; | ||
| export type { Stylesheet } from './module-loader'; | ||
| export { | ||
| clearStylesheets, | ||
| installRenderLoader, | ||
| loadStylesheet, | ||
| registeredStylesheets, | ||
| stylesFor, | ||
| transformTsx, | ||
| } from './module-loader'; | ||
| export type { | ||
@@ -135,45 +119,3 @@ CompiledPattern, | ||
| } from './registry'; | ||
| export type { RenderHtmlOptions } from './render-html'; | ||
| export { ROOT_ELEMENT_ID, renderComponent, renderToHtml } from './render-html'; | ||
| export type { | ||
| IsrController, | ||
| IsrControllerOptions, | ||
| IsrEntry, | ||
| IsrRenderFn, | ||
| IsrServeResult, | ||
| IsrState, | ||
| IsrStore, | ||
| MemoryIsrStoreOptions, | ||
| } from './render-isr'; | ||
| export { | ||
| createIsrController, | ||
| DEFAULT_ISR_MAX_ENTRIES, | ||
| invalidateAndRevalidate, | ||
| isrKey, | ||
| memoryIsrStore, | ||
| } from './render-isr'; | ||
| export type { SsrOptions, SsrRenderFn, SsrRenderInput } from './render-ssr'; | ||
| export { renderSsr, ssrHeaders } from './render-ssr'; | ||
| export type { StaticArtifact, StaticBuildOptions, StaticRenderFn } from './render-static'; | ||
| export { | ||
| assertNoPerRequestState, | ||
| contentHash, | ||
| enumeratePrerender, | ||
| fillPath, | ||
| renderStatic, | ||
| staticHeaders, | ||
| staticResult, | ||
| } from './render-static'; | ||
| export type { StreamHole, StreamOptions, StreamPlan } from './render-stream'; | ||
| export { | ||
| collectStream, | ||
| DEFAULT_HOLE_TIMEOUT_MS, | ||
| holeId, | ||
| holeMarker, | ||
| REVEAL_SCRIPT, | ||
| renderStreamHtml, | ||
| revealChunk, | ||
| streamResult, | ||
| } from './render-stream'; | ||
| export type { | ||
| LoadRequirement, | ||
@@ -180,0 +122,0 @@ PrerenderFn, |
238183
1.72%31
3.33%4122
0.32%396
7.03%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated