@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.9 → 3.2.0-ultramodern.91
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/index.js +12 -0
- package/dist/cjs/cli/routeSplitting.js +83 -0
- package/dist/cjs/cli/tanstackTypes.js +146 -58
- package/dist/cjs/runtime/index.js +38 -6
- package/dist/cjs/runtime/plugin.js +6 -5
- package/dist/cjs/runtime/plugin.node.js +27 -10
- package/dist/cjs/runtime/plugin.worker.js +49 -0
- package/dist/cjs/runtime/routeTree.js +55 -4
- package/dist/cjs/runtime/types.js +27 -1
- package/dist/esm/cli/index.mjs +4 -1
- package/dist/esm/cli/routeSplitting.mjs +43 -0
- package/dist/esm/cli/tanstackTypes.mjs +146 -58
- package/dist/esm/runtime/index.mjs +2 -1
- package/dist/esm/runtime/plugin.mjs +10 -9
- package/dist/esm/runtime/plugin.node.mjs +28 -11
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/routeTree.mjs +55 -4
- package/dist/esm/runtime/types.mjs +7 -0
- package/dist/esm-node/cli/index.mjs +4 -1
- package/dist/esm-node/cli/routeSplitting.mjs +44 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +146 -58
- package/dist/esm-node/runtime/index.mjs +2 -1
- package/dist/esm-node/runtime/plugin.mjs +10 -9
- package/dist/esm-node/runtime/plugin.node.mjs +28 -11
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/routeTree.mjs +55 -4
- package/dist/esm-node/runtime/types.mjs +7 -0
- package/dist/types/cli/index.d.ts +4 -0
- package/dist/types/cli/routeSplitting.d.ts +29 -0
- package/dist/types/runtime/index.d.ts +3 -1
- package/dist/types/runtime/plugin.d.ts +1 -1
- package/dist/types/runtime/plugin.node.d.ts +1 -1
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- package/dist/types/runtime/types.d.ts +7 -0
- package/package.json +14 -14
- package/src/cli/index.ts +17 -0
- package/src/cli/routeSplitting.ts +81 -0
- package/src/cli/tanstackTypes.ts +216 -67
- package/src/runtime/index.tsx +13 -1
- package/src/runtime/plugin.node.tsx +54 -7
- package/src/runtime/plugin.tsx +8 -5
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/routeTree.ts +125 -8
- package/src/runtime/types.ts +13 -0
- package/tests/router/cli.test.ts +239 -0
- package/tests/router/fastDefaults.test.ts +25 -0
- package/tests/router/routeTree.test.ts +193 -1
- package/tests/router/tanstackTypes.test.ts +184 -0
package/src/cli/tanstackTypes.ts
CHANGED
|
@@ -86,6 +86,17 @@ function pickModernLoaderModule(route: NestedRouteForCli | PageRoute) {
|
|
|
86
86
|
return { loaderPath, inline };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function pickRouteSearchContractModules(route: NestedRouteForCli | PageRoute) {
|
|
90
|
+
const validateSearchPath = (route as any).validateSearch;
|
|
91
|
+
const loaderDepsPath = (route as any).loaderDeps;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
validateSearchPath:
|
|
95
|
+
typeof validateSearchPath === 'string' ? validateSearchPath : null,
|
|
96
|
+
loaderDepsPath: typeof loaderDepsPath === 'string' ? loaderDepsPath : null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
function isPathlessLayout(route: NestedRouteForCli | PageRoute) {
|
|
90
101
|
return (
|
|
91
102
|
(route as any).type === 'nested' &&
|
|
@@ -182,9 +193,29 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
182
193
|
const statements: string[] = [];
|
|
183
194
|
|
|
184
195
|
const loaderImportMap = new Map<string, string>();
|
|
196
|
+
const searchContractImportMap = new Map<string, string>();
|
|
197
|
+
const usedRouteVarNames = new Set<string>();
|
|
185
198
|
let loaderIndex = 0;
|
|
199
|
+
let validateSearchIndex = 0;
|
|
200
|
+
let loaderDepsIndex = 0;
|
|
186
201
|
let routeIndex = 0;
|
|
187
202
|
|
|
203
|
+
const resolveRouteModuleNoExt = async (aliasedNoExtPath: string) => {
|
|
204
|
+
const prefix = `${appContext.internalSrcAlias}/`;
|
|
205
|
+
let absNoExt: string;
|
|
206
|
+
if (aliasedNoExtPath.startsWith(prefix)) {
|
|
207
|
+
const rel = aliasedNoExtPath.slice(prefix.length);
|
|
208
|
+
absNoExt = path.join(appContext.srcDirectory, rel);
|
|
209
|
+
} else if (path.isAbsolute(aliasedNoExtPath)) {
|
|
210
|
+
absNoExt = aliasedNoExtPath;
|
|
211
|
+
} else {
|
|
212
|
+
// Unknown format; treat as already relative to src.
|
|
213
|
+
absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return resolveFileNoExt(absNoExt);
|
|
217
|
+
};
|
|
218
|
+
|
|
188
219
|
const getImportNamesForLoader = async (
|
|
189
220
|
aliasedNoExtPath: string,
|
|
190
221
|
inline: boolean,
|
|
@@ -201,19 +232,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
201
232
|
};
|
|
202
233
|
}
|
|
203
234
|
|
|
204
|
-
const
|
|
205
|
-
let absNoExt: string;
|
|
206
|
-
if (aliasedNoExtPath.startsWith(prefix)) {
|
|
207
|
-
const rel = aliasedNoExtPath.slice(prefix.length);
|
|
208
|
-
absNoExt = path.join(appContext.srcDirectory, rel);
|
|
209
|
-
} else if (path.isAbsolute(aliasedNoExtPath)) {
|
|
210
|
-
absNoExt = aliasedNoExtPath;
|
|
211
|
-
} else {
|
|
212
|
-
// Unknown format; treat as already relative to src.
|
|
213
|
-
absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const resolvedNoExt = await resolveFileNoExt(absNoExt);
|
|
235
|
+
const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
|
|
217
236
|
if (!resolvedNoExt) {
|
|
218
237
|
return null;
|
|
219
238
|
}
|
|
@@ -242,10 +261,49 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
242
261
|
return { loaderName: importName, actionName };
|
|
243
262
|
};
|
|
244
263
|
|
|
264
|
+
const getImportNameForSearchContract = async (
|
|
265
|
+
aliasedNoExtPath: string,
|
|
266
|
+
exportName: 'validateSearch' | 'loaderDeps',
|
|
267
|
+
) => {
|
|
268
|
+
const key = `${exportName}:${aliasedNoExtPath}`;
|
|
269
|
+
const existing = searchContractImportMap.get(key);
|
|
270
|
+
if (existing) {
|
|
271
|
+
return existing;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
|
|
275
|
+
if (!resolvedNoExt) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const relImport = normalizeRelativeImport(
|
|
280
|
+
path.relative(outDir, resolvedNoExt),
|
|
281
|
+
);
|
|
282
|
+
const importName =
|
|
283
|
+
exportName === 'validateSearch'
|
|
284
|
+
? `validateSearch_${validateSearchIndex++}`
|
|
285
|
+
: `loaderDeps_${loaderDepsIndex++}`;
|
|
286
|
+
imports.push(
|
|
287
|
+
`import { ${exportName} as ${importName} } from ${quote(relImport)};`,
|
|
288
|
+
);
|
|
289
|
+
searchContractImportMap.set(key, importName);
|
|
290
|
+
return importName;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const reserveRouteVarName = (preferred: string) => {
|
|
294
|
+
let candidate = preferred;
|
|
295
|
+
let suffix = 1;
|
|
296
|
+
while (usedRouteVarNames.has(candidate)) {
|
|
297
|
+
candidate = `${preferred}_${suffix++}`;
|
|
298
|
+
}
|
|
299
|
+
usedRouteVarNames.add(candidate);
|
|
300
|
+
return candidate;
|
|
301
|
+
};
|
|
302
|
+
|
|
245
303
|
const createRouteVarName = (route: NestedRouteForCli | PageRoute) => {
|
|
246
304
|
const id = (route as any).id as string | undefined;
|
|
247
305
|
const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
|
|
248
|
-
return `route_${base}
|
|
306
|
+
return reserveRouteVarName(`route_${base}`);
|
|
249
307
|
};
|
|
250
308
|
|
|
251
309
|
const buildRoute = async (opts: {
|
|
@@ -267,6 +325,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
267
325
|
: null;
|
|
268
326
|
const loaderName = loaderImports?.loaderName || null;
|
|
269
327
|
const actionName = loaderImports?.actionName || null;
|
|
328
|
+
const searchContractInfo = pickRouteSearchContractModules(route);
|
|
329
|
+
const validateSearchName = searchContractInfo.validateSearchPath
|
|
330
|
+
? await getImportNameForSearchContract(
|
|
331
|
+
searchContractInfo.validateSearchPath,
|
|
332
|
+
'validateSearch',
|
|
333
|
+
)
|
|
334
|
+
: null;
|
|
335
|
+
const loaderDepsName = searchContractInfo.loaderDepsPath
|
|
336
|
+
? await getImportNameForSearchContract(
|
|
337
|
+
searchContractInfo.loaderDepsPath,
|
|
338
|
+
'loaderDeps',
|
|
339
|
+
)
|
|
340
|
+
: null;
|
|
270
341
|
|
|
271
342
|
const rawPath = (route as any).path as string | undefined;
|
|
272
343
|
const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
|
|
@@ -286,6 +357,12 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
286
357
|
`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
|
|
287
358
|
);
|
|
288
359
|
}
|
|
360
|
+
if (validateSearchName) {
|
|
361
|
+
routeOpts.push(`validateSearch: ${validateSearchName},`);
|
|
362
|
+
}
|
|
363
|
+
if (loaderDepsName) {
|
|
364
|
+
routeOpts.push(`loaderDeps: ${loaderDepsName},`);
|
|
365
|
+
}
|
|
289
366
|
|
|
290
367
|
const staticDataSnippet = createRouteStaticDataSnippet({
|
|
291
368
|
modernRouteId: (route as any).id as string | undefined,
|
|
@@ -296,18 +373,27 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
296
373
|
routeOpts.push(staticDataSnippet);
|
|
297
374
|
}
|
|
298
375
|
|
|
299
|
-
statements.push(
|
|
300
|
-
`const ${varName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
|
|
301
|
-
);
|
|
302
|
-
|
|
303
376
|
const children = (route as any).children as
|
|
304
377
|
| Array<NestedRouteForCli | PageRoute>
|
|
305
378
|
| undefined;
|
|
379
|
+
const hasChildren = Boolean(children && children.length > 0);
|
|
380
|
+
const routeCtorVarName = hasChildren
|
|
381
|
+
? reserveRouteVarName(`${varName}__base`)
|
|
382
|
+
: varName;
|
|
383
|
+
|
|
384
|
+
statements.push(
|
|
385
|
+
`const ${routeCtorVarName} = createRoute({\n ${routeOpts.join('\n ')}\n});`,
|
|
386
|
+
);
|
|
387
|
+
|
|
306
388
|
if (children && children.length > 0) {
|
|
307
389
|
const childVars = await Promise.all(
|
|
308
|
-
children.map(child =>
|
|
390
|
+
children.map(child =>
|
|
391
|
+
buildRoute({ parentVar: routeCtorVarName, route: child }),
|
|
392
|
+
),
|
|
393
|
+
);
|
|
394
|
+
statements.push(
|
|
395
|
+
`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
|
|
309
396
|
);
|
|
310
|
-
statements.push(`${varName}.addChildren([${childVars.join(', ')}]);`);
|
|
311
397
|
}
|
|
312
398
|
|
|
313
399
|
return varName;
|
|
@@ -326,6 +412,21 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
326
412
|
: null;
|
|
327
413
|
const rootLoaderName = rootLoaderImports?.loaderName || null;
|
|
328
414
|
const rootActionName = rootLoaderImports?.actionName || null;
|
|
415
|
+
const rootSearchContractInfo = rootModern
|
|
416
|
+
? pickRouteSearchContractModules(rootModern)
|
|
417
|
+
: null;
|
|
418
|
+
const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
|
|
419
|
+
? await getImportNameForSearchContract(
|
|
420
|
+
rootSearchContractInfo.validateSearchPath,
|
|
421
|
+
'validateSearch',
|
|
422
|
+
)
|
|
423
|
+
: null;
|
|
424
|
+
const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
|
|
425
|
+
? await getImportNameForSearchContract(
|
|
426
|
+
rootSearchContractInfo.loaderDepsPath,
|
|
427
|
+
'loaderDeps',
|
|
428
|
+
)
|
|
429
|
+
: null;
|
|
329
430
|
|
|
330
431
|
const topLevelVars = await Promise.all(
|
|
331
432
|
topLevel.map(route => buildRoute({ parentVar: 'rootRoute', route })),
|
|
@@ -337,12 +438,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
337
438
|
`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
|
|
338
439
|
);
|
|
339
440
|
}
|
|
441
|
+
if (rootValidateSearchName) {
|
|
442
|
+
rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
|
|
443
|
+
}
|
|
444
|
+
if (rootLoaderDepsName) {
|
|
445
|
+
rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
|
|
446
|
+
}
|
|
340
447
|
|
|
341
448
|
const routerGenTs = `/* eslint-disable */
|
|
342
449
|
// This file is auto-generated by Modern.js. Do not edit manually.
|
|
343
450
|
|
|
344
451
|
import {
|
|
345
452
|
createMemoryHistory,
|
|
453
|
+
modernTanstackRouterFastDefaults,
|
|
346
454
|
createRootRouteWithContext,
|
|
347
455
|
createRoute,
|
|
348
456
|
createRouter,
|
|
@@ -370,7 +478,7 @@ function isRedirectResponse(res: Response) {
|
|
|
370
478
|
}
|
|
371
479
|
|
|
372
480
|
function throwTanstackRedirect(location: string) {
|
|
373
|
-
const target = location
|
|
481
|
+
const target = location.length > 0 ? location : '/';
|
|
374
482
|
try {
|
|
375
483
|
void new URL(target);
|
|
376
484
|
throw redirect({ href: target });
|
|
@@ -396,21 +504,87 @@ function createRouteStaticData(opts: {
|
|
|
396
504
|
modernRouteAction?: unknown;
|
|
397
505
|
modernRouteLoader?: unknown;
|
|
398
506
|
}) {
|
|
399
|
-
const staticData:
|
|
507
|
+
const staticData: {
|
|
508
|
+
modernRouteId?: string;
|
|
509
|
+
modernRouteAction?: unknown;
|
|
510
|
+
modernRouteLoader?: unknown;
|
|
511
|
+
} = {};
|
|
400
512
|
|
|
401
|
-
if (opts.modernRouteId) {
|
|
513
|
+
if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
|
|
402
514
|
staticData.modernRouteId = opts.modernRouteId;
|
|
403
515
|
}
|
|
404
516
|
|
|
405
|
-
if (opts.modernRouteLoader) {
|
|
517
|
+
if (typeof opts.modernRouteLoader !== 'undefined') {
|
|
406
518
|
staticData.modernRouteLoader = opts.modernRouteLoader;
|
|
407
519
|
}
|
|
408
520
|
|
|
409
|
-
if (opts.modernRouteAction) {
|
|
521
|
+
if (typeof opts.modernRouteAction !== 'undefined') {
|
|
410
522
|
staticData.modernRouteAction = opts.modernRouteAction;
|
|
411
523
|
}
|
|
412
524
|
|
|
413
|
-
return
|
|
525
|
+
return staticData;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function getLoaderSignal(ctx: any): AbortSignal {
|
|
529
|
+
const abortSignal = ctx?.abortController?.signal;
|
|
530
|
+
if (abortSignal instanceof AbortSignal) {
|
|
531
|
+
return abortSignal;
|
|
532
|
+
}
|
|
533
|
+
if (ctx?.signal instanceof AbortSignal) {
|
|
534
|
+
return ctx.signal;
|
|
535
|
+
}
|
|
536
|
+
return new AbortController().signal;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function getLoaderHref(ctx: any): string {
|
|
540
|
+
if (typeof ctx?.location === 'string') {
|
|
541
|
+
return ctx.location;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const publicHref = ctx?.location?.publicHref;
|
|
545
|
+
if (typeof publicHref === 'string') {
|
|
546
|
+
return publicHref;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const href = ctx?.location?.href;
|
|
550
|
+
if (typeof href === 'string') {
|
|
551
|
+
return href;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const urlHref = ctx?.location?.url?.href;
|
|
555
|
+
return typeof urlHref === 'string' ? urlHref : '';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function getLoaderParams(ctx: any): Record<string, string> {
|
|
559
|
+
return typeof ctx?.params === 'object' && ctx.params !== null ? ctx.params : {};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function handleModernLoaderResult<LoaderResult>(result: LoaderResult): LoaderResult {
|
|
563
|
+
if (isResponse(result)) {
|
|
564
|
+
if (isRedirectResponse(result)) {
|
|
565
|
+
const location = result.headers.get('Location') ?? '/';
|
|
566
|
+
throwTanstackRedirect(location);
|
|
567
|
+
}
|
|
568
|
+
if (result.status === 404) {
|
|
569
|
+
throw notFound();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function handleModernLoaderError(err: unknown): never {
|
|
577
|
+
if (isResponse(err)) {
|
|
578
|
+
if (isRedirectResponse(err)) {
|
|
579
|
+
const location = err.headers.get('Location') ?? '/';
|
|
580
|
+
throwTanstackRedirect(location);
|
|
581
|
+
}
|
|
582
|
+
if (err.status === 404) {
|
|
583
|
+
throw notFound();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
throw err;
|
|
414
588
|
}
|
|
415
589
|
|
|
416
590
|
function modernLoaderToTanstack<TLoader extends (args: any) => any>(
|
|
@@ -419,57 +593,31 @@ function modernLoaderToTanstack<TLoader extends (args: any) => any>(
|
|
|
419
593
|
) {
|
|
420
594
|
type LoaderResult = Awaited<ReturnType<TLoader>>;
|
|
421
595
|
|
|
422
|
-
return
|
|
596
|
+
return (ctx: any): Promise<LoaderResult> => {
|
|
423
597
|
try {
|
|
424
|
-
const signal
|
|
425
|
-
ctx?.abortController?.signal ||
|
|
426
|
-
ctx?.signal ||
|
|
427
|
-
new AbortController().signal;
|
|
598
|
+
const signal = getLoaderSignal(ctx);
|
|
428
599
|
const baseRequest: Request | undefined =
|
|
429
600
|
ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
|
|
430
601
|
|
|
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
|
-
'';
|
|
602
|
+
const href = getLoaderHref(ctx);
|
|
438
603
|
|
|
439
|
-
const request = baseRequest
|
|
604
|
+
const request = baseRequest !== undefined
|
|
440
605
|
? new Request(baseRequest, { signal })
|
|
441
606
|
: new Request(href, { signal });
|
|
442
607
|
|
|
443
|
-
const params = mapParamsForModernLoader(ctx
|
|
608
|
+
const params = mapParamsForModernLoader(getLoaderParams(ctx), opts.hasSplat);
|
|
444
609
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
throwTanstackRedirect(location);
|
|
455
|
-
}
|
|
456
|
-
if (result.status === 404) {
|
|
457
|
-
throw notFound();
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return result as LoaderResult;
|
|
610
|
+
return Promise.resolve(
|
|
611
|
+
(modernLoader as any)({
|
|
612
|
+
request,
|
|
613
|
+
params,
|
|
614
|
+
context: ctx?.context?.requestContext,
|
|
615
|
+
}),
|
|
616
|
+
)
|
|
617
|
+
.then((result: LoaderResult) => handleModernLoaderResult(result))
|
|
618
|
+
.catch(handleModernLoaderError);
|
|
462
619
|
} 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;
|
|
620
|
+
handleModernLoaderError(err);
|
|
473
621
|
}
|
|
474
622
|
};
|
|
475
623
|
}
|
|
@@ -492,6 +640,7 @@ ${statements.join('\n\n')}
|
|
|
492
640
|
export const routeTree = rootRoute.addChildren([${topLevelVars.join(', ')}]);
|
|
493
641
|
|
|
494
642
|
export const router = createRouter({
|
|
643
|
+
...modernTanstackRouterFastDefaults,
|
|
495
644
|
routeTree,
|
|
496
645
|
history: createMemoryHistory({
|
|
497
646
|
initialEntries: ['/'],
|
package/src/runtime/index.tsx
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export * from '@tanstack/react-router';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
Outlet,
|
|
4
|
+
useLocation,
|
|
5
|
+
useMatch,
|
|
6
|
+
useMatches,
|
|
7
|
+
useNavigate,
|
|
8
|
+
useRouter,
|
|
9
|
+
} from '@tanstack/react-router';
|
|
3
10
|
export type {
|
|
4
11
|
Fetcher,
|
|
5
12
|
FetcherState,
|
|
@@ -28,3 +35,8 @@ export type {
|
|
|
28
35
|
CompositeComponentProps,
|
|
29
36
|
} from './rsc/client';
|
|
30
37
|
export { CompositeComponent } from './rsc/client';
|
|
38
|
+
export type { RouterConfig } from './types';
|
|
39
|
+
export {
|
|
40
|
+
getModernTanstackRouterFastDefaults,
|
|
41
|
+
modernTanstackRouterFastDefaults,
|
|
42
|
+
} from './types';
|
|
@@ -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,
|
|
@@ -52,7 +52,11 @@ import {
|
|
|
52
52
|
createTanstackRscServerPayload,
|
|
53
53
|
handleTanstackRscRedirect,
|
|
54
54
|
} from './rsc/payloadRouter';
|
|
55
|
-
import
|
|
55
|
+
import {
|
|
56
|
+
getModernTanstackRouterFastDefaults,
|
|
57
|
+
type InternalRouterServerSnapshot,
|
|
58
|
+
type RouterConfig,
|
|
59
|
+
} from './types';
|
|
56
60
|
import { createRouteObjectsFromConfig, urlJoin } from './utils';
|
|
57
61
|
|
|
58
62
|
type ModernTanstackRouterContext = {
|
|
@@ -119,6 +123,17 @@ type PreloadableRouteComponent = {
|
|
|
119
123
|
preload?: (props?: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
120
124
|
};
|
|
121
125
|
|
|
126
|
+
type ReactLazyRouteComponent = {
|
|
127
|
+
_init?: (payload: unknown) => unknown;
|
|
128
|
+
_payload?: unknown;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
132
|
+
return Boolean(
|
|
133
|
+
value && typeof (value as PromiseLike<unknown>).then === 'function',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
type TanstackRouterWithServerSsr = AnyRouter & {
|
|
123
138
|
resolveRedirect?: (redirect: Response) => Response;
|
|
124
139
|
routesById?: Record<string, RouterRouteWithOptions>;
|
|
@@ -149,7 +164,37 @@ function isPreloadableRouteComponent(
|
|
|
149
164
|
);
|
|
150
165
|
}
|
|
151
166
|
|
|
167
|
+
function isReactLazyRouteComponent(
|
|
168
|
+
component: unknown,
|
|
169
|
+
): component is ReactLazyRouteComponent {
|
|
170
|
+
return (
|
|
171
|
+
Boolean(component) &&
|
|
172
|
+
typeof component === 'object' &&
|
|
173
|
+
typeof (component as ReactLazyRouteComponent)._init === 'function' &&
|
|
174
|
+
'_payload' in component
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function preloadReactLazyRouteComponent(
|
|
179
|
+
component: ReactLazyRouteComponent,
|
|
180
|
+
) {
|
|
181
|
+
try {
|
|
182
|
+
component._init?.(component._payload);
|
|
183
|
+
} catch (thrown) {
|
|
184
|
+
if (!isPromiseLike(thrown)) {
|
|
185
|
+
throw thrown;
|
|
186
|
+
}
|
|
187
|
+
await thrown;
|
|
188
|
+
component._init?.(component._payload);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
152
192
|
async function preloadRouteComponent(component: unknown) {
|
|
193
|
+
if (isReactLazyRouteComponent(component)) {
|
|
194
|
+
await preloadReactLazyRouteComponent(component);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
153
198
|
if (!isPreloadableRouteComponent(component)) {
|
|
154
199
|
return;
|
|
155
200
|
}
|
|
@@ -398,6 +443,7 @@ export const tanstackRouterPlugin = (
|
|
|
398
443
|
hooks.onBeforeCreateRouter.call(routerLifecycleContext);
|
|
399
444
|
|
|
400
445
|
const tanstackRouter = createRouter({
|
|
446
|
+
...getModernTanstackRouterFastDefaults(mergedConfig),
|
|
401
447
|
routeTree,
|
|
402
448
|
history,
|
|
403
449
|
basepath: '/',
|
|
@@ -458,9 +504,12 @@ export const tanstackRouterPlugin = (
|
|
|
458
504
|
context.ssrContext?.response.status(tanstackRouter.state.statusCode);
|
|
459
505
|
|
|
460
506
|
await serverRouter.serverSsr?.dehydrate?.();
|
|
461
|
-
await waitForRouterSerialization(serverRouter);
|
|
462
507
|
|
|
463
508
|
if (isRSCNavigation) {
|
|
509
|
+
// RSC navigations consume the server payload directly. Normal HTML SSR
|
|
510
|
+
// emits the buffered bootstrap script below and must not wait here
|
|
511
|
+
// because Modern's non-streaming hook has not rendered the app yet.
|
|
512
|
+
await waitForRouterSerialization(serverRouter);
|
|
464
513
|
setTanstackRscServerPayload(
|
|
465
514
|
createTanstackRscServerPayload(serverRouter, {
|
|
466
515
|
omitClientLoaderData: true,
|
|
@@ -516,9 +565,7 @@ export const tanstackRouterPlugin = (
|
|
|
516
565
|
}
|
|
517
566
|
|
|
518
567
|
const routerWrapper = (
|
|
519
|
-
<
|
|
520
|
-
<RouterProvider router={router as AnyRouter} />
|
|
521
|
-
</Suspense>
|
|
568
|
+
<RouterProvider router={router as AnyRouter} />
|
|
522
569
|
);
|
|
523
570
|
|
|
524
571
|
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,9 +40,13 @@ 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
|
-
import
|
|
46
|
+
import {
|
|
47
|
+
getModernTanstackRouterFastDefaults,
|
|
48
|
+
type RouterConfig,
|
|
49
|
+
} from './types';
|
|
47
50
|
import { createRouteObjectsFromConfig, urlJoin } from './utils';
|
|
48
51
|
|
|
49
52
|
const BLOCKING_SUBSCRIBE_SYMBOL = Symbol.for(
|
|
@@ -184,6 +187,7 @@ export const tanstackRouterPlugin = (
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
context.router = {
|
|
190
|
+
Link,
|
|
187
191
|
useMatches,
|
|
188
192
|
useLocation,
|
|
189
193
|
useNavigate,
|
|
@@ -321,6 +325,7 @@ export const tanstackRouterPlugin = (
|
|
|
321
325
|
: undefined;
|
|
322
326
|
|
|
323
327
|
cachedRouter = createRouter({
|
|
328
|
+
...getModernTanstackRouterFastDefaults(mergedConfig),
|
|
324
329
|
routeTree,
|
|
325
330
|
basepath: '/',
|
|
326
331
|
rewrite,
|
|
@@ -368,9 +373,7 @@ export const tanstackRouterPlugin = (
|
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
const RouterContent = hasSSRBootstrap ? (
|
|
371
|
-
<
|
|
372
|
-
<RouterClient router={router} />
|
|
373
|
-
</React.Suspense>
|
|
376
|
+
<RouterClient router={router} />
|
|
374
377
|
) : (
|
|
375
378
|
<RouterProvider router={router} />
|
|
376
379
|
);
|