@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.98 → 3.4.0-ultramodern.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.
Files changed (127) hide show
  1. package/dist/cjs/cli/index.js +80 -34
  2. package/dist/cjs/cli/routeSplitting.js +13 -41
  3. package/dist/cjs/cli/tanstackTypes.js +114 -200
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  6. package/dist/cjs/runtime/dataMutation.js +9 -5
  7. package/dist/cjs/runtime/hooks.js +20 -19
  8. package/dist/cjs/runtime/hydrationBoundary.js +12 -7
  9. package/dist/cjs/runtime/index.js +66 -54
  10. package/dist/cjs/runtime/lifecycle.js +21 -91
  11. package/dist/cjs/runtime/loaderBridge.js +173 -0
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +190 -110
  14. package/dist/cjs/runtime/plugin.node.js +25 -40
  15. package/dist/cjs/runtime/plugin.worker.js +9 -5
  16. package/dist/cjs/runtime/pluginCore.js +55 -0
  17. package/dist/cjs/runtime/prefetchLink.js +10 -6
  18. package/dist/cjs/runtime/register.js +56 -0
  19. package/dist/cjs/runtime/routeTree.js +30 -212
  20. package/dist/cjs/runtime/router.js +41 -0
  21. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  22. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  23. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  24. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  25. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  26. package/dist/cjs/runtime/rsc/client.js +9 -5
  27. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  28. package/dist/cjs/runtime/rsc/index.js +9 -5
  29. package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
  30. package/dist/cjs/runtime/rsc/server.js +9 -5
  31. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  32. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  33. package/dist/cjs/runtime/state.js +45 -0
  34. package/dist/cjs/runtime/types.js +13 -9
  35. package/dist/cjs/runtime/utils.js +9 -11
  36. package/dist/cjs/runtime.js +9 -5
  37. package/dist/esm/cli/index.mjs +73 -28
  38. package/dist/esm/cli/routeSplitting.mjs +1 -30
  39. package/dist/esm/cli/tanstackTypes.mjs +100 -190
  40. package/dist/esm/runtime/hooks.mjs +1 -8
  41. package/dist/esm/runtime/hydrationBoundary.mjs +3 -2
  42. package/dist/esm/runtime/index.mjs +4 -2
  43. package/dist/esm/runtime/lifecycle.mjs +1 -82
  44. package/dist/esm/runtime/loaderBridge.mjs +114 -0
  45. package/dist/esm/runtime/outlet.mjs +17 -0
  46. package/dist/esm/runtime/plugin.mjs +183 -107
  47. package/dist/esm/runtime/plugin.node.mjs +19 -38
  48. package/dist/esm/runtime/pluginCore.mjs +14 -0
  49. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  50. package/dist/esm/runtime/register.mjs +18 -0
  51. package/dist/esm/runtime/routeTree.mjs +15 -198
  52. package/dist/esm/runtime/router.mjs +2 -0
  53. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  54. package/dist/esm/runtime/state.mjs +7 -0
  55. package/dist/esm/runtime/utils.mjs +0 -6
  56. package/dist/esm-node/cli/index.mjs +73 -28
  57. package/dist/esm-node/cli/routeSplitting.mjs +1 -30
  58. package/dist/esm-node/cli/tanstackTypes.mjs +100 -190
  59. package/dist/esm-node/runtime/hooks.mjs +1 -8
  60. package/dist/esm-node/runtime/hydrationBoundary.mjs +3 -2
  61. package/dist/esm-node/runtime/index.mjs +4 -2
  62. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  63. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  64. package/dist/esm-node/runtime/outlet.mjs +18 -0
  65. package/dist/esm-node/runtime/plugin.mjs +183 -107
  66. package/dist/esm-node/runtime/plugin.node.mjs +19 -38
  67. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  68. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  69. package/dist/esm-node/runtime/register.mjs +19 -0
  70. package/dist/esm-node/runtime/routeTree.mjs +15 -198
  71. package/dist/esm-node/runtime/router.mjs +3 -0
  72. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  73. package/dist/esm-node/runtime/state.mjs +8 -0
  74. package/dist/esm-node/runtime/utils.mjs +0 -6
  75. package/dist/types/cli/index.d.ts +11 -2
  76. package/dist/types/cli/routeSplitting.d.ts +6 -15
  77. package/dist/types/cli/tanstackTypes.d.ts +21 -1
  78. package/dist/types/runtime/hooks.d.ts +8 -33
  79. package/dist/types/runtime/index.d.ts +6 -3
  80. package/dist/types/runtime/lifecycle.d.ts +7 -22
  81. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  82. package/dist/types/runtime/outlet.d.ts +2 -0
  83. package/dist/types/runtime/plugin.d.ts +1 -14
  84. package/dist/types/runtime/plugin.node.d.ts +1 -14
  85. package/dist/types/runtime/pluginCore.d.ts +21 -0
  86. package/dist/types/runtime/register.d.ts +9 -0
  87. package/dist/types/runtime/routeTree.d.ts +0 -2
  88. package/dist/types/runtime/router.d.ts +14 -0
  89. package/dist/types/runtime/state.d.ts +16 -0
  90. package/dist/types/runtime/types.d.ts +7 -53
  91. package/package.json +40 -38
  92. package/rstest.config.mts +6 -0
  93. package/src/cli/index.ts +146 -24
  94. package/src/cli/routeSplitting.ts +6 -44
  95. package/src/cli/tanstackTypes.ts +203 -208
  96. package/src/runtime/hooks.ts +10 -27
  97. package/src/runtime/hydrationBoundary.tsx +5 -5
  98. package/src/runtime/index.tsx +12 -14
  99. package/src/runtime/lifecycle.ts +16 -151
  100. package/src/runtime/loaderBridge.ts +257 -0
  101. package/src/runtime/outlet.tsx +48 -0
  102. package/src/runtime/plugin.node.tsx +28 -83
  103. package/src/runtime/plugin.tsx +350 -202
  104. package/src/runtime/pluginCore.ts +48 -0
  105. package/src/runtime/prefetchLink.tsx +1 -1
  106. package/src/runtime/register.ts +58 -0
  107. package/src/runtime/routeTree.ts +41 -365
  108. package/src/runtime/router.ts +15 -0
  109. package/src/runtime/rsc/payloadRouter.ts +45 -2
  110. package/src/runtime/ssr-shim.d.ts +1 -3
  111. package/src/runtime/state.ts +29 -0
  112. package/src/runtime/types.ts +20 -67
  113. package/src/runtime/utils.tsx +3 -7
  114. package/tests/router/cli.test.ts +373 -31
  115. package/tests/router/hooks.test.ts +26 -0
  116. package/tests/router/loaderBridge.test.ts +211 -0
  117. package/tests/router/packageSurface.test.ts +24 -0
  118. package/tests/router/prefetchLink.test.tsx +43 -7
  119. package/tests/router/register.test.ts +46 -0
  120. package/tests/router/routeTree.test.ts +176 -139
  121. package/tests/router/rsc.test.tsx +70 -0
  122. package/tests/router/tanstackTypes.test.ts +395 -7
  123. package/dist/cjs/runtime/DefaultNotFound.js +0 -47
  124. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  125. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  126. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  127. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -1,151 +1,16 @@
