@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.102 → 3.2.0-ultramodern.104

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 (48) hide show
  1. package/dist/cjs/cli/index.js +9 -5
  2. package/dist/cjs/cli/routeSplitting.js +14 -10
  3. package/dist/cjs/cli/tanstackTypes.js +9 -5
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/DefaultNotFound.js +9 -5
  6. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  7. package/dist/cjs/runtime/dataMutation.js +9 -5
  8. package/dist/cjs/runtime/hooks.js +9 -5
  9. package/dist/cjs/runtime/hydrationBoundary.js +9 -5
  10. package/dist/cjs/runtime/index.js +9 -5
  11. package/dist/cjs/runtime/lifecycle.js +15 -11
  12. package/dist/cjs/runtime/outlet.js +10 -6
  13. package/dist/cjs/runtime/plugin.js +42 -41
  14. package/dist/cjs/runtime/plugin.node.js +10 -6
  15. package/dist/cjs/runtime/plugin.worker.js +9 -5
  16. package/dist/cjs/runtime/prefetchLink.js +10 -6
  17. package/dist/cjs/runtime/routeTree.js +9 -5
  18. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  19. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  20. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  21. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  22. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  23. package/dist/cjs/runtime/rsc/client.js +9 -5
  24. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  25. package/dist/cjs/runtime/rsc/index.js +9 -5
  26. package/dist/cjs/runtime/rsc/payloadRouter.js +9 -5
  27. package/dist/cjs/runtime/rsc/server.js +9 -5
  28. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  29. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  30. package/dist/cjs/runtime/types.js +13 -9
  31. package/dist/cjs/runtime/utils.js +9 -5
  32. package/dist/cjs/runtime.js +9 -5
  33. package/dist/esm/runtime/outlet.mjs +1 -1
  34. package/dist/esm/runtime/plugin.mjs +33 -36
  35. package/dist/esm/runtime/plugin.node.mjs +1 -1
  36. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  37. package/dist/esm-node/runtime/outlet.mjs +1 -1
  38. package/dist/esm-node/runtime/plugin.mjs +33 -36
  39. package/dist/esm-node/runtime/plugin.node.mjs +1 -1
  40. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  41. package/dist/types/runtime/hooks.d.ts +9 -24
  42. package/package.json +9 -9
  43. package/src/runtime/outlet.tsx +13 -7
  44. package/src/runtime/plugin.node.tsx +2 -1
  45. package/src/runtime/plugin.tsx +62 -51
  46. package/src/runtime/prefetchLink.tsx +1 -1
  47. package/src/runtime/routeTree.ts +3 -3
  48. package/tests/router/prefetchLink.test.tsx +43 -7
@@ -5,7 +5,7 @@ function resolvePreloadFromPrefetch(prefetch, preload) {
5
5
  if (void 0 !== preload) return preload;
6
6
  if ('none' === prefetch) return false;
7
7
  if ('intent' === prefetch || 'render' === prefetch || 'viewport' === prefetch) return prefetch;
8
- return preload;
8
+ return 'viewport';
9
9
  }
