@bleedingdev/modern-js-plugin-tanstack 3.2.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/cli/index.js +268 -0
  3. package/dist/cjs/cli/tanstackTypes.js +388 -0
  4. package/dist/cjs/cli.js +65 -0
  5. package/dist/cjs/runtime/DefaultNotFound.js +47 -0
  6. package/dist/cjs/runtime/basepathRewrite.js +62 -0
  7. package/dist/cjs/runtime/dataMutation.js +345 -0
  8. package/dist/cjs/runtime/hooks.js +57 -0
  9. package/dist/cjs/runtime/index.js +114 -0
  10. package/dist/cjs/runtime/lifecycle.js +125 -0
  11. package/dist/cjs/runtime/plugin.js +250 -0
  12. package/dist/cjs/runtime/plugin.node.js +304 -0
  13. package/dist/cjs/runtime/prefetchLink.js +55 -0
  14. package/dist/cjs/runtime/routeTree.js +492 -0
  15. package/dist/cjs/runtime/rsc/ClientSlot.js +53 -0
  16. package/dist/cjs/runtime/rsc/CompositeComponent.js +75 -0
  17. package/dist/cjs/runtime/rsc/ReplayableStream.js +141 -0
  18. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +65 -0
  19. package/dist/cjs/runtime/rsc/SlotContext.js +54 -0
  20. package/dist/cjs/runtime/rsc/client.js +93 -0
  21. package/dist/cjs/runtime/rsc/createRscProxy.js +141 -0
  22. package/dist/cjs/runtime/rsc/index.js +42 -0
  23. package/dist/cjs/runtime/rsc/payloadRouter.js +211 -0
  24. package/dist/cjs/runtime/rsc/server.js +246 -0
  25. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +65 -0
  26. package/dist/cjs/runtime/rsc/symbols.js +72 -0
  27. package/dist/cjs/runtime/types.js +18 -0
  28. package/dist/cjs/runtime/utils.js +142 -0
  29. package/dist/cjs/runtime.js +58 -0
  30. package/dist/esm/cli/index.mjs +201 -0
  31. package/dist/esm/cli/tanstackTypes.mjs +341 -0
  32. package/dist/esm/cli.mjs +2 -0
  33. package/dist/esm/rslib-runtime.mjs +18 -0
  34. package/dist/esm/runtime/DefaultNotFound.mjs +13 -0
  35. package/dist/esm/runtime/basepathRewrite.mjs +28 -0
  36. package/dist/esm/runtime/dataMutation.mjs +305 -0
  37. package/dist/esm/runtime/hooks.mjs +8 -0
  38. package/dist/esm/runtime/index.mjs +6 -0
  39. package/dist/esm/runtime/lifecycle.mjs +82 -0
  40. package/dist/esm/runtime/plugin.mjs +214 -0
  41. package/dist/esm/runtime/plugin.node.mjs +268 -0
  42. package/dist/esm/runtime/prefetchLink.mjs +18 -0
  43. package/dist/esm/runtime/routeTree.mjs +452 -0
  44. package/dist/esm/runtime/rsc/ClientSlot.mjs +19 -0
  45. package/dist/esm/runtime/rsc/CompositeComponent.mjs +41 -0
  46. package/dist/esm/runtime/rsc/ReplayableStream.mjs +104 -0
  47. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +31 -0
  48. package/dist/esm/runtime/rsc/SlotContext.mjs +17 -0
  49. package/dist/esm/runtime/rsc/client.mjs +53 -0
  50. package/dist/esm/runtime/rsc/createRscProxy.mjs +107 -0
  51. package/dist/esm/runtime/rsc/index.mjs +1 -0
  52. package/dist/esm/runtime/rsc/payloadRouter.mjs +162 -0
  53. package/dist/esm/runtime/rsc/server.mjs +200 -0
  54. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +31 -0
  55. package/dist/esm/runtime/rsc/symbols.mjs +17 -0
  56. package/dist/esm/runtime/types.mjs +0 -0
  57. package/dist/esm/runtime/utils.mjs +89 -0
  58. package/dist/esm/runtime.mjs +1 -0
  59. package/dist/esm-node/cli/index.mjs +205 -0
  60. package/dist/esm-node/cli/tanstackTypes.mjs +342 -0
  61. package/dist/esm-node/cli.mjs +3 -0
  62. package/dist/esm-node/rslib-runtime.mjs +19 -0
  63. package/dist/esm-node/runtime/DefaultNotFound.mjs +14 -0
  64. package/dist/esm-node/runtime/basepathRewrite.mjs +29 -0
  65. package/dist/esm-node/runtime/dataMutation.mjs +306 -0
  66. package/dist/esm-node/runtime/hooks.mjs +9 -0
  67. package/dist/esm-node/runtime/index.mjs +7 -0
  68. package/dist/esm-node/runtime/lifecycle.mjs +83 -0
  69. package/dist/esm-node/runtime/plugin.mjs +215 -0
  70. package/dist/esm-node/runtime/plugin.node.mjs +269 -0
  71. package/dist/esm-node/runtime/prefetchLink.mjs +19 -0
  72. package/dist/esm-node/runtime/routeTree.mjs +453 -0
  73. package/dist/esm-node/runtime/rsc/ClientSlot.mjs +20 -0
  74. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +42 -0
  75. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +105 -0
  76. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +32 -0
  77. package/dist/esm-node/runtime/rsc/SlotContext.mjs +18 -0
  78. package/dist/esm-node/runtime/rsc/client.mjs +54 -0
  79. package/dist/esm-node/runtime/rsc/createRscProxy.mjs +108 -0
  80. package/dist/esm-node/runtime/rsc/index.mjs +2 -0
  81. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +163 -0
  82. package/dist/esm-node/runtime/rsc/server.mjs +201 -0
  83. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +32 -0
  84. package/dist/esm-node/runtime/rsc/symbols.mjs +18 -0
  85. package/dist/esm-node/runtime/types.mjs +1 -0
  86. package/dist/esm-node/runtime/utils.mjs +90 -0
  87. package/dist/esm-node/runtime.mjs +2 -0
  88. package/dist/types/cli/index.d.ts +20 -0
  89. package/dist/types/cli/tanstackTypes.d.ts +11 -0
  90. package/dist/types/cli.d.ts +2 -0
  91. package/dist/types/runtime/DefaultNotFound.d.ts +2 -0
  92. package/dist/types/runtime/basepathRewrite.d.ts +8 -0
  93. package/dist/types/runtime/dataMutation.d.ts +29 -0
  94. package/dist/types/runtime/hooks.d.ts +18 -0
  95. package/dist/types/runtime/index.d.ts +9 -0
  96. package/dist/types/runtime/lifecycle.d.ts +22 -0
  97. package/dist/types/runtime/plugin.d.ts +17 -0
  98. package/dist/types/runtime/plugin.node.d.ts +17 -0
  99. package/dist/types/runtime/prefetchLink.d.ts +11 -0
  100. package/dist/types/runtime/routeTree.d.ts +11 -0
  101. package/dist/types/runtime/rsc/ClientSlot.d.ts +5 -0
  102. package/dist/types/runtime/rsc/CompositeComponent.d.ts +3 -0
  103. package/dist/types/runtime/rsc/ReplayableStream.d.ts +24 -0
  104. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +5 -0
  105. package/dist/types/runtime/rsc/SlotContext.d.ts +11 -0
  106. package/dist/types/runtime/rsc/client.d.ts +11 -0
  107. package/dist/types/runtime/rsc/createRscProxy.d.ts +7 -0
  108. package/dist/types/runtime/rsc/index.d.ts +2 -0
  109. package/dist/types/runtime/rsc/payloadRouter.d.ts +24 -0
  110. package/dist/types/runtime/rsc/server.d.ts +14 -0
  111. package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
  112. package/dist/types/runtime/rsc/symbols.d.ts +46 -0
  113. package/dist/types/runtime/types.d.ts +68 -0
  114. package/dist/types/runtime/utils.d.ts +36 -0
  115. package/dist/types/runtime.d.ts +1 -0
  116. package/dist/types-direct/cli/index.d.ts +20 -0
  117. package/dist/types-direct/cli/tanstackTypes.d.ts +11 -0
  118. package/dist/types-direct/cli.d.ts +2 -0
  119. package/dist/types-direct/runtime/DefaultNotFound.d.ts +2 -0
  120. package/dist/types-direct/runtime/basepathRewrite.d.ts +8 -0
  121. package/dist/types-direct/runtime/dataMutation.d.ts +29 -0
  122. package/dist/types-direct/runtime/hooks.d.ts +18 -0
  123. package/dist/types-direct/runtime/index.d.ts +9 -0
  124. package/dist/types-direct/runtime/lifecycle.d.ts +22 -0
  125. package/dist/types-direct/runtime/plugin.d.ts +17 -0
  126. package/dist/types-direct/runtime/plugin.node.d.ts +17 -0
  127. package/dist/types-direct/runtime/prefetchLink.d.ts +11 -0
  128. package/dist/types-direct/runtime/routeTree.d.ts +11 -0
  129. package/dist/types-direct/runtime/rsc/ClientSlot.d.ts +5 -0
  130. package/dist/types-direct/runtime/rsc/CompositeComponent.d.ts +3 -0
  131. package/dist/types-direct/runtime/rsc/ReplayableStream.d.ts +24 -0
  132. package/dist/types-direct/runtime/rsc/RscNodeRenderer.d.ts +5 -0
  133. package/dist/types-direct/runtime/rsc/SlotContext.d.ts +11 -0
  134. package/dist/types-direct/runtime/rsc/client.d.ts +11 -0
  135. package/dist/types-direct/runtime/rsc/createRscProxy.d.ts +7 -0
  136. package/dist/types-direct/runtime/rsc/index.d.ts +2 -0
  137. package/dist/types-direct/runtime/rsc/payloadRouter.d.ts +24 -0
  138. package/dist/types-direct/runtime/rsc/server.d.ts +14 -0
  139. package/dist/types-direct/runtime/rsc/slotUsageSanitizer.d.ts +2 -0
  140. package/dist/types-direct/runtime/rsc/symbols.d.ts +46 -0
  141. package/dist/types-direct/runtime/types.d.ts +68 -0
  142. package/dist/types-direct/runtime/utils.d.ts +36 -0
  143. package/dist/types-direct/runtime.d.ts +1 -0
  144. package/package.json +126 -0
  145. package/rslib.config.mts +4 -0
  146. package/rstest.config.mts +43 -0
  147. package/src/cli/index.ts +388 -0
  148. package/src/cli/tanstackTypes.ts +503 -0
  149. package/src/cli.ts +2 -0
  150. package/src/runtime/DefaultNotFound.tsx +15 -0
  151. package/src/runtime/basepathRewrite.ts +59 -0
  152. package/src/runtime/dataMutation.tsx +517 -0
  153. package/src/runtime/hooks.ts +34 -0
  154. package/src/runtime/index.tsx +30 -0
  155. package/src/runtime/lifecycle.ts +150 -0
  156. package/src/runtime/plugin.node.tsx +534 -0
  157. package/src/runtime/plugin.tsx +395 -0
  158. package/src/runtime/prefetchLink.tsx +87 -0
  159. package/src/runtime/routeTree.ts +942 -0
  160. package/src/runtime/rsc/ClientSlot.tsx +25 -0
  161. package/src/runtime/rsc/CompositeComponent.tsx +65 -0
  162. package/src/runtime/rsc/ReplayableStream.ts +155 -0
  163. package/src/runtime/rsc/RscNodeRenderer.tsx +45 -0
  164. package/src/runtime/rsc/SlotContext.tsx +31 -0
  165. package/src/runtime/rsc/client.tsx +90 -0
  166. package/src/runtime/rsc/createRscProxy.tsx +189 -0
  167. package/src/runtime/rsc/index.ts +10 -0
  168. package/src/runtime/rsc/payloadRouter.ts +318 -0
  169. package/src/runtime/rsc/server.tsx +303 -0
  170. package/src/runtime/rsc/slotUsageSanitizer.ts +76 -0
  171. package/src/runtime/rsc/symbols.ts +106 -0
  172. package/src/runtime/ssr-shim.d.ts +12 -0
  173. package/src/runtime/types.ts +83 -0
  174. package/src/runtime/utils.tsx +161 -0
  175. package/src/runtime.ts +1 -0
  176. package/tests/router/cli.test.ts +386 -0
  177. package/tests/router/dataMutation.test.tsx +396 -0
  178. package/tests/router/prefetchLink.test.tsx +43 -0
  179. package/tests/router/routeTree.test.ts +502 -0
  180. package/tests/router/rsc.test.tsx +256 -0
  181. package/tests/router/tanstackTypes.test.ts +62 -0
  182. package/tsconfig.json +12 -0
  183. package/tsconfig.tsgo.json +6 -0