1
- // @effect-diagnostics strictBooleanExpressions:off
2
- import type { TInternalRuntimeContext } from '@modern-js/runtime/context';
3
- import type { RouteObject } from '@modern-js/runtime-utils/router';
4
- import type {
5
- InternalRouterRuntimeState,
6
- InternalRouterServerSnapshot,
7
- RouterFramework,
8
- RouterRouteMatchSnapshot,
9
- RouterServerPrepareResult,
10
- } from './types';
11
-
12
- export type RouterLifecyclePhase = 'ssr-prepare' | 'client-create' | 'hydrate';
13
-
14
- export type RouterLifecycleContext = {
15
- framework: RouterFramework;
16
- phase: RouterLifecyclePhase;
17
- routes: RouteObject[];
18
- runtimeContext: TInternalRuntimeContext;
19
- basename?: string;
20
- hydrationData?: unknown;
21
- router?: unknown;
22
- matches?: RouterRouteMatchSnapshot[];
23
- cleanup?: () => void | Promise<void>;
24
- serverSnapshot?: InternalRouterServerSnapshot;
25
- };
26
-
27
- type RouterSnapshotLike = Partial<InternalRouterServerSnapshot>;
28
-
29
- function toHydrationScripts(state: {
30
- hydrationScript?: string;
31
- hydrationScripts?: string[];
32
- }) {
33
- if (state.hydrationScripts?.length) {
34
- return state.hydrationScripts;
35
- }
36
-
37
- return state.hydrationScript ? [state.hydrationScript] : undefined;
38
- }
39
-
40
- function getMatchedRouteIdsFromMatches(matches?: RouterRouteMatchSnapshot[]) {
41
- const routeIds = matches
42
- ?.map(match => match.assetRouteId ?? match.routeId)
43
- .filter((routeId): routeId is string => typeof routeId === 'string');
44
-
45
- return routeIds?.length ? routeIds : undefined;
46
- }
47
-
48
- export function createRouterServerSnapshot(
49
- state: RouterSnapshotLike,
50
- ): InternalRouterServerSnapshot {
51
- const hydrationScripts = toHydrationScripts(state);
52
- const matchedRouteIds =
53
- state.matchedRouteIds ?? getMatchedRouteIdsFromMatches(state.matches);
54
-
55
- return {
56
- ...state,
57
- ...(hydrationScripts?.length
58
- ? {
59
- hydrationScript: state.hydrationScript ?? hydrationScripts[0],
60
- hydrationScripts,
61
- }
62
- : {}),
63
- ...(matchedRouteIds ? { matchedRouteIds } : {}),
64
- };
65
- }
66
-
67
- export function createRouterRuntimeState(
68
- state: InternalRouterRuntimeState,
69
- ): InternalRouterRuntimeState {
70
- const hasSnapshotState =
71
- Boolean(state.serverSnapshot) ||
72
- Boolean(state.hydrationScript) ||
73
- Boolean(state.hydrationScripts?.length) ||
74
- Boolean(state.matchedRouteIds?.length) ||
75
- Boolean(state.matches?.length);
76
- const serverSnapshot = state.serverSnapshot
77
- ? createRouterServerSnapshot({
78
- ...state.serverSnapshot,
79
- framework: state.serverSnapshot.framework ?? state.framework,
80
- basename: state.serverSnapshot.basename ?? state.basename,
81
- hydrationScript:
82
- state.serverSnapshot.hydrationScript ?? state.hydrationScript,
83
- hydrationScripts:
84
- state.serverSnapshot.hydrationScripts ?? state.hydrationScripts,
85
- matchedRouteIds:
86
- state.serverSnapshot.matchedRouteIds ?? state.matchedRouteIds,
87
- matches: state.serverSnapshot.matches ?? state.matches,
88
- })
89
- : hasSnapshotState
90
- ? createRouterServerSnapshot({
91
- framework: state.framework,
92
- basename: state.basename,
93
- hydrationScript: state.hydrationScript,
94
- hydrationScripts: state.hydrationScripts,
95
- matchedRouteIds: state.matchedRouteIds,
96
- matches: state.matches,
97
- })
98
- : undefined;
99
- const hydrationScripts = toHydrationScripts({
100
- hydrationScript: state.hydrationScript ?? serverSnapshot?.hydrationScript,
101
- hydrationScripts:
102
- state.hydrationScripts ?? serverSnapshot?.hydrationScripts,
103
- });
104
- const matchedRouteIds =
105
- state.matchedRouteIds ??
106
- serverSnapshot?.matchedRouteIds ??
107
- getMatchedRouteIdsFromMatches(state.matches);
108
-
109
- return {
110
- ...state,
111
- ...(hydrationScripts?.length
112
- ? {
113
- hydrationScript: state.hydrationScript ?? hydrationScripts[0],
114
- hydrationScripts,
115
- }
116
- : {}),
117
- ...(matchedRouteIds ? { matchedRouteIds } : {}),
118
- ...(serverSnapshot ? { serverSnapshot } : {}),
119
- };
120
- }
121
-
122
- export function applyRouterRuntimeState(
123
- runtimeContext: TInternalRuntimeContext,
124
- state: InternalRouterRuntimeState,
125
- ) {
126
- const normalized = createRouterRuntimeState(state);
127
- const mutableRuntimeContext = runtimeContext as any;
128
- mutableRuntimeContext.routerFramework = normalized.framework;
129
- mutableRuntimeContext.routerInstance = normalized.instance;
130
- mutableRuntimeContext.routerHydrationScript = normalized.hydrationScript;
131
- mutableRuntimeContext.routerMatchedRouteIds = normalized.matchedRouteIds;
132
- mutableRuntimeContext.routerRuntime = normalized;
133
- if (normalized.serverSnapshot) {
134
- mutableRuntimeContext.routerServerSnapshot = normalized.serverSnapshot;
135
- }
136
-
137
- return runtimeContext;
138
- }
139
-
140
- export function applyRouterServerPrepareResult(
141
- runtimeContext: TInternalRuntimeContext,
142
- result: RouterServerPrepareResult,
143
- ) {
144
- const state = createRouterRuntimeState({
145
- ...result.state,
146
- cleanup: result.cleanup ?? result.state.cleanup,
147
- serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
148
- });
149
- applyRouterRuntimeState(runtimeContext, state);
150
- return runtimeContext;
151
- }
1
+ /**
2
+ * The router runtime state helpers are owned by @modern-js/runtime (the same
3
+ * implementation backs the built-in react-router provider and the SSR
4
+ * pipeline). This module only re-exports them so every router provider
5
+ * writes to the exact same runtime-context extension slot.
6
+ */
7
+ export {
8
+ applyRouterRuntimeState,
9
+ applyRouterServerPrepareResult,
10
+ createRouterRuntimeState,
11
+ createRouterServerSnapshot,
12
+ getRouterRuntimeState,
13
+ getRouterServerSnapshot,
14
+ type RouterLifecycleContext,
15
+ type RouterLifecyclePhase,
16
+ } from '@modern-js/runtime/context';
@@ -0,0 +1,257 @@
1
+ // @effect-diagnostics strictBooleanExpressions:off
2
+ /**
3
+ * Runtime bridge between Modern.js data loaders and TanStack Router.
4
+ *
5
+ * The generated `src/modern-tanstack/<entry>/router.gen.ts` files import these
6
+ * helpers instead of inlining them, so loader/redirect bugfixes ship with the
7
+ * package instead of requiring every app to regenerate its files. The
8
+ * hand-written route-tree builder (`routeTree.ts`) shares the response/redirect
9
+ * helpers for the same reason.
10
+ */
11
+ import { notFound, redirect } from '@tanstack/react-router';
12
+
13
+ /** Router context shape used by the generated TanStack router types. */
14
+ export type ModernRouterContext = {
15
+ request?: Request;
16
+ requestContext?: unknown;
17
+ };
18
+
19
+ export function isResponse(value: unknown): value is Response {
20
+ const record = value as { headers?: unknown; status?: unknown } | null;
21
+ return (
22
+ record != null &&
23
+ typeof record === 'object' &&
24
+ typeof record.status === 'number' &&
25
+ typeof record.headers === 'object'
26
+ );
27
+ }
28
+
29
+ const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
30
+
31
+ export function isRedirectResponse(res: Response): boolean {
32
+ return redirectStatusCodes.has(res.status);
33
+ }
34
+
35
+ /**
36
+ * TanStack redirects are Response objects carrying the original redirect
37
+ * `options`. They must be re-thrown untouched — re-translating them through
38
+ * the Modern Response handling would lose `to`-based (internal) targets.
39
+ */
40
+ export function isTanstackRedirect(value: unknown): boolean {
41
+ return (
42
+ isResponse(value) &&
43
+ typeof (value as { options?: unknown }).options === 'object'
44
+ );
45
+ }
46
+
47
+ export function isAbsoluteUrl(value: string): boolean {
48
+ try {
49
+ void new URL(value);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Re-throw a Modern.js `Location` redirect as a TanStack redirect.
58
+ *
59
+ * Prefers `to` for internal/relative redirects so the basepath rewrite can be
60
+ * applied; absolute (external) URLs go through `href` untouched.
61
+ */
62
+ export function throwTanstackRedirect(location: string): never {
63
+ const target = location || '/';
64
+ if (isAbsoluteUrl(target)) {
65
+ throw redirect({ href: target });
66
+ }
67
+
68
+ throw redirect({ to: target });
69
+ }
70
+
71
+ /**
72
+ * React Router uses `*` for splat params, TanStack Router uses `_splat`.
73
+ * Modern loaders expect the React Router spelling.
74
+ */
75
+ export function mapSplatParamsForModernLoader(
76
+ params: Record<string, string>,
77
+ hasSplat: boolean,
78
+ ): Record<string, string> {
79
+ if (!hasSplat) {
80
+ return params;
81
+ }
82
+
83
+ const { _splat, ...rest } = params as Record<string, string> & {
84
+ _splat?: string;
85
+ };
86
+ if (typeof _splat !== 'undefined') {
87
+ return { ...rest, '*': _splat };
88
+ }
89
+ return rest;
90
+ }
91
+
92
+ /**
93
+ * Static-data factory used by the generated router files: drops empty fields
94
+ * so route static data stays minimal.
95
+ */
96
+ export function createRouteStaticData(opts: {
97
+ modernRouteId?: string;
98
+ modernRouteAction?: unknown;
99
+ modernRouteLoader?: unknown;
100
+ }): {
101
+ modernRouteId?: string;
102
+ modernRouteAction?: unknown;
103
+ modernRouteLoader?: unknown;
104
+ } {
105
+ const staticData: {
106
+ modernRouteId?: string;
107
+ modernRouteAction?: unknown;
108
+ modernRouteLoader?: unknown;
109
+ } = {};
110
+
111
+ if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
112
+ staticData.modernRouteId = opts.modernRouteId;
113
+ }
114
+
115
+ if (typeof opts.modernRouteLoader !== 'undefined') {
116
+ staticData.modernRouteLoader = opts.modernRouteLoader;
117
+ }
118
+
119
+ if (typeof opts.modernRouteAction !== 'undefined') {
120
+ staticData.modernRouteAction = opts.modernRouteAction;
121
+ }
122
+
123
+ return staticData;
124
+ }
125
+
126
+ type LoaderLikeContext = {
127
+ abortController?: AbortController;
128
+ signal?: AbortSignal;
129
+ context?: ModernRouterContext;
130
+ location?:
131
+ | string
132
+ | {
133
+ publicHref?: string;
134
+ href?: string;
135
+ url?: { href?: string };
136
+ };
137
+ params?: Record<string, string>;
138
+ };
139
+
140
+ function getLoaderSignal(ctx: LoaderLikeContext | undefined): AbortSignal {
141
+ const abortSignal = ctx?.abortController?.signal;
142
+ if (abortSignal instanceof AbortSignal) {
143
+ return abortSignal;
144
+ }
145
+ if (ctx?.signal instanceof AbortSignal) {
146
+ return ctx.signal;
147
+ }
148
+ return new AbortController().signal;
149
+ }
150
+
151
+ function getLoaderHref(ctx: LoaderLikeContext | undefined): string {
152
+ if (typeof ctx?.location === 'string') {
153
+ return ctx.location;
154
+ }
155
+
156
+ const publicHref = ctx?.location?.publicHref;
157
+ if (typeof publicHref === 'string') {
158
+ return publicHref;
159
+ }
160
+
161
+ const href = ctx?.location?.href;
162
+ if (typeof href === 'string') {
163
+ return href;
164
+ }
165
+
166
+ const urlHref = ctx?.location?.url?.href;
167
+ return typeof urlHref === 'string' ? urlHref : '';
168
+ }
169
+
170
+ function getLoaderParams(
171
+ ctx: LoaderLikeContext | undefined,
172
+ ): Record<string, string> {
173
+ return typeof ctx?.params === 'object' && ctx.params !== null
174
+ ? ctx.params
175
+ : {};
176
+ }
177
+
178
+ function handleModernLoaderResult<LoaderResult>(
179
+ result: LoaderResult,
180
+ ): LoaderResult {
181
+ if (isResponse(result)) {
182
+ if (isRedirectResponse(result)) {
183
+ const location = result.headers.get('Location') ?? '/';
184
+ throwTanstackRedirect(location);
185
+ }
186
+ if (result.status === 404) {
187
+ throw notFound();
188
+ }
189
+ }
190
+
191
+ return result;
192
+ }
193
+
194
+ function handleModernLoaderError(err: unknown): never {
195
+ if (isResponse(err)) {
196
+ if (isTanstackRedirect(err)) {
197
+ throw err;
198
+ }
199
+ if (isRedirectResponse(err)) {
200
+ const location = err.headers.get('Location') ?? '/';
201
+ throwTanstackRedirect(location);
202
+ }
203
+ if (err.status === 404) {
204
+ throw notFound();
205
+ }
206
+ }
207
+
208
+ throw err;
209
+ }
210
+
211
+ /**
212
+ * Wrap a Modern.js data loader (`page.data.ts` loader/action style) into a
213
+ * TanStack Router loader: builds a `Request` from the loader context, maps
214
+ * splat params, and translates Response redirects/404s into TanStack
215
+ * `redirect()`/`notFound()`.
216
+ */
217
+ export function modernLoaderToTanstack<TLoader extends (args: any) => any>(
218
+ opts: { hasSplat: boolean },
219
+ modernLoader: TLoader,
220
+ ): (ctx: unknown) => Promise<Awaited<ReturnType<TLoader>>> {
221
+ type LoaderResult = Awaited<ReturnType<TLoader>>;
222
+
223
+ return (rawCtx: unknown): Promise<LoaderResult> => {
224
+ const ctx = rawCtx as LoaderLikeContext | undefined;
225
+ try {
226
+ const signal = getLoaderSignal(ctx);
227
+ const baseRequest: Request | undefined =
228
+ ctx?.context?.request instanceof Request
229
+ ? ctx.context.request
230
+ : undefined;
231
+
232
+ const href = getLoaderHref(ctx);
233
+
234
+ const request =
235
+ baseRequest !== undefined
236
+ ? new Request(baseRequest, { signal })
237
+ : new Request(href, { signal });
238
+
239
+ const params = mapSplatParamsForModernLoader(
240
+ getLoaderParams(ctx),
241
+ opts.hasSplat,
242
+ );
243
+
244
+ return Promise.resolve(
245
+ (modernLoader as (args: unknown) => unknown)({
246
+ request,
247
+ params,
248
+ context: ctx?.context?.requestContext,
249
+ }) as LoaderResult,
250
+ )
251
+ .then((result: LoaderResult) => handleModernLoaderResult(result))
252
+ .catch(handleModernLoaderError);
253
+ } catch (err) {
254
+ handleModernLoaderError(err);
255
+ }
256
+ };
257
+ }
@@ -0,0 +1,48 @@
1
+ import { Outlet as TanstackOutlet } from '@tanstack/react-router';
2
+ import {
3
+ createElement,
4
+ type ElementType,
5
+ memo,
6
+ type ReactElement,
7
+ } from 'react';
8
+
9
+ type RouteComponentProps = Record<string, unknown>;
10
+ type PreloadableComponent = ElementType<RouteComponentProps> & {
11
+ load?: () => Promise<unknown>;
12
+ preload?: () => Promise<unknown>;
13
+ };
14
+ type WrappedPreloadableComponent = ((
15
+ props: RouteComponentProps,
16
+ ) => ReactElement | null) & {
17
+ load?: () => Promise<unknown>;
18
+ preload?: () => Promise<unknown>;
19
+ };
20
+
21
+ export const Outlet = memo(function ModernTanstackOutlet() {
22
+ return <TanstackOutlet />;
23
+ });
24
+
25
+ export function withModernRouteMatchContext(
26
+ component: unknown,
27
+ _routeId: string,
28
+ ): unknown {
29
+ if (component === null || component === undefined) {
30
+ return component;
31
+ }
32
+
33
+ const Component = component as ElementType<RouteComponentProps>;
34
+ const WrappedRouteComponent: WrappedPreloadableComponent = props =>
35
+ createElement(Component, props);
36
+
37
+ const preloadable = component as PreloadableComponent;
38
+ if (typeof preloadable.load === 'function') {
39
+ WrappedRouteComponent.load = preloadable.load.bind(preloadable);
40
+ }
41
+ if (typeof preloadable.preload === 'function') {
42
+ WrappedRouteComponent.preload = preloadable.preload.bind(preloadable);
43
+ } else if (typeof preloadable.load === 'function') {
44
+ WrappedRouteComponent.preload = WrappedRouteComponent.load;
45
+ }
46
+
47
+ return WrappedRouteComponent;
48
+ }
@@ -1,23 +1,17 @@
1
1
  // @effect-diagnostics asyncFunction:off newPromise:off strictBooleanExpressions:off unnecessaryArrowBlock:off
2
2
  /// <reference path="./ssr-shim.d.ts" />
3
3
 
4
- import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
5
- import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
6
4
  import {
7
5
  getGlobalEnableRsc,
8
- getGlobalLayoutApp,
9
- getGlobalRoutes,
10
6
  InternalRuntimeContext,
11
7
  type ServerPayload,
12
8
  type TInternalRuntimeContext,
13
9
  } from '@modern-js/runtime/context';
14
- import { merge } from '@modern-js/runtime-utils/merge';
15
10
  import {
16
11
  createRequestContext,
17
12
  type RequestContext,
18
13
  storage,
19
14
  } from '@modern-js/runtime-utils/node';
20
- import type { RouteObject } from '@modern-js/runtime-utils/router';
21
15
  import { time } from '@modern-js/runtime-utils/time';
22
16
  import { LOADER_REPORTER_NAME } from '@modern-js/utils/universal/constants';
23
17
  import {
@@ -26,25 +20,23 @@ import {
26
20
  createRouter,
27
21
  RouterProvider,
28
22
  } from '@tanstack/react-router';
29
- import { attachRouterServerSsrUtils } from '@tanstack/router-core/ssr/server';
30
23
  import type React from 'react';
31
24
  import { useContext } from 'react';
32
25
  import { createModernBasepathRewrite } from './basepathRewrite';
33
- import {
34
- modifyRoutes as modifyRoutesHook,
35
- onAfterCreateRouter as onAfterCreateRouterHook,
36
- onAfterHydrateRouter as onAfterHydrateRouterHook,
37
- onBeforeCreateRouter as onBeforeCreateRouterHook,
38
- onBeforeCreateRoutes as onBeforeCreateRoutesHook,
39
- onBeforeHydrateRouter as onBeforeHydrateRouterHook,
40
- type RouterExtendsHooks,
41
- } from './hooks';
26
+ import { routerProviderRegistryHooks } from './hooks';
42
27
  import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
43
28
  import {
44
29
  applyRouterServerPrepareResult,
45
30
  createRouterServerSnapshot,
31
+ getRouterRuntimeState,
46
32
  type RouterLifecycleContext,
47
33
  } from './lifecycle';
34
+ import {
35
+ getFinalRouteConfig,
36
+ getMergedRouterConfig,
37
+ type TanstackRouterPluginAPI,
38
+ type TanstackRouterRuntimePlugin,
39
+ } from './pluginCore';
48
40
  import {
49
41
  createRouteTreeFromRouteObjects,
50
42
  getModernRouteIdsFromMatches,
@@ -65,25 +57,6 @@ type ModernTanstackRouterContext = {
65
57
  requestContext: RequestContext<Record<string, unknown>>;
66
58
  };
67
59
 
68
- type TanstackRouterRuntimeConfig = {
69
- plugins?: TanstackRouterRuntimePlugin[];
70
- router?: Partial<RouterConfig>;
71
- [key: string]: unknown;
72
- };
73
-
74
- type TanstackRouterRuntimeExtends = Required<
75
- RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>
76
- > & {
77
- extendHooks: RouterExtendsHooks;
78
- };
79
-
80
- type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
81
-
82
- type TanstackRouterRuntimePlugin = Plugin<
83
- TanstackRouterPluginAPI,
84
- TInternalRuntimeContext
85
- >;
86
-
87
60
  const setTanstackRscServerPayload = (payload: ServerPayload) => {
88
61
  const storageContext = storage.useContext?.() as
89
62
  | { serverPayload?: ServerPayload }
@@ -169,7 +142,8 @@ function isReactLazyRouteComponent(
169
142
  component: unknown,
170
143
  ): component is ReactLazyRouteComponent {
171
144
  return (
172
- Boolean(component) &&
145
+ component !== null &&
146
+ component !== undefined &&
173
147
  typeof component === 'object' &&
174
148
  typeof (component as ReactLazyRouteComponent)._init === 'function' &&
175
149
  '_payload' in component
@@ -299,20 +273,6 @@ function createGetSsrHref(request: Request): string {
299
273
  return `${url.pathname}${url.search}${url.hash}`;
300
274
  }
301
275
 
302
- function stripSyntheticNotFoundRoute(routes: RouteObject[]): RouteObject[] {
303
- return routes
304
- .filter(route => !(route.path === '*' && !route.id && !route.loader))
305
- .map(route => {
306
- if (!route.children?.length) {
307
- return route;
308
- }
309
- return {
310
- ...route,
311
- children: stripSyntheticNotFoundRoute(route.children),
312
- };
313
- });
314
- }
315
-
316
276
  function collectRouterErrors(
317
277
  tanstackRouter: AnyRouter,
318
278
  ): Record<string, unknown> | undefined {
@@ -339,40 +299,34 @@ function collectRouterErrors(
339
299
  return Object.keys(errors).length > 0 ? errors : undefined;
340
300
  }
341
301
 
302
+ async function attachServerSsrUtils(router: TanstackRouterWithServerSsr) {
303
+ const { attachRouterServerSsrUtils } = await import(
304
+ '@tanstack/router-core/ssr/server'
305
+ );
306
+
307
+ attachRouterServerSsrUtils({
308
+ router,
309
+ manifest: undefined,
310
+ });
311
+ }
312
+
342
313
  export const tanstackRouterPlugin = (
343
314
  userConfig: Partial<RouterConfig> = {},
344
315
  ): TanstackRouterRuntimePlugin => {
345
316
  const plugin: TanstackRouterRuntimePlugin = {
346
317
  name: '@modern-js/plugin-router-tanstack',
347
- registryHooks: {
348
- modifyRoutes: modifyRoutesHook,
349
- onAfterCreateRouter: onAfterCreateRouterHook,
350
- onAfterHydrateRouter: onAfterHydrateRouterHook,
351
- onBeforeCreateRouter: onBeforeCreateRouterHook,
352
- onBeforeCreateRoutes: onBeforeCreateRoutesHook,
353
- onBeforeHydrateRouter: onBeforeHydrateRouterHook,
354
- },
318
+ registryHooks: routerProviderRegistryHooks,
355
319
  setup: (api: TanstackRouterPluginAPI) => {
356
320
  api.onBeforeRender(async (context, interrupt) => {
357
- const pluginConfig = api.getRuntimeConfig() as {
358
- router?: Partial<RouterConfig>;
359
- };
360
- const mergedConfig = merge(
361
- pluginConfig.router || {},
362
- userConfig,
363
- ) as RouterConfig;
321
+ const mergedConfig = getMergedRouterConfig(api, userConfig);
364
322
  const serializationAdapters = getGlobalEnableRsc()
365
323
  ? (await import('./rsc/server')).getTanstackRscSerializationAdapters()
366
324
  : undefined;
367
325
  const enableRsc = getGlobalEnableRsc();
368
326
 
369
- const { basename = '', routesConfig, createRoutes } = mergedConfig;
327
+ const { basename = '', createRoutes } = mergedConfig;
370
328
 
371
- const finalRouteConfig = {
372
- routes: getGlobalRoutes(),
373
- globalApp: getGlobalLayoutApp(),
374
- ...routesConfig,
375
- };
329
+ const finalRouteConfig = getFinalRouteConfig(mergedConfig);
376
330
 
377
331
  if (!finalRouteConfig.routes && !createRoutes) {
378
332
  return;
@@ -387,12 +341,7 @@ export const tanstackRouterPlugin = (
387
341
  routesConfig: finalRouteConfig,
388
342
  ssrMode: context.ssrContext?.mode,
389
343
  }) || [];
390
- const normalizedRouteObjects = createRoutes
391
- ? routeObjects
392
- : stripSyntheticNotFoundRoute(routeObjects);
393
- const modifiedRouteObjects = hooks.modifyRoutes.call(
394
- normalizedRouteObjects,
395
- );
344
+ const modifiedRouteObjects = hooks.modifyRoutes.call(routeObjects);
396
345
 
397
346
  if (!modifiedRouteObjects.length) {
398
347
  return;
@@ -457,10 +406,7 @@ export const tanstackRouterPlugin = (
457
406
  const serverRouter =
458
407
  tanstackRouter as unknown as TanstackRouterWithServerSsr;
459
408
 
460
- attachRouterServerSsrUtils({
461
- router: serverRouter,
462
- manifest: undefined,
463
- });
409
+ await attachServerSsrUtils(serverRouter);
464
410
 
465
411
  const end = time();
466
412
 
@@ -559,8 +505,7 @@ export const tanstackRouterPlugin = (
559
505
  const context = useContext(
560
506
  InternalRuntimeContext,
561
507
  ) as unknown as TInternalRuntimeContext;
562
- const router =
563
- context.routerInstance ?? context.routerRuntime?.instance;
508
+ const router = getRouterRuntimeState(context)?.instance;
564
509
  if (!router) {
565
510
  return App ? <App {...props} /> : null;
566
511
  }