@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.10 → 3.2.0-ultramodern.100
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/hydrationBoundary.js +44 -0
- package/dist/cjs/runtime/index.js +321 -69
- package/dist/cjs/runtime/outlet.js +54 -0
- package/dist/cjs/runtime/plugin.js +194 -90
- package/dist/cjs/runtime/plugin.node.js +29 -11
- package/dist/cjs/runtime/plugin.worker.js +49 -0
- package/dist/cjs/runtime/routeTree.js +72 -12
- 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/hydrationBoundary.mjs +10 -0
- package/dist/esm/runtime/index.mjs +3 -2
- package/dist/esm/runtime/outlet.mjs +17 -0
- package/dist/esm/runtime/plugin.mjs +197 -93
- package/dist/esm/runtime/plugin.node.mjs +30 -12
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/routeTree.mjs +73 -13
- 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/hydrationBoundary.mjs +11 -0
- package/dist/esm-node/runtime/index.mjs +3 -2
- package/dist/esm-node/runtime/outlet.mjs +18 -0
- package/dist/esm-node/runtime/plugin.mjs +197 -93
- package/dist/esm-node/runtime/plugin.node.mjs +30 -12
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/routeTree.mjs +73 -13
- 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/hydrationBoundary.d.ts +2 -0
- package/dist/types/runtime/index.d.ts +5 -2
- package/dist/types/runtime/outlet.d.ts +2 -0
- 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 +15 -15
- package/src/cli/index.ts +17 -0
- package/src/cli/routeSplitting.ts +81 -0
- package/src/cli/tanstackTypes.ts +216 -67
- package/src/runtime/hydrationBoundary.tsx +12 -0
- package/src/runtime/index.tsx +107 -2
- package/src/runtime/outlet.tsx +42 -0
- package/src/runtime/plugin.node.tsx +57 -8
- package/src/runtime/plugin.tsx +353 -149
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/routeTree.ts +194 -23
- package/src/runtime/ssr-shim.d.ts +1 -3
- 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/hydrationBoundary.test.tsx +23 -0
- package/tests/router/routeTree.test.ts +416 -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: ['/'],
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactElement, Suspense } from 'react';
|
|
2
|
+
|
|
3
|
+
export function wrapTanstackSsrHydrationBoundary(
|
|
4
|
+
routerContent: ReactElement,
|
|
5
|
+
shouldWrap: boolean,
|
|
6
|
+
) {
|
|
7
|
+
if (shouldWrap) {
|
|
8
|
+
return <Suspense fallback={null}>{routerContent}</Suspense>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return routerContent;
|
|
12
|
+
}
|
package/src/runtime/index.tsx
CHANGED
|
@@ -1,5 +1,104 @@
|
|
|
1
|
-
export * from '@tanstack/react-router';
|
|
2
|
-
export {
|
|
1
|
+
export type * from '@tanstack/react-router';
|
|
2
|
+
export {
|
|
3
|
+
Asset,
|
|
4
|
+
Await,
|
|
5
|
+
Block,
|
|
6
|
+
CatchBoundary,
|
|
7
|
+
CatchNotFound,
|
|
8
|
+
ClientOnly,
|
|
9
|
+
cleanPath,
|
|
10
|
+
composeRewrites,
|
|
11
|
+
createBrowserHistory,
|
|
12
|
+
createControlledPromise,
|
|
13
|
+
createFileRoute,
|
|
14
|
+
createHashHistory,
|
|
15
|
+
createHistory,
|
|
16
|
+
createLazyFileRoute,
|
|
17
|
+
createLazyRoute,
|
|
18
|
+
createLink,
|
|
19
|
+
createMemoryHistory,
|
|
20
|
+
createRootRoute,
|
|
21
|
+
createRootRouteWithContext,
|
|
22
|
+
createRoute,
|
|
23
|
+
createRouteMask,
|
|
24
|
+
createRouter,
|
|
25
|
+
createRouterConfig,
|
|
26
|
+
createSerializationAdapter,
|
|
27
|
+
DEFAULT_PROTOCOL_ALLOWLIST,
|
|
28
|
+
DefaultGlobalNotFound,
|
|
29
|
+
deepEqual,
|
|
30
|
+
defaultParseSearch,
|
|
31
|
+
defaultStringifySearch,
|
|
32
|
+
defer,
|
|
33
|
+
ErrorComponent,
|
|
34
|
+
FileRoute,
|
|
35
|
+
FileRouteLoader,
|
|
36
|
+
functionalUpdate,
|
|
37
|
+
getRouteApi,
|
|
38
|
+
HeadContent,
|
|
39
|
+
interpolatePath,
|
|
40
|
+
isMatch,
|
|
41
|
+
isNotFound,
|
|
42
|
+
isPlainArray,
|
|
43
|
+
isPlainObject,
|
|
44
|
+
isRedirect,
|
|
45
|
+
joinPaths,
|
|
46
|
+
LazyRoute,
|
|
47
|
+
lazyFn,
|
|
48
|
+
lazyRouteComponent,
|
|
49
|
+
linkOptions,
|
|
50
|
+
Match,
|
|
51
|
+
Matches,
|
|
52
|
+
MatchRoute,
|
|
53
|
+
Navigate,
|
|
54
|
+
NotFoundRoute,
|
|
55
|
+
notFound,
|
|
56
|
+
parseSearchWith,
|
|
57
|
+
RootRoute,
|
|
58
|
+
Route,
|
|
59
|
+
RouteApi,
|
|
60
|
+
Router,
|
|
61
|
+
RouterContextProvider,
|
|
62
|
+
RouterProvider,
|
|
63
|
+
reactUse,
|
|
64
|
+
redirect,
|
|
65
|
+
replaceEqualDeep,
|
|
66
|
+
resolvePath,
|
|
67
|
+
retainSearchParams,
|
|
68
|
+
rootRouteId,
|
|
69
|
+
rootRouteWithContext,
|
|
70
|
+
ScriptOnce,
|
|
71
|
+
Scripts,
|
|
72
|
+
ScrollRestoration,
|
|
73
|
+
SearchParamError,
|
|
74
|
+
stringifySearchWith,
|
|
75
|
+
stripSearchParams,
|
|
76
|
+
trimPath,
|
|
77
|
+
trimPathLeft,
|
|
78
|
+
trimPathRight,
|
|
79
|
+
useAwaited,
|
|
80
|
+
useBlocker,
|
|
81
|
+
useCanGoBack,
|
|
82
|
+
useChildMatches,
|
|
83
|
+
useElementScrollRestoration,
|
|
84
|
+
useHydrated,
|
|
85
|
+
useLayoutEffect,
|
|
86
|
+
useLinkProps,
|
|
87
|
+
useLoaderData,
|
|
88
|
+
useLoaderDeps,
|
|
89
|
+
useLocation,
|
|
90
|
+
useMatch,
|
|
91
|
+
useMatches,
|
|
92
|
+
useMatchRoute,
|
|
93
|
+
useNavigate,
|
|
94
|
+
useParams,
|
|
95
|
+
useParentMatches,
|
|
96
|
+
useRouteContext,
|
|
97
|
+
useRouter,
|
|
98
|
+
useRouterState,
|
|
99
|
+
useSearch,
|
|
100
|
+
useTags,
|
|
101
|
+
} from '@tanstack/react-router';
|
|
3
102
|
export type {
|
|
4
103
|
Fetcher,
|
|
5
104
|
FetcherState,
|
|
@@ -12,6 +111,7 @@ export {
|
|
|
12
111
|
RouteActionResponseError,
|
|
13
112
|
useFetcher,
|
|
14
113
|
} from './dataMutation';
|
|
114
|
+
export { Outlet } from './outlet';
|
|
15
115
|
export {
|
|
16
116
|
tanstackRouterPlugin,
|
|
17
117
|
tanstackRouterPlugin as default,
|
|
@@ -28,3 +128,8 @@ export type {
|
|
|
28
128
|
CompositeComponentProps,
|
|
29
129
|
} from './rsc/client';
|
|
30
130
|
export { CompositeComponent } from './rsc/client';
|
|
131
|
+
export type { RouterConfig } from './types';
|
|
132
|
+
export {
|
|
133
|
+
getModernTanstackRouterFastDefaults,
|
|
134
|
+
modernTanstackRouterFastDefaults,
|
|
135
|
+
} from './types';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Outlet as TanstackOutlet } from '@tanstack/react-router';
|
|
2
|
+
import {
|
|
3
|
+
type ComponentProps,
|
|
4
|
+
createElement,
|
|
5
|
+
type ElementType,
|
|
6
|
+
memo,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
type PreloadableComponent = ElementType<Record<string, unknown>> & {
|
|
10
|
+
load?: () => Promise<unknown>;
|
|
11
|
+
preload?: () => Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Outlet = memo(function ModernTanstackOutlet() {
|
|
15
|
+
return <TanstackOutlet />;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function withModernRouteMatchContext(
|
|
19
|
+
component: unknown,
|
|
20
|
+
_routeId: string,
|
|
21
|
+
): unknown {
|
|
22
|
+
if (!component) {
|
|
23
|
+
return component;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Component = component as ElementType<Record<string, unknown>>;
|
|
27
|
+
const WrappedRouteComponent = (
|
|
28
|
+
props: ComponentProps<ElementType<Record<string, unknown>>>,
|
|
29
|
+
) => createElement(Component, props);
|
|
30
|
+
|
|
31
|
+
const preloadable = component as PreloadableComponent;
|
|
32
|
+
if (typeof preloadable.load === 'function') {
|
|
33
|
+
WrappedRouteComponent.load = preloadable.load.bind(preloadable);
|
|
34
|
+
}
|
|
35
|
+
if (typeof preloadable.preload === 'function') {
|
|
36
|
+
WrappedRouteComponent.preload = preloadable.preload.bind(preloadable);
|
|
37
|
+
} else if (typeof preloadable.load === 'function') {
|
|
38
|
+
WrappedRouteComponent.preload = WrappedRouteComponent.load;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return WrappedRouteComponent;
|
|
42
|
+
}
|
|
@@ -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,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
onBeforeHydrateRouter as onBeforeHydrateRouterHook,
|
|
40
40
|
type RouterExtendsHooks,
|
|
41
41
|
} from './hooks';
|
|
42
|
+
import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
|
|
42
43
|
import {
|
|
43
44
|
applyRouterServerPrepareResult,
|
|
44
45
|
createRouterServerSnapshot,
|
|
@@ -52,7 +53,11 @@ import {
|
|
|
52
53
|
createTanstackRscServerPayload,
|
|
53
54
|
handleTanstackRscRedirect,
|
|
54
55
|
} from './rsc/payloadRouter';
|
|
55
|
-
import
|
|
56
|
+
import {
|
|
57
|
+
getModernTanstackRouterFastDefaults,
|
|
58
|
+
type InternalRouterServerSnapshot,
|
|
59
|
+
type RouterConfig,
|
|
60
|
+
} from './types';
|
|
56
61
|
import { createRouteObjectsFromConfig, urlJoin } from './utils';
|
|
57
62
|
|
|
58
63
|
type ModernTanstackRouterContext = {
|
|
@@ -119,6 +124,17 @@ type PreloadableRouteComponent = {
|
|
|
119
124
|
preload?: (props?: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
120
125
|
};
|
|
121
126
|
|
|
127
|
+
type ReactLazyRouteComponent = {
|
|
128
|
+
_init?: (payload: unknown) => unknown;
|
|
129
|
+
_payload?: unknown;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
133
|
+
return Boolean(
|
|
134
|
+
value && typeof (value as PromiseLike<unknown>).then === 'function',
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
type TanstackRouterWithServerSsr = AnyRouter & {
|
|
123
139
|
resolveRedirect?: (redirect: Response) => Response;
|
|
124
140
|
routesById?: Record<string, RouterRouteWithOptions>;
|
|
@@ -149,7 +165,37 @@ function isPreloadableRouteComponent(
|
|
|
149
165
|
);
|
|
150
166
|
}
|
|
151
167
|
|
|
168
|
+
function isReactLazyRouteComponent(
|
|
169
|
+
component: unknown,
|
|
170
|
+
): component is ReactLazyRouteComponent {
|
|
171
|
+
return (
|
|
172
|
+
Boolean(component) &&
|
|
173
|
+
typeof component === 'object' &&
|
|
174
|
+
typeof (component as ReactLazyRouteComponent)._init === 'function' &&
|
|
175
|
+
'_payload' in component
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function preloadReactLazyRouteComponent(
|
|
180
|
+
component: ReactLazyRouteComponent,
|
|
181
|
+
) {
|
|
182
|
+
try {
|
|
183
|
+
component._init?.(component._payload);
|
|
184
|
+
} catch (thrown) {
|
|
185
|
+
if (!isPromiseLike(thrown)) {
|
|
186
|
+
throw thrown;
|
|
187
|
+
}
|
|
188
|
+
await thrown;
|
|
189
|
+
component._init?.(component._payload);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
152
193
|
async function preloadRouteComponent(component: unknown) {
|
|
194
|
+
if (isReactLazyRouteComponent(component)) {
|
|
195
|
+
await preloadReactLazyRouteComponent(component);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
153
199
|
if (!isPreloadableRouteComponent(component)) {
|
|
154
200
|
return;
|
|
155
201
|
}
|
|
@@ -398,6 +444,7 @@ export const tanstackRouterPlugin = (
|
|
|
398
444
|
hooks.onBeforeCreateRouter.call(routerLifecycleContext);
|
|
399
445
|
|
|
400
446
|
const tanstackRouter = createRouter({
|
|
447
|
+
...getModernTanstackRouterFastDefaults(mergedConfig),
|
|
401
448
|
routeTree,
|
|
402
449
|
history,
|
|
403
450
|
basepath: '/',
|
|
@@ -458,9 +505,12 @@ export const tanstackRouterPlugin = (
|
|
|
458
505
|
context.ssrContext?.response.status(tanstackRouter.state.statusCode);
|
|
459
506
|
|
|
460
507
|
await serverRouter.serverSsr?.dehydrate?.();
|
|
461
|
-
await waitForRouterSerialization(serverRouter);
|
|
462
508
|
|
|
463
509
|
if (isRSCNavigation) {
|
|
510
|
+
// RSC navigations consume the server payload directly. Normal HTML SSR
|
|
511
|
+
// emits the buffered bootstrap script below and must not wait here
|
|
512
|
+
// because Modern's non-streaming hook has not rendered the app yet.
|
|
513
|
+
await waitForRouterSerialization(serverRouter);
|
|
464
514
|
setTanstackRscServerPayload(
|
|
465
515
|
createTanstackRscServerPayload(serverRouter, {
|
|
466
516
|
omitClientLoaderData: true,
|
|
@@ -515,10 +565,9 @@ export const tanstackRouterPlugin = (
|
|
|
515
565
|
return App ? <App {...props} /> : null;
|
|
516
566
|
}
|
|
517
567
|
|
|
518
|
-
const routerWrapper = (
|
|
519
|
-
<
|
|
520
|
-
|
|
521
|
-
</Suspense>
|
|
568
|
+
const routerWrapper = wrapTanstackSsrHydrationBoundary(
|
|
569
|
+
<RouterProvider router={router as AnyRouter} />,
|
|
570
|
+
true,
|
|
522
571
|
);
|
|
523
572
|
|
|
524
573
|
return App ? <App>{routerWrapper}</App> : routerWrapper;
|