@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.10 → 3.9.0-ultramodern.12
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/artifacts.js +2 -1
- package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
- package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
- package/dist/cjs/cli/tanstackTypes/source.js +28 -63
- package/dist/cjs/runtime/lifecycle.js +0 -3
- package/dist/cjs/runtime/loaderBridge.js +10 -4
- package/dist/cjs/runtime/navigation.js +102 -0
- package/dist/cjs/runtime/plugin.js +6 -1
- package/dist/cjs/runtime/plugin.node.js +82 -64
- package/dist/cjs/runtime/routeTree/construction.js +19 -61
- package/dist/cjs/runtime/routeTree/loaders.js +1 -1
- package/dist/cjs/runtime/routeTree/paths.js +0 -6
- package/dist/cjs/runtime/utils.js +1 -0
- package/dist/cjs/shared/routeDescriptor.js +60 -0
- package/dist/esm/cli/artifacts.mjs +2 -1
- package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/loaderBridge.mjs +8 -5
- package/dist/esm/runtime/navigation.mjs +64 -0
- package/dist/esm/runtime/plugin.mjs +6 -1
- package/dist/esm/runtime/plugin.node.mjs +83 -65
- package/dist/esm/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm/runtime/utils.mjs +1 -0
- package/dist/esm/shared/routeDescriptor.mjs +22 -0
- package/dist/esm-node/cli/artifacts.mjs +2 -1
- package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
- package/dist/esm-node/runtime/navigation.mjs +65 -0
- package/dist/esm-node/runtime/plugin.mjs +6 -1
- package/dist/esm-node/runtime/plugin.node.mjs +83 -65
- package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm-node/runtime/utils.mjs +1 -0
- package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
- package/dist/types/runtime/lifecycle.d.ts +1 -1
- package/dist/types/runtime/loaderBridge.d.ts +1 -0
- package/dist/types/runtime/navigation.d.ts +4 -0
- package/dist/types/runtime/routeTree/paths.d.ts +0 -2
- package/dist/types/shared/routeDescriptor.d.ts +26 -0
- package/package.json +12 -12
- package/src/cli/artifacts.ts +6 -1
- package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
- package/src/cli/tanstackTypes/shared.ts +0 -14
- package/src/cli/tanstackTypes/source.ts +43 -119
- package/src/runtime/lifecycle.ts +0 -1
- package/src/runtime/loaderBridge.ts +9 -9
- package/src/runtime/navigation.ts +78 -0
- package/src/runtime/plugin.node.tsx +111 -89
- package/src/runtime/plugin.tsx +6 -0
- package/src/runtime/routeTree/construction.ts +32 -112
- package/src/runtime/routeTree/loaders.ts +2 -4
- package/src/runtime/routeTree/paths.ts +0 -6
- package/src/runtime/utils.tsx +1 -0
- package/src/shared/routeDescriptor.ts +46 -0
- package/tests/router/generateRouteArtifacts.test.ts +36 -2
- package/tests/router/localisedIdentity.test.tsx +1 -0
- package/tests/router/prefetchLink.test.tsx +29 -0
- package/tests/router/serverPlugin.test.ts +145 -0
- package/tests/router/tanstackTypes.test.ts +120 -2
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
AppNormalizedConfig,
|
|
5
|
-
AppTools,
|
|
6
|
-
AppToolsContext,
|
|
7
|
-
} from '@modern-js/app-tools';
|
|
8
|
-
import { canonicaliseLocalisedRoutes } from '@modern-js/i18n-runtime-extensions';
|
|
3
|
+
import type { AppToolsContext } from '@modern-js/app-tools';
|
|
9
4
|
import { makeLegalIdentifier } from '@modern-js/runtime/cli';
|
|
10
5
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
11
6
|
import { upath as path } from '@modern-js/utils';
|
|
12
7
|
|
|
8
|
+
import {
|
|
9
|
+
describeRouteTree,
|
|
10
|
+
type RouteDescriptor,
|
|
11
|
+
} from '../../shared/routeDescriptor';
|
|
12
|
+
|
|
13
13
|
import {
|
|
14
14
|
createRouteStaticDataSnippet,
|
|
15
|
-
isIndexRoute,
|
|
16
|
-
isPathlessLayout,
|
|
17
15
|
normalizeRelativeImport,
|
|
18
16
|
pickModernLoaderModule,
|
|
19
17
|
pickRouteSearchContractModules,
|
|
20
18
|
quote,
|
|
21
19
|
resolveFileNoExt,
|
|
22
|
-
toTanstackPath,
|
|
23
20
|
} from './shared';
|
|
24
21
|
|
|
25
22
|
type RouteForCli = NestedRouteForCli | PageRoute;
|
|
@@ -46,22 +43,15 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
46
43
|
appContext,
|
|
47
44
|
entryName,
|
|
48
45
|
generatedDirName = 'modern-tanstack',
|
|
49
|
-
routes
|
|
46
|
+
routes,
|
|
50
47
|
} = opts;
|
|
51
|
-
const routes: RouteForCli[] = canonicaliseLocalisedRoutes(rawRoutes);
|
|
52
48
|
const outDir = path.join(
|
|
53
49
|
appContext.srcDirectory,
|
|
54
50
|
generatedDirName,
|
|
55
51
|
entryName,
|
|
56
52
|
);
|
|
57
53
|
|
|
58
|
-
const rootModern = routes
|
|
59
|
-
const extras = r as RouteExtras;
|
|
60
|
-
return r && extras.type === 'nested' && extras.isRoot;
|
|
61
|
-
}) as NestedRouteForCli | undefined;
|
|
62
|
-
|
|
63
|
-
const rootExtras = rootModern as RouteExtras | undefined;
|
|
64
|
-
const topLevel = rootModern ? rootExtras?.children || [] : routes;
|
|
54
|
+
const { root: rootModern, children: topLevel } = describeRouteTree(routes);
|
|
65
55
|
|
|
66
56
|
const imports: string[] = [];
|
|
67
57
|
const statements: string[] = [];
|
|
@@ -92,15 +82,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
92
82
|
// value carries the internal `@_modern_js_src` alias, which the app's
|
|
93
83
|
// tsconfig does not map — the generated file must use relative
|
|
94
84
|
// imports.
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
85
|
+
const relImport = await resolveRouteImport(componentPath);
|
|
86
|
+
if (!relImport) {
|
|
97
87
|
return null;
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
const relImport = normalizeRelativeImport(
|
|
101
|
-
path.relative(outDir, resolvedNoExt),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
90
|
const componentName = `component_${componentIndex++}`;
|
|
105
91
|
imports.push(`import ${componentName} from ${quote(relImport)};`);
|
|
106
92
|
return componentName;
|
|
@@ -111,7 +97,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
111
97
|
return pendingImportName;
|
|
112
98
|
};
|
|
113
99
|
|
|
114
|
-
const
|
|
100
|
+
const resolveRouteImport = async (aliasedNoExtPath: string) => {
|
|
115
101
|
const prefix = `${appContext.internalSrcAlias}/`;
|
|
116
102
|
let absNoExt: string;
|
|
117
103
|
if (aliasedNoExtPath.startsWith(prefix)) {
|
|
@@ -124,7 +110,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
124
110
|
absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
|
|
125
111
|
}
|
|
126
112
|
|
|
127
|
-
|
|
113
|
+
const resolvedNoExt = await resolveFileNoExt(absNoExt);
|
|
114
|
+
return resolvedNoExt
|
|
115
|
+
? normalizeRelativeImport(path.relative(outDir, resolvedNoExt))
|
|
116
|
+
: null;
|
|
128
117
|
};
|
|
129
118
|
|
|
130
119
|
const getImportNamesForLoader = async (
|
|
@@ -143,15 +132,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
143
132
|
};
|
|
144
133
|
}
|
|
145
134
|
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
135
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
136
|
+
if (!relImport) {
|
|
148
137
|
return null;
|
|
149
138
|
}
|
|
150
139
|
|
|
151
|
-
const relImport = normalizeRelativeImport(
|
|
152
|
-
path.relative(outDir, resolvedNoExt),
|
|
153
|
-
);
|
|
154
|
-
|
|
155
140
|
const importName = `loader_${loaderIndex++}`;
|
|
156
141
|
const actionName = hasAction
|
|
157
142
|
? importName.replace(/^loader_/, 'action_')
|
|
@@ -182,14 +167,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
182
167
|
return existing;
|
|
183
168
|
}
|
|
184
169
|
|
|
185
|
-
const
|
|
186
|
-
if (!
|
|
170
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
171
|
+
if (!relImport) {
|
|
187
172
|
return null;
|
|
188
173
|
}
|
|
189
|
-
|
|
190
|
-
const relImport = normalizeRelativeImport(
|
|
191
|
-
path.relative(outDir, resolvedNoExt),
|
|
192
|
-
);
|
|
193
174
|
const importName =
|
|
194
175
|
exportName === 'validateSearch'
|
|
195
176
|
? `validateSearch_${validateSearchIndex++}`
|
|
@@ -217,15 +198,8 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
217
198
|
return reserveRouteVarName(`route_${base}`);
|
|
218
199
|
};
|
|
219
200
|
|
|
220
|
-
const
|
|
221
|
-
parentVar: string;
|
|
222
|
-
route: NestedRouteForCli | PageRoute;
|
|
223
|
-
}): Promise<string> => {
|
|
224
|
-
const { parentVar, route } = opts;
|
|
225
|
-
|
|
226
|
-
const varName = createRouteVarName(route);
|
|
201
|
+
const createRouteOptions = async (route: RouteForCli, hasSplat: boolean) => {
|
|
227
202
|
const routeExtras = route as RouteExtras;
|
|
228
|
-
|
|
229
203
|
const loaderInfo = pickModernLoaderModule(route);
|
|
230
204
|
const routeAction = routeExtras.action;
|
|
231
205
|
const loaderImports = loaderInfo
|
|
@@ -251,10 +225,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
251
225
|
)
|
|
252
226
|
: null;
|
|
253
227
|
|
|
254
|
-
const
|
|
255
|
-
const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
|
|
256
|
-
|
|
257
|
-
const routeOpts: string[] = [`getParentRoute: () => ${parentVar},`];
|
|
228
|
+
const routeOpts: string[] = [];
|
|
258
229
|
|
|
259
230
|
const componentName = await getImportNameForComponent(
|
|
260
231
|
routeExtras._component,
|
|
@@ -263,14 +234,6 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
263
234
|
routeOpts.push(`component: ${componentName},`);
|
|
264
235
|
}
|
|
265
236
|
|
|
266
|
-
if (isPathlessLayout(route)) {
|
|
267
|
-
const id = routeExtras.id;
|
|
268
|
-
routeOpts.push(`id: ${quote(id || 'pathless')},`);
|
|
269
|
-
} else {
|
|
270
|
-
const p = isIndexRoute(route) ? '/' : toTanstackPath(rawPath || '');
|
|
271
|
-
routeOpts.push(`path: ${quote(p)},`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
237
|
if (loaderName) {
|
|
275
238
|
routeOpts.push(
|
|
276
239
|
`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
|
|
@@ -292,7 +255,22 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
292
255
|
routeOpts.push(staticDataSnippet);
|
|
293
256
|
}
|
|
294
257
|
|
|
295
|
-
|
|
258
|
+
return routeOpts;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const buildRoute = async (
|
|
262
|
+
descriptor: RouteDescriptor<RouteForCli>,
|
|
263
|
+
parentVar: string,
|
|
264
|
+
): Promise<string> => {
|
|
265
|
+
const { source: route, location, hasSplat, children } = descriptor;
|
|
266
|
+
const varName = createRouteVarName(route);
|
|
267
|
+
const routeOpts = [
|
|
268
|
+
`getParentRoute: () => ${parentVar},`,
|
|
269
|
+
...Object.entries(location).map(
|
|
270
|
+
([key, value]) => `${key}: ${quote(value)},`,
|
|
271
|
+
),
|
|
272
|
+
...(await createRouteOptions(route, hasSplat)),
|
|
273
|
+
];
|
|
296
274
|
const hasChildren = Boolean(children && children.length > 0);
|
|
297
275
|
const routeCtorVarName = hasChildren
|
|
298
276
|
? reserveRouteVarName(`${varName}__base`)
|
|
@@ -305,9 +283,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
305
283
|
if (children && children.length > 0) {
|
|
306
284
|
const childVars: string[] = [];
|
|
307
285
|
for (const child of children) {
|
|
308
|
-
childVars.push(
|
|
309
|
-
await buildRoute({ parentVar: routeCtorVarName, route: child }),
|
|
310
|
-
);
|
|
286
|
+
childVars.push(await buildRoute(child, routeCtorVarName));
|
|
311
287
|
}
|
|
312
288
|
statements.push(
|
|
313
289
|
`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
|
|
@@ -317,60 +293,14 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
317
293
|
return varName;
|
|
318
294
|
};
|
|
319
295
|
|
|
320
|
-
const rootLoaderInfo = rootModern ? pickModernLoaderModule(rootModern) : null;
|
|
321
|
-
const rootAction = rootExtras?.action;
|
|
322
|
-
const rootLoaderImports = rootLoaderInfo?.loaderPath
|
|
323
|
-
? await getImportNamesForLoader(
|
|
324
|
-
rootLoaderInfo.loaderPath,
|
|
325
|
-
rootLoaderInfo.inline,
|
|
326
|
-
Boolean(
|
|
327
|
-
rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath,
|
|
328
|
-
),
|
|
329
|
-
)
|
|
330
|
-
: null;
|
|
331
|
-
const rootLoaderName = rootLoaderImports?.loaderName || null;
|
|
332
|
-
const rootActionName = rootLoaderImports?.actionName || null;
|
|
333
|
-
const rootSearchContractInfo = rootModern
|
|
334
|
-
? pickRouteSearchContractModules(rootModern)
|
|
335
|
-
: null;
|
|
336
|
-
const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
|
|
337
|
-
? await getImportNameForSearchContract(
|
|
338
|
-
rootSearchContractInfo.validateSearchPath,
|
|
339
|
-
'validateSearch',
|
|
340
|
-
)
|
|
341
|
-
: null;
|
|
342
|
-
const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
|
|
343
|
-
? await getImportNameForSearchContract(
|
|
344
|
-
rootSearchContractInfo.loaderDepsPath,
|
|
345
|
-
'loaderDeps',
|
|
346
|
-
)
|
|
347
|
-
: null;
|
|
348
|
-
|
|
349
296
|
const topLevelVars: string[] = [];
|
|
350
297
|
for (const route of topLevel) {
|
|
351
|
-
topLevelVars.push(await buildRoute(
|
|
298
|
+
topLevelVars.push(await buildRoute(route, 'rootRoute'));
|
|
352
299
|
}
|
|
353
300
|
|
|
354
|
-
const rootOpts
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
rootExtras?._component,
|
|
358
|
-
);
|
|
359
|
-
if (rootComponentName) {
|
|
360
|
-
rootOpts.push(`component: ${rootComponentName},`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (rootLoaderName) {
|
|
364
|
-
rootOpts.push(
|
|
365
|
-
`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
if (rootValidateSearchName) {
|
|
369
|
-
rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
|
|
370
|
-
}
|
|
371
|
-
if (rootLoaderDepsName) {
|
|
372
|
-
rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
|
|
373
|
-
}
|
|
301
|
+
const rootOpts = rootModern
|
|
302
|
+
? await createRouteOptions(rootModern, false)
|
|
303
|
+
: [];
|
|
374
304
|
|
|
375
305
|
const routerGenTs = `// This file is auto-generated by Modern.js. Do not edit manually.
|
|
376
306
|
|
|
@@ -388,13 +318,7 @@ ${imports.join('\n')}
|
|
|
388
318
|
|
|
389
319
|
export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
|
|
390
320
|
${rootOpts.join('\n ')}
|
|
391
|
-
|
|
392
|
-
createRouteStaticDataSnippet({
|
|
393
|
-
modernRouteId: rootExtras?.id,
|
|
394
|
-
loaderName: rootLoaderName,
|
|
395
|
-
actionName: rootActionName,
|
|
396
|
-
}) || ''
|
|
397
|
-
}
|
|
321
|
+
|
|
398
322
|
});
|
|
399
323
|
|
|
400
324
|
${statements.join('\n\n')}
|
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -98,15 +98,15 @@ type LoaderLikeContext = {
|
|
|
98
98
|
params?: Record<string, string>;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
function getLoaderSignal(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
101
|
+
export function getLoaderSignal(
|
|
102
|
+
ctx: LoaderLikeContext | undefined,
|
|
103
|
+
): AbortSignal {
|
|
104
|
+
const matchSignal = ctx?.abortController?.signal ?? ctx?.signal;
|
|
105
|
+
const requestSignal = ctx?.context?.request?.signal;
|
|
106
|
+
const signals = [matchSignal, requestSignal].filter(
|
|
107
|
+
(signal): signal is AbortSignal => signal instanceof AbortSignal,
|
|
108
|
+
);
|
|
109
|
+
return AbortSignal.any(signals);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export function getLoaderHref(ctx: LoaderLikeContext | undefined): string {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RouterNavigationCapability,
|
|
3
|
+
RouterNavigationSnapshot,
|
|
4
|
+
} from '@modern-js/runtime-extensions/router-state';
|
|
5
|
+
import type { AnyRouter } from '@tanstack/react-router';
|
|
6
|
+
import { Link } from './prefetchLink';
|
|
7
|
+
|
|
8
|
+
/** Native TanStack navigation, exposed through the provider contract. */
|
|
9
|
+
export function createTanstackNavigation(
|
|
10
|
+
router: AnyRouter,
|
|
11
|
+
): RouterNavigationCapability {
|
|
12
|
+
let location: AnyRouter['state']['location'] | undefined;
|
|
13
|
+
let matches: AnyRouter['state']['matches'] | undefined;
|
|
14
|
+
let snapshot: RouterNavigationSnapshot;
|
|
15
|
+
return {
|
|
16
|
+
Link: Link as RouterNavigationCapability['Link'],
|
|
17
|
+
getSnapshot() {
|
|
18
|
+
const state = router.state;
|
|
19
|
+
if (
|
|
20
|
+
location !== state.location ||
|
|
21
|
+
matches === undefined ||
|
|
22
|
+
matches.length !== state.matches.length ||
|
|
23
|
+
matches.some(
|
|
24
|
+
(match, index) => match.params !== state.matches[index]?.params,
|
|
25
|
+
)
|
|
26
|
+
) {
|
|
27
|
+
location = state.location;
|
|
28
|
+
matches = state.matches;
|
|
29
|
+
snapshot = {
|
|
30
|
+
location: {
|
|
31
|
+
pathname: location.pathname,
|
|
32
|
+
search: location.searchStr,
|
|
33
|
+
hash:
|
|
34
|
+
location.hash !== '' ? `#${location.hash.replace(/^#/, '')}` : '',
|
|
35
|
+
},
|
|
36
|
+
params: Object.assign({}, ...matches.map(match => match.params)),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return snapshot;
|
|
40
|
+
},
|
|
41
|
+
subscribe(listener) {
|
|
42
|
+
const stops = (
|
|
43
|
+
['onBeforeNavigate', 'onBeforeLoad', 'onLoad', 'onResolved'] as const
|
|
44
|
+
).map(event => router.subscribe(event, listener));
|
|
45
|
+
return () => {
|
|
46
|
+
for (const stop of stops) stop();
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
navigate(href, options) {
|
|
50
|
+
return router.navigate({
|
|
51
|
+
to: href,
|
|
52
|
+
replace: options?.replace,
|
|
53
|
+
...(options?.state === undefined
|
|
54
|
+
? {}
|
|
55
|
+
: { state: options.state as never }),
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
createLinkProps(target) {
|
|
59
|
+
const preload =
|
|
60
|
+
target.preload !== undefined
|
|
61
|
+
? target.preload
|
|
62
|
+
: target.prefetch === 'none'
|
|
63
|
+
? false
|
|
64
|
+
: target.prefetch;
|
|
65
|
+
return {
|
|
66
|
+
to: target.pathname,
|
|
67
|
+
...(target.search !== undefined ? { search: target.search } : {}),
|
|
68
|
+
...(target.hash !== undefined && target.hash !== ''
|
|
69
|
+
? { hash: target.hash }
|
|
70
|
+
: {}),
|
|
71
|
+
...(target.hashScrollIntoView === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { hashScrollIntoView: target.hashScrollIntoView }),
|
|
74
|
+
...(preload === undefined ? {} : { preload }),
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -23,11 +23,13 @@ import { createModernBasepathRewrite } from './basepathRewrite';
|
|
|
23
23
|
import { routerProviderRegistryHooks } from './hooks';
|
|
24
24
|
import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
|
|
25
25
|
import {
|
|
26
|
+
applyRouterRuntimeState,
|
|
26
27
|
applyRouterServerPrepareResult,
|
|
27
|
-
createRouterServerSnapshot,
|
|
28
28
|
getRouterRuntimeState,
|
|
29
|
+
getRouterServerSnapshot,
|
|
29
30
|
type RouterLifecycleContext,
|
|
30
31
|
} from './lifecycle';
|
|
32
|
+
import { createTanstackNavigation } from './navigation';
|
|
31
33
|
import {
|
|
32
34
|
createTanstackRouteObjects,
|
|
33
35
|
getTanstackRouteConfig,
|
|
@@ -168,116 +170,136 @@ export const tanstackRouterPlugin = (
|
|
|
168
170
|
setTanstackRscRouter(serverRouter);
|
|
169
171
|
}
|
|
170
172
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
173
|
+
let disposed = false;
|
|
174
|
+
let prepared = false;
|
|
175
|
+
const onAbort = () => controller.abort(rawRequest.signal.reason);
|
|
176
|
+
const cleanup = () => {
|
|
177
|
+
if (disposed) return;
|
|
178
|
+
disposed = true;
|
|
179
|
+
rawRequest.signal.removeEventListener('abort', onAbort);
|
|
180
|
+
controller.abort();
|
|
181
|
+
serverRouter.serverSsr?.cleanup?.();
|
|
182
|
+
};
|
|
183
|
+
// Acquire the disposer before attachment: every subsequent failure,
|
|
184
|
+
// including partial attachment, has an owner. Successful preparation
|
|
185
|
+
// leaves disposal to the response lifetime in the SSR pipeline.
|
|
186
|
+
applyRouterRuntimeState(context, {
|
|
187
|
+
framework: 'tanstack',
|
|
188
|
+
basename: _basename,
|
|
189
|
+
instance: serverRouter,
|
|
190
|
+
cleanup,
|
|
191
|
+
});
|
|
192
|
+
rawRequest.signal.addEventListener('abort', onAbort, { once: true });
|
|
175
193
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
rawRequest.signal.throwIfAborted();
|
|
195
|
+
await attachServerSsrUtils(serverRouter);
|
|
196
|
+
rawRequest.signal.throwIfAborted();
|
|
197
|
+
|
|
198
|
+
const end = time();
|
|
181
199
|
|
|
182
|
-
const serverLoadResult = serverRouter._serverResult;
|
|
183
|
-
if (!serverLoadResult) {
|
|
184
200
|
try {
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
201
|
+
await tanstackRouter.load({ sync: true });
|
|
202
|
+
} finally {
|
|
203
|
+
const cost = end();
|
|
204
|
+
context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
|
|
205
|
+
}
|
|
191
206
|
|
|
192
|
-
|
|
193
|
-
const
|
|
207
|
+
rawRequest.signal.throwIfAborted();
|
|
208
|
+
const serverLoadResult = serverRouter._serverResult;
|
|
209
|
+
if (!serverLoadResult) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
'TanStack Router completed an SSR load without a server result.',
|
|
212
|
+
);
|
|
213
|
+
}
|
|
194
214
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
if (serverLoadResult.type === 'redirect') {
|
|
216
|
+
const { redirect } = serverLoadResult;
|
|
217
|
+
|
|
218
|
+
return interrupt(
|
|
219
|
+
isRSCNavigation
|
|
220
|
+
? handleTanstackRscRedirect(
|
|
221
|
+
redirect.headers,
|
|
222
|
+
_basename,
|
|
223
|
+
redirect.status,
|
|
224
|
+
)
|
|
225
|
+
: redirect,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const routerErrors = collectRouterErrors(tanstackRouter);
|
|
230
|
+
if (routerErrors && loaderFailureMode === 'clientRender') {
|
|
231
|
+
(
|
|
232
|
+
context.ssrContext?.response as
|
|
233
|
+
| { status: (code: number) => void }
|
|
234
|
+
| undefined
|
|
235
|
+
)?.status(200);
|
|
236
|
+
throw Object.values(routerErrors)[0];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await preloadMatchedRouteComponents(serverRouter);
|
|
209
240
|
|
|
210
|
-
const routerErrors = collectRouterErrors(tanstackRouter);
|
|
211
|
-
if (routerErrors && loaderFailureMode === 'clientRender') {
|
|
212
241
|
(
|
|
213
242
|
context.ssrContext?.response as
|
|
214
243
|
| { status: (code: number) => void }
|
|
215
244
|
| undefined
|
|
216
|
-
)?.status(
|
|
217
|
-
try {
|
|
218
|
-
serverRouter.serverSsr?.cleanup?.();
|
|
219
|
-
} catch {}
|
|
220
|
-
throw Object.values(routerErrors)[0];
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await preloadMatchedRouteComponents(serverRouter);
|
|
245
|
+
)?.status(serverLoadResult.status);
|
|
224
246
|
|
|
225
|
-
|
|
226
|
-
context.ssrContext?.response as
|
|
227
|
-
| { status: (code: number) => void }
|
|
228
|
-
| undefined
|
|
229
|
-
)?.status(serverLoadResult.status);
|
|
247
|
+
await serverRouter.serverSsr?.dehydrate?.();
|
|
230
248
|
|
|
231
|
-
|
|
249
|
+
if (enableRsc) {
|
|
250
|
+
if (isRSCNavigation) {
|
|
251
|
+
// RSC navigations consume the server payload directly. Normal HTML SSR
|
|
252
|
+
// emits the buffered bootstrap script below and must not wait here
|
|
253
|
+
// because Modern's non-streaming hook has not rendered the app yet.
|
|
254
|
+
await waitForRouterSerialization(serverRouter);
|
|
255
|
+
}
|
|
232
256
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
await waitForRouterSerialization(serverRouter);
|
|
257
|
+
setTanstackRscServerPayload(
|
|
258
|
+
createTanstackRscServerPayload(serverRouter, {
|
|
259
|
+
omitClientLoaderData: isRSCNavigation,
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
239
262
|
}
|
|
240
263
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
|
|
249
|
-
const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
|
|
250
|
-
const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
|
|
251
|
-
const routerServerSnapshot: InternalRouterServerSnapshot =
|
|
252
|
-
createRouterServerSnapshot({
|
|
264
|
+
const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
|
|
265
|
+
const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
|
|
266
|
+
const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
|
|
267
|
+
const routerServerSnapshot: InternalRouterServerSnapshot = {
|
|
253
268
|
framework: 'tanstack',
|
|
254
269
|
basename: _basename,
|
|
255
270
|
statusCode: serverLoadResult.status,
|
|
256
271
|
errors: routerErrors,
|
|
257
272
|
matchedRouteIds,
|
|
258
273
|
hydrationScripts,
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
serverSnapshot: routerServerSnapshot,
|
|
274
|
+
};
|
|
275
|
+
const runtimeContext = applyRouterServerPrepareResult(
|
|
276
|
+
context as TInternalRuntimeContext,
|
|
277
|
+
{
|
|
278
|
+
snapshot: routerServerSnapshot,
|
|
279
|
+
cleanup,
|
|
280
|
+
state: {
|
|
281
|
+
framework: 'tanstack',
|
|
282
|
+
basename: _basename,
|
|
283
|
+
instance: serverRouter,
|
|
284
|
+
navigation: createTanstackNavigation(tanstackRouter),
|
|
285
|
+
},
|
|
272
286
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
);
|
|
288
|
+
rawRequest.signal.throwIfAborted();
|
|
289
|
+
hooks.onAfterCreateRouter.call({
|
|
290
|
+
...routerLifecycleContext,
|
|
291
|
+
router: serverRouter,
|
|
292
|
+
serverSnapshot: getRouterServerSnapshot(runtimeContext),
|
|
293
|
+
runtimeContext,
|
|
294
|
+
});
|
|
295
|
+
prepared = true;
|
|
296
|
+
} finally {
|
|
297
|
+
if (!prepared) {
|
|
298
|
+
try {
|
|
299
|
+
cleanup();
|
|
300
|
+
} catch {}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
281
303
|
});
|
|
282
304
|
|
|
283
305
|
api.wrapRoot(App => {
|
package/src/runtime/plugin.tsx
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
applyRouterRuntimeState,
|
|
33
33
|
type RouterLifecycleContext,
|
|
34
34
|
} from './lifecycle';
|
|
35
|
+
import { createTanstackNavigation } from './navigation';
|
|
35
36
|
import {
|
|
36
37
|
createTanstackRouteObjects,
|
|
37
38
|
getMergedRouterConfig,
|
|
@@ -259,10 +260,15 @@ export const tanstackRouterPlugin = (
|
|
|
259
260
|
if (!router) {
|
|
260
261
|
return App ? <App /> : null;
|
|
261
262
|
}
|
|
263
|
+
const navigation = useMemo(
|
|
264
|
+
() => createTanstackNavigation(router),
|
|
265
|
+
[router],
|
|
266
|
+
);
|
|
262
267
|
const runtimeState = applyRouterRuntimeState(runtimeContext, {
|
|
263
268
|
framework: 'tanstack',
|
|
264
269
|
basename: _basename,
|
|
265
270
|
instance: router,
|
|
271
|
+
navigation,
|
|
266
272
|
});
|
|
267
273
|
const lifecycleContext: RouterLifecycleContext = {
|
|
268
274
|
framework: 'tanstack',
|