@@ -0,0 +1,150 @@
1
+ import type { TInternalRuntimeContext } from '@modern-js/runtime/context';
2
+ import type { RouteObject } from '@modern-js/runtime-utils/router';
3
+ import type {
4
+ InternalRouterRuntimeState,
5
+ InternalRouterServerSnapshot,
6
+ RouterFramework,
7
+ RouterRouteMatchSnapshot,
8
+ RouterServerPrepareResult,
9
+ } from './types';
10
+
11
+ export type RouterLifecyclePhase = 'ssr-prepare' | 'client-create' | 'hydrate';
12
+
13
+ export type RouterLifecycleContext = {
14
+ framework: RouterFramework;
15
+ phase: RouterLifecyclePhase;
16
+ routes: RouteObject[];
17
+ runtimeContext: TInternalRuntimeContext;
18
+ basename?: string;
19
+ hydrationData?: unknown;
20
+ router?: unknown;
21
+ matches?: RouterRouteMatchSnapshot[];
22
+ cleanup?: () => void | Promise<void>;
23
+ serverSnapshot?: InternalRouterServerSnapshot;
24
+ };
25
+
26
+ type RouterSnapshotLike = Partial<InternalRouterServerSnapshot>;
27
+
28
+ function toHydrationScripts(state: {
29
+ hydrationScript?: string;
30
+ hydrationScripts?: string[];
31
+ }) {
32
+ if (state.hydrationScripts?.length) {
33
+ return state.hydrationScripts;
34
+ }
35
+
36
+ return state.hydrationScript ? [state.hydrationScript] : undefined;
37
+ }
38
+
39
+ function getMatchedRouteIdsFromMatches(matches?: RouterRouteMatchSnapshot[]) {
40
+ const routeIds = matches
41
+ ?.map(match => match.assetRouteId ?? match.routeId)
42
+ .filter((routeId): routeId is string => typeof routeId === 'string');
43
+
44
+ return routeIds?.length ? routeIds : undefined;
45
+ }
46
+
47
+ export function createRouterServerSnapshot(
48
+ state: RouterSnapshotLike,
49
+ ): InternalRouterServerSnapshot {
50
+ const hydrationScripts = toHydrationScripts(state);
51
+ const matchedRouteIds =
52
+ state.matchedRouteIds ?? getMatchedRouteIdsFromMatches(state.matches);
53
+
54
+ return {
55
+ ...state,
56
+ ...(hydrationScripts?.length
57
+ ? {
58
+ hydrationScript: state.hydrationScript ?? hydrationScripts[0],
59
+ hydrationScripts,
60
+ }
61
+ : {}),
62
+ ...(matchedRouteIds ? { matchedRouteIds } : {}),
63
+ };
64
+ }
65
+
66
+ export function createRouterRuntimeState(
67
+ state: InternalRouterRuntimeState,
68
+ ): InternalRouterRuntimeState {
69
+ const hasSnapshotState =
70
+ Boolean(state.serverSnapshot) ||
71
+ Boolean(state.hydrationScript) ||
72
+ Boolean(state.hydrationScripts?.length) ||
73
+ Boolean(state.matchedRouteIds?.length) ||
74
+ Boolean(state.matches?.length);
75
+ const serverSnapshot = state.serverSnapshot
76
+ ? createRouterServerSnapshot({
77
+ ...state.serverSnapshot,
78
+ framework: state.serverSnapshot.framework ?? state.framework,
79
+ basename: state.serverSnapshot.basename ?? state.basename,
80
+ hydrationScript:
81
+ state.serverSnapshot.hydrationScript ?? state.hydrationScript,
82
+ hydrationScripts:
83
+ state.serverSnapshot.hydrationScripts ?? state.hydrationScripts,
84
+ matchedRouteIds:
85
+ state.serverSnapshot.matchedRouteIds ?? state.matchedRouteIds,
86
+ matches: state.serverSnapshot.matches ?? state.matches,
87
+ })
88
+ : hasSnapshotState
89
+ ? createRouterServerSnapshot({
90
+ framework: state.framework,
91
+ basename: state.basename,
92
+ hydrationScript: state.hydrationScript,
93
+ hydrationScripts: state.hydrationScripts,
94
+ matchedRouteIds: state.matchedRouteIds,
95
+ matches: state.matches,
96
+ })
97
+ : undefined;
98
+ const hydrationScripts = toHydrationScripts({
99
+ hydrationScript: state.hydrationScript ?? serverSnapshot?.hydrationScript,
100
+ hydrationScripts:
101
+ state.hydrationScripts ?? serverSnapshot?.hydrationScripts,
102
+ });
103
+ const matchedRouteIds =
104
+ state.matchedRouteIds ??
105
+ serverSnapshot?.matchedRouteIds ??
106
+ getMatchedRouteIdsFromMatches(state.matches);
107
+
108
+ return {
109
+ ...state,
110
+ ...(hydrationScripts?.length
111
+ ? {
112
+ hydrationScript: state.hydrationScript ?? hydrationScripts[0],
113
+ hydrationScripts,
114
+ }
115
+ : {}),
116
+ ...(matchedRouteIds ? { matchedRouteIds } : {}),
117
+ ...(serverSnapshot ? { serverSnapshot } : {}),
118
+ };
119
+ }
120
+
121
+ export function applyRouterRuntimeState(
122
+ runtimeContext: TInternalRuntimeContext,
123
+ state: InternalRouterRuntimeState,
124
+ ) {
125
+ const normalized = createRouterRuntimeState(state);
126
+ const mutableRuntimeContext = runtimeContext as any;
127
+ mutableRuntimeContext.routerFramework = normalized.framework;
128
+ mutableRuntimeContext.routerInstance = normalized.instance;
129
+ mutableRuntimeContext.routerHydrationScript = normalized.hydrationScript;
130
+ mutableRuntimeContext.routerMatchedRouteIds = normalized.matchedRouteIds;
131
+ mutableRuntimeContext.routerRuntime = normalized;
132
+ if (normalized.serverSnapshot) {
133
+ mutableRuntimeContext.routerServerSnapshot = normalized.serverSnapshot;
134
+ }
135
+
136
+ return runtimeContext;
137
+ }
138
+
139
+ export function applyRouterServerPrepareResult(
140
+ runtimeContext: TInternalRuntimeContext,
141
+ result: RouterServerPrepareResult,
142
+ ) {
143
+ const state = createRouterRuntimeState({
144
+ ...result.state,
145
+ cleanup: result.cleanup ?? result.state.cleanup,
146
+ serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
147
+ });
148
+ applyRouterRuntimeState(runtimeContext, state);
149
+ return runtimeContext;
150
+ }
@@ -0,0 +1,534 @@
1
+ /// <reference path="./ssr-shim.d.ts" />
2
+
3
+ import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
4
+ import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
5
+ import {
6
+ getGlobalEnableRsc,
7
+ getGlobalLayoutApp,
8
+ getGlobalRoutes,
9
+ InternalRuntimeContext,
10
+ type ServerPayload,
11
+ type TInternalRuntimeContext,
12
+ } from '@modern-js/runtime/context';
13
+ import { merge } from '@modern-js/runtime-utils/merge';
14
+ import {
15
+ createRequestContext,
16
+ type RequestContext,
17
+ storage,
18
+ } from '@modern-js/runtime-utils/node';
19
+ import type { RouteObject } from '@modern-js/runtime-utils/router';
20
+ import { time } from '@modern-js/runtime-utils/time';
21
+ import { LOADER_REPORTER_NAME } from '@modern-js/utils/universal/constants';
22
+ import {
23
+ type AnyRouter,
24
+ createMemoryHistory,
25
+ createRouter,
26
+ RouterProvider,
27
+ } from '@tanstack/react-router';
28
+ import { attachRouterServerSsrUtils } from '@tanstack/react-router/ssr/server';
29
+ import type React from 'react';
30
+ import { Suspense, useContext } from 'react';
31
+ import { createModernBasepathRewrite } from './basepathRewrite';
32
+ import {
33
+ modifyRoutes as modifyRoutesHook,
34
+ onAfterCreateRouter as onAfterCreateRouterHook,
35
+ onAfterHydrateRouter as onAfterHydrateRouterHook,
36
+ onBeforeCreateRouter as onBeforeCreateRouterHook,
37
+ onBeforeCreateRoutes as onBeforeCreateRoutesHook,
38
+ onBeforeHydrateRouter as onBeforeHydrateRouterHook,
39
+ type RouterExtendsHooks,
40
+ } from './hooks';
41
+ import {
42
+ applyRouterServerPrepareResult,
43
+ createRouterServerSnapshot,
44
+ type RouterLifecycleContext,
45
+ } from './lifecycle';
46
+ import {
47
+ createRouteTreeFromRouteObjects,
48
+ getModernRouteIdsFromMatches,
49
+ } from './routeTree';
50
+ import {
51
+ createTanstackRscServerPayload,
52
+ handleTanstackRscRedirect,
53
+ } from './rsc/payloadRouter';
54
+ import type { InternalRouterServerSnapshot, RouterConfig } from './types';
55
+ import { createRouteObjectsFromConfig, urlJoin } from './utils';
56
+
57
+ type ModernTanstackRouterContext = {
58
+ request: Request;
59
+ requestContext: RequestContext<Record<string, unknown>>;
60
+ };
61
+
62
+ type TanstackRouterRuntimeConfig = {
63
+ plugins?: TanstackRouterRuntimePlugin[];
64
+ router?: Partial<RouterConfig>;
65
+ [key: string]: unknown;
66
+ };
67
+
68
+ type TanstackRouterRuntimeExtends = Required<
69
+ RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>
70
+ > & {
71
+ extendHooks: RouterExtendsHooks;
72
+ };
73
+
74
+ type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
75
+
76
+ type TanstackRouterRuntimePlugin = Plugin<
77
+ TanstackRouterPluginAPI,
78
+ TInternalRuntimeContext
79
+ >;
80
+
81
+ const setTanstackRscServerPayload = (payload: ServerPayload) => {
82
+ const storageContext = storage.useContext?.() as
83
+ | { serverPayload?: ServerPayload }
84
+ | undefined;
85
+ if (storageContext) {
86
+ storageContext.serverPayload = payload;
87
+ }
88
+ };
89
+
90
+ type RouterManagedTag = {
91
+ attrs?: Record<string, unknown>;
92
+ children?: unknown;
93
+ tag?: unknown;
94
+ };
95
+
96
+ type RouterMatchWithError = {
97
+ error?: unknown;
98
+ route?: {
99
+ id?: unknown;
100
+ options?: RouterRouteOptions;
101
+ };
102
+ routeId?: unknown;
103
+ };
104
+
105
+ type RouterRouteOptions = {
106
+ component?: unknown;
107
+ errorComponent?: unknown;
108
+ notFoundComponent?: unknown;
109
+ pendingComponent?: unknown;
110
+ };
111
+
112
+ type RouterRouteWithOptions = {
113
+ options?: RouterRouteOptions;
114
+ };
115
+
116
+ type PreloadableRouteComponent = {
117
+ load?: (props?: Record<string, unknown>) => Promise<unknown> | unknown;
118
+ preload?: (props?: Record<string, unknown>) => Promise<unknown> | unknown;
119
+ };
120
+
121
+ type TanstackRouterWithServerSsr = AnyRouter & {
122
+ resolveRedirect?: (redirect: Response) => Response;
123
+ routesById?: Record<string, RouterRouteWithOptions>;
124
+ serverSsr?: {
125
+ cleanup?: () => void;
126
+ dehydrate?: () => Promise<void> | void;
127
+ isSerializationFinished?: () => boolean;
128
+ onSerializationFinished?: (listener: () => void) => void;
129
+ takeBufferedScripts?: () => unknown;
130
+ };
131
+ state: AnyRouter['state'] & {
132
+ matches?: unknown;
133
+ redirect?: Response;
134
+ };
135
+ };
136
+
137
+ function isPreloadableRouteComponent(
138
+ component: unknown,
139
+ ): component is PreloadableRouteComponent {
140
+ if (!component || typeof component !== 'function') {
141
+ return false;
142
+ }
143
+
144
+ const preloadable = component as PreloadableRouteComponent;
145
+ return (
146
+ typeof preloadable.load === 'function' ||
147
+ typeof preloadable.preload === 'function'
148
+ );
149
+ }
150
+
151
+ async function preloadRouteComponent(component: unknown) {
152
+ if (!isPreloadableRouteComponent(component)) {
153
+ return;
154
+ }
155
+
156
+ if (typeof component.load === 'function') {
157
+ await component.load({});
158
+ return;
159
+ }
160
+
161
+ await component.preload?.({});
162
+ }
163
+
164
+ async function preloadMatchedRouteComponents(
165
+ tanstackRouter: TanstackRouterWithServerSsr,
166
+ ) {
167
+ const matches = Array.isArray(tanstackRouter.state.matches)
168
+ ? (tanstackRouter.state.matches as RouterMatchWithError[])
169
+ : [];
170
+ const routesById = tanstackRouter.routesById || {};
171
+
172
+ await Promise.all(
173
+ matches.map(async match => {
174
+ const routeId =
175
+ typeof match.routeId === 'string'
176
+ ? match.routeId
177
+ : typeof match.route?.id === 'string'
178
+ ? match.route.id
179
+ : undefined;
180
+ const route = routeId ? routesById[routeId] : match.route;
181
+ const options = route?.options;
182
+ if (!options) {
183
+ return;
184
+ }
185
+
186
+ await Promise.all([
187
+ preloadRouteComponent(options.component),
188
+ preloadRouteComponent(options.pendingComponent),
189
+ preloadRouteComponent(options.errorComponent),
190
+ preloadRouteComponent(options.notFoundComponent),
191
+ ]);
192
+ }),
193
+ );
194
+ }
195
+
196
+ async function waitForRouterSerialization(
197
+ tanstackRouter: TanstackRouterWithServerSsr,
198
+ ) {
199
+ const serverSsr = tanstackRouter.serverSsr;
200
+ if (
201
+ !serverSsr ||
202
+ typeof serverSsr.onSerializationFinished !== 'function' ||
203
+ serverSsr.isSerializationFinished?.()
204
+ ) {
205
+ return;
206
+ }
207
+
208
+ await new Promise<void>(resolve => {
209
+ serverSsr.onSerializationFinished?.(resolve);
210
+ });
211
+ }
212
+
213
+ function htmlEscapeAttr(value: string) {
214
+ return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
215
+ }
216
+
217
+ function routerManagedTagToHtml(tag: unknown): string {
218
+ if (!tag || typeof tag !== 'object') {
219
+ return '';
220
+ }
221
+
222
+ const managedTag = tag as RouterManagedTag;
223
+ if (!managedTag || managedTag.tag !== 'script') {
224
+ return '';
225
+ }
226
+
227
+ const attrs: Record<string, unknown> = managedTag.attrs || {};
228
+ const attrsStr = Object.entries(attrs)
229
+ .filter(([, v]) => v != null && v !== false)
230
+ .map(([k, v]) => {
231
+ const name = k === 'className' ? 'class' : k;
232
+ if (v === true) {
233
+ return name;
234
+ }
235
+ return `${name}="${htmlEscapeAttr(String(v))}"`;
236
+ })
237
+ .join(' ');
238
+
239
+ const open = attrsStr.length ? `<script ${attrsStr}>` : '<script>';
240
+ const children =
241
+ typeof managedTag.children === 'string' ? managedTag.children : '';
242
+ return `${open}${children}</script>`;
243
+ }
244
+
245
+ function routerManagedTagsToHtml(tags: unknown): string[] {
246
+ const normalizedTags = Array.isArray(tags) ? tags : [tags];
247
+ return normalizedTags.map(routerManagedTagToHtml).filter(Boolean);
248
+ }
249
+
250
+ function createGetSsrHref(request: Request): string {
251
+ const url = new URL(request.url);
252
+ return `${url.pathname}${url.search}${url.hash}`;
253
+ }
254
+
255
+ function stripSyntheticNotFoundRoute(routes: RouteObject[]): RouteObject[] {
256
+ return routes
257
+ .filter(route => !(route.path === '*' && !route.id && !route.loader))
258
+ .map(route => {
259
+ if (!route.children?.length) {
260
+ return route;
261
+ }
262
+ return {
263
+ ...route,
264
+ children: stripSyntheticNotFoundRoute(route.children),
265
+ };
266
+ });
267
+ }
268
+
269
+ function collectRouterErrors(
270
+ tanstackRouter: AnyRouter,
271
+ ): Record<string, unknown> | undefined {
272
+ const state = tanstackRouter.state as { matches?: unknown };
273
+ const matches = Array.isArray(state.matches)
274
+ ? (state.matches as RouterMatchWithError[])
275
+ : [];
276
+ const errors = matches.reduce((acc: Record<string, unknown>, match) => {
277
+ if (!match.error) {
278
+ return acc;
279
+ }
280
+
281
+ const routeId =
282
+ typeof match.routeId === 'string'
283
+ ? match.routeId
284
+ : typeof match.route?.id === 'string'
285
+ ? match.route.id
286
+ : `match-${Object.keys(acc).length}`;
287
+
288
+ acc[routeId] = match.error;
289
+ return acc;
290
+ }, {});
291
+
292
+ return Object.keys(errors).length > 0 ? errors : undefined;
293
+ }
294
+
295
+ export const tanstackRouterPlugin = (
296
+ userConfig: Partial<RouterConfig> = {},
297
+ ): TanstackRouterRuntimePlugin => {
298
+ const plugin: TanstackRouterRuntimePlugin = {
299
+ name: '@modern-js/plugin-router-tanstack',
300
+ registryHooks: {
301
+ modifyRoutes: modifyRoutesHook,
302
+ onAfterCreateRouter: onAfterCreateRouterHook,
303
+ onAfterHydrateRouter: onAfterHydrateRouterHook,
304
+ onBeforeCreateRouter: onBeforeCreateRouterHook,
305
+ onBeforeCreateRoutes: onBeforeCreateRoutesHook,
306
+ onBeforeHydrateRouter: onBeforeHydrateRouterHook,
307
+ },
308
+ setup: (api: TanstackRouterPluginAPI) => {
309
+ api.onBeforeRender(async (context, interrupt) => {
310
+ const pluginConfig = api.getRuntimeConfig() as {
311
+ router?: Partial<RouterConfig>;
312
+ };
313
+ const mergedConfig = merge(
314
+ pluginConfig.router || {},
315
+ userConfig,
316
+ ) as RouterConfig;
317
+ const serializationAdapters = getGlobalEnableRsc()
318
+ ? (await import('./rsc/server')).getTanstackRscSerializationAdapters()
319
+ : undefined;
320
+ const enableRsc = getGlobalEnableRsc();
321
+
322
+ const { basename = '', routesConfig, createRoutes } = mergedConfig;
323
+
324
+ const finalRouteConfig = {
325
+ routes: getGlobalRoutes(),
326
+ globalApp: getGlobalLayoutApp(),
327
+ ...routesConfig,
328
+ };
329
+
330
+ if (!finalRouteConfig.routes && !createRoutes) {
331
+ return;
332
+ }
333
+
334
+ const hooks = api.getHooks();
335
+ await hooks.onBeforeCreateRoutes.call(context);
336
+
337
+ const routeObjects = createRoutes
338
+ ? createRoutes()
339
+ : createRouteObjectsFromConfig({
340
+ routesConfig: finalRouteConfig,
341
+ ssrMode: context.ssrContext?.mode,
342
+ }) || [];
343
+ const normalizedRouteObjects = createRoutes
344
+ ? routeObjects
345
+ : stripSyntheticNotFoundRoute(routeObjects);
346
+ const modifiedRouteObjects = hooks.modifyRoutes.call(
347
+ normalizedRouteObjects,
348
+ );
349
+
350
+ if (!modifiedRouteObjects.length) {
351
+ return;
352
+ }
353
+
354
+ const {
355
+ request,
356
+ nonce,
357
+ baseUrl,
358
+ loaderFailureMode = 'errorBoundary',
359
+ } = context.ssrContext!;
360
+
361
+ const _basename =
362
+ baseUrl === '/' ? urlJoin(baseUrl, basename || '') : baseUrl;
363
+
364
+ const initialHref = createGetSsrHref(request.raw);
365
+ const isRSCNavigation =
366
+ enableRsc && request.raw.headers.get('x-rsc-tree') === 'true';
367
+
368
+ const requestContext = createRequestContext(
369
+ context.ssrContext?.loaderContext,
370
+ ) as RequestContext<Record<string, unknown>>;
371
+
372
+ const controller = new AbortController();
373
+ const ssrRequest = new Request(request.raw.url, {
374
+ method: 'GET',
375
+ headers: request.raw.headers,
376
+ signal: controller.signal,
377
+ });
378
+
379
+ const routerContext: ModernTanstackRouterContext = {
380
+ request: ssrRequest,
381
+ requestContext,
382
+ };
383
+
384
+ const routeTree = createRouteTreeFromRouteObjects(modifiedRouteObjects);
385
+ const history = createMemoryHistory({
386
+ initialEntries: [initialHref],
387
+ });
388
+
389
+ const rewrite = createModernBasepathRewrite(_basename);
390
+ const routerLifecycleContext: RouterLifecycleContext = {
391
+ framework: 'tanstack',
392
+ phase: 'ssr-prepare',
393
+ routes: modifiedRouteObjects,
394
+ runtimeContext: context as TInternalRuntimeContext,
395
+ basename: _basename,
396
+ };
397
+ hooks.onBeforeCreateRouter.call(routerLifecycleContext);
398
+
399
+ const tanstackRouter = createRouter({
400
+ routeTree,
401
+ history,
402
+ basepath: '/',
403
+ rewrite,
404
+ origin: new URL(request.raw.url).origin,
405
+ ssr: { nonce },
406
+ context: routerContext as never,
407
+ ...(serializationAdapters ? { serializationAdapters } : {}),
408
+ });
409
+ const serverRouter =
410
+ tanstackRouter as unknown as TanstackRouterWithServerSsr;
411
+
412
+ attachRouterServerSsrUtils({
413
+ router: serverRouter,
414
+ manifest: undefined,
415
+ });
416
+
417
+ const end = time();
418
+
419
+ try {
420
+ await tanstackRouter.load({ sync: true });
421
+ } finally {
422
+ const cost = end();
423
+ context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
424
+ }
425
+
426
+ if (serverRouter.state.redirect) {
427
+ const resolved = serverRouter.resolveRedirect
428
+ ? serverRouter.resolveRedirect(serverRouter.state.redirect)
429
+ : serverRouter.state.redirect;
430
+
431
+ try {
432
+ serverRouter.serverSsr?.cleanup?.();
433
+ } catch {}
434
+
435
+ return interrupt(
436
+ isRSCNavigation
437
+ ? handleTanstackRscRedirect(
438
+ resolved.headers,
439
+ _basename,
440
+ resolved.status,
441
+ )
442
+ : resolved,
443
+ );
444
+ }
445
+
446
+ const routerErrors = collectRouterErrors(tanstackRouter);
447
+ if (routerErrors && loaderFailureMode === 'clientRender') {
448
+ context.ssrContext?.response.status(200);
449
+ try {
450
+ serverRouter.serverSsr?.cleanup?.();
451
+ } catch {}
452
+ throw Object.values(routerErrors)[0];
453
+ }
454
+
455
+ await preloadMatchedRouteComponents(serverRouter);
456
+
457
+ context.ssrContext?.response.status(tanstackRouter.state.statusCode);
458
+
459
+ await serverRouter.serverSsr?.dehydrate?.();
460
+ await waitForRouterSerialization(serverRouter);
461
+
462
+ if (isRSCNavigation) {
463
+ setTanstackRscServerPayload(
464
+ createTanstackRscServerPayload(serverRouter, {
465
+ omitClientLoaderData: true,
466
+ }),
467
+ );
468
+ }
469
+
470
+ const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
471
+ const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
472
+ const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
473
+ const routerServerSnapshot: InternalRouterServerSnapshot =
474
+ createRouterServerSnapshot({
475
+ framework: 'tanstack',
476
+ basename: _basename,
477
+ statusCode: tanstackRouter.state.statusCode,
478
+ errors: routerErrors,
479
+ matchedRouteIds,
480
+ hydrationScripts,
481
+ });
482
+ const runtimeContext = applyRouterServerPrepareResult(
483
+ context as TInternalRuntimeContext,
484
+ {
485
+ snapshot: routerServerSnapshot,
486
+ cleanup: () => serverRouter.serverSsr?.cleanup?.(),
487
+ state: {
488
+ framework: 'tanstack',
489
+ basename: _basename,
490
+ instance: serverRouter,
491
+ hydrationScripts,
492
+ matchedRouteIds,
493
+ serverSnapshot: routerServerSnapshot,
494
+ },
495
+ },
496
+ );
497
+ hooks.onAfterCreateRouter.call({
498
+ ...routerLifecycleContext,
499
+ router: serverRouter,
500
+ serverSnapshot: routerServerSnapshot,
501
+ runtimeContext,
502
+ });
503
+ });
504
+
505
+ api.wrapRoot(App => {
506
+ const getRouteApp = () => {
507
+ return (props => {
508
+ const context = useContext(
509
+ InternalRuntimeContext,
510
+ ) as unknown as TInternalRuntimeContext;
511
+ const router =
512
+ context.routerInstance ?? context.routerRuntime?.instance;
513
+ if (!router) {
514
+ return App ? <App {...props} /> : null;
515
+ }
516
+
517
+ const routerWrapper = (
518
+ <Suspense fallback={null}>
519
+ <RouterProvider router={router as AnyRouter} />
520
+ </Suspense>
521
+ );
522
+
523
+ return App ? <App>{routerWrapper}</App> : routerWrapper;
524
+ }) as React.FC<Record<string, unknown>>;
525
+ };
526
+
527
+ return getRouteApp();
528
+ });
529
+ },
530
+ };
531
+ return plugin;
532
+ };
533
+
534
+ export default tanstackRouterPlugin;