@ultimat3/render 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +15 -1
- package/README.md +29 -3
- package/package.json +8 -7
- package/src/index.ts +5 -63
- package/src/server.ts +72 -0
package/CLAUDE.md
CHANGED
|
@@ -4,6 +4,20 @@ Owns: the `route` primitive, the four render modes, the route table, the surface
|
|
|
4
4
|
islands + budgets, hydration directives, `<head>` merge, **the server JSX runtime and the two Bun
|
|
5
5
|
loaders that make an app's `.tsx` and `.scss` runnable**.
|
|
6
6
|
|
|
7
|
+
**Two entry points, disjoint, split 2026-08-22** — the same `"."` / `"./server"` shape
|
|
8
|
+
`@ultimat3/realtime` took, for the same reason. `"."` (`index.ts`) is the CLIENT half and BUNDLES
|
|
9
|
+
for the browser; `"./server"` (`server.ts`) is the build-time half — `css-modules`,
|
|
10
|
+
`module-loader`, `render-html`, `render-isr`, `render-ssr`, `render-static`, `render-stream` — and
|
|
11
|
+
does not. `css-modules.ts` imports `fileURLToPath`/`pathToFileURL` from `node:url`, which Bun's
|
|
12
|
+
browser polyfill exports NEITHER of, so the single barrel was not a fat bundle, it was a
|
|
13
|
+
`bun build --target=browser` that FAILED at link time on any app entry that reached this package.
|
|
14
|
+
Measured: no `sideEffects` value repairs it (`false`, `[]`, an array naming only `errors.ts` — all
|
|
15
|
+
fail identically), which is where this split differs from realtime's, where the array alone was
|
|
16
|
+
enough. Only not importing the module does. Never re-export a name from both barrels: disjointness
|
|
17
|
+
is what makes "which half does this live in" a mechanical fact, and `index.test.ts` asserts both
|
|
18
|
+
the empty name intersection AND that `index.ts`'s transitive runtime import graph reaches none of
|
|
19
|
+
the seven modules above. `scripts/browser-barrel.test.ts` holds the end property, both directions.
|
|
20
|
+
|
|
7
21
|
`island()` is a **factory over the route's own `hydrate`**, not a ninth primitive and not a
|
|
8
22
|
second render mode — the same rule `llm()` and `backfill()` follow. It adds no key to
|
|
9
23
|
`defineRoute`.
|
|
@@ -68,7 +82,7 @@ for the mirror-image reason, keeping `render → pwa` refused. Never `cli` (upwa
|
|
|
68
82
|
| Responses | return `RenderResult`. `@ultimat3/http` builds the `Response`. |
|
|
69
83
|
| Solid | no `solid-js` import anywhere in this package — `type-pins.tsx` satisfies its `JSX.Element` structurally, through `jsxImportSource`, and never names it. The JSX factory in `jsx.ts` builds inert nodes — it is not a Solid renderer and must never become one. The client half runs in an island chunk, which `@ultimat3/cli`'s `solid-loader.ts` compiles with `babel-preset-solid`: Solid's reactivity is a COMPILE-time contract, so nothing this package could inject would substitute for it. `router-client.ts` was the one file built on that premise ("inject primitives") and it never had a caller. |
|
|
70
84
|
| 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. |
|
|
71
|
-
| The loaders | `module-loader.ts` installs them at
|
|
85
|
+
| 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. |
|
|
72
86
|
| `<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. |
|
|
73
87
|
| 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. |
|
|
74
88
|
| Script and style CONTENT | never emitted raw. Three rules, one choice: HTML text (`escapeText`), raw text for code (`escapeRawTextContent`: `</` → `<\/`, `<!--` → `<\!--`), and the total JSON rule for a `type` ending in `json` (`escapeJsonContent`: `<`, `>`, `&`, U+2028/9 → `\uXXXX`, still valid JSON). `meta.ld` is built from route data, and it was emitted VERBATIM until `As of 2026-08` — a title could close the element. Never HTML-escape a script body: a character reference is not decoded there, so `<` corrupts the code AND leaves the hole. |
|
package/README.md
CHANGED
|
@@ -321,8 +321,32 @@ never flushed into an island that did not mount. `ISLAND_MOUNTED_ATTRIBUTE` and
|
|
|
321
321
|
from, exported for the same reason: anything waiting for hydration has to wait at least this long,
|
|
322
322
|
and a second copy of the number is a settle that shoots early and calls a healthy page broken.
|
|
323
323
|
|
|
324
|
+
## Two entry points
|
|
325
|
+
|
|
326
|
+
**Split 2026-08-22, and every claim in this section holds `As of 2026-08`.**
|
|
327
|
+
|
|
328
|
+
`@ultimat3/render` is the **client** half — the `route` primitive, the JSX factory, islands,
|
|
329
|
+
hydration, `<head>`, the route table. It bundles for the browser, and
|
|
330
|
+
`scripts/browser-barrel.test.ts` builds it that way and asserts it.
|
|
331
|
+
|
|
332
|
+
`@ultimat3/render/server` is the **build-time** half — the `.tsx`/`.scss` Bun loaders and the
|
|
333
|
+
render pipeline. It imports `sass` and `node:url`, so it never reaches a browser bundle.
|
|
334
|
+
|
|
335
|
+
The two are **disjoint**: no name is on both, and a file needing both imports both. That is the
|
|
336
|
+
price of the split and it is the point of it — a single barrel could not be bundled for the
|
|
337
|
+
browser at all, because `node:url`'s browser polyfill exports neither `fileURLToPath` nor
|
|
338
|
+
`pathToFileURL` and the build fails at link time. No `sideEffects` value fixes that (measured:
|
|
339
|
+
`false`, `[]` and an array naming only `errors.ts` all fail identically) — only not importing it
|
|
340
|
+
does.
|
|
341
|
+
|
|
342
|
+
**Importing `@ultimat3/render/server` installs the `.tsx`/`.scss` loaders**, once, as a module
|
|
343
|
+
side effect. Anything that loads an app's source — `x dev`, `x build`, `server.ts`, a test that
|
|
344
|
+
`await import()`s a `page.tsx` — reaches it before the module it loads.
|
|
345
|
+
|
|
324
346
|
## Public API
|
|
325
347
|
|
|
348
|
+
`†` marks a name on `@ultimat3/render/server`.
|
|
349
|
+
|
|
326
350
|
| Export | Owns |
|
|
327
351
|
|---|---|
|
|
328
352
|
| `defineRoute` | the `route` primitive |
|
|
@@ -330,9 +354,11 @@ and a second copy of the number is a settle that shoots early and calls a health
|
|
|
330
354
|
| `MODE_SPECS`, `assertModeShape`, `assertModeInvariants` | the mode invariant table |
|
|
331
355
|
| `registerRoute`, `describeRoutes`, `routeFor`, `routePathFromFile` | the route table |
|
|
332
356
|
| `checkSurfaceBoundary`, `assertSurfaceBoundary`, `surfaceOf` | the hard boundary |
|
|
333
|
-
| `renderStatic
|
|
334
|
-
| `createIsrController
|
|
335
|
-
| `renderSsr
|
|
357
|
+
| `renderStatic`†, `enumeratePrerender`† | build-time render, content hashing |
|
|
358
|
+
| `createIsrController`†, `invalidateAndRevalidate`† | SWR + single-flight + tag triggers |
|
|
359
|
+
| `renderSsr`†, `streamResult`† | the per-request modes |
|
|
360
|
+
| `renderToHtml`†, `renderComponent`†, `stylesFor`† | the server JSX writer and the surface's css |
|
|
361
|
+
| `installRenderLoader`†, `compileStylesheet`† | the `.tsx`/`.scss` loaders, installed on import |
|
|
336
362
|
| `emitIslandAttributes`, `hydrateRuntime` | the four hydration strategies |
|
|
337
363
|
| `ISLAND_MOUNTED_ATTRIBUTE`, `ISLAND_FAILED_ATTRIBUTE`, `IDLE_HYDRATE_TIMEOUT_MS` | what hydration looks like from outside the page |
|
|
338
364
|
| `graphFor`, `checkBudget`, `assertBudget` | two bundle graphs, per-route budgets |
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/render",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
|
8
8
|
"./src/errors.ts",
|
|
9
|
-
"./src/
|
|
9
|
+
"./src/server.ts"
|
|
10
10
|
],
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"provenance": true
|
|
19
19
|
},
|
|
20
20
|
"exports": {
|
|
21
|
-
".": "./src/index.ts"
|
|
21
|
+
".": "./src/index.ts",
|
|
22
|
+
"./server": "./src/server.ts"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"src",
|
|
@@ -35,10 +36,10 @@
|
|
|
35
36
|
"test": "bun test"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@ultimat3/cache": "
|
|
39
|
-
"@ultimat3/core": "
|
|
40
|
-
"@ultimat3/i18n": "
|
|
41
|
-
"@ultimat3/seo": "
|
|
39
|
+
"@ultimat3/cache": "9.0.0",
|
|
40
|
+
"@ultimat3/core": "9.0.0",
|
|
41
|
+
"@ultimat3/i18n": "9.0.0",
|
|
42
|
+
"@ultimat3/seo": "9.0.0",
|
|
42
43
|
"sass": "1.102.0"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// every consumer that will ever load a `.tsx` route or a `.scss` module imports this package first
|
|
7
|
-
// (an app's route file imports `defineRoute` from here before it imports anything else it owns).
|
|
8
|
-
// Any later hook — `x dev`, `x build`, `server.ts` — would each have to remember, which is four
|
|
9
|
-
// places one fact can be wrong instead of none.
|
|
10
|
-
installRenderLoader();
|
|
1
|
+
/**
|
|
2
|
+
* The CLIENT half of `@ultimat3/render` — the `route` primitive, the JSX factory, islands, and the
|
|
3
|
+
* tables describing them — kept disjoint from `@ultimat3/render/server` because everything here
|
|
4
|
+
* must bundle for a browser, which the loaders cannot (axiom 6).
|
|
5
|
+
*/
|
|
11
6
|
|
|
12
7
|
/**
|
|
13
8
|
* The route vocabulary is declared once, at tier 0 (`@ultimat3/core`), and re-exported here
|
|
@@ -21,8 +16,6 @@ export type { HydrateStrategy, OfflineStrategy, RenderMode } from '@ultimat3/cor
|
|
|
21
16
|
// never had); still named here because `@ultimat3/cli`'s budget reporter reads it beside the route
|
|
22
17
|
// table it prints against.
|
|
23
18
|
export { formatBytes, HYDRATE_STRATEGIES, OFFLINE_STRATEGIES, RENDER_MODES } from '@ultimat3/core';
|
|
24
|
-
export type { CompiledStylesheet } from './css-modules';
|
|
25
|
-
export { compileStylesheet, isCssModule, isGlobalStylesheet, scopeClasses } from './css-modules';
|
|
26
19
|
export { parseTtlMs } from './duration';
|
|
27
20
|
export type { RenderErrorCode } from './errors';
|
|
28
21
|
export {
|
|
@@ -104,15 +97,6 @@ export {
|
|
|
104
97
|
defaultIslandBudget,
|
|
105
98
|
MODE_SPECS,
|
|
106
99
|
} from './modes';
|
|
107
|
-
export type { Stylesheet } from './module-loader';
|
|
108
|
-
export {
|
|
109
|
-
clearStylesheets,
|
|
110
|
-
installRenderLoader,
|
|
111
|
-
loadStylesheet,
|
|
112
|
-
registeredStylesheets,
|
|
113
|
-
stylesFor,
|
|
114
|
-
transformTsx,
|
|
115
|
-
} from './module-loader';
|
|
116
100
|
export type {
|
|
117
101
|
CompiledPattern,
|
|
118
102
|
RegisterRouteInput,
|
|
@@ -130,48 +114,6 @@ export {
|
|
|
130
114
|
routeFor,
|
|
131
115
|
routePathFromFile,
|
|
132
116
|
} from './registry';
|
|
133
|
-
export type { RenderHtmlOptions } from './render-html';
|
|
134
|
-
export { ROOT_ELEMENT_ID, renderComponent, renderToHtml } from './render-html';
|
|
135
|
-
export type {
|
|
136
|
-
IsrController,
|
|
137
|
-
IsrControllerOptions,
|
|
138
|
-
IsrEntry,
|
|
139
|
-
IsrRenderFn,
|
|
140
|
-
IsrServeResult,
|
|
141
|
-
IsrState,
|
|
142
|
-
IsrStore,
|
|
143
|
-
MemoryIsrStoreOptions,
|
|
144
|
-
} from './render-isr';
|
|
145
|
-
export {
|
|
146
|
-
createIsrController,
|
|
147
|
-
DEFAULT_ISR_MAX_ENTRIES,
|
|
148
|
-
invalidateAndRevalidate,
|
|
149
|
-
isrKey,
|
|
150
|
-
memoryIsrStore,
|
|
151
|
-
} from './render-isr';
|
|
152
|
-
export type { SsrOptions, SsrRenderFn, SsrRenderInput } from './render-ssr';
|
|
153
|
-
export { renderSsr, ssrHeaders } from './render-ssr';
|
|
154
|
-
export type { StaticArtifact, StaticBuildOptions, StaticRenderFn } from './render-static';
|
|
155
|
-
export {
|
|
156
|
-
assertNoPerRequestState,
|
|
157
|
-
contentHash,
|
|
158
|
-
enumeratePrerender,
|
|
159
|
-
fillPath,
|
|
160
|
-
renderStatic,
|
|
161
|
-
staticHeaders,
|
|
162
|
-
staticResult,
|
|
163
|
-
} from './render-static';
|
|
164
|
-
export type { StreamHole, StreamOptions, StreamPlan } from './render-stream';
|
|
165
|
-
export {
|
|
166
|
-
collectStream,
|
|
167
|
-
DEFAULT_HOLE_TIMEOUT_MS,
|
|
168
|
-
holeId,
|
|
169
|
-
holeMarker,
|
|
170
|
-
REVEAL_SCRIPT,
|
|
171
|
-
renderStreamHtml,
|
|
172
|
-
revealChunk,
|
|
173
|
-
streamResult,
|
|
174
|
-
} from './render-stream';
|
|
175
117
|
export type {
|
|
176
118
|
LoadRequirement,
|
|
177
119
|
PrerenderFn,
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BUILD-TIME half of `@ultimat3/render` — the loaders and the route → bytes pipeline — split
|
|
3
|
+
* off because `css-modules.ts` imports `node:url`, whose browser polyfill exports neither name it
|
|
4
|
+
* asks for: one barrel carrying both halves could not be bundled for a browser at all (axiom 6).
|
|
5
|
+
* Disjoint from `@ultimat3/render` by construction, which `server.test.ts` checks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { installRenderLoader } from './module-loader';
|
|
9
|
+
|
|
10
|
+
// A side effect on import, deliberately, and the reason this barrel is named in `sideEffects`: a
|
|
11
|
+
// Bun plugin only transforms modules loaded AFTER it, so the install has to happen before any
|
|
12
|
+
// `.tsx` route or `.scss` module is imported. It lives on THIS barrel rather than on
|
|
13
|
+
// `@ultimat3/render` because the loader is build-time code — a browser bundle that reached it
|
|
14
|
+
// would carry `sass` and `node:fs`, which is the defect this split closes.
|
|
15
|
+
installRenderLoader();
|
|
16
|
+
|
|
17
|
+
// ---- scss → css, and the scoped class map every `import styles from` receives -------------------
|
|
18
|
+
export type { CompiledStylesheet } from './css-modules';
|
|
19
|
+
export { compileStylesheet, isCssModule, isGlobalStylesheet, scopeClasses } from './css-modules';
|
|
20
|
+
// ---- the two Bun loaders: `.tsx` → the server JSX factory, `.scss` → css + a class map ----------
|
|
21
|
+
export type { Stylesheet } from './module-loader';
|
|
22
|
+
export {
|
|
23
|
+
clearStylesheets,
|
|
24
|
+
installRenderLoader,
|
|
25
|
+
loadStylesheet,
|
|
26
|
+
registeredStylesheets,
|
|
27
|
+
stylesFor,
|
|
28
|
+
transformTsx,
|
|
29
|
+
} from './module-loader';
|
|
30
|
+
// ---- the render pipeline: one entry point per mode ----------------------------------------------
|
|
31
|
+
export type { RenderHtmlOptions } from './render-html';
|
|
32
|
+
export { ROOT_ELEMENT_ID, renderComponent, renderToHtml } from './render-html';
|
|
33
|
+
export type {
|
|
34
|
+
IsrController,
|
|
35
|
+
IsrControllerOptions,
|
|
36
|
+
IsrEntry,
|
|
37
|
+
IsrRenderFn,
|
|
38
|
+
IsrServeResult,
|
|
39
|
+
IsrState,
|
|
40
|
+
IsrStore,
|
|
41
|
+
MemoryIsrStoreOptions,
|
|
42
|
+
} from './render-isr';
|
|
43
|
+
export {
|
|
44
|
+
createIsrController,
|
|
45
|
+
DEFAULT_ISR_MAX_ENTRIES,
|
|
46
|
+
invalidateAndRevalidate,
|
|
47
|
+
isrKey,
|
|
48
|
+
memoryIsrStore,
|
|
49
|
+
} from './render-isr';
|
|
50
|
+
export type { SsrOptions, SsrRenderFn, SsrRenderInput } from './render-ssr';
|
|
51
|
+
export { renderSsr, ssrHeaders } from './render-ssr';
|
|
52
|
+
export type { StaticArtifact, StaticBuildOptions, StaticRenderFn } from './render-static';
|
|
53
|
+
export {
|
|
54
|
+
assertNoPerRequestState,
|
|
55
|
+
contentHash,
|
|
56
|
+
enumeratePrerender,
|
|
57
|
+
fillPath,
|
|
58
|
+
renderStatic,
|
|
59
|
+
staticHeaders,
|
|
60
|
+
staticResult,
|
|
61
|
+
} from './render-static';
|
|
62
|
+
export type { StreamHole, StreamOptions, StreamPlan } from './render-stream';
|
|
63
|
+
export {
|
|
64
|
+
collectStream,
|
|
65
|
+
DEFAULT_HOLE_TIMEOUT_MS,
|
|
66
|
+
holeId,
|
|
67
|
+
holeMarker,
|
|
68
|
+
REVEAL_SCRIPT,
|
|
69
|
+
renderStreamHtml,
|
|
70
|
+
revealChunk,
|
|
71
|
+
streamResult,
|
|
72
|
+
} from './render-stream';
|