10
10
  const LinkComponentImpl = (props)=>{
11
11
  const { prefetch, preload, ...rest } = props;
@@ -1,27 +1,12 @@
1
- declare const modifyRoutes: {
2
- tap: (cb: any) => void;
3
- call: (...params: any[]) => any;
4
- };
5
- declare const onBeforeCreateRoutes: {
6
- tap: (cb: any) => void;
7
- call: (...params: any[]) => any;
8
- };
9
- declare const onBeforeCreateRouter: {
10
- tap: (cb: any) => void;
11
- call: (...params: any[]) => any;
12
- };
13
- declare const onAfterCreateRouter: {
14
- tap: (cb: any) => void;
15
- call: (...params: any[]) => any;
16
- };
17
- declare const onBeforeHydrateRouter: {
18
- tap: (cb: any) => void;
19
- call: (...params: any[]) => any;
20
- };
21
- declare const onAfterHydrateRouter: {
22
- tap: (cb: any) => void;
23
- call: (...params: any[]) => any;
24
- };
1
+ import type { TRuntimeContext } from '@modern-js/runtime/context';
2
+ import type { RouteObject } from '@modern-js/runtime-utils/router';
3
+ import type { RouterLifecycleContext } from './lifecycle';
4
+ declare const modifyRoutes: import("@modern-js/plugin").SyncHook<(routes: RouteObject[]) => RouteObject[]>;
5
+ declare const onBeforeCreateRoutes: import("@modern-js/plugin").SyncHook<(context: TRuntimeContext) => void>;
6
+ declare const onBeforeCreateRouter: import("@modern-js/plugin").SyncHook<(context: RouterLifecycleContext) => void>;
7
+ declare const onAfterCreateRouter: import("@modern-js/plugin").SyncHook<(context: RouterLifecycleContext) => void>;
8
+ declare const onBeforeHydrateRouter: import("@modern-js/plugin").SyncHook<(context: RouterLifecycleContext) => void>;
9
+ declare const onAfterHydrateRouter: import("@modern-js/plugin").SyncHook<(context: RouterLifecycleContext) => void>;
25
10
  export { modifyRoutes, onAfterCreateRouter, onAfterHydrateRouter, onBeforeCreateRouter, onBeforeCreateRoutes, onBeforeHydrateRouter, };
26
11
  export type RouterExtendsHooks = {
27
12
  modifyRoutes: typeof modifyRoutes;
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "tanstack-router"
20
20
  ],
21
- "version": "3.2.0-ultramodern.102",
21
+ "version": "3.2.0-ultramodern.104",
22
22
  "engines": {
23
23
  "node": ">=20"
24
24
  },
@@ -88,13 +88,13 @@
88
88
  "@swc/helpers": "^0.5.23",
89
89
  "@tanstack/react-router": "1.170.11",
90
90
  "@tanstack/router-core": "1.171.9",
91
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.102",
92
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.102",
93
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.102",
94
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.102"
91
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.104",
92
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.104",
93
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.104",
94
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.104"
95
95
  },
96
96
  "peerDependencies": {
97
- "@modern-js/runtime": "3.2.0-ultramodern.102",
97
+ "@modern-js/runtime": "3.2.0-ultramodern.104",
98
98
  "react": "^19.2.6",
99
99
  "react-dom": "^19.2.6"
100
100
  },
@@ -109,9 +109,9 @@
109
109
  "@typescript/native-preview": "7.0.0-dev.20260527.2",
110
110
  "react": "^19.2.6",
111
111
  "react-dom": "^19.2.6",
112
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.102",
113
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.102",
114
- "@scripts/rstest-config": "2.66.0"
112
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.104",
113
+ "@scripts/rstest-config": "2.66.0",
114
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.104"
115
115
  },
116
116
  "sideEffects": false,
