@nifrajs/web 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +18 -0
- package/css-modules.d.ts +34 -0
- package/dist/build.d.ts +110 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +238 -0
- package/dist/build.js.map +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +204 -0
- package/dist/client.js.map +1 -0
- package/dist/deferred.d.ts +90 -0
- package/dist/deferred.d.ts.map +1 -0
- package/dist/deferred.js +310 -0
- package/dist/deferred.js.map +1 -0
- package/dist/dev-error.d.ts +17 -0
- package/dist/dev-error.d.ts.map +1 -0
- package/dist/dev-error.js +59 -0
- package/dist/dev-error.js.map +1 -0
- package/dist/dev.d.ts +25 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +83 -0
- package/dist/dev.js.map +1 -0
- package/dist/draft.d.ts +58 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +55 -0
- package/dist/draft.js.map +1 -0
- package/dist/fonts-google.d.ts +109 -0
- package/dist/fonts-google.d.ts.map +1 -0
- package/dist/fonts-google.js +291 -0
- package/dist/fonts-google.js.map +1 -0
- package/dist/fonts.d.ts +67 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +103 -0
- package/dist/fonts.js.map +1 -0
- package/dist/fs.d.ts +10 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +24 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +784 -0
- package/dist/index.js.map +1 -0
- package/dist/islands.d.ts +37 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +107 -0
- package/dist/islands.js.map +1 -0
- package/dist/isr.d.ts +154 -0
- package/dist/isr.d.ts.map +1 -0
- package/dist/isr.js +331 -0
- package/dist/isr.js.map +1 -0
- package/dist/manifest.d.ts +178 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +213 -0
- package/dist/manifest.js.map +1 -0
- package/dist/plugins/css-modules.d.ts +40 -0
- package/dist/plugins/css-modules.d.ts.map +1 -0
- package/dist/plugins/css-modules.js +0 -0
- package/dist/plugins/css-modules.js.map +1 -0
- package/dist/plugins/kit.d.ts +44 -0
- package/dist/plugins/kit.d.ts.map +1 -0
- package/dist/plugins/kit.js +105 -0
- package/dist/plugins/kit.js.map +1 -0
- package/dist/plugins/scss.d.ts +27 -0
- package/dist/plugins/scss.d.ts.map +1 -0
- package/dist/plugins/scss.js +78 -0
- package/dist/plugins/scss.js.map +1 -0
- package/dist/prerender.d.ts +77 -0
- package/dist/prerender.d.ts.map +1 -0
- package/dist/prerender.js +139 -0
- package/dist/prerender.js.map +1 -0
- package/dist/query.d.ts +62 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +184 -0
- package/dist/query.js.map +1 -0
- package/dist/router.d.ts +169 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +531 -0
- package/dist/router.js.map +1 -0
- package/dist/vite.d.ts +49 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +144 -0
- package/dist/vite.js.map +1 -0
- package/package.json +127 -0
- package/src/build.ts +375 -0
- package/src/client.ts +218 -0
- package/src/deferred.ts +339 -0
- package/src/dev-error.ts +62 -0
- package/src/dev.ts +108 -0
- package/src/draft.ts +82 -0
- package/src/fonts-google.ts +397 -0
- package/src/fonts.ts +147 -0
- package/src/fs.ts +26 -0
- package/src/index.ts +1202 -0
- package/src/islands.ts +121 -0
- package/src/isr.ts +461 -0
- package/src/manifest.ts +366 -0
- package/src/plugins/css-modules.ts +0 -0
- package/src/plugins/kit.ts +123 -0
- package/src/plugins/scss.ts +108 -0
- package/src/prerender.ts +209 -0
- package/src/query.ts +250 -0
- package/src/router.ts +676 -0
- package/src/vite.ts +212 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach history + link interception to a router. Returns a teardown function that removes the
|
|
3
|
+
* listeners. A data-fetch failure during a client navigation falls back to a full-page load, so
|
|
4
|
+
* navigation degrades gracefully rather than leaving the user stuck.
|
|
5
|
+
*/
|
|
6
|
+
export function installHistory(router, options = {}) {
|
|
7
|
+
const fallback = options.fallback ?? ((path) => location.assign(path));
|
|
8
|
+
// We manage scroll ourselves (save per history entry, restore after the navigated content
|
|
9
|
+
// renders), so disable the browser's native restoration which would otherwise fight us.
|
|
10
|
+
history.scrollRestoration = "manual";
|
|
11
|
+
let pendingScroll = null;
|
|
12
|
+
const scrollOf = (state) => {
|
|
13
|
+
const saved = state?.nifraScroll;
|
|
14
|
+
return Array.isArray(saved) ? saved : [0, 0];
|
|
15
|
+
};
|
|
16
|
+
// The fragment id from a `#hash` (percent-decoded; a malformed escape falls back to the raw value).
|
|
17
|
+
const hashId = (hash) => {
|
|
18
|
+
const raw = hash.slice(1);
|
|
19
|
+
try {
|
|
20
|
+
return decodeURIComponent(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return raw;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
// Resolve a fragment id to its element — by `id`, then a legacy `<a name>` — or null if absent.
|
|
27
|
+
const findAnchor = (id) => {
|
|
28
|
+
if (id === "")
|
|
29
|
+
return null;
|
|
30
|
+
const byId = document.getElementById(id);
|
|
31
|
+
if (byId !== null)
|
|
32
|
+
return byId;
|
|
33
|
+
try {
|
|
34
|
+
return document.querySelector(`a[name="${CSS.escape(id)}"]`);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null; // CSS/CSS.escape unavailable or an exotic id → no named-anchor match
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const transition = (run) => {
|
|
41
|
+
const doc = document;
|
|
42
|
+
if (typeof doc.startViewTransition !== "function") {
|
|
43
|
+
void run();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const vt = doc.startViewTransition(run);
|
|
47
|
+
// Rapid in-app clicks supersede an in-flight transition; the browser skips it and rejects its
|
|
48
|
+
// promises. Reasons vary by engine — InvalidStateError "Transition was aborted because of
|
|
49
|
+
// invalid state", or AbortError "Transition was skipped". The navigation still completes, so
|
|
50
|
+
// we swallow these expected aborts rather than let them surface as unhandled rejections.
|
|
51
|
+
for (const p of [vt.ready, vt.finished, vt.updateCallbackDone])
|
|
52
|
+
p.catch(() => { });
|
|
53
|
+
};
|
|
54
|
+
const go = (path, push) => {
|
|
55
|
+
const url = new URL(path, location.origin);
|
|
56
|
+
if (push) {
|
|
57
|
+
// Save the leaving entry's scroll, push a fresh entry (URL incl. any #hash), then scroll the new
|
|
58
|
+
// route: to the fragment target if one was given, else to the top.
|
|
59
|
+
history.replaceState({ ...(history.state ?? {}), nifraScroll: [scrollX, scrollY] }, "");
|
|
60
|
+
history.pushState({}, "", path);
|
|
61
|
+
pendingScroll = url.hash !== "" ? { hash: hashId(url.hash) } : { pos: [0, 0] };
|
|
62
|
+
}
|
|
63
|
+
// The data layer fetches by path+search; the #hash is client-only (never sent to the server).
|
|
64
|
+
transition(() => router.navigate(url.pathname + url.search).catch(() => fallback(path)));
|
|
65
|
+
};
|
|
66
|
+
// Resolve an event target to an in-app route path, or null (cross-origin, unknown route, or an
|
|
67
|
+
// anchor opting out via target/download/rel=external). Shared by click + hover/focus prefetch.
|
|
68
|
+
const inAppHref = (target) => {
|
|
69
|
+
const anchor = target instanceof Element ? target.closest("a") : null;
|
|
70
|
+
if (anchor === null)
|
|
71
|
+
return null;
|
|
72
|
+
if (anchor.target !== "" && anchor.target !== "_self")
|
|
73
|
+
return null;
|
|
74
|
+
if (anchor.hasAttribute("download"))
|
|
75
|
+
return null;
|
|
76
|
+
if (anchor.getAttribute("rel")?.split(/\s+/).includes("external"))
|
|
77
|
+
return null;
|
|
78
|
+
const url = new URL(anchor.href);
|
|
79
|
+
if (url.origin !== location.origin)
|
|
80
|
+
return null;
|
|
81
|
+
if (router.match(url.pathname) === null)
|
|
82
|
+
return null;
|
|
83
|
+
// A same-page fragment link (`#section`, `/here#section` — only the hash differs) → null: let the
|
|
84
|
+
// browser do its native in-page anchor jump. Intercepting it would drop the fragment from the URL
|
|
85
|
+
// and force a scroll-to-top (a fresh push restores [0,0]) — i.e. break every in-page anchor (AUDIT
|
|
86
|
+
// H2). (A same-page link with NO hash still soft-navigates, as before.)
|
|
87
|
+
if (url.pathname === location.pathname && url.search === location.search && url.hash !== "")
|
|
88
|
+
return null;
|
|
89
|
+
// Keep the hash: a cross-page link to `/docs#install` lands on that anchor once the page renders.
|
|
90
|
+
return url.pathname + url.search + url.hash;
|
|
91
|
+
};
|
|
92
|
+
const onClick = (event) => {
|
|
93
|
+
// Let the browser handle anything that isn't a plain left-click (modifiers = new tab/window).
|
|
94
|
+
if (event.defaultPrevented || event.button !== 0)
|
|
95
|
+
return;
|
|
96
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
97
|
+
return;
|
|
98
|
+
const href = inAppHref(event.target);
|
|
99
|
+
if (href === null)
|
|
100
|
+
return;
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
go(href, true);
|
|
103
|
+
};
|
|
104
|
+
// Hover/focus an in-app link → warm its chunk + data (the store dedupes the spam).
|
|
105
|
+
const onPrefetch = (event) => {
|
|
106
|
+
const href = inAppHref(event.target);
|
|
107
|
+
if (href === null)
|
|
108
|
+
return;
|
|
109
|
+
const url = new URL(href, location.origin);
|
|
110
|
+
void router.prefetch(url.pathname + url.search); // warm by path+search; the #hash isn't data
|
|
111
|
+
};
|
|
112
|
+
// Back/forward: the entry exists (no push); restore its saved scroll after it renders.
|
|
113
|
+
const onPopState = () => {
|
|
114
|
+
pendingScroll = { pos: scrollOf(history.state) };
|
|
115
|
+
transition(() => router.navigate(location.pathname + location.search).catch(() => fallback(location.pathname)));
|
|
116
|
+
};
|
|
117
|
+
// After a navigation settles (content rendered), apply the pending scroll target on the next frame:
|
|
118
|
+
// a fragment's element for a cross-page `#hash`, the saved position for back/forward, else the top.
|
|
119
|
+
// Skipped for submits (pending).
|
|
120
|
+
const restoreScroll = () => {
|
|
121
|
+
if (router.snapshot().pending || pendingScroll === null)
|
|
122
|
+
return;
|
|
123
|
+
const target = pendingScroll;
|
|
124
|
+
pendingScroll = null;
|
|
125
|
+
requestAnimationFrame(() => {
|
|
126
|
+
if ("hash" in target) {
|
|
127
|
+
const el = findAnchor(target.hash);
|
|
128
|
+
if (el !== null)
|
|
129
|
+
el.scrollIntoView();
|
|
130
|
+
else
|
|
131
|
+
window.scrollTo(0, 0); // fragment not found → top, like a fresh page load
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
window.scrollTo(target.pos[0], target.pos[1]);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const unsubscribe = router.subscribe(restoreScroll);
|
|
139
|
+
document.addEventListener("click", onClick);
|
|
140
|
+
document.addEventListener("pointerover", onPrefetch);
|
|
141
|
+
document.addEventListener("focusin", onPrefetch);
|
|
142
|
+
window.addEventListener("popstate", onPopState);
|
|
143
|
+
return () => {
|
|
144
|
+
unsubscribe();
|
|
145
|
+
document.removeEventListener("click", onClick);
|
|
146
|
+
document.removeEventListener("pointerover", onPrefetch);
|
|
147
|
+
document.removeEventListener("focusin", onPrefetch);
|
|
148
|
+
window.removeEventListener("popstate", onPopState);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Intercept submissions of same-origin `<form method="post">` whose action targets an app route:
|
|
153
|
+
* submit via the router (no full reload — POST the action, then revalidate the active loader). On
|
|
154
|
+
* failure it falls back to a native submit, so the form still works. Returns a teardown function.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
157
|
+
* Sync the document head to a route's resolved {@link Meta} on client navigation. Sets the title
|
|
158
|
+
* (when provided) and replaces the **managed** (`data-nifra`) `<meta>`/`<link>` tags — static head
|
|
159
|
+
* content (charset, hand-written tags) is never touched. SSR injects the same `data-nifra` tags, so
|
|
160
|
+
* the first navigation cleanly takes over from the server-rendered head.
|
|
161
|
+
*/
|
|
162
|
+
export function applyHead(head) {
|
|
163
|
+
if (head.title !== undefined)
|
|
164
|
+
document.title = head.title;
|
|
165
|
+
for (const el of document.head.querySelectorAll("[data-nifra]"))
|
|
166
|
+
el.remove();
|
|
167
|
+
const add = (tag, attrs) => {
|
|
168
|
+
const el = document.createElement(tag);
|
|
169
|
+
for (const [name, value] of Object.entries(attrs))
|
|
170
|
+
el.setAttribute(name, value);
|
|
171
|
+
el.setAttribute("data-nifra", "");
|
|
172
|
+
document.head.appendChild(el);
|
|
173
|
+
};
|
|
174
|
+
for (const m of head.meta ?? [])
|
|
175
|
+
add("meta", m);
|
|
176
|
+
for (const l of head.link ?? [])
|
|
177
|
+
add("link", l);
|
|
178
|
+
}
|
|
179
|
+
export function installForms(router) {
|
|
180
|
+
const onSubmit = (event) => {
|
|
181
|
+
if (event.defaultPrevented)
|
|
182
|
+
return;
|
|
183
|
+
const form = event.target;
|
|
184
|
+
if (!(form instanceof HTMLFormElement))
|
|
185
|
+
return;
|
|
186
|
+
if (form.method.toLowerCase() !== "post")
|
|
187
|
+
return; // GET forms submit natively
|
|
188
|
+
const url = new URL(form.action); // resolved (or the current document URL if unset)
|
|
189
|
+
if (url.origin !== location.origin)
|
|
190
|
+
return;
|
|
191
|
+
if (router.match(url.pathname) === null)
|
|
192
|
+
return; // not an app route → native submit
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
// `data-nifra-revalidate="false"` opts out of the post-action loader revalidation (the action's
|
|
195
|
+
// actionData drives the update); absent or any other value keeps the default revalidation.
|
|
196
|
+
const revalidate = form.dataset.nifraRevalidate !== "false";
|
|
197
|
+
router.submit(url.pathname + url.search, new FormData(form), { revalidate }).catch(() => {
|
|
198
|
+
form.submit(); // data submit failed — fall back to a full-page POST
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
document.addEventListener("submit", onSubmit);
|
|
202
|
+
return () => document.removeEventListener("submit", onSubmit);
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred loader data (`defer()`). A loader may mark slow values as deferred: the critical data
|
|
3
|
+
* renders in the shell, the deferred data streams in behind a `<Suspense>` (the adapter's `<Await>`
|
|
4
|
+
* consumes it) and resolves on the client **without a re-fetch**. This module owns the agnostic
|
|
5
|
+
* protocol — the marker, the resolved/deferred split, and the tiny client registry that streamed
|
|
6
|
+
* resolution scripts settle. The per-adapter `<Await>` lives in `@nifrajs/web-{solid,react}/client`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A loader value marked to stream in after the shell. The component consumes it with the adapter's
|
|
10
|
+
* `<Await resolve={...}>`; until the promise settles the shell shows the `<Suspense>` fallback.
|
|
11
|
+
* `id` is assigned by the server at serialization time — the streamed resolve script keys off it.
|
|
12
|
+
*/
|
|
13
|
+
export interface Deferred<T> {
|
|
14
|
+
readonly __nifra_deferred: true;
|
|
15
|
+
readonly id: number;
|
|
16
|
+
readonly promise: Promise<T>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mark a loader value as deferred — it streams in after the shell instead of blocking it. Works
|
|
20
|
+
* **anywhere** in the loader's returned data — a top-level key, or nested in objects/arrays:
|
|
21
|
+
*
|
|
22
|
+
* return { user: await api.user.get(), feed: defer(api.user.feed()),
|
|
23
|
+
* panels: [{ chart: defer(api.metrics()) }] }
|
|
24
|
+
*
|
|
25
|
+
* `LoaderData<typeof loader>` surfaces each deferred value as `Deferred<…>` (not awaited), so the
|
|
26
|
+
* component knows to `<Await>` it. The `id` is a placeholder until the server assigns the real one.
|
|
27
|
+
*/
|
|
28
|
+
export declare function defer<T>(promise: Promise<T>): Deferred<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Split a loader (or action) result into the **component-facing** data (deferred values carry their
|
|
31
|
+
* assigned `id` + promise, for `<Await>`) and the **client-serializable** data (deferred values
|
|
32
|
+
* replaced with a `{__nifra_deferred: id}` placeholder — promises don't serialize). Walks the tree
|
|
33
|
+
* **recursively** — `defer()` works at any depth, inside nested objects and arrays — assigning ids in
|
|
34
|
+
* walk order. `idOffset` continues the id space when a page splits two results (loader data **and**
|
|
35
|
+
* action data) into one shared client registry, so their ids don't collide. `deferred` lists the
|
|
36
|
+
* promises for callers that await them. Recurses plain objects + arrays only; the data is expected to
|
|
37
|
+
* be plain JSON-serializable values.
|
|
38
|
+
*/
|
|
39
|
+
export declare function prepareDeferred(data: unknown, idOffset?: number): {
|
|
40
|
+
readonly forComponent: unknown;
|
|
41
|
+
readonly forClient: unknown;
|
|
42
|
+
readonly deferred: ReadonlyArray<{
|
|
43
|
+
readonly id: number;
|
|
44
|
+
readonly promise: Promise<unknown>;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Stable, non-leaking payload streamed to the client when a deferred value rejects — never the raw
|
|
49
|
+
* error text. The real reason is logged server-side; `<Await>`'s `errorFallback` receives
|
|
50
|
+
* this code. (A future typed `DeferredError` could opt into a public, intentional message.) Shared by
|
|
51
|
+
* the NDJSON soft-nav transport and the full-document SSR path (`renderPage`'s `streamDocument`).
|
|
52
|
+
*/
|
|
53
|
+
export declare const DEFERRED_ERROR_CODE = "deferred_error";
|
|
54
|
+
/**
|
|
55
|
+
* Stream a loader result as NDJSON for a client (soft) navigation: line 1 is the critical data with
|
|
56
|
+
* `{__nifra_deferred: id}` placeholders (`forClient`), then one line per deferred as its promise
|
|
57
|
+
* settles — `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a
|
|
58
|
+
* rejection is data, not a stream error; the opaque code never leaks the raw reason). Closes when all
|
|
59
|
+
* settle. The client (`defaultFetchData`) returns the
|
|
60
|
+
* data after line 1 and settles `<Await>`'s markers as the resolution lines arrive. `JSON.stringify`
|
|
61
|
+
* escapes newlines in values, so each line is NDJSON-safe; no HTML escaping (this is a fetch body,
|
|
62
|
+
* parsed with `JSON.parse`, never injected into markup).
|
|
63
|
+
*/
|
|
64
|
+
export declare function ndjsonStream(forClient: unknown, deferred: ReadonlyArray<{
|
|
65
|
+
readonly id: number;
|
|
66
|
+
readonly promise: Promise<unknown>;
|
|
67
|
+
}>): ReadableStream<Uint8Array>;
|
|
68
|
+
/**
|
|
69
|
+
* Inline client runtime injected into the shell `<head>` when a page has deferred data: a per-id
|
|
70
|
+
* promise registry that streamed `__nifraResolve(id, value)` / `__nifraReject(id, err)` scripts settle
|
|
71
|
+
* (created lazily, so a resolve script that arrives before the client maps the placeholder still
|
|
72
|
+
* works), plus `__nifraDeferred(id)` to read the promise. A plain inline script (not the deferred
|
|
73
|
+
* module entry), so it runs before any streamed resolve script.
|
|
74
|
+
*/
|
|
75
|
+
export declare const DEFERRED_RUNTIME = "(() => {\n const reg = new Map();\n const get = (id) => {\n let e = reg.get(id);\n if (!e) {\n let r, j;\n const p = new Promise((a, b) => { r = a; j = b; });\n // <Await> reads the tagged status/reason synchronously (it never .then()s a rejected promise),\n // so attach a no-op catch to keep a rejection from surfacing as an unhandled rejection.\n p.catch(() => {});\n e = { p, r, j };\n reg.set(id, e);\n }\n return e;\n };\n window.__nifraDeferred = (id) => get(id).p;\n // Tag the promise (status/value/reason) \u2014 React's use() reads them and returns SYNCHRONOUSLY at\n // hydration, so it renders the resolved content directly into the <Suspense> boundary the server\n // streamed (no re-suspend, no fallback flash). r/j also wake awaiters (Solid uses its own _$HY).\n window.__nifraResolve = (id, v) => { const e = get(id); e.p.status = \"fulfilled\"; e.p.value = v; e.r(v); };\n window.__nifraReject = (id, x) => { const e = get(id); e.p.status = \"rejected\"; e.p.reason = x; e.j(x); };\n})();";
|
|
76
|
+
/**
|
|
77
|
+
* Source emitted into the generated client entry: maps serialized `{__nifra_deferred: id}` placeholders
|
|
78
|
+
* (at any depth — nested objects/arrays) to the registry's promises, so the component receives real
|
|
79
|
+
* promises to `<Await>`. A no-op for data without placeholders (so non-deferred pages are unchanged).
|
|
80
|
+
*/
|
|
81
|
+
export declare const MAP_DEFERRED_SOURCE = "const mapDeferred = (d) => {\n if (!d || typeof d !== \"object\") return d\n if (typeof d.__nifra_deferred === \"number\") return { __nifra_deferred: true, id: d.__nifra_deferred, promise: window.__nifraDeferred(d.__nifra_deferred) }\n if (Array.isArray(d)) return d.map(mapDeferred)\n const out = {}\n for (const k in d) out[k] = mapDeferred(d[k])\n return out\n}";
|
|
82
|
+
/**
|
|
83
|
+
* Client side of {@link ndjsonStream} (used by the router's `defaultFetchData` on a soft nav): read
|
|
84
|
+
* the NDJSON body into a data object whose deferred markers settle as resolution lines arrive.
|
|
85
|
+
* Returns after **line 1** so the router can apply the critical data + render immediately; the
|
|
86
|
+
* markers' promises settle/reject in the background. If the stream ends (or `signal` aborts) with
|
|
87
|
+
* markers unsettled, they reject — so `<Await>` never hangs.
|
|
88
|
+
*/
|
|
89
|
+
export declare function parseNdjsonData(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): Promise<unknown>;
|
|
90
|
+
//# sourceMappingURL=deferred.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred.d.ts","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAEzD;AAYD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,QAAQ,SAAI,GACX;IACD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC,CAAA;CAC9F,CA2EA;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAInD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,GACnF,cAAc,CAAC,UAAU,CAAC,CAsB5B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,2iCAqBvB,CAAA;AAEN;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,uXAO9B,CAAA;AA0CF;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CA4DlB"}
|
package/dist/deferred.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred loader data (`defer()`). A loader may mark slow values as deferred: the critical data
|
|
3
|
+
* renders in the shell, the deferred data streams in behind a `<Suspense>` (the adapter's `<Await>`
|
|
4
|
+
* consumes it) and resolves on the client **without a re-fetch**. This module owns the agnostic
|
|
5
|
+
* protocol — the marker, the resolved/deferred split, and the tiny client registry that streamed
|
|
6
|
+
* resolution scripts settle. The per-adapter `<Await>` lives in `@nifrajs/web-{solid,react}/client`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Mark a loader value as deferred — it streams in after the shell instead of blocking it. Works
|
|
10
|
+
* **anywhere** in the loader's returned data — a top-level key, or nested in objects/arrays:
|
|
11
|
+
*
|
|
12
|
+
* return { user: await api.user.get(), feed: defer(api.user.feed()),
|
|
13
|
+
* panels: [{ chart: defer(api.metrics()) }] }
|
|
14
|
+
*
|
|
15
|
+
* `LoaderData<typeof loader>` surfaces each deferred value as `Deferred<…>` (not awaited), so the
|
|
16
|
+
* component knows to `<Await>` it. The `id` is a placeholder until the server assigns the real one.
|
|
17
|
+
*/
|
|
18
|
+
export function defer(promise) {
|
|
19
|
+
return { __nifra_deferred: true, id: -1, promise };
|
|
20
|
+
}
|
|
21
|
+
/** A loader value is a deferred marker (vs. a serialized `{__nifra_deferred: <number>}` placeholder,
|
|
22
|
+
* whose field is a number, not `true`). */
|
|
23
|
+
function isDeferred(value) {
|
|
24
|
+
return (typeof value === "object" &&
|
|
25
|
+
value !== null &&
|
|
26
|
+
value.__nifra_deferred === true);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Split a loader (or action) result into the **component-facing** data (deferred values carry their
|
|
30
|
+
* assigned `id` + promise, for `<Await>`) and the **client-serializable** data (deferred values
|
|
31
|
+
* replaced with a `{__nifra_deferred: id}` placeholder — promises don't serialize). Walks the tree
|
|
32
|
+
* **recursively** — `defer()` works at any depth, inside nested objects and arrays — assigning ids in
|
|
33
|
+
* walk order. `idOffset` continues the id space when a page splits two results (loader data **and**
|
|
34
|
+
* action data) into one shared client registry, so their ids don't collide. `deferred` lists the
|
|
35
|
+
* promises for callers that await them. Recurses plain objects + arrays only; the data is expected to
|
|
36
|
+
* be plain JSON-serializable values.
|
|
37
|
+
*/
|
|
38
|
+
export function prepareDeferred(data, idOffset = 0) {
|
|
39
|
+
const deferred = [];
|
|
40
|
+
// Structural sharing: a subtree with no deferred value returns the ORIGINAL node for
|
|
41
|
+
// both sides (`fc`/`cl` identical to the input), so a deferred-free result — the common case — is
|
|
42
|
+
// returned by reference with no persisted clone, no downstream copy, and no throwaway clone
|
|
43
|
+
// allocations. Only the path to a `defer()` marker is rebuilt; unchanged siblings are shared by
|
|
44
|
+
// reference.
|
|
45
|
+
const split = (value) => {
|
|
46
|
+
if (isDeferred(value)) {
|
|
47
|
+
const id = idOffset + deferred.length;
|
|
48
|
+
deferred.push({ id, promise: value.promise });
|
|
49
|
+
return {
|
|
50
|
+
fc: { __nifra_deferred: true, id, promise: value.promise },
|
|
51
|
+
cl: { __nifra_deferred: id },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
let fc;
|
|
56
|
+
let cl;
|
|
57
|
+
for (let i = 0; i < value.length; i++) {
|
|
58
|
+
const v = value[i];
|
|
59
|
+
const r = split(v);
|
|
60
|
+
if (r === undefined) {
|
|
61
|
+
if (fc !== undefined && cl !== undefined) {
|
|
62
|
+
fc[i] = v;
|
|
63
|
+
cl[i] = v;
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (fc === undefined || cl === undefined) {
|
|
68
|
+
fc = value.slice(0, i);
|
|
69
|
+
cl = value.slice(0, i);
|
|
70
|
+
}
|
|
71
|
+
fc[i] = r.fc;
|
|
72
|
+
cl[i] = r.cl;
|
|
73
|
+
}
|
|
74
|
+
return fc === undefined || cl === undefined ? undefined : { fc, cl };
|
|
75
|
+
}
|
|
76
|
+
if (value !== null && typeof value === "object") {
|
|
77
|
+
const input = value;
|
|
78
|
+
let fc;
|
|
79
|
+
let cl;
|
|
80
|
+
for (const k in input) {
|
|
81
|
+
if (!Object.hasOwn(input, k))
|
|
82
|
+
continue;
|
|
83
|
+
const v = input[k];
|
|
84
|
+
const r = split(v);
|
|
85
|
+
if (r === undefined) {
|
|
86
|
+
if (fc !== undefined && cl !== undefined) {
|
|
87
|
+
fc[k] = v;
|
|
88
|
+
cl[k] = v;
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (fc === undefined || cl === undefined) {
|
|
93
|
+
fc = {};
|
|
94
|
+
cl = {};
|
|
95
|
+
for (const prev in input) {
|
|
96
|
+
if (!Object.hasOwn(input, prev))
|
|
97
|
+
continue;
|
|
98
|
+
if (prev === k)
|
|
99
|
+
break;
|
|
100
|
+
const prevValue = input[prev];
|
|
101
|
+
fc[prev] = prevValue;
|
|
102
|
+
cl[prev] = prevValue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
fc[k] = r.fc;
|
|
106
|
+
cl[k] = r.cl;
|
|
107
|
+
}
|
|
108
|
+
return fc === undefined || cl === undefined ? undefined : { fc, cl };
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
};
|
|
112
|
+
const result = split(data);
|
|
113
|
+
return result === undefined
|
|
114
|
+
? { forComponent: data, forClient: data, deferred }
|
|
115
|
+
: { forComponent: result.fc, forClient: result.cl, deferred };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stable, non-leaking payload streamed to the client when a deferred value rejects — never the raw
|
|
119
|
+
* error text. The real reason is logged server-side; `<Await>`'s `errorFallback` receives
|
|
120
|
+
* this code. (A future typed `DeferredError` could opt into a public, intentional message.) Shared by
|
|
121
|
+
* the NDJSON soft-nav transport and the full-document SSR path (`renderPage`'s `streamDocument`).
|
|
122
|
+
*/
|
|
123
|
+
export const DEFERRED_ERROR_CODE = "deferred_error";
|
|
124
|
+
// Shared, stateless — allocated once at module load, not per stream.
|
|
125
|
+
const NDJSON_ENCODER = new TextEncoder();
|
|
126
|
+
/**
|
|
127
|
+
* Stream a loader result as NDJSON for a client (soft) navigation: line 1 is the critical data with
|
|
128
|
+
* `{__nifra_deferred: id}` placeholders (`forClient`), then one line per deferred as its promise
|
|
129
|
+
* settles — `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a
|
|
130
|
+
* rejection is data, not a stream error; the opaque code never leaks the raw reason). Closes when all
|
|
131
|
+
* settle. The client (`defaultFetchData`) returns the
|
|
132
|
+
* data after line 1 and settles `<Await>`'s markers as the resolution lines arrive. `JSON.stringify`
|
|
133
|
+
* escapes newlines in values, so each line is NDJSON-safe; no HTML escaping (this is a fetch body,
|
|
134
|
+
* parsed with `JSON.parse`, never injected into markup).
|
|
135
|
+
*/
|
|
136
|
+
export function ndjsonStream(forClient, deferred) {
|
|
137
|
+
return new ReadableStream({
|
|
138
|
+
async start(controller) {
|
|
139
|
+
controller.enqueue(NDJSON_ENCODER.encode(`${JSON.stringify(forClient)}\n`));
|
|
140
|
+
await Promise.all(deferred.map(async (d) => {
|
|
141
|
+
try {
|
|
142
|
+
const value = await d.promise;
|
|
143
|
+
controller.enqueue(NDJSON_ENCODER.encode(`${JSON.stringify({ i: d.id, v: value })}\n`));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
// Redact: stream a stable opaque code, never the raw error text; log the real reason
|
|
147
|
+
// server-side. The client's <Await> errorFallback receives this code.
|
|
148
|
+
console.error("[nifra/web] deferred value rejected:", err);
|
|
149
|
+
controller.enqueue(NDJSON_ENCODER.encode(`${JSON.stringify({ i: d.id, e: DEFERRED_ERROR_CODE })}\n`));
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
controller.close();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Inline client runtime injected into the shell `<head>` when a page has deferred data: a per-id
|
|
158
|
+
* promise registry that streamed `__nifraResolve(id, value)` / `__nifraReject(id, err)` scripts settle
|
|
159
|
+
* (created lazily, so a resolve script that arrives before the client maps the placeholder still
|
|
160
|
+
* works), plus `__nifraDeferred(id)` to read the promise. A plain inline script (not the deferred
|
|
161
|
+
* module entry), so it runs before any streamed resolve script.
|
|
162
|
+
*/
|
|
163
|
+
export const DEFERRED_RUNTIME = `(() => {
|
|
164
|
+
const reg = new Map();
|
|
165
|
+
const get = (id) => {
|
|
166
|
+
let e = reg.get(id);
|
|
167
|
+
if (!e) {
|
|
168
|
+
let r, j;
|
|
169
|
+
const p = new Promise((a, b) => { r = a; j = b; });
|
|
170
|
+
// <Await> reads the tagged status/reason synchronously (it never .then()s a rejected promise),
|
|
171
|
+
// so attach a no-op catch to keep a rejection from surfacing as an unhandled rejection.
|
|
172
|
+
p.catch(() => {});
|
|
173
|
+
e = { p, r, j };
|
|
174
|
+
reg.set(id, e);
|
|
175
|
+
}
|
|
176
|
+
return e;
|
|
177
|
+
};
|
|
178
|
+
window.__nifraDeferred = (id) => get(id).p;
|
|
179
|
+
// Tag the promise (status/value/reason) — React's use() reads them and returns SYNCHRONOUSLY at
|
|
180
|
+
// hydration, so it renders the resolved content directly into the <Suspense> boundary the server
|
|
181
|
+
// streamed (no re-suspend, no fallback flash). r/j also wake awaiters (Solid uses its own _$HY).
|
|
182
|
+
window.__nifraResolve = (id, v) => { const e = get(id); e.p.status = "fulfilled"; e.p.value = v; e.r(v); };
|
|
183
|
+
window.__nifraReject = (id, x) => { const e = get(id); e.p.status = "rejected"; e.p.reason = x; e.j(x); };
|
|
184
|
+
})();`;
|
|
185
|
+
/**
|
|
186
|
+
* Source emitted into the generated client entry: maps serialized `{__nifra_deferred: id}` placeholders
|
|
187
|
+
* (at any depth — nested objects/arrays) to the registry's promises, so the component receives real
|
|
188
|
+
* promises to `<Await>`. A no-op for data without placeholders (so non-deferred pages are unchanged).
|
|
189
|
+
*/
|
|
190
|
+
export const MAP_DEFERRED_SOURCE = `const mapDeferred = (d) => {
|
|
191
|
+
if (!d || typeof d !== "object") return d
|
|
192
|
+
if (typeof d.__nifra_deferred === "number") return { __nifra_deferred: true, id: d.__nifra_deferred, promise: window.__nifraDeferred(d.__nifra_deferred) }
|
|
193
|
+
if (Array.isArray(d)) return d.map(mapDeferred)
|
|
194
|
+
const out = {}
|
|
195
|
+
for (const k in d) out[k] = mapDeferred(d[k])
|
|
196
|
+
return out
|
|
197
|
+
}`;
|
|
198
|
+
/** Replace `{__nifra_deferred: <number>}` placeholders (at any depth — nested objects/arrays) with
|
|
199
|
+
* `Deferred` markers whose promises are held open in `pending` (settled later by
|
|
200
|
+
* {@link parseNdjsonData}'s drain). Mirrors `prepareDeferred`'s recursive walk. */
|
|
201
|
+
function markersFromPlaceholders(data, pending) {
|
|
202
|
+
// Markers already created in THIS parse, keyed by id — so a repeated id aliases the first marker.
|
|
203
|
+
const byId = new Map();
|
|
204
|
+
const walk = (value) => {
|
|
205
|
+
const id = value?.__nifra_deferred;
|
|
206
|
+
if (value !== null && typeof value === "object" && typeof id === "number") {
|
|
207
|
+
// The NDJSON stream is a trust boundary; the server assigns unique ids, but a corrupt/crafted
|
|
208
|
+
// line-1 could repeat one. Alias a duplicate id to the first marker (same promise) instead of
|
|
209
|
+
// overwriting its resolver in `pending` — otherwise the first marker would never settle (a
|
|
210
|
+
// permanently-pending promise that leaks on abort). One resolve line then settles both.
|
|
211
|
+
const seen = byId.get(id);
|
|
212
|
+
if (seen !== undefined)
|
|
213
|
+
return seen;
|
|
214
|
+
let resolve;
|
|
215
|
+
let reject;
|
|
216
|
+
const promise = new Promise((res, rej) => {
|
|
217
|
+
resolve = res;
|
|
218
|
+
reject = rej;
|
|
219
|
+
});
|
|
220
|
+
promise.catch(() => { }); // no unhandled rejection (<Await> drives the actual handling)
|
|
221
|
+
pending.set(id, { resolve, reject });
|
|
222
|
+
const marker = { __nifra_deferred: true, id, promise };
|
|
223
|
+
byId.set(id, marker);
|
|
224
|
+
return marker;
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(value))
|
|
227
|
+
return value.map(walk);
|
|
228
|
+
if (value !== null && typeof value === "object") {
|
|
229
|
+
const out = {};
|
|
230
|
+
for (const [key, v] of Object.entries(value))
|
|
231
|
+
out[key] = walk(v);
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
};
|
|
236
|
+
return walk(data);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Client side of {@link ndjsonStream} (used by the router's `defaultFetchData` on a soft nav): read
|
|
240
|
+
* the NDJSON body into a data object whose deferred markers settle as resolution lines arrive.
|
|
241
|
+
* Returns after **line 1** so the router can apply the critical data + render immediately; the
|
|
242
|
+
* markers' promises settle/reject in the background. If the stream ends (or `signal` aborts) with
|
|
243
|
+
* markers unsettled, they reject — so `<Await>` never hangs.
|
|
244
|
+
*/
|
|
245
|
+
export async function parseNdjsonData(stream, signal) {
|
|
246
|
+
const reader = stream.getReader();
|
|
247
|
+
const dec = new TextDecoder();
|
|
248
|
+
let buffer = "";
|
|
249
|
+
// `{ once: true }` + explicit removal in the drain's `finally` so a reused long-lived signal doesn't
|
|
250
|
+
// accumulate listeners across navigations (a per-nav signal would be GC'd, but don't rely on it).
|
|
251
|
+
const onAbort = () => void reader.cancel();
|
|
252
|
+
if (signal)
|
|
253
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
254
|
+
const readLine = async () => {
|
|
255
|
+
for (;;) {
|
|
256
|
+
const nl = buffer.indexOf("\n");
|
|
257
|
+
if (nl !== -1) {
|
|
258
|
+
const line = buffer.slice(0, nl);
|
|
259
|
+
buffer = buffer.slice(nl + 1);
|
|
260
|
+
return line;
|
|
261
|
+
}
|
|
262
|
+
const { done, value } = await reader.read();
|
|
263
|
+
if (done) {
|
|
264
|
+
const rest = buffer;
|
|
265
|
+
buffer = "";
|
|
266
|
+
return rest === "" ? null : rest;
|
|
267
|
+
}
|
|
268
|
+
buffer += dec.decode(value, { stream: true });
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const pending = new Map();
|
|
272
|
+
const first = await readLine();
|
|
273
|
+
const data = markersFromPlaceholders(first === null ? null : JSON.parse(first), pending);
|
|
274
|
+
void (async () => {
|
|
275
|
+
try {
|
|
276
|
+
for (;;) {
|
|
277
|
+
const line = await readLine();
|
|
278
|
+
if (line === null)
|
|
279
|
+
break;
|
|
280
|
+
if (line === "")
|
|
281
|
+
continue;
|
|
282
|
+
const msg = JSON.parse(line);
|
|
283
|
+
const entry = pending.get(msg.i);
|
|
284
|
+
if (entry === undefined)
|
|
285
|
+
continue;
|
|
286
|
+
pending.delete(msg.i);
|
|
287
|
+
if ("e" in msg)
|
|
288
|
+
entry.reject(new Error(msg.e));
|
|
289
|
+
else
|
|
290
|
+
entry.resolve(msg.v);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// stream errored — leftovers are rejected below (unless this was an abort)
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
signal?.removeEventListener("abort", onAbort); // no-op if it already fired ({ once: true })
|
|
298
|
+
// A natural stream end with markers unsettled = truncation → reject (so `<Await>` shows its
|
|
299
|
+
// error). An `abort` = a superseded navigation → leave them pending: the markers are detached
|
|
300
|
+
// (their data was never applied), so rejecting would only risk an error flash mid-transition.
|
|
301
|
+
if (!signal?.aborted) {
|
|
302
|
+
for (const [, entry] of pending) {
|
|
303
|
+
entry.reject(new Error("[nifra/web] deferred stream ended before this value resolved"));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
})();
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=deferred.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred.js","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"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"}
|