@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.9 → 3.9.3-ultramodern.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.
Files changed (112) hide show
  1. package/dist/cjs/cli/artifacts.js +15 -1
  2. package/dist/cjs/cli/index.js +13 -2
  3. package/dist/cjs/cli/routeMetadata.js +131 -0
  4. package/dist/cjs/cli/rscRouteLayer.js +86 -0
  5. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
  6. package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
  7. package/dist/cjs/cli/tanstackTypes/source.js +28 -63
  8. package/dist/cjs/runtime/lifecycle.js +0 -3
  9. package/dist/cjs/runtime/loaderBridge.js +10 -4
  10. package/dist/cjs/runtime/navigation.js +102 -0
  11. package/dist/cjs/runtime/plugin.js +10 -1
  12. package/dist/cjs/runtime/plugin.node.js +89 -64
  13. package/dist/cjs/runtime/routeTree/construction.js +19 -61
  14. package/dist/cjs/runtime/routeTree/loaders.js +1 -1
  15. package/dist/cjs/runtime/routeTree/paths.js +0 -6
  16. package/dist/cjs/runtime/ssrManagedTags.js +2 -12
  17. package/dist/cjs/runtime/utils.js +1 -0
  18. package/dist/cjs/shared/routeDescriptor.js +88 -0
  19. package/dist/esm/cli/artifacts.mjs +13 -2
  20. package/dist/esm/cli/index.mjs +13 -11
  21. package/dist/esm/cli/routeMetadata.mjs +71 -0
  22. package/dist/esm/cli/rscRouteLayer.mjs +35 -0
  23. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  24. package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
  25. package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
  26. package/dist/esm/runtime/lifecycle.mjs +1 -1
  27. package/dist/esm/runtime/loaderBridge.mjs +8 -5
  28. package/dist/esm/runtime/navigation.mjs +64 -0
  29. package/dist/esm/runtime/plugin.mjs +10 -1
  30. package/dist/esm/runtime/plugin.node.mjs +91 -66
  31. package/dist/esm/runtime/routeTree/construction.mjs +19 -61
  32. package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
  33. package/dist/esm/runtime/routeTree/paths.mjs +1 -4
  34. package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
  35. package/dist/esm/runtime/utils.mjs +1 -0
  36. package/dist/esm/shared/routeDescriptor.mjs +50 -0
  37. package/dist/esm-node/cli/artifacts.mjs +13 -2
  38. package/dist/esm-node/cli/index.mjs +13 -11
  39. package/dist/esm-node/cli/routeMetadata.mjs +72 -0
  40. package/dist/esm-node/cli/rscRouteLayer.mjs +36 -0
  41. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  42. package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
  43. package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
  44. package/dist/esm-node/runtime/lifecycle.mjs +1 -1
  45. package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
  46. package/dist/esm-node/runtime/navigation.mjs +65 -0
  47. package/dist/esm-node/runtime/plugin.mjs +10 -1
  48. package/dist/esm-node/runtime/plugin.node.mjs +91 -66
  49. package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
  50. package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
  51. package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
  52. package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
  53. package/dist/esm-node/runtime/utils.mjs +1 -0
  54. package/dist/esm-node/shared/routeDescriptor.mjs +51 -0
  55. package/dist/types/cli/artifacts.d.ts +16 -7
  56. package/dist/types/cli/index.d.ts +1 -1
  57. package/dist/types/cli/routeMetadata.d.ts +18 -0
  58. package/dist/types/cli/rscRouteLayer.d.ts +11 -0
  59. package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
  60. package/dist/types/cli/tanstackTypes/source.d.ts +3 -2
  61. package/dist/types/runtime/lifecycle.d.ts +1 -1
  62. package/dist/types/runtime/loaderBridge.d.ts +1 -0
  63. package/dist/types/runtime/navigation.d.ts +4 -0
  64. package/dist/types/runtime/pluginShared.d.ts +5 -0
  65. package/dist/types/runtime/routeTree/paths.d.ts +0 -2
  66. package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
  67. package/dist/types/runtime/ssrTypes.d.ts +0 -7
  68. package/dist/types/shared/routeDescriptor.d.ts +26 -0
  69. package/package.json +32 -26
  70. package/src/cli/artifacts.ts +59 -8
  71. package/src/cli/index.ts +17 -4
  72. package/src/cli/routeMetadata.ts +121 -0
  73. package/src/cli/rscRouteLayer.ts +93 -0
  74. package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
  75. package/src/cli/tanstackTypes/shared.ts +0 -14
  76. package/src/cli/tanstackTypes/source.ts +48 -120
  77. package/src/runtime/lifecycle.ts +0 -1
  78. package/src/runtime/loaderBridge.ts +9 -9
  79. package/src/runtime/navigation.ts +78 -0
  80. package/src/runtime/plugin.node.tsx +121 -91
  81. package/src/runtime/plugin.tsx +10 -0
  82. package/src/runtime/pluginShared.ts +12 -0
  83. package/src/runtime/routeTree/construction.ts +32 -112
  84. package/src/runtime/routeTree/loaders.ts +2 -4
  85. package/src/runtime/routeTree/paths.ts +0 -6
  86. package/src/runtime/ssrManagedTags.ts +0 -17
  87. package/src/runtime/ssrTypes.ts +0 -7
  88. package/src/runtime/utils.tsx +1 -0
  89. package/src/shared/routeDescriptor.ts +102 -0
  90. package/dist/esm/rslib-runtime~1.mjs +0 -18
  91. package/dist/esm-node/rslib-runtime~0.mjs +0 -19
  92. package/rslib.config.mts +0 -4
  93. package/rstest.config.mts +0 -53
  94. package/tests/router/cli.test.ts +0 -420
  95. package/tests/router/clientHydration.test.tsx +0 -200
  96. package/tests/router/dataMutation.test.tsx +0 -428
  97. package/tests/router/fixtures/router-gen.oxlint.json +0 -5
  98. package/tests/router/flightSerialization.roundtrip.test.tsx +0 -140
  99. package/tests/router/generateRouteArtifacts.test.ts +0 -146
  100. package/tests/router/loaderBridge.test.ts +0 -207
  101. package/tests/router/localisedIdentity.test.tsx +0 -95
  102. package/tests/router/prefetchLink.test.tsx +0 -122
  103. package/tests/router/prefetchLinkPreload.test.tsx +0 -65
  104. package/tests/router/routeHooks.test.ts +0 -49
  105. package/tests/router/routeTree.test.ts +0 -240
  106. package/tests/router/rsc.test.tsx +0 -480
  107. package/tests/router/serverPlugin.test.ts +0 -157
  108. package/tests/router/slotUsageSanitizer.test.ts +0 -23
  109. package/tests/router/ssrPreload.test.ts +0 -30
  110. package/tests/router/tanstackTypes.test.ts +0 -384
  111. package/tsconfig.json +0 -12
  112. package/tsconfig.tsgo.json +0 -6