117
117
  "publishConfig": {
@@ -1,12 +1,19 @@
1
1
  import { Outlet as TanstackOutlet } from '@tanstack/react-router';
2
2
  import {
3
- type ComponentProps,
4
3
  createElement,
5
4
  type ElementType,
6
5
  memo,
6
+ type ReactElement,
7
7
  } from 'react';
8
8
 
9
- type PreloadableComponent = ElementType<Record<string, unknown>> & {
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) & {
10
17
  load?: () => Promise<unknown>;
11
18
  preload?: () => Promise<unknown>;
12
19
  };
@@ -19,14 +26,13 @@ export function withModernRouteMatchContext(
19
26
  component: unknown,
20
27
  _routeId: string,
21
28
  ): unknown {
22
- if (!component) {
29
+ if (component === null || component === undefined) {
23
30
  return component;
24
31
  }
25
32
 
26
- const Component = component as ElementType<Record<string, unknown>>;
27
- const WrappedRouteComponent = (
28
- props: ComponentProps<ElementType<Record<string, unknown>>>,
29
- ) => createElement(Component, props);
33
+ const Component = component as ElementType<RouteComponentProps>;
34
+ const WrappedRouteComponent: WrappedPreloadableComponent = props =>
35
+ createElement(Component, props);
30
36
 
31
37
  const preloadable = component as PreloadableComponent;
32
38
  if (typeof preloadable.load === 'function') {
@@ -169,7 +169,8 @@ function isReactLazyRouteComponent(
169
169
  component: unknown,
170
170
  ): component is ReactLazyRouteComponent {
171
171
  return (
172
- Boolean(component) &&
172
+ component !== null &&
173
+ component !== undefined &&
173
174
  typeof component === 'object' &&
174
175
  typeof (component as ReactLazyRouteComponent)._init === 'function' &&
175
176
  '_payload' in component
@@ -208,16 +208,16 @@ function getCachedRouteModule(routeId: string) {
208
208
  ]?.[routeId];
209
209
  }
210
210
 
211
- async function preloadHydratedRouteComponents(router: AnyRouter) {
211
+ function preloadHydratedRouteComponents(router: AnyRouter): Promise<void> {
212
212
  const preloadableRouter = router as RouterWithPreloadableRoutes;
213
213
  const routesById = preloadableRouter.routesById || {};
214
214
  const matches = preloadableRouter.stores.matches.get() as Array<{
215
215
  routeId?: string;
216
216
  }>;
217
217
 
218
- await Promise.all(
218
+ return Promise.all(
219
219
  matches.map(match => {
220
- if (!match.routeId) {
220
+ if (match.routeId === undefined || match.routeId === '') {
221
221
  return undefined;
222
222
  }
223
223
 
@@ -230,10 +230,18 @@ async function preloadHydratedRouteComponents(router: AnyRouter) {
230
230
 
231
231
  return Promise.resolve(preload.call(component)).then(routeModule => {
232
232
  const modernRouteId = route?.options?.staticData?.modernRouteId;
233
+ const cachedRouteModule =
234
+ typeof modernRouteId === 'string' && modernRouteId !== ''
235
+ ? getCachedRouteModule(modernRouteId)
236
+ : undefined;
233
237
  const resolvedComponent = pickRouteModuleComponent(
234
- (modernRouteId && getCachedRouteModule(modernRouteId)) || routeModule,
238
+ cachedRouteModule ?? routeModule,
235
239
  );
236
- if (resolvedComponent && modernRouteId) {
240
+ if (
241
+ resolvedComponent !== undefined &&
242
+ typeof modernRouteId === 'string' &&
243
+ modernRouteId !== ''
244
+ ) {
237
245
  route.options.component = withModernRouteMatchContext(
238
246
  resolvedComponent,
239
247
  modernRouteId,
@@ -241,40 +249,39 @@ async function preloadHydratedRouteComponents(router: AnyRouter) {
241
249
  }
242
250
  });
243
251
  }),
244
- );
252
+ ).then(() => undefined);
245
253
  }
246
254
 
247
255
  function getTanstackSsrHydrationRecord(router: AnyRouter) {
248
- let hydrationRecord = routerHydrationRecords.get(router);
249
- if (!hydrationRecord) {
250
- hydrationRecord = {
251
- promise: Promise.resolve(),
252
- status: 'pending',
253
- };
254
- routerHydrationRecords.set(router, hydrationRecord);
255
- try {
256
- hydrationRecord.promise = hydrateTanstackRouter(router)
257
- .then(async value => {
258
- await preloadHydratedRouteComponents(router);
256
+ const existingHydrationRecord = routerHydrationRecords.get(router);
257
+ if (existingHydrationRecord !== undefined) {
258
+ return existingHydrationRecord;
259
+ }
260
+
261
+ const hydrationRecord: RouterHydrationRecord = {
262
+ promise: Promise.resolve(),
263
+ status: 'pending',
264
+ };
265
+ routerHydrationRecords.set(router, hydrationRecord);
266
+ try {
267
+ hydrationRecord.promise = hydrateTanstackRouter(router)
268
+ .then(value => preloadHydratedRouteComponents(router).then(() => value))
269
+ .then(
270
+ value => {
271
+ hydrationRecord.status = 'fulfilled';
259
272
  return value;
260
- })
261
- .then(
262
- value => {
263
- hydrationRecord.status = 'fulfilled';
264
- return value;
265
- },
266
- error => {
267
- hydrationRecord.status = 'rejected';
268
- hydrationRecord.error = error;
269
- throw error;
270
- },
271
- );
272
- } catch (error) {
273
- hydrationRecord.status = 'rejected';
274
- hydrationRecord.error = error;
275
- hydrationRecord.promise = Promise.reject(error);
276
- hydrationRecord.promise.catch(() => {});
277
- }
273
+ },
274
+ error => {
275
+ hydrationRecord.status = 'rejected';
276
+ hydrationRecord.error = error;
277
+ throw error;
278
+ },
279
+ );
280
+ } catch (error) {
281
+ hydrationRecord.status = 'rejected';
282
+ hydrationRecord.error = error;
283
+ hydrationRecord.promise = Promise.reject(error);
284
+ hydrationRecord.promise.catch(() => {});
278
285
  }
279
286
  return hydrationRecord;
280
287
  }
@@ -466,20 +473,25 @@ export const tanstackRouterPlugin = (
466
473
  return cachedRouter;
467
474
  };
468
475
 
469
- api.onBeforeRender(async context => {
476
+ api.onBeforeRender(context => {
470
477
  const mergedConfig = getMergedConfig();
471
478
  if (
472
479
  typeof window !== 'undefined' &&
473
- (window as { _SSR_DATA?: unknown })._SSR_DATA &&
480
+ (window as { _SSR_DATA?: unknown })._SSR_DATA !== undefined &&
474
481
  mergedConfig.unstable_reloadOnURLMismatch
475
482
  ) {
476
483
  const { ssrContext } = context;
477
484
  const currentPathname = normalizePathname(window.location.pathname);
478
485
  const initialPathname =
479
- ssrContext?.request?.pathname &&
480
- normalizePathname(ssrContext.request.pathname);
481
-
482
- if (initialPathname && initialPathname !== currentPathname) {
486
+ typeof ssrContext?.request?.pathname === 'string'
487
+ ? normalizePathname(ssrContext.request.pathname)
488
+ : undefined;
489
+
490
+ if (
491
+ initialPathname !== undefined &&
492
+ initialPathname !== '' &&
493
+ initialPathname !== currentPathname
494
+ ) {
483
495
  const errorMsg = `The initial URL ${initialPathname} and the URL ${currentPathname} to be hydrated do not match, reload.`;
484
496
  console.error(errorMsg);
485
497
  window.location.reload();
@@ -497,14 +509,14 @@ export const tanstackRouterPlugin = (
497
509
  const hasSSRBootstrap =
498
510
  typeof window !== 'undefined' &&
499
511
  Boolean((window as WindowWithTanstackSsr).$_TSR);
500
- if (hasSSRBootstrap && getRouteObjects().length) {
512
+ if (hasSSRBootstrap && getRouteObjects().length > 0) {
501
513
  const runtimeContext = context as TInternalRuntimeContext;
502
514
  const router = getRouter(
503
515
  runtimeContext,
504
516
  getClientBasename(runtimeContext),
505
517
  );
506
- if (router) {
507
- await getTanstackSsrHydrationPromise(router);
518
+ if (router !== undefined && router !== null) {
519
+ return getTanstackSsrHydrationPromise(router).then(() => undefined);
508
520
  }
509
521
  }
510
522
 
@@ -512,7 +524,7 @@ export const tanstackRouterPlugin = (
512
524
  });
513
525
 
514
526
  api.wrapRoot(App => {
515
- if (!getRouteObjects().length) {
527
+ if (getRouteObjects().length === 0) {
516
528
  return App;
517
529
  }
518
530
 
@@ -523,17 +535,16 @@ export const tanstackRouterPlugin = (
523
535
 
524
536
  const _basename = getClientBasename(runtimeContext);
525
537
 
526
- const routeTree = useMemo(() => {
527
- return getRouteTree();
528
- }, []);
538
+ const routeTree = useMemo(() => getRouteTree(), []);
529
539
 
530
540
  if (!routeTree) {
531
541
  return App ? <App /> : null;
532
542
  }
533
543
 
534
- const router = useMemo(() => {
535
- return getRouter(runtimeContext, _basename);
536
- }, [_basename, routeTree, runtimeContext]);
544
+ const router = useMemo(
545
+ () => getRouter(runtimeContext, _basename),
546
+ [_basename, routeTree, runtimeContext],
547
+ );
537
548
  if (!router) {
538
549
  return App ? <App /> : null;
539
550
  }
@@ -28,7 +28,7 @@ function resolvePreloadFromPrefetch(
28
28
  return prefetch;
29
29
  }
30
30
 
31
- return preload;
31
+ return 'viewport';
32
32
  }
33
33
 
34
34
  export type LinkProps<
@@ -164,7 +164,7 @@ function wrapRouteComponentWithModernContext(
164
164
  route.options.component = withModernRouteMatchContext(
165
165
  component,
166
166
  routeMatchId,
167
- );
167
+ ) as typeof route.options.component;
168
168
  }
169
169
  }
170
170
 
@@ -974,7 +974,7 @@ export function createRouteTreeFromModernRoutes(
974
974
  rootRoute.options.component = withModernRouteMatchContext(
975
975
  rootComponent,
976
976
  rootRouteId,
977
- );
977
+ ) as typeof rootRoute.options.component;
978
978
  }
979
979
 
980
980
  const topLevel = rootModern
@@ -1056,7 +1056,7 @@ export function createRouteTreeFromRouteObjects(
1056
1056
  rootRoute.options.component = withModernRouteMatchContext(
1057
1057
  rootComponent,
1058
1058
  rootRouteId,
1059
- );
1059
+ ) as typeof rootRoute.options.component;
1060
1060
  }
1061
1061
 
1062
1062
  const topLevel = rootLikeRoute
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import React from 'react';
3
- import { Link } from '../../src/runtime/prefetchLink';
3
+ import { Link, NavLink } from '../../src/runtime/prefetchLink';
4
4
 
5
5
  type MockLinkProps = {
6
6
  children?: React.ReactNode;
@@ -21,23 +21,59 @@ describe('tanstack prefetch link adapter', () => {
21
21
  capturedPreloads = [];
22
22
  });
23
23
 
24
- it('maps viewport prefetch to TanStack preload', () => {
24
+ it('defaults TanStack preload to viewport', () => {
25
+ render(<Link to="/settings">Settings</Link>);
26
+
27
+ expect(capturedPreloads).toEqual(['viewport']);
28
+ });
29
+
30
+ it('preserves explicit preload', () => {
25
31
  render(
26
- <Link to="/settings" prefetch="viewport">
32
+ <Link to="/settings" prefetch="render" preload="intent">
27
33
  Settings
28
34
  </Link>,
29
35
  );
30
36
 
31
- expect(capturedPreloads).toEqual(['viewport']);
37
+ expect(capturedPreloads).toEqual(['intent']);
32
38
  });
33
39
 
34
- it('does not override explicit preload', () => {
40
+ it('preserves explicit disabled preload', () => {
35
41
  render(
36
- <Link to="/settings" prefetch="viewport" preload="intent">
42
+ <Link to="/settings" prefetch="render" preload={false}>
37
43
  Settings
38
44
  </Link>,
39
45
  );
40
46
 
41
- expect(capturedPreloads).toEqual(['intent']);
47
+ expect(capturedPreloads).toEqual([false]);
48
+ });
49
+
50
+ it('maps none prefetch to disabled TanStack preload', () => {
51
+ render(
52
+ <Link to="/settings" prefetch="none">
53
+ Settings
54
+ </Link>,
55
+ );
56
+
57
+ expect(capturedPreloads).toEqual([false]);
58
+ });
59
+
60
+ it.each([
61
+ 'intent',
62
+ 'render',
63
+ 'viewport',
64
+ ] as const)('maps %s prefetch to TanStack preload', prefetch => {
65
+ render(
66
+ <Link to="/settings" prefetch={prefetch}>
67
+ Settings
68
+ </Link>,
69
+ );
70
+
71
+ expect(capturedPreloads).toEqual([prefetch]);
72
+ });
73
+
74
+ it('defaults NavLink preload to viewport', () => {
75
+ render(<NavLink to="/settings">Settings</NavLink>);
76
+
77
+ expect(capturedPreloads).toEqual(['viewport']);
42
78
  });
43
79
  });