@void/react 0.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.
@@ -0,0 +1,417 @@
1
+ import { i as SharedContext, n as NavigationContext, r as RouterContext, t as ErrorsContext } from "../context-BCeFV8Jy.mjs";
2
+ import { n as setActionRouter } from "../action-BFWtavbf.mjs";
3
+ import React, { Suspense, use, useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useReducer, useRef, useTransition } from "react";
4
+ import { createPageNavigationEngine, createRouterFacade, idleNavigationState, isAbortError, isCallSiteActionError } from "void/pages-client";
5
+ import { hydrateRoot } from "react-dom/client";
6
+ //#region src/runtime/pages-client.ts
7
+ const deferredState = /* @__PURE__ */ new WeakMap();
8
+ const viewTransitionWaiters = /* @__PURE__ */ new Map();
9
+ const pendingVisitSettlers = /* @__PURE__ */ new Map();
10
+ const committedResources = /* @__PURE__ */ new Set();
11
+ let activePendingNavigationId = null;
12
+ let scheduleVisit = null;
13
+ function createDeferredResource() {
14
+ let resolve;
15
+ let reject;
16
+ const promise = new Promise((res, rej) => {
17
+ resolve = res;
18
+ reject = rej;
19
+ });
20
+ promise.catch(() => {});
21
+ deferredState.set(promise, {
22
+ status: "pending",
23
+ resolve,
24
+ reject
25
+ });
26
+ return promise;
27
+ }
28
+ function resolveDeferredResource(ref, value) {
29
+ const state = deferredState.get(ref);
30
+ if (state?.status === "pending") {
31
+ state.resolve(value);
32
+ deferredState.set(ref, { status: "resolved" });
33
+ }
34
+ return ref;
35
+ }
36
+ function rejectDeferredResource(ref, error) {
37
+ const state = deferredState.get(ref);
38
+ if (state?.status === "pending") {
39
+ state.reject(new Error(error));
40
+ deferredState.set(ref, { status: "rejected" });
41
+ }
42
+ return ref;
43
+ }
44
+ function isDeferredResourceLoading(ref) {
45
+ return deferredState.get(ref)?.status === "pending";
46
+ }
47
+ function currentUrl() {
48
+ return window.location.pathname + window.location.search + window.location.hash;
49
+ }
50
+ function normalizeUrl(value) {
51
+ if (typeof value === "string" && value.startsWith("#")) return value;
52
+ const url = value instanceof URL ? value : new URL(value, window.location.origin);
53
+ return url.origin === window.location.origin ? url.pathname + url.search + url.hash : url.href;
54
+ }
55
+ function navigationForResource(resource) {
56
+ const method = resource.method.toUpperCase();
57
+ return {
58
+ state: method !== "GET" ? "submitting" : "loading",
59
+ location: new URL(resource.fetchUrl + resource.hash, window.location.origin),
60
+ method: method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE" ? method : "GET"
61
+ };
62
+ }
63
+ function appReducer(state, action) {
64
+ switch (action.type) {
65
+ case "begin": return {
66
+ resource: state.resource,
67
+ rollbackResource: state.resource,
68
+ pendingId: action.resource.id,
69
+ routeUrl: state.routeUrl,
70
+ navigation: action.navigation
71
+ };
72
+ case "reveal":
73
+ if (state.pendingId !== action.resource.id) return state;
74
+ return {
75
+ ...state,
76
+ resource: action.resource
77
+ };
78
+ case "finish":
79
+ if (state.pendingId !== action.resource.id) return state;
80
+ return {
81
+ resource: action.resource.immediate ? state.resource : action.resource,
82
+ rollbackResource: null,
83
+ pendingId: null,
84
+ routeUrl: action.location,
85
+ navigation: idleNavigationState()
86
+ };
87
+ case "fail":
88
+ if (state.pendingId !== action.resource.id) return state;
89
+ return {
90
+ ...state,
91
+ rollbackResource: null,
92
+ pendingId: null,
93
+ routeUrl: action.location,
94
+ navigation: idleNavigationState()
95
+ };
96
+ case "abandon":
97
+ if (state.pendingId !== action.resource.id) return state;
98
+ return {
99
+ resource: state.rollbackResource || state.resource,
100
+ rollbackResource: null,
101
+ pendingId: null,
102
+ routeUrl: action.location,
103
+ navigation: idleNavigationState()
104
+ };
105
+ }
106
+ }
107
+ function createRouter(engine) {
108
+ const router = {
109
+ _hoverDelay: engine._hoverDelay,
110
+ get url() {
111
+ return new URL(currentUrl(), window.location.origin);
112
+ },
113
+ get path() {
114
+ return this.url.pathname;
115
+ },
116
+ get query() {
117
+ return this.url.searchParams;
118
+ },
119
+ visit(url, options = {}) {
120
+ if (!scheduleVisit) return Promise.reject(/* @__PURE__ */ new Error("router.visit() called before router initialized"));
121
+ return scheduleVisit(normalizeUrl(url), options);
122
+ },
123
+ refresh(options = {}) {
124
+ return router.visit(window.location.pathname + window.location.search, {
125
+ preserveState: true,
126
+ preserveScroll: true,
127
+ ...options
128
+ });
129
+ },
130
+ prefetch(url, options = {}) {
131
+ return engine.prefetch(normalizeUrl(url), options);
132
+ },
133
+ flush(url, method = "GET") {
134
+ engine.flush(normalizeUrl(url), method);
135
+ },
136
+ flushAll() {
137
+ engine.flushAll();
138
+ }
139
+ };
140
+ return router;
141
+ }
142
+ function RouteResource({ resource, onCommit }) {
143
+ const result = use(resource.shell);
144
+ useLayoutEffect(() => {
145
+ if (result) onCommit(resource, result);
146
+ }, [
147
+ onCommit,
148
+ resource,
149
+ result
150
+ ]);
151
+ if (!result) return null;
152
+ const pageData = result.pageData;
153
+ const PageComponent = result.component;
154
+ let element = React.createElement(PageComponent, pageData.props);
155
+ for (let i = result.layouts.length - 1; i >= 0; i--) {
156
+ const Layout = result.layouts[i];
157
+ element = React.createElement(Layout, null, element);
158
+ }
159
+ return React.createElement(SharedContext.Provider, { value: pageData.shared }, React.createElement(ErrorsContext.Provider, { value: pageData.errors ?? {} }, element));
160
+ }
161
+ function startViewTransition(resource, reveal, enabled) {
162
+ if (!enabled || typeof document.startViewTransition !== "function") {
163
+ reveal();
164
+ return;
165
+ }
166
+ let resolveViewTransition;
167
+ const readyForCapture = new Promise((resolve) => {
168
+ resolveViewTransition = resolve;
169
+ });
170
+ viewTransitionWaiters.set(resource.id, resolveViewTransition);
171
+ document.startViewTransition(() => {
172
+ reveal();
173
+ return readyForCapture;
174
+ });
175
+ }
176
+ function App({ engine, router, viewTransitions }) {
177
+ const initialPageData = engine.initialPageData;
178
+ const sharedStateRef = useRef(initialPageData.shared);
179
+ const [state, dispatch] = useReducer(appReducer, {
180
+ resource: useMemo(() => ({
181
+ id: 0,
182
+ url: initialPageData.url || currentUrl(),
183
+ fetchUrl: initialPageData.url || currentUrl(),
184
+ hash: window.location.hash,
185
+ method: "GET",
186
+ options: {},
187
+ immediate: false,
188
+ shell: Promise.resolve({
189
+ pageData: {
190
+ ...initialPageData,
191
+ shared: initialPageData.shared
192
+ },
193
+ component: engine.initialComponent,
194
+ layouts: engine.initialLayouts
195
+ }),
196
+ abort() {},
197
+ async commit() {},
198
+ dispose() {}
199
+ }), [engine, initialPageData]),
200
+ rollbackResource: null,
201
+ pendingId: null,
202
+ routeUrl: currentUrl(),
203
+ navigation: idleNavigationState()
204
+ });
205
+ const [isTransitionPending, startTransition] = useTransition();
206
+ const mergeShared = useCallback((result) => {
207
+ if (!result) return result;
208
+ sharedStateRef.current = {
209
+ ...sharedStateRef.current,
210
+ ...result.pageData.shared || {}
211
+ };
212
+ const shared = sharedStateRef.current;
213
+ result.pageData = {
214
+ ...result.pageData,
215
+ shared
216
+ };
217
+ return result;
218
+ }, []);
219
+ const finishNavigation = useCallback((resource, result) => {
220
+ if (committedResources.has(resource.id)) return;
221
+ committedResources.add(resource.id);
222
+ resource.commit().then(() => {
223
+ const resolveViewTransition = viewTransitionWaiters.get(resource.id);
224
+ if (resolveViewTransition) {
225
+ viewTransitionWaiters.delete(resource.id);
226
+ resolveViewTransition();
227
+ }
228
+ const visitSettler = pendingVisitSettlers.get(resource.id);
229
+ if (visitSettler) {
230
+ pendingVisitSettlers.delete(resource.id);
231
+ visitSettler.resolve(result.pageData);
232
+ }
233
+ if (activePendingNavigationId === resource.id) activePendingNavigationId = null;
234
+ dispatch({
235
+ type: "finish",
236
+ resource,
237
+ location: currentUrl()
238
+ });
239
+ }).catch((error) => {
240
+ const visitSettler = pendingVisitSettlers.get(resource.id);
241
+ if (visitSettler) {
242
+ pendingVisitSettlers.delete(resource.id);
243
+ visitSettler.reject(error);
244
+ }
245
+ });
246
+ }, []);
247
+ const schedule = useCallback((url, options = {}) => {
248
+ const prepared = engine.prepareVisit(url, options);
249
+ if (prepared.immediate) {
250
+ const navigation = navigationForResource(prepared);
251
+ activePendingNavigationId = prepared.id;
252
+ dispatch({
253
+ type: "begin",
254
+ resource: prepared,
255
+ navigation
256
+ });
257
+ return prepared.shell.then((result) => {
258
+ if (activePendingNavigationId === prepared.id) activePendingNavigationId = null;
259
+ dispatch({
260
+ type: "finish",
261
+ resource: prepared,
262
+ location: currentUrl()
263
+ });
264
+ return result?.pageData;
265
+ }, (error) => {
266
+ if (activePendingNavigationId === prepared.id) activePendingNavigationId = null;
267
+ dispatch({
268
+ type: "fail",
269
+ resource: prepared,
270
+ location: currentUrl()
271
+ });
272
+ throw error;
273
+ });
274
+ }
275
+ let resolveVisit;
276
+ let rejectVisit;
277
+ const pagePromise = new Promise((resolve, reject) => {
278
+ resolveVisit = resolve;
279
+ rejectVisit = reject;
280
+ });
281
+ const next = {
282
+ ...prepared,
283
+ shell: prepared.shell.then(mergeShared).then((result) => {
284
+ if (!result) {
285
+ const resolveViewTransition = viewTransitionWaiters.get(prepared.id);
286
+ if (resolveViewTransition) {
287
+ viewTransitionWaiters.delete(prepared.id);
288
+ resolveViewTransition();
289
+ }
290
+ pendingVisitSettlers.delete(prepared.id);
291
+ if (activePendingNavigationId === prepared.id) activePendingNavigationId = null;
292
+ dispatch({
293
+ type: "finish",
294
+ resource: prepared,
295
+ location: currentUrl()
296
+ });
297
+ resolveVisit(void 0);
298
+ } else if (prepared.options._resolveOnShell) {
299
+ pendingVisitSettlers.delete(prepared.id);
300
+ resolveVisit(result.pageData);
301
+ }
302
+ return result;
303
+ }, (error) => {
304
+ const resolveViewTransition = viewTransitionWaiters.get(prepared.id);
305
+ if (resolveViewTransition) {
306
+ viewTransitionWaiters.delete(prepared.id);
307
+ resolveViewTransition();
308
+ }
309
+ pendingVisitSettlers.delete(prepared.id);
310
+ rejectVisit(error);
311
+ if (activePendingNavigationId === prepared.id) activePendingNavigationId = null;
312
+ if (prepared.options._resolveOnShell || isAbortError(error) || isCallSiteActionError(error)) {
313
+ dispatch({
314
+ type: "abandon",
315
+ resource: prepared,
316
+ location: currentUrl()
317
+ });
318
+ return;
319
+ }
320
+ dispatch({
321
+ type: "fail",
322
+ resource: prepared,
323
+ location: currentUrl()
324
+ });
325
+ throw error;
326
+ })
327
+ };
328
+ const navigation = navigationForResource(next);
329
+ pendingVisitSettlers.set(next.id, {
330
+ resolve: resolveVisit,
331
+ reject: rejectVisit
332
+ });
333
+ activePendingNavigationId = next.id;
334
+ dispatch({
335
+ type: "begin",
336
+ resource: next,
337
+ navigation
338
+ });
339
+ const reveal = () => {
340
+ startTransition(() => {
341
+ dispatch({
342
+ type: "reveal",
343
+ resource: next
344
+ });
345
+ });
346
+ };
347
+ startViewTransition(next, reveal, options.viewTransition ?? viewTransitions);
348
+ return pagePromise;
349
+ }, [
350
+ engine,
351
+ mergeShared,
352
+ startTransition,
353
+ viewTransitions
354
+ ]);
355
+ useInsertionEffect(() => {
356
+ scheduleVisit = schedule;
357
+ return () => {
358
+ if (scheduleVisit === schedule) scheduleVisit = null;
359
+ };
360
+ }, [schedule]);
361
+ useEffect(() => {
362
+ engine.setPopStateHandler((url, options) => {
363
+ router.visit(url, options);
364
+ });
365
+ }, [engine, router]);
366
+ const navigationValue = useMemo(() => {
367
+ if (state.navigation.state !== "idle" || !isTransitionPending) return state.navigation;
368
+ return {
369
+ state: "loading",
370
+ location: null,
371
+ method: "GET"
372
+ };
373
+ }, [isTransitionPending, state.navigation]);
374
+ const routerValue = useMemo(() => createRouterFacade(router, () => state.routeUrl), [router, state.routeUrl]);
375
+ return React.createElement(RouterContext.Provider, { value: routerValue }, React.createElement(NavigationContext.Provider, { value: navigationValue }, React.createElement(Suspense, { fallback: null }, React.createElement(RouteResource, {
376
+ resource: state.resource,
377
+ onCommit: finishNavigation
378
+ }))));
379
+ }
380
+ async function startReactPages(config) {
381
+ viewTransitionWaiters.clear();
382
+ pendingVisitSettlers.clear();
383
+ committedResources.clear();
384
+ committedResources.add(0);
385
+ activePendingNavigationId = null;
386
+ const engine = await createPageNavigationEngine({
387
+ adapter: {
388
+ createDeferred: createDeferredResource,
389
+ resolveDeferred: resolveDeferredResource,
390
+ rejectDeferred: rejectDeferredResource,
391
+ isLoading: isDeferredResourceLoading
392
+ },
393
+ components: config.components,
394
+ layoutTree: config.layoutTree,
395
+ routeMeta: config.routeMeta,
396
+ matchRoute: config.matchRoute,
397
+ staticPageData: config.staticPageData,
398
+ staticPageDataFastPath: config.staticPageDataFastPath,
399
+ viewTransitions: config.viewTransitions,
400
+ prefetchHoverDelay: config.prefetchHoverDelay,
401
+ prefetchDefaultCacheFor: config.prefetchDefaultCacheFor
402
+ });
403
+ const router = createRouter(engine);
404
+ setActionRouter(router);
405
+ const appEl = document.getElementById("app");
406
+ if (!appEl) throw new Error("pages: Missing #app element for Void React pages hydration.");
407
+ hydrateRoot(appEl, React.createElement(App, {
408
+ engine,
409
+ router,
410
+ viewTransitions: config.viewTransitions
411
+ }));
412
+ appEl.setAttribute("data-hydrated", "true");
413
+ engine.start();
414
+ return { router };
415
+ }
416
+ //#endregion
417
+ export { startReactPages };
@@ -0,0 +1,27 @@
1
+ import { ComponentType } from "react";
2
+ import { PageObject } from "void/pages-protocol";
3
+
4
+ //#region src/runtime/pages-server.d.ts
5
+ type ComponentModule = {
6
+ default: ComponentType<any>;
7
+ };
8
+ interface ReactPagesServerConfig {
9
+ pageObj: PageObject;
10
+ assetTags: {
11
+ css: string;
12
+ preloads: string;
13
+ body: string;
14
+ };
15
+ components: Record<string, () => Promise<ComponentModule>>;
16
+ layoutTree: Record<string, Array<string>>;
17
+ preambleScript?: string;
18
+ }
19
+ declare function renderReactPage({
20
+ pageObj,
21
+ assetTags,
22
+ components,
23
+ layoutTree,
24
+ preambleScript
25
+ }: ReactPagesServerConfig): Promise<ReadableStream<Uint8Array>>;
26
+ //#endregion
27
+ export { ReactPagesServerConfig, renderReactPage };
@@ -0,0 +1,112 @@
1
+ import { i as SharedContext, n as NavigationContext, r as RouterContext, t as ErrorsContext } from "../context-BCeFV8Jy.mjs";
2
+ import React from "react";
3
+ import { createSsrRouter, idleNavigationState } from "void/pages-client";
4
+ import { renderToReadableStream } from "react-dom/server";
5
+ import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs } from "void/pages-head";
6
+ import { serializePageData } from "void/pages-server";
7
+ //#region src/runtime/pages-server.ts
8
+ const DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
9
+ const DEFERRED_SHELL_END_ID = "__VOID_DEFERRED_SHELL_END__";
10
+ const DEFERRED_SHELL_END_MARKER = `<template id="${DEFERRED_SHELL_END_ID}"></template>`;
11
+ function createPendingDeferred() {
12
+ return new Promise(() => {});
13
+ }
14
+ function propsForReactRender(pageObj) {
15
+ if (!pageObj.deferredKeys) return pageObj.props;
16
+ const props = { ...pageObj.props };
17
+ for (const key of Object.keys(pageObj.deferredKeys)) props[key] = createPendingDeferred();
18
+ return props;
19
+ }
20
+ function textStream(text) {
21
+ const encoder = new TextEncoder();
22
+ return new ReadableStream({ start(controller) {
23
+ controller.enqueue(encoder.encode(text));
24
+ controller.close();
25
+ } });
26
+ }
27
+ function concatStreams(streams) {
28
+ return new ReadableStream({ async start(controller) {
29
+ try {
30
+ for (const stream of streams) {
31
+ const reader = stream.getReader();
32
+ while (true) {
33
+ const { done, value } = await reader.read();
34
+ if (done) break;
35
+ controller.enqueue(value);
36
+ }
37
+ }
38
+ controller.close();
39
+ } catch (error) {
40
+ controller.error(error);
41
+ }
42
+ } });
43
+ }
44
+ function readDeferredShell(stream) {
45
+ const encoder = new TextEncoder();
46
+ return new ReadableStream({ async start(controller) {
47
+ const reader = stream.getReader();
48
+ const decoder = new TextDecoder();
49
+ let buffer = "";
50
+ const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
51
+ const flush = (text) => {
52
+ if (text) controller.enqueue(encoder.encode(text));
53
+ };
54
+ try {
55
+ while (true) {
56
+ const { done, value } = await reader.read();
57
+ if (done) {
58
+ buffer += decoder.decode();
59
+ flush(buffer);
60
+ break;
61
+ }
62
+ buffer += decoder.decode(value, { stream: true });
63
+ const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
64
+ if (markerIndex !== -1) {
65
+ flush(buffer.slice(0, markerIndex));
66
+ await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
67
+ break;
68
+ }
69
+ if (buffer.length > keepLength) {
70
+ flush(buffer.slice(0, -keepLength));
71
+ buffer = buffer.slice(-keepLength);
72
+ }
73
+ }
74
+ controller.close();
75
+ } catch (error) {
76
+ controller.error(error);
77
+ }
78
+ } });
79
+ }
80
+ async function renderReactPage({ pageObj, assetTags, components, layoutTree, preambleScript = "" }) {
81
+ const PageComponent = (await components[pageObj.component]()).default;
82
+ const layoutIds = layoutTree[pageObj.component] || [];
83
+ const layoutComponents = await Promise.all(layoutIds.map(async (id) => (await components[id]()).default));
84
+ const renderProps = propsForReactRender(pageObj);
85
+ let element = React.createElement(PageComponent, renderProps);
86
+ for (let i = layoutComponents.length - 1; i >= 0; i--) element = React.createElement(layoutComponents[i], null, element);
87
+ element = React.createElement(RouterContext.Provider, { value: createSsrRouter(pageObj.url || "/") }, React.createElement(NavigationContext.Provider, { value: idleNavigationState() }, React.createElement(React.Suspense, { fallback: null }, React.createElement(SharedContext.Provider, { value: pageObj.shared || {} }, React.createElement(ErrorsContext.Provider, { value: pageObj.errors || {} }, element)))));
88
+ const appStream = await renderToReadableStream(pageObj.deferredKeys ? React.createElement(React.Fragment, null, element, React.createElement("template", { id: DEFERRED_SHELL_END_ID })) : element, { onError(error) {
89
+ if (error === DEFERRED_SHELL_CANCEL_REASON) return;
90
+ throw error;
91
+ } });
92
+ const renderedAppStream = pageObj.deferredKeys ? readDeferredShell(appStream) : appStream;
93
+ const pageData = serializePageData(pageObj);
94
+ const headHtml = pageObj.head ? renderHeadToString(pageObj.head) : "";
95
+ const htmlAttrs = pageObj.head ? renderHtmlAttrs(pageObj.head) : "";
96
+ const bodyAttrs = pageObj.head ? renderBodyAttrs(pageObj.head) : "";
97
+ return concatStreams([
98
+ textStream(`<!doctype html>
99
+ <html${htmlAttrs}>
100
+ <head>${headHtml}${preambleScript}${assetTags.css}${assetTags.preloads}</head>
101
+ <body${bodyAttrs}>
102
+ <script id="__VOID_PAGE_DATA__" type="application/json">${pageData}<\/script>
103
+ <div id="app">`),
104
+ renderedAppStream,
105
+ textStream(`</div>
106
+ ${assetTags.body}
107
+ </body>
108
+ </html>`)
109
+ ]);
110
+ }
111
+ //#endregion
112
+ export { renderReactPage };
@@ -0,0 +1,2 @@
1
+ import { CacheEntry, CacheForInput, CacheForResult, PrefetchCache, parseCacheFor } from "void/pages-prefetch";
2
+ export { type CacheEntry, type CacheForInput, type CacheForResult, PrefetchCache, parseCacheFor };
@@ -0,0 +1,2 @@
1
+ import { PrefetchCache, parseCacheFor } from "void/pages-prefetch";
2
+ export { PrefetchCache, parseCacheFor };