@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.
- package/dist/cjs/cli/tanstackTypes.js +101 -51
- package/dist/cjs/runtime/plugin.js +4 -5
- package/dist/cjs/runtime/plugin.node.js +25 -10
- package/dist/cjs/runtime/plugin.worker.js +49 -0
- package/dist/cjs/runtime/routeTree.js +40 -4
- package/dist/esm/cli/tanstackTypes.mjs +101 -51
- package/dist/esm/runtime/plugin.mjs +8 -9
- package/dist/esm/runtime/plugin.node.mjs +26 -11
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/routeTree.mjs +40 -4
- package/dist/esm-node/cli/tanstackTypes.mjs +101 -51
- package/dist/esm-node/runtime/plugin.mjs +8 -9
- package/dist/esm-node/runtime/plugin.node.mjs +26 -11
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/routeTree.mjs +40 -4
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- package/package.json +13 -13
- package/src/cli/tanstackTypes.ts +114 -54
- package/src/runtime/plugin.node.tsx +48 -6
- package/src/runtime/plugin.tsx +3 -4
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/routeTree.ts +109 -8
- package/tests/router/routeTree.test.ts +72 -1
- package/tests/router/tanstackTypes.test.ts +64 -0
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
90
|
-
"@tanstack/router-core": "1.
|
|
91
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.
|
|
92
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.
|
|
93
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.
|
|
94
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.
|
|
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.
|
|
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.
|
|
107
|
-
"@types/react": "^19.2.
|
|
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.
|
|
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.
|
|
113
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.
|
|
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,
|
package/src/cli/tanstackTypes.ts
CHANGED
|
@@ -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 =>
|
|
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:
|
|
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
|
|
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
|
|
508
|
+
return (ctx: any): Promise<LoaderResult> => {
|
|
423
509
|
try {
|
|
424
|
-
const signal
|
|
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
|
|
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
|
|
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
|
-
|
|
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/
|
|
29
|
+
import { attachRouterServerSsrUtils } from '@tanstack/router-core/ssr/server';
|
|
30
30
|
import type React from 'react';
|
|
31
|
-
import {
|
|
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
|
-
<
|
|
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;
|
package/src/runtime/plugin.tsx
CHANGED
|
@@ -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
|
-
<
|
|
372
|
-
<RouterClient router={router} />
|
|
373
|
-
</React.Suspense>
|
|
372
|
+
<RouterClient router={router} />
|
|
374
373
|
) : (
|
|
375
374
|
<RouterProvider router={router} />
|
|
376
375
|
);
|