@@ -98,15 +98,15 @@ type LoaderLikeContext = {
98
98
  params?: Record<string, string>;
99
99
  };
100
100
 
101
- function getLoaderSignal(ctx: LoaderLikeContext | undefined): AbortSignal {
102
- const abortSignal = ctx?.abortController?.signal;
103
- if (abortSignal instanceof AbortSignal) {
104
- return abortSignal;
105
- }
106
- if (ctx?.signal instanceof AbortSignal) {
107
- return ctx.signal;
108
- }
109
- return new AbortController().signal;
101
+ export function getLoaderSignal(
102
+ ctx: LoaderLikeContext | undefined,
103
+ ): AbortSignal {
104
+ const matchSignal = ctx?.abortController?.signal ?? ctx?.signal;
105
+ const requestSignal = ctx?.context?.request?.signal;
106
+ const signals = [matchSignal, requestSignal].filter(
107
+ (signal): signal is AbortSignal => signal instanceof AbortSignal,
108
+ );
109
+ return AbortSignal.any(signals);
110
110
  }
111
111
 
112
112
  export function getLoaderHref(ctx: LoaderLikeContext | undefined): string {
@@ -0,0 +1,78 @@
1
+ import type {
2
+ RouterNavigationCapability,
3
+ RouterNavigationSnapshot,
4
+ } from '@modern-js/runtime-extensions/router-state';
5
+ import type { AnyRouter } from '@tanstack/react-router';
6
+ import { Link } from './prefetchLink';
7
+
8
+ /** Native TanStack navigation, exposed through the provider contract. */
9
+ export function createTanstackNavigation(
10
+ router: AnyRouter,
11
+ ): RouterNavigationCapability {
12
+ let location: AnyRouter['state']['location'] | undefined;
13
+ let matches: AnyRouter['state']['matches'] | undefined;
14
+ let snapshot: RouterNavigationSnapshot;
15
+ return {
16
+ Link: Link as RouterNavigationCapability['Link'],
17
+ getSnapshot() {
18
+ const state = router.state;
19
+ if (
20
+ location !== state.location ||
21
+ matches === undefined ||
22
+ matches.length !== state.matches.length ||
23
+ matches.some(
24
+ (match, index) => match.params !== state.matches[index]?.params,
25
+ )
26
+ ) {
27
+ location = state.location;
28
+ matches = state.matches;
29
+ snapshot = {
30
+ location: {
31
+ pathname: location.pathname,
32
+ search: location.searchStr,
33
+ hash:
34
+ location.hash !== '' ? `#${location.hash.replace(/^#/, '')}` : '',
35
+ },
36
+ params: Object.assign({}, ...matches.map(match => match.params)),
37
+ };
38
+ }
39
+ return snapshot;
40
+ },
41
+ subscribe(listener) {
42
+ const stops = (
43
+ ['onBeforeNavigate', 'onBeforeLoad', 'onLoad', 'onResolved'] as const
44
+ ).map(event => router.subscribe(event, listener));
45
+ return () => {
46
+ for (const stop of stops) stop();
47
+ };
48
+ },
49
+ navigate(href, options) {
50
+ return router.navigate({
51
+ to: href,
52
+ replace: options?.replace,
53
+ ...(options?.state === undefined
54
+ ? {}
55
+ : { state: options.state as never }),
56
+ });
57
+ },
58
+ createLinkProps(target) {
59
+ const preload =
60
+ target.preload !== undefined
61
+ ? target.preload
62
+ : target.prefetch === 'none'
63
+ ? false
64
+ : target.prefetch;
65
+ return {
66
+ to: target.pathname,
67
+ ...(target.search !== undefined ? { search: target.search } : {}),
68
+ ...(target.hash !== undefined && target.hash !== ''
69
+ ? { hash: target.hash }
70
+ : {}),
71
+ ...(target.hashScrollIntoView === undefined
72
+ ? {}
73
+ : { hashScrollIntoView: target.hashScrollIntoView }),
74
+ ...(preload === undefined ? {} : { preload }),
75
+ };
76
+ },
77
+ };
78
+ }
@@ -5,6 +5,7 @@ import {
5
5
  type TInternalRuntimeContext,
6
6
  } from '@modern-js/runtime/context';
7
7
  import type { InternalRouterServerSnapshot } from '@modern-js/runtime-extensions/router-state';
8
+ import { createRouterStatePlugin } from '@modern-js/runtime-extensions/router-state-plugin';
8
9
  import {
9
10
  createRequestContext,
10
11
  type RequestContext,
@@ -23,11 +24,13 @@ import { createModernBasepathRewrite } from './basepathRewrite';
23
24
  import { routerProviderRegistryHooks } from './hooks';
24
25
  import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
25
26
  import {
27
+ applyRouterRuntimeState,
26
28
  applyRouterServerPrepareResult,
27
- createRouterServerSnapshot,
28
29
  getRouterRuntimeState,
30
+ getRouterServerSnapshot,
29
31
  type RouterLifecycleContext,
30
32
  } from './lifecycle';
33
+ import { createTanstackNavigation } from './navigation';
31
34
  import {
32
35
  createTanstackRouteObjects,
33
36
  getTanstackRouteConfig,
@@ -54,7 +57,6 @@ import {
54
57
  collectRouterErrors,
55
58
  createGetSsrHref,
56
59
  routerManagedTagsToHtml,
57
- waitForRouterSerialization,
58
60
  } from './ssrManagedTags';
59
61
  import { preloadMatchedRouteComponents } from './ssrPreload';
60
62
  import type {
@@ -73,6 +75,9 @@ export const tanstackRouterPlugin = (
73
75
  name: '@modern-js/plugin-router-tanstack',
74
76
  registryHooks: routerProviderRegistryHooks,
75
77
  setup: (api: TanstackRouterPluginAPI) => {
78
+ createRouterStatePlugin({
79
+ registryHooks: routerProviderRegistryHooks,
80
+ }).setup(api);
76
81
  api.onBeforeRender(async (context, interrupt) => {
77
82
  const routeConfig = getTanstackRouteConfig(api, userConfig);
78
83
  const { mergedConfig } = routeConfig;
@@ -168,116 +173,141 @@ export const tanstackRouterPlugin = (
168
173
  setTanstackRscRouter(serverRouter);
169
174
  }
170
175
 
171
- await attachServerSsrUtils(serverRouter);
172
-
173
- const end = time();
174
-
176
+ let disposed = false;
177
+ let prepared = false;
178
+ const onAbort = () => controller.abort(rawRequest.signal.reason);
179
+ const cleanup = () => {
180
+ if (disposed) return;
181
+ disposed = true;
182
+ rawRequest.signal.removeEventListener('abort', onAbort);
183
+ controller.abort();
184
+ serverRouter.serverSsr?.cleanup();
185
+ };
186
+ // Acquire the disposer before attachment: every subsequent failure,
187
+ // including partial attachment, has an owner. Successful preparation
188
+ // leaves disposal to the response lifetime in the SSR pipeline.
189
+ applyRouterRuntimeState(context, {
190
+ framework: 'tanstack',
191
+ basename: _basename,
192
+ instance: serverRouter,
193
+ cleanup,
194
+ });
195
+ rawRequest.signal.addEventListener('abort', onAbort, { once: true });
175
196
  try {
176
- await tanstackRouter.load({ sync: true });
177
- } finally {
178
- const cost = end();
179
- context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
180
- }
197
+ rawRequest.signal.throwIfAborted();
198
+ await attachServerSsrUtils(serverRouter);
199
+ rawRequest.signal.throwIfAborted();
200
+
201
+ const end = time();
181
202
 
182
- const serverLoadResult = serverRouter._serverResult;
183
- if (!serverLoadResult) {
184
203
  try {
185
- serverRouter.serverSsr?.cleanup?.();
186
- } catch {}
187
- throw new Error(
188
- 'TanStack Router completed an SSR load without a server result.',
189
- );
190
- }
204
+ await tanstackRouter.load({ sync: true });
205
+ } finally {
206
+ const cost = end();
207
+ context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
208
+ }
191
209
 
192
- if (serverLoadResult.type === 'redirect') {
193
- const { redirect } = serverLoadResult;
210
+ rawRequest.signal.throwIfAborted();
211
+ const serverLoadResult = serverRouter._serverResult;
212
+ if (!serverLoadResult) {
213
+ throw new Error(
214
+ 'TanStack Router completed an SSR load without a server result.',
215
+ );
216
+ }
194
217
 
195
- try {
196
- serverRouter.serverSsr?.cleanup?.();
197
- } catch {}
198
-
199
- return interrupt(
200
- isRSCNavigation
201
- ? handleTanstackRscRedirect(
202
- redirect.headers,
203
- _basename,
204
- redirect.status,
205
- )
206
- : redirect,
207
- );
208
- }
218
+ if (serverLoadResult.type === 'redirect') {
219
+ const { redirect } = serverLoadResult;
220
+
221
+ return interrupt(
222
+ isRSCNavigation
223
+ ? handleTanstackRscRedirect(
224
+ redirect.headers,
225
+ _basename,
226
+ redirect.status,
227
+ )
228
+ : redirect,
229
+ );
230
+ }
231
+
232
+ const routerErrors = collectRouterErrors(tanstackRouter);
233
+ if (routerErrors && loaderFailureMode === 'clientRender') {
234
+ (
235
+ context.ssrContext?.response as
236
+ | { status: (code: number) => void }
237
+ | undefined
238
+ )?.status(200);
239
+ throw Object.values(routerErrors)[0];
240
+ }
241
+
242
+ await preloadMatchedRouteComponents(serverRouter);
209
243
 
210
- const routerErrors = collectRouterErrors(tanstackRouter);
211
- if (routerErrors && loaderFailureMode === 'clientRender') {
212
244
  (
213
245
  context.ssrContext?.response as
214
246
  | { status: (code: number) => void }
215
247
  | undefined
216
- )?.status(200);
217
- try {
218
- serverRouter.serverSsr?.cleanup?.();
219
- } catch {}
220
- throw Object.values(routerErrors)[0];
221
- }
222
-
223
- await preloadMatchedRouteComponents(serverRouter);
224
-
225
- (
226
- context.ssrContext?.response as
227
- | { status: (code: number) => void }
228
- | undefined
229
- )?.status(serverLoadResult.status);
248
+ )?.status(serverLoadResult.status);
230
249
 
231
- await serverRouter.serverSsr?.dehydrate?.();
250
+ await serverRouter.serverSsr?.dehydrate();
232
251
 
233
- if (enableRsc) {
234
- if (isRSCNavigation) {
235
- // RSC navigations consume the server payload directly. Normal HTML SSR
236
- // emits the buffered bootstrap script below and must not wait here
237
- // because Modern's non-streaming hook has not rendered the app yet.
238
- await waitForRouterSerialization(serverRouter);
252
+ if (enableRsc) {
253
+ setTanstackRscServerPayload(
254
+ createTanstackRscServerPayload(serverRouter, {
255
+ omitClientLoaderData: isRSCNavigation,
256
+ }),
257
+ );
258
+ if (isRSCNavigation) {
259
+ context.ssrContext!.response.setHeader(
260
+ 'Content-Type',
261
+ 'text/x-component',
262
+ );
263
+ }
239
264
  }
240
265
 
241
- setTanstackRscServerPayload(
242
- createTanstackRscServerPayload(serverRouter, {
243
- omitClientLoaderData: isRSCNavigation,
244
- }),
245
- );
246
- }
247
-
248
- const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
249
- const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
250
- const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
251
- const routerServerSnapshot: InternalRouterServerSnapshot =
252
- createRouterServerSnapshot({
266
+ const initialScriptTags =
267
+ serverRouter.serverSsr?.takeInitialHydrationScriptTags();
268
+ const hydrationScripts = initialScriptTags
269
+ ? routerManagedTagsToHtml([
270
+ ...initialScriptTags.before,
271
+ initialScriptTags.boundary,
272
+ ])
273
+ : [];
274
+ const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
275
+ const routerServerSnapshot: InternalRouterServerSnapshot = {
253
276
  framework: 'tanstack',
254
277
  basename: _basename,
255
278
  statusCode: serverLoadResult.status,
256
279
  errors: routerErrors,
257
280
  matchedRouteIds,
258
281
  hydrationScripts,
259
- });
260
- const runtimeContext = applyRouterServerPrepareResult(
261
- context as TInternalRuntimeContext,
262
- {
263
- snapshot: routerServerSnapshot,
264
- cleanup: () => serverRouter.serverSsr?.cleanup?.(),
265
- state: {
266
- framework: 'tanstack',
267
- basename: _basename,
268
- instance: serverRouter,
269
- hydrationScripts,
270
- matchedRouteIds,
271
- serverSnapshot: routerServerSnapshot,
282
+ };
283
+ const runtimeContext = applyRouterServerPrepareResult(
284
+ context as TInternalRuntimeContext,
285
+ {
286
+ snapshot: routerServerSnapshot,
287
+ cleanup,
288
+ state: {
289
+ framework: 'tanstack',
290
+ basename: _basename,
291
+ instance: serverRouter,
292
+ navigation: createTanstackNavigation(tanstackRouter),
293
+ },
272
294
  },
273
- },
274
- );
275
- hooks.onAfterCreateRouter.call({
276
- ...routerLifecycleContext,
277
- router: serverRouter,
278
- serverSnapshot: routerServerSnapshot,
279
- runtimeContext,
280
- });
295
+ );
296
+ rawRequest.signal.throwIfAborted();
297
+ hooks.onAfterCreateRouter.call({
298
+ ...routerLifecycleContext,
299
+ router: serverRouter,
300
+ serverSnapshot: getRouterServerSnapshot(runtimeContext),
301
+ runtimeContext,
302
+ });
303
+ prepared = true;
304
+ } finally {
305
+ if (!prepared) {
306
+ try {
307
+ cleanup();
308
+ } catch {}
309
+ }
310
+ }
281
311
  });
282
312
 
283
313
  api.wrapRoot(App => {
@@ -4,6 +4,7 @@ import {
4
4
  InternalRuntimeContext,
5
5
  type TInternalRuntimeContext,
6
6
  } from '@modern-js/runtime/context';
7
+ import { createRouterStatePlugin } from '@modern-js/runtime-extensions/router-state-plugin';
7
8
  import type { RouteObject } from '@modern-js/runtime-utils/router';
8
9
  import { normalizePathname } from '@modern-js/runtime-utils/url';
9
10
  import {
@@ -32,6 +33,7 @@ import {
32
33
  applyRouterRuntimeState,
33
34
  type RouterLifecycleContext,
34
35
  } from './lifecycle';
36
+ import { createTanstackNavigation } from './navigation';
35
37
  import {
36
38
  createTanstackRouteObjects,
37
39
  getMergedRouterConfig,
@@ -56,6 +58,9 @@ export const tanstackRouterPlugin = (
56
58
  name: '@modern-js/plugin-router-tanstack',
57
59
  registryHooks: routerProviderRegistryHooks,
58
60
  setup: (api: TanstackRouterPluginAPI) => {
61
+ createRouterStatePlugin({
62
+ registryHooks: routerProviderRegistryHooks,
63
+ }).setup(api);
59
64
  const hooks = api.getHooks();
60
65
  let cachedRouteObjects: RouteObject[] | undefined;
61
66
  let cachedRouteTree: ReturnType<
@@ -259,10 +264,15 @@ export const tanstackRouterPlugin = (
259
264
  if (!router) {
260
265
  return App ? <App /> : null;
261
266
  }
267
+ const navigation = useMemo(
268
+ () => createTanstackNavigation(router),
269
+ [router],
270
+ );
262
271
  const runtimeState = applyRouterRuntimeState(runtimeContext, {
263
272
  framework: 'tanstack',
264
273
  basename: _basename,
265
274
  instance: router,
275
+ navigation,
266
276
  });
267
277
  const lifecycleContext: RouterLifecycleContext = {
268
278
  framework: 'tanstack',
@@ -1,3 +1,7 @@
1
+ import type {
2
+ OnRenderPreparedFn,
3
+ OnRequestEndFn,
4
+ } from '@modern-js/plugin/runtime';
1
5
  import {
2
6
  getGlobalLayoutApp,
3
7
  getGlobalRoutes,
@@ -6,6 +10,7 @@ import {
6
10
  import { merge } from '@modern-js/runtime-utils/merge';
7
11
  import type { RouteObject } from '@modern-js/runtime-utils/router';
8
12
  import type { RouterExtendsHooks } from './hooks';
13
+ import type { RouterLifecycleContext } from './lifecycle';
9
14
  import type { RouterConfig } from './types';
10
15
  import { createTanstackRouteObjectsFromConfig } from './utils';
11
16
 
@@ -26,6 +31,13 @@ export type TanstackRouterPluginAPI = {
26
31
  interrupt: RuntimeInterrupt,
27
32
  ) => unknown,
28
33
  ) => void;
34
+ onRenderPrepared: (
35
+ listener: OnRenderPreparedFn<TInternalRuntimeContext>,
36
+ ) => void;
37
+ onRequestEnd: (listener: OnRequestEndFn<TInternalRuntimeContext>) => void;
38
+ onAfterCreateRouter: (
39
+ listener: (event: RouterLifecycleContext) => void,
40
+ ) => void;
29
41
  wrapRoot: (listener: (App: any) => any) => void;
30
42
  [key: string]: any;
31
43
  };
@@ -1,11 +1,14 @@
1
1
  // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
2
 
3
- import { canonicaliseLocalisedRoutes } from '@modern-js/i18n-runtime-extensions';
4
3
  import { DefaultNotFound } from '@modern-js/runtime/context';
5
4
  import type { RouteObject } from '@modern-js/runtime-utils/router';
6
5
  import type { AnyRoute, AnyRouter } from '@tanstack/react-router';
7
6
  import { rootRouteId } from '@tanstack/react-router';
8
7
 
8
+ import {
9
+ describeRouteTree,
10
+ type RouteDescriptor,
11
+ } from '../../shared/routeDescriptor';
9
12
  import { createRouteStaticData } from '../../shared/routeStaticData';
10
13
  import { withModernRouteMatchContext } from '../outlet';
11
14
  import {
@@ -14,7 +17,6 @@ import {
14
17
  toRouteComponent,
15
18
  } from './components';
16
19
  import { createModernShouldReload, wrapRouteObjectLoader } from './loaders';
17
- import { isRouteObjectPathlessLayout, toTanstackPath } from './paths';
18
20
  import { mergeModernRouteHandle } from './staticData';
19
21
  import {
20
22
  createTanstackRootRoute,
@@ -24,25 +26,16 @@ import {
24
26
  import type {
25
27
  ModernRouteObject,
26
28
  ModernTanstackRootRoute,
27
- MutableTanstackRoute,
28
29
  RouteRevalidationState,
29
30
  RouteTreeOptions,
30
31
  TanstackRootRouteOptions,
31
32
  TanstackRouteOptions,
32
33
  } from './types';
33
34
 
34
- function createRouteFromRouteObject(opts: {
35
- options?: RouteTreeOptions;
36
- parent: AnyRoute;
37
- pathlessFallbackId?: string;
38
- routeObject: RouteObject;
39
- }): AnyRoute {
40
- const {
41
- options = {},
42
- parent,
43
- pathlessFallbackId = 'pathless',
44
- routeObject,
45
- } = opts;
35
+ function createRouteOptions(
36
+ routeObject: RouteObject,
37
+ options: RouteTreeOptions,
38
+ ): TanstackRouteOptions {
46
39
  const modernRouteObject = routeObject as ModernRouteObject;
47
40
  const revalidationState: RouteRevalidationState = {};
48
41
  const shouldRevalidate = modernRouteObject.shouldRevalidate;
@@ -50,13 +43,8 @@ function createRouteFromRouteObject(opts: {
50
43
  shouldRevalidate,
51
44
  revalidationState,
52
45
  );
53
-
54
- const stableFallbackId =
55
- routeObject.id || modernRouteObject.file || pathlessFallbackId;
56
-
57
46
  const component = toRouteComponent(routeObject);
58
47
  const base: TanstackRouteOptions = {
59
- getParentRoute: () => parent,
60
48
  component,
61
49
  pendingComponent: toPendingComponent(routeObject),
62
50
  errorComponent: toErrorComponent(routeObject),
@@ -87,117 +75,49 @@ function createRouteFromRouteObject(opts: {
87
75
  base.shouldReload = shouldReload;
88
76
  }
89
77
 
90
- if (isRouteObjectPathlessLayout(routeObject)) {
91
- base.id = stableFallbackId;
92
- } else {
93
- base.path = routeObject.index
94
- ? '/'
95
- : toTanstackPath((routeObject.path as string) || '');
96
- }
97
-
98
- const route = createTanstackRoute(base);
99
- wrapRouteComponentWithModernContext(route, component, routeObject.id);
78
+ return base;
79
+ }
100
80
 
101
- const children = routeObject.children;
102
- if (children && children.length > 0) {
103
- const childRoutes = children.map((child: RouteObject, index) =>
104
- createRouteFromRouteObject({
105
- options,
106
- parent: route,
107
- pathlessFallbackId: `pathless-${index}`,
108
- routeObject: child,
109
- }),
81
+ function createRouteFromDescriptor(
82
+ descriptor: RouteDescriptor<RouteObject>,
83
+ parent: AnyRoute,
84
+ options: RouteTreeOptions,
85
+ ): AnyRoute {
86
+ const { source, location, children } = descriptor;
87
+ const routeOptions = createRouteOptions(source, options);
88
+ const route = createTanstackRoute({
89
+ ...routeOptions,
90
+ ...location,
91
+ getParentRoute: () => parent,
92
+ });
93
+ wrapRouteComponentWithModernContext(route, routeOptions.component, source.id);
94
+ if (children.length) {
95
+ route.addChildren(
96
+ children.map(child => createRouteFromDescriptor(child, route, options)),
110
97
  );
111
- route.addChildren(childRoutes);
112
98
  }
113
-
114
99
  return route;
115
100
  }
116
101
 
117
- function getRootLikeRouteObject(routes: RouteObject[]) {
118
- return routes.find(route => route.path === '/' && !route.index);
119
- }
120
-
121
102
  export function createRouteTreeFromRouteObjects(
122
103
  routes: RouteObject[],
123
104
  options: RouteTreeOptions = {},
124
105
  ): ModernTanstackRootRoute {
125
- routes = canonicaliseLocalisedRoutes(routes);
126
- const rootLikeRoute = getRootLikeRouteObject(routes) as
127
- | ModernRouteObject
128
- | undefined;
129
- const rootRevalidationState: RouteRevalidationState = {};
130
- const rootShouldRevalidate = rootLikeRoute?.shouldRevalidate;
131
- const rootShouldReload = createModernShouldReload(
132
- rootShouldRevalidate,
133
- rootRevalidationState,
134
- );
135
-
136
- const rootComponent = rootLikeRoute
137
- ? toRouteComponent(rootLikeRoute)
138
- : undefined;
106
+ const { root, children } = describeRouteTree(routes);
139
107
  const rootRouteOptions: TanstackRootRouteOptions = {
140
- component: rootComponent,
141
- pendingComponent: rootLikeRoute
142
- ? toPendingComponent(rootLikeRoute)
143
- : undefined,
144
- errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : undefined,
145
- validateSearch: rootLikeRoute?.validateSearch,
146
- loaderDeps: rootLikeRoute?.loaderDeps,
108
+ ...(root ? createRouteOptions(root, options) : {}),
147
109
  notFoundComponent: DefaultNotFound,
148
- staticData: createRouteStaticData({
149
- modernRouteId: rootLikeRoute?.id,
150
- modernRouteAction: rootLikeRoute?.action,
151
- modernRouteHandle: rootLikeRoute
152
- ? mergeModernRouteHandle(rootLikeRoute)
153
- : undefined,
154
- modernRouteHasAction:
155
- rootLikeRoute?.hasAction || Boolean(rootLikeRoute?.action),
156
- modernRouteHasClientLoader:
157
- rootLikeRoute?.hasClientLoader ||
158
- typeof rootLikeRoute?.clientData !== 'undefined',
159
- modernRouteHasLoader:
160
- rootLikeRoute?.hasLoader || typeof rootLikeRoute?.loader === 'function',
161
- modernRouteIsClientComponent: rootLikeRoute?.isClientComponent,
162
- modernRouteLoader: rootLikeRoute?.loader,
163
- modernRouteShouldRevalidate: rootShouldRevalidate,
164
- }),
165
- loader: rootLikeRoute
166
- ? wrapRouteObjectLoader(rootLikeRoute, rootRevalidationState, options)
167
- : undefined,
168
110
  };
169
- if (rootShouldReload) {
170
- rootRouteOptions.shouldReload = rootShouldReload;
171
- }
172
- if (rootLikeRoute?.inValidSSRRoute) {
173
- rootRouteOptions.ssr = false;
174
- }
175
-
176
111
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
177
- if (rootComponent) {
112
+ if (rootRouteOptions.component) {
178
113
  rootRoute.options.component = withModernRouteMatchContext(
179
- rootComponent,
114
+ rootRouteOptions.component,
180
115
  rootRouteId,
181
116
  ) as typeof rootRoute.options.component;
182
117
  }
183
-
184
- const topLevel = rootLikeRoute
185
- ? [
186
- ...((rootLikeRoute.children as RouteObject[] | undefined) || []),
187
- ...routes.filter(route => route !== rootLikeRoute),
188
- ]
189
- : routes;
190
-
191
- const childRoutes = topLevel.map((routeObject, index) =>
192
- createRouteFromRouteObject({
193
- options,
194
- parent: rootRoute,
195
- pathlessFallbackId: `pathless-${index}`,
196
- routeObject,
197
- }),
118
+ rootRoute.addChildren(
119
+ children.map(child => createRouteFromDescriptor(child, rootRoute, options)),
198
120
  );
199
-
200
- rootRoute.addChildren(childRoutes);
201
121
  return rootRoute as unknown as ModernTanstackRootRoute;
202
122
  }
203
123
 
@@ -3,6 +3,7 @@
3
3
  import type { RouteObject } from '@modern-js/runtime-utils/router';
4
4
  import {
5
5
  getLoaderHref,
6
+ getLoaderSignal,
6
7
  handleModernLoaderError,
7
8
  handleModernLoaderResult,
8
9
  mapSplatParamsForModernLoader,
@@ -118,10 +119,7 @@ export function wrapRouteObjectLoader(
118
119
  } catch {}
119
120
  }
120
121
 
121
- const signal: AbortSignal =
122
- ctx?.abortController?.signal ||
123
- ctx?.signal ||
124
- new AbortController().signal;
122
+ const signal = getLoaderSignal(ctx);
125
123
  const baseRequest: Request | undefined =
126
124
  ctx?.context?.request instanceof Request
127
125
  ? ctx.context.request