@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.8 → 3.2.0-ultramodern.81

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.
@@ -1,5 +1,6 @@
1
1
  import "node:module";
2
2
  import { createRootRoute, createRoute, notFound, redirect } from "@tanstack/react-router";
3
+ import { createElement } from "react";
3
4
  import { DefaultNotFound } from "./DefaultNotFound.mjs";
4
5
  import { isTanstackRscPayloadNavigationEnabled, loadTanstackRscRouteData } from "./rsc/payloadRouter.mjs";
5
6
  function createTanstackRoute(options) {
@@ -55,6 +56,33 @@ function normalizeModernLoaderResponse(result) {
55
56
  }
56
57
  return normalizeModernLoaderResult(result);
57
58
  }
59
+ function pickRouteModuleComponent(routeModule) {
60
+ if ('function' == typeof routeModule || routeModule && 'object' == typeof routeModule && '$$typeof' in routeModule) return routeModule;
61
+ if (!routeModule || 'object' != typeof routeModule) return;
62
+ const module = routeModule;
63
+ const component = module.default || module.Component;
64
+ if ('function' == typeof component || component && 'object' == typeof component && '$$typeof' in component) return component;
65
+ }
66
+ function createServerLazyImportComponent(lazyImport, fallbackComponent) {
67
+ if ("u" > typeof document) return fallbackComponent;
68
+ let resolvedComponent;
69
+ let pendingLoad;
70
+ const load = async ()=>{
71
+ if (resolvedComponent) return resolvedComponent;
72
+ const routeModule = await lazyImport();
73
+ const component = pickRouteModuleComponent(routeModule);
74
+ if (component) resolvedComponent = component;
75
+ return resolvedComponent;
76
+ };
77
+ const Component = (props)=>{
78
+ if (resolvedComponent) return createElement(resolvedComponent, props);
79
+ pendingLoad ||= load();
80
+ throw pendingLoad;
81
+ };
82
+ Component.load = load;
83
+ Component.preload = load;
84
+ return Component;
85
+ }
58
86
  function isAbsoluteUrl(value) {
59
87
  try {
60
88
  new URL(value);
@@ -127,7 +155,7 @@ function wrapModernLoader(modernRoute, modernLoader, revalidationState, options
127
155
  const signal = ctx?.abortController?.signal || ctx?.signal || new AbortController().signal;
128
156
  const baseRequest = ctx?.context?.request instanceof Request ? ctx.context.request : void 0;
129
157
  const href = 'string' == typeof ctx?.location ? ctx.location : ctx?.location?.publicHref || ctx?.location?.href || ctx?.location?.url?.href || '';
130
- const request = baseRequest ? new Request(baseRequest, {
158
+ const request = void 0 !== baseRequest ? new Request(baseRequest, {
131
159
  signal
132
160
  }) : createModernRequest(href, signal);
133
161
  const params = mapParamsForModernLoader({
@@ -192,7 +220,7 @@ function wrapRouteObjectLoader(route, revalidationState, options = {}) {
192
220
  const signal = ctx?.abortController?.signal || ctx?.signal || new AbortController().signal;
193
221
  const baseRequest = ctx?.context?.request instanceof Request ? ctx.context.request : void 0;
194
222
  const href = 'string' == typeof ctx?.location ? ctx.location : ctx?.location?.publicHref || ctx?.location?.href || ctx?.location?.url?.href || '';
195
- const request = baseRequest ? new Request(baseRequest, {
223
+ const request = void 0 !== baseRequest ? new Request(baseRequest, {
196
224
  signal
197
225
  }) : createModernRequest(href, signal);
198
226
  const params = mapParamsForRouteObjectLoader({
@@ -229,10 +257,18 @@ function wrapRouteObjectLoader(route, revalidationState, options = {}) {
229
257
  }
230
258
  function toRouteComponent(routeObject) {
231
259
  const route = routeObject;
260
+ const lazyImport = 'function' == typeof route.lazyImport ? route.lazyImport : void 0;
261
+ const fallbackComponent = route.Component ? route.Component : route.element ? ()=>route.element : void 0;
262
+ if (lazyImport && fallbackComponent) return createServerLazyImportComponent(lazyImport, fallbackComponent);
232
263
  if (route.Component) return route.Component;
233
264
  const element = route.element;
234
265
  if (element) return ()=>element;
235
266
  }
267
+ function toModernRouteComponent(route) {
268
+ const component = route.component || void 0;
269
+ if ('function' == typeof route.lazyImport && component) return createServerLazyImportComponent(route.lazyImport, component);
270
+ return component;
271
+ }
236
272
  function toErrorComponent(routeObject) {
237
273
  const route = routeObject;
238
274
  if (route.ErrorBoundary) return route.ErrorBoundary;
@@ -313,7 +349,7 @@ function createRouteFromModernRoute(opts) {
313
349
  const stableFallbackId = modernId || route._component || route.filename || route.data || ('function' == typeof route.loader ? route.id : void 0);
314
350
  const pendingComponent = route.loading || route.pendingComponent;
315
351
  const errorComponent = route.error || route.errorComponent;
316
- const component = route.component;
352
+ const component = toModernRouteComponent(route);
317
353
  const modernLoader = route.loader;
318
354
  const modernAction = route.action;
319
355
  const modernShouldRevalidate = route.shouldRevalidate;
@@ -360,7 +396,7 @@ function createRouteFromModernRoute(opts) {
360
396
  }
361
397
  function createRouteTreeFromModernRoutes(routes, options = {}) {
362
398
  const rootModern = routes.find((r)=>r && 'nested' === r.type && r.isRoot);
363
- const rootComponent = rootModern?.component;
399
+ const rootComponent = rootModern ? toModernRouteComponent(rootModern) : void 0;
364
400
  const pendingComponent = rootModern?.loading;
365
401
  const errorComponent = rootModern?.error;
366
402
  const rootLoader = rootModern?.loader;
@@ -0,0 +1 @@
1
+ export { default, tanstackRouterPlugin, } from './plugin.node';
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "tanstack-router"
20
20
  ],
21
- "version": "3.2.0-ultramodern.8",
21
+ "version": "3.2.0-ultramodern.81",
22
22
  "engines": {
23
23
  "node": ">=20"
24
24
  },
@@ -86,15 +86,15 @@
86
86
  },
87
87
  "dependencies": {
88
88
  "@swc/helpers": "^0.5.21",
89
- "@tanstack/react-router": "1.170.1",
90
- "@tanstack/router-core": "1.170.1",
91
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.8",
92
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.8",
93
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.8",
94
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.8"
89
+ "@tanstack/react-router": "1.170.8",
90
+ "@tanstack/router-core": "1.171.6",
91
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.81",
92
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.81",
93
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.81",
94
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.81"
95
95
  },
96
96
  "peerDependencies": {
97
- "@modern-js/runtime": "3.2.0-ultramodern.8",
97
+ "@modern-js/runtime": "3.2.0-ultramodern.81",
98
98
  "react": "^19.2.6",
99
99
  "react-dom": "^19.2.6"
100
100
  },
@@ -103,14 +103,14 @@
103
103
  "@tanstack/history": "1.162.0",
104
104
  "@testing-library/dom": "^10.4.1",
105
105
  "@testing-library/react": "^16.3.2",
106
- "@types/node": "^25.8.0",
107
- "@types/react": "^19.2.14",
106
+ "@types/node": "^25.9.1",
107
+ "@types/react": "^19.2.15",
108
108
  "@types/react-dom": "^19.2.3",
109
- "@typescript/native-preview": "7.0.0-dev.20260516.1",
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.8",
113
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.8",
112
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.81",
113
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.81",
114
114
  "@scripts/rstest-config": "2.66.0"
115
115
  },
116
116
  "sideEffects": false,
@@ -182,6 +182,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
182
182
  const statements: string[] = [];
183
183
 
184
184
  const loaderImportMap = new Map<string, string>();
185
+ const usedRouteVarNames = new Set<string>();
185
186
  let loaderIndex = 0;
186
187
  let routeIndex = 0;
187
188
 
@@ -242,10 +243,20 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
242
243
  return { loaderName: importName, actionName };
243
244
  };
244
245
 
246
+ const reserveRouteVarName = (preferred: string) => {
247
+ let candidate = preferred;
248
+ let suffix = 1;
249
+ while (usedRouteVarNames.has(candidate)) {
250
+ candidate = `${preferred}_${suffix++}`;
251
+ }
252
+ usedRouteVarNames.add(candidate);
253
+ return candidate;
254
+ };
255
+
245
256
  const createRouteVarName = (route: NestedRouteForCli | PageRoute) => {
246
257
  const id = (route as any).id as string | undefined;
247
258
  const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
248
- return `route_${base}`;
259
+ return reserveRouteVarName(`route_${base}`);
249
260
  };
250
261
 
251
262
  const buildRoute = async (opts: {
@@ -296,18 +307,27 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
296
307
  routeOpts.push(staticDataSnippet);
297
308
  }
298
309
 
299
- statements.push(
300
- `const ${varName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
301
- );
302
-
303
310
  const children = (route as any).children as
304
311
  | Array<NestedRouteForCli | PageRoute>
305
312
  | undefined;
313
+ const hasChildren = Boolean(children && children.length > 0);
314
+ const routeCtorVarName = hasChildren
315
+ ? reserveRouteVarName(`${varName}__base`)
316
+ : varName;
317
+
318
+ statements.push(
319
+ `const ${routeCtorVarName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
320
+ );
321
+
306
322
  if (children && children.length > 0) {
307
323
  const childVars = await Promise.all(
308
- children.map(child => buildRoute({ parentVar: varName, route: child })),
324
+ children.map(child =>
325
+ buildRoute({ parentVar: routeCtorVarName, route: child }),
326
+ ),
327
+ );
328
+ statements.push(
329
+ `const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
309
330
  );
310
- statements.push(`${varName}.addChildren([${childVars.join(', ')}]);`);
311
331
  }
312
332
 
313
333
  return varName;
@@ -370,7 +390,7 @@ function isRedirectResponse(res: Response) {
370
390
  }
371
391
 
372
392
  function throwTanstackRedirect(location: string) {
373
- const target = location || '/';
393
+ const target = location.length > 0 ? location : '/';
374
394
  try {
375
395
  void new URL(target);
376
396
  throw redirect({ href: target });
@@ -396,21 +416,87 @@ function createRouteStaticData(opts: {
396
416
  modernRouteAction?: unknown;
397
417
  modernRouteLoader?: unknown;
398
418
  }) {
399
- const staticData: Record<string, unknown> = {};
419
+ const staticData: {
420
+ modernRouteId?: string;
421
+ modernRouteAction?: unknown;
422
+ modernRouteLoader?: unknown;
423
+ } = {};
400
424
 
401
- if (opts.modernRouteId) {
425
+ if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
402
426
  staticData.modernRouteId = opts.modernRouteId;
403
427
  }
404
428
 
405
- if (opts.modernRouteLoader) {
429
+ if (typeof opts.modernRouteLoader !== 'undefined') {
406
430
  staticData.modernRouteLoader = opts.modernRouteLoader;
407
431
  }
408
432
 
409
- if (opts.modernRouteAction) {
433
+ if (typeof opts.modernRouteAction !== 'undefined') {
410
434
  staticData.modernRouteAction = opts.modernRouteAction;
411
435
  }
412
436
 
413
- return Object.keys(staticData).length > 0 ? staticData : undefined;
437
+ return staticData;
438
+ }
439
+
440
+ function getLoaderSignal(ctx: any): AbortSignal {
441
+ const abortSignal = ctx?.abortController?.signal;
442
+ if (abortSignal instanceof AbortSignal) {
443
+ return abortSignal;
444
+ }
445
+ if (ctx?.signal instanceof AbortSignal) {
446
+ return ctx.signal;
447
+ }
448
+ return new AbortController().signal;
449
+ }
450
+
451
+ function getLoaderHref(ctx: any): string {
452
+ if (typeof ctx?.location === 'string') {
453
+ return ctx.location;
454
+ }
455
+
456
+ const publicHref = ctx?.location?.publicHref;
457
+ if (typeof publicHref === 'string') {
458
+ return publicHref;
459
+ }
460
+
461
+ const href = ctx?.location?.href;
462
+ if (typeof href === 'string') {
463
+ return href;
464
+ }
465
+
466
+ const urlHref = ctx?.location?.url?.href;
467
+ return typeof urlHref === 'string' ? urlHref : '';
468
+ }
469
+
470
+ function getLoaderParams(ctx: any): Record<string, string> {
471
+ return typeof ctx?.params === 'object' && ctx.params !== null ? ctx.params : {};
472
+ }
473
+
474
+ function handleModernLoaderResult<LoaderResult>(result: LoaderResult): LoaderResult {
475
+ if (isResponse(result)) {
476
+ if (isRedirectResponse(result)) {
477
+ const location = result.headers.get('Location') ?? '/';
478
+ throwTanstackRedirect(location);
479
+ }
480
+ if (result.status === 404) {
481
+ throw notFound();
482
+ }
483
+ }
484
+
485
+ return result;
486
+ }
487
+
488
+ function handleModernLoaderError(err: unknown): never {
489
+ if (isResponse(err)) {
490
+ if (isRedirectResponse(err)) {
491
+ const location = err.headers.get('Location') ?? '/';
492
+ throwTanstackRedirect(location);
493
+ }
494
+ if (err.status === 404) {
495
+ throw notFound();
496
+ }
497
+ }
498
+
499
+ throw err;
414
500
  }
415
501
 
416
502
  function modernLoaderToTanstack<TLoader extends (args: any) => any>(
@@ -419,57 +505,31 @@ function modernLoaderToTanstack<TLoader extends (args: any) => any>(
419
505
  ) {
420
506
  type LoaderResult = Awaited<ReturnType<TLoader>>;
421
507
 
422
- return async (ctx: any): Promise<LoaderResult> => {
508
+ return (ctx: any): Promise<LoaderResult> => {
423
509
  try {
424
- const signal: AbortSignal =
425
- ctx?.abortController?.signal ||
426
- ctx?.signal ||
427
- new AbortController().signal;
510
+ const signal = getLoaderSignal(ctx);
428
511
  const baseRequest: Request | undefined =
429
512
  ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
430
513
 
431
- const href =
432
- typeof ctx?.location === 'string'
433
- ? ctx.location
434
- : ctx?.location?.publicHref ||
435
- ctx?.location?.href ||
436
- ctx?.location?.url?.href ||
437
- '';
514
+ const href = getLoaderHref(ctx);
438
515
 
439
- const request = baseRequest
516
+ const request = baseRequest !== undefined
440
517
  ? new Request(baseRequest, { signal })
441
518
  : new Request(href, { signal });
442
519
 
443
- const params = mapParamsForModernLoader(ctx?.params || {}, opts.hasSplat);
444
-
445
- const result = await (modernLoader as any)({
446
- request,
447
- params,
448
- context: ctx?.context?.requestContext,
449
- });
450
-
451
- if (isResponse(result)) {
452
- if (isRedirectResponse(result)) {
453
- const location = result.headers.get('Location') || '/';
454
- throwTanstackRedirect(location);
455
- }
456
- if (result.status === 404) {
457
- throw notFound();
458
- }
459
- }
520
+ const params = mapParamsForModernLoader(getLoaderParams(ctx), opts.hasSplat);
460
521
 
461
- return result as LoaderResult;
522
+ return Promise.resolve(
523
+ (modernLoader as any)({
524
+ request,
525
+ params,
526
+ context: ctx?.context?.requestContext,
527
+ }),
528
+ )
529
+ .then((result: LoaderResult) => handleModernLoaderResult(result))
530
+ .catch(handleModernLoaderError);
462
531
  } catch (err) {
463
- if (isResponse(err)) {
464
- if (isRedirectResponse(err)) {
465
- const location = err.headers.get('Location') || '/';
466
- throwTanstackRedirect(location);
467
- }
468
- if (err.status === 404) {
469
- throw notFound();
470
- }
471
- }
472
- throw err;
532
+ handleModernLoaderError(err);
473
533
  }
474
534
  };
475
535
  }
@@ -26,9 +26,9 @@ import {
26
26
  createRouter,
27
27
  RouterProvider,
28
28
  } from '@tanstack/react-router';
29
- import { attachRouterServerSsrUtils } from '@tanstack/react-router/ssr/server';
29
+ import { attachRouterServerSsrUtils } from '@tanstack/router-core/ssr/server';
30
30
  import type React from 'react';
31
- import { Suspense, useContext } from 'react';
31
+ import { useContext } from 'react';
32
32
  import { createModernBasepathRewrite } from './basepathRewrite';
33
33
  import {
34
34
  modifyRoutes as modifyRoutesHook,
@@ -119,6 +119,17 @@ type PreloadableRouteComponent = {
119
119
  preload?: (props?: Record<string, unknown>) => Promise<unknown> | unknown;
120
120
  };
121
121
 
122
+ type ReactLazyRouteComponent = {
123
+ _init?: (payload: unknown) => unknown;
124
+ _payload?: unknown;
125
+ };
126
+
127
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
128
+ return Boolean(
129
+ value && typeof (value as PromiseLike<unknown>).then === 'function',
130
+ );
131
+ }
132
+
122
133
  type TanstackRouterWithServerSsr = AnyRouter & {
123
134
  resolveRedirect?: (redirect: Response) => Response;
124
135
  routesById?: Record<string, RouterRouteWithOptions>;
@@ -149,7 +160,37 @@ function isPreloadableRouteComponent(
149
160
  );
150
161
  }
151
162
 
163
+ function isReactLazyRouteComponent(
164
+ component: unknown,
165
+ ): component is ReactLazyRouteComponent {
166
+ return (
167
+ Boolean(component) &&
168
+ typeof component === 'object' &&
169
+ typeof (component as ReactLazyRouteComponent)._init === 'function' &&
170
+ '_payload' in component
171
+ );
172
+ }
173
+
174
+ async function preloadReactLazyRouteComponent(
175
+ component: ReactLazyRouteComponent,
176
+ ) {
177
+ try {
178
+ component._init?.(component._payload);
179
+ } catch (thrown) {
180
+ if (!isPromiseLike(thrown)) {
181
+ throw thrown;
182
+ }
183
+ await thrown;
184
+ component._init?.(component._payload);
185
+ }
186
+ }
187
+
152
188
  async function preloadRouteComponent(component: unknown) {
189
+ if (isReactLazyRouteComponent(component)) {
190
+ await preloadReactLazyRouteComponent(component);
191
+ return;
192
+ }
193
+
153
194
  if (!isPreloadableRouteComponent(component)) {
154
195
  return;
155
196
  }
@@ -458,9 +499,12 @@ export const tanstackRouterPlugin = (
458
499
  context.ssrContext?.response.status(tanstackRouter.state.statusCode);
459
500
 
460
501
  await serverRouter.serverSsr?.dehydrate?.();
461
- await waitForRouterSerialization(serverRouter);
462
502
 
463
503
  if (isRSCNavigation) {
504
+ // RSC navigations consume the server payload directly. Normal HTML SSR
505
+ // emits the buffered bootstrap script below and must not wait here
506
+ // because Modern's non-streaming hook has not rendered the app yet.
507
+ await waitForRouterSerialization(serverRouter);
464
508
  setTanstackRscServerPayload(
465
509
  createTanstackRscServerPayload(serverRouter, {
466
510
  omitClientLoaderData: true,
@@ -516,9 +560,7 @@ export const tanstackRouterPlugin = (
516
560
  }
517
561
 
518
562
  const routerWrapper = (
519
- <Suspense fallback={null}>
520
- <RouterProvider router={router as AnyRouter} />
521
- </Suspense>
563
+ <RouterProvider router={router as AnyRouter} />
522
564
  );
523
565
 
524
566
  return App ? <App>{routerWrapper}</App> : routerWrapper;
@@ -25,7 +25,6 @@ import {
25
25
  useRouter,
26
26
  } from '@tanstack/react-router';
27
27
  import { RouterClient } from '@tanstack/react-router/ssr/client';
28
- import * as React from 'react';
29
28
  import { useContext, useMemo } from 'react';
30
29
  import { createModernBasepathRewrite } from './basepathRewrite';
31
30
  import {
@@ -41,6 +40,7 @@ import {
41
40
  applyRouterRuntimeState,
42
41
  type RouterLifecycleContext,
43
42
  } from './lifecycle';
43
+ import { Link } from './prefetchLink';
44
44
  import { createRouteTreeFromRouteObjects } from './routeTree';
45
45
  import { getTanstackRscSerializationAdapters } from './rsc/client';
46
46
  import type { RouterConfig } from './types';
@@ -184,6 +184,7 @@ export const tanstackRouterPlugin = (
184
184
  }
185
185
 
186
186
  context.router = {
187
+ Link,
187
188
  useMatches,
188
189
  useLocation,
189
190
  useNavigate,
@@ -368,9 +369,7 @@ export const tanstackRouterPlugin = (
368
369
  }
369
370
 
370
371
  const RouterContent = hasSSRBootstrap ? (
371
- <React.Suspense fallback={null}>
372
- <RouterClient router={router} />
373
- </React.Suspense>
372
+ <RouterClient router={router} />
374
373
  ) : (
375
374
  <RouterProvider router={router} />
376
375
  );
@@ -0,0 +1,4 @@
1
+ export {
2
+ default,
3
+ tanstackRouterPlugin,
4
+ } from './plugin.node';