react-on-rails-pro 16.7.0-rc.0 → 16.7.0-rc.2
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/README.md
CHANGED
|
@@ -97,4 +97,4 @@ See the [full installation guide](https://reactonrails.com/docs/pro/installation
|
|
|
97
97
|
|
|
98
98
|
## License
|
|
99
99
|
|
|
100
|
-
Commercial software. No license required for evaluation, development, testing, or CI/CD. A paid license is required for production deployments. Contact [
|
|
100
|
+
Commercial software. No license required for evaluation, development, testing, or CI/CD. A paid license is required for production deployments. Contact [ShakaCode](https://pro.reactonrails.com/contact) for licensing.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
2
|
import type { TanStackHistory, TanStackRouter, TanStackRouterOptions } from './types.ts';
|
|
3
3
|
import type { RailsContext } from 'react-on-rails/types';
|
|
4
4
|
export declare function clientHydrateTanStackApp(options: TanStackRouterOptions, props: Record<string, unknown>, railsContext: RailsContext & {
|
|
5
5
|
serverSide: false;
|
|
6
6
|
}, RouterProvider: React.ComponentType<{
|
|
7
7
|
router: TanStackRouter;
|
|
8
|
-
}>, createBrowserHistory: () => TanStackHistory): ReactElement;
|
|
8
|
+
}>, createBrowserHistory: () => TanStackHistory): React.ReactElement;
|
|
9
9
|
//# sourceMappingURL=clientHydrate.d.ts.map
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// hydration mismatch: the server rendered RouterProvider content directly,
|
|
5
|
-
// so throwing a promise here would cause Suspense to render the null fallback
|
|
6
|
-
// instead of matching the server HTML. After hydration completes (the
|
|
7
|
-
// post-mount effect sets didTriggerPostHydrationLoadRef), the gate activates
|
|
8
|
-
// normally for any subsequent re-renders.
|
|
9
|
-
if (!isHydrating && preloadPromise && !preloadSettledRef.current) {
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/only-throw-error -- Suspense boundaries intentionally suspend on thrown Promise.
|
|
11
|
-
throw preloadPromise;
|
|
12
|
-
}
|
|
13
|
-
return children;
|
|
14
|
-
}
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/* eslint-disable import/prefer-default-export, no-underscore-dangle */
|
|
3
|
+
const { createElement, useEffect, useRef } = React;
|
|
15
4
|
function extractDehydratedData(dehydratedRouter) {
|
|
16
5
|
if (!dehydratedRouter || typeof dehydratedRouter !== 'object') {
|
|
17
6
|
return undefined;
|
|
@@ -94,8 +83,10 @@ railsContext: _railsContext, RouterProvider, createBrowserHistory, }) {
|
|
|
94
83
|
const routerRef = useRef(null);
|
|
95
84
|
const didTriggerPostHydrationLoadRef = useRef(false);
|
|
96
85
|
const didSetSsrFlagRef = useRef(false);
|
|
86
|
+
const latestEffectRunIdRef = useRef(0); // 0 = no post-hydration effect run yet.
|
|
87
|
+
// Set during render-phase SSR init; awaited in runPostHydrationLoad before
|
|
88
|
+
// router.load() so post-hydration navigation waits for matched lazy chunks.
|
|
97
89
|
const routeChunkPreloadPromiseRef = useRef(null);
|
|
98
|
-
const routeChunkPreloadSettledRef = useRef(true);
|
|
99
90
|
const hydrationCallbackPromiseRef = useRef(null);
|
|
100
91
|
const didWarnPrivateInternalsRef = useRef(false);
|
|
101
92
|
const warnedMissingSsrMatchIdsRef = useRef(new Set());
|
|
@@ -154,15 +145,6 @@ railsContext: _railsContext, RouterProvider, createBrowserHistory, }) {
|
|
|
154
145
|
// throwing loadPromise (which would cause Suspense suspension).
|
|
155
146
|
const rawMatches = hydrationRouter.matchRoutes(hydrationRouter.state.location);
|
|
156
147
|
routeChunkPreloadPromiseRef.current = preloadMatchedRouteChunks(router, rawMatches);
|
|
157
|
-
if (routeChunkPreloadPromiseRef.current) {
|
|
158
|
-
routeChunkPreloadSettledRef.current = false;
|
|
159
|
-
void routeChunkPreloadPromiseRef.current.finally(() => {
|
|
160
|
-
routeChunkPreloadSettledRef.current = true;
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
routeChunkPreloadSettledRef.current = true;
|
|
165
|
-
}
|
|
166
148
|
const ssrMatches = dehydratedState?.ssrRouter?.matches;
|
|
167
149
|
const matches = ssrMatches?.length
|
|
168
150
|
? applyDehydratedMatchData(rawMatches, ssrMatches, warnMissingSsrMatch)
|
|
@@ -230,6 +212,21 @@ railsContext: _railsContext, RouterProvider, createBrowserHistory, }) {
|
|
|
230
212
|
return undefined;
|
|
231
213
|
}
|
|
232
214
|
didTriggerPostHydrationLoadRef.current = true;
|
|
215
|
+
const effectRunId = latestEffectRunIdRef.current + 1;
|
|
216
|
+
latestEffectRunIdRef.current = effectRunId;
|
|
217
|
+
// Dev-mode sanity check: router.ssr should still hold the value we wrote
|
|
218
|
+
// during render-phase init. Our window-safety argument (parent re-renders
|
|
219
|
+
// are safe because router.ssr blocks Transitioner navigation) depends on
|
|
220
|
+
// a private TanStack Router API. If that API is renamed or removed in a
|
|
221
|
+
// future version, this warning surfaces the breakage before it manifests
|
|
222
|
+
// as a hard-to-diagnose navigation race.
|
|
223
|
+
if (process.env.NODE_ENV === 'development' && didSetSsrFlagRef.current && router.ssr == null) {
|
|
224
|
+
console.warn('react-on-rails-pro/tanstack-router: router.ssr was unexpectedly ' +
|
|
225
|
+
'cleared between render-phase init and the post-hydration effect. ' +
|
|
226
|
+
'TanStack Router\'s private "ssr" API may have changed — verify ' +
|
|
227
|
+
'@tanstack/react-router is within the supported range ' +
|
|
228
|
+
'(>=1.139.0 <2.0.0).');
|
|
229
|
+
}
|
|
233
230
|
let cancelled = false;
|
|
234
231
|
const runPostHydrationLoad = async () => {
|
|
235
232
|
if (hydrationCallbackPromiseRef.current) {
|
|
@@ -244,9 +241,10 @@ railsContext: _railsContext, RouterProvider, createBrowserHistory, }) {
|
|
|
244
241
|
return;
|
|
245
242
|
}
|
|
246
243
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
244
|
+
// No final cancellation check is needed for the no-await fast path:
|
|
245
|
+
// without pending hydration or preload promises, cleanup cannot run between
|
|
246
|
+
// the checks above and this call. If unmount happens after router.load()
|
|
247
|
+
// starts, the cleanup's cancelLoad() call handles the in-flight load.
|
|
250
248
|
await router.load();
|
|
251
249
|
};
|
|
252
250
|
void runPostHydrationLoad()
|
|
@@ -256,45 +254,51 @@ railsContext: _railsContext, RouterProvider, createBrowserHistory, }) {
|
|
|
256
254
|
}
|
|
257
255
|
})
|
|
258
256
|
.finally(() => {
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
257
|
+
// Invariant: temporary router.ssr is cleared by exactly one path:
|
|
258
|
+
// 1. this finally block after the post-hydration load settles/cancels;
|
|
259
|
+
// 2. this effect cleanup's deferred continuation when unmount happens
|
|
260
|
+
// before that settle.
|
|
261
|
+
// didSetSsrFlagRef is the shared latch, preserving user-provided
|
|
262
|
+
// router.ssr from createRouter(). latestEffectRunIdRef prevents stale
|
|
263
|
+
// StrictMode passive-effect finally blocks from racing a remount. Today
|
|
264
|
+
// React runs passive cleanup/setup inside one synchronous
|
|
265
|
+
// flushPassiveEffects() call, so queued promise continuations cannot
|
|
266
|
+
// drain between the cleanup and the re-setup.
|
|
267
|
+
if (latestEffectRunIdRef.current === effectRunId && didSetSsrFlagRef.current) {
|
|
267
268
|
router.ssr = undefined;
|
|
268
269
|
didSetSsrFlagRef.current = false;
|
|
269
270
|
}
|
|
270
271
|
});
|
|
271
272
|
return () => {
|
|
272
273
|
cancelled = true;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
didTriggerPostHydrationLoadRef.current = false;
|
|
275
|
+
// Defer the unmount clear so React 18 StrictMode's passive cleanup/setup
|
|
276
|
+
// replay can increment latestEffectRunIdRef before this continuation runs.
|
|
277
|
+
void Promise.resolve().then(() => {
|
|
278
|
+
if (latestEffectRunIdRef.current === effectRunId && didSetSsrFlagRef.current) {
|
|
279
|
+
router.ssr = undefined;
|
|
280
|
+
didSetSsrFlagRef.current = false;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
277
283
|
const cancellableRouter = router;
|
|
278
284
|
if (typeof cancellableRouter.cancelLoad === 'function') {
|
|
279
285
|
cancellableRouter.cancelLoad();
|
|
280
286
|
}
|
|
281
287
|
};
|
|
282
288
|
}, [hasSsrPayload, router]);
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
isHydrating: hasSsrPayload && !didTriggerPostHydrationLoadRef.current,
|
|
297
|
-
}, createElement(RouterProvider, { router })));
|
|
289
|
+
// Render RouterProvider directly — matching the server-rendered tree
|
|
290
|
+
// (AppWrapper > RouterProvider). Any extra Suspense boundary here produces
|
|
291
|
+
// a shape mismatch during hydration and React bails to full client-side
|
|
292
|
+
// rendering. The old RouteChunkPreloadGate also suspended re-renders during
|
|
293
|
+
// chunk preload; that behavior is intentionally not replicated here because
|
|
294
|
+
// chunk-preload sequencing is enforced by runPostHydrationLoad before
|
|
295
|
+
// router.load(). A consequence is that any parent-triggered re-render
|
|
296
|
+
// landing in the window between render-phase preload init and
|
|
297
|
+
// runPostHydrationLoad completion now reaches RouterProvider unguarded —
|
|
298
|
+
// safe because router.ssr blocks Transitioner-initiated navigation across
|
|
299
|
+
// that window, and the route components themselves throw their own
|
|
300
|
+
// Suspense promises if a chunk is still loading.
|
|
301
|
+
let app = createElement(RouterProvider, { router });
|
|
298
302
|
if (options.AppWrapper) {
|
|
299
303
|
const wrapperProps = { ...incomingProps };
|
|
300
304
|
delete wrapperProps.__tanstackRouterDehydratedState;
|
|
@@ -12,6 +12,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
12
12
|
* For licensing terms, please see:
|
|
13
13
|
* https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
|
|
14
14
|
*/
|
|
15
|
+
// Side-effect import: keeps `react-on-rails-rsc/client.browser` in the webpack
|
|
16
|
+
// module graph for the client bundle so RSCWebpackPlugin (which scans every
|
|
17
|
+
// parsed module for this exact resource) can detect the client runtime and
|
|
18
|
+
// emit `react-client-manifest.json`. Without this direct import, the plugin
|
|
19
|
+
// relies on a 3-level transitive chain
|
|
20
|
+
// (`wrapServerComponentRenderer/client` → `getReactServerComponent.client`
|
|
21
|
+
// → `react-on-rails-rsc/client.browser`). Any tooling that severs that chain
|
|
22
|
+
// (tree-shaking, transpilers, NormalModuleReplacement, custom externals)
|
|
23
|
+
// silently drops the manifest and breaks RSC hydration on the renderer.
|
|
24
|
+
import 'react-on-rails-rsc/client.browser';
|
|
15
25
|
import * as React from 'react';
|
|
16
26
|
import * as ReactDOMClient from 'react-dom/client';
|
|
17
27
|
import isRenderFunction from 'react-on-rails/isRenderFunction';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-on-rails-pro",
|
|
3
|
-
"version": "16.7.0-rc.
|
|
3
|
+
"version": "16.7.0-rc.2",
|
|
4
4
|
"description": "React on Rails Pro package with React Server Components support",
|
|
5
5
|
"main": "lib/ReactOnRails.full.js",
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"./ServerComponentFetchError": "./lib/ServerComponentFetchError.js"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"react-on-rails": "16.7.0-rc.
|
|
51
|
+
"react-on-rails": "16.7.0-rc.2"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"react": ">= 16",
|