@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.99 → 3.4.0-ultramodern.0
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 +80 -34
- package/dist/cjs/cli/routeSplitting.js +13 -41
- package/dist/cjs/cli/tanstackTypes.js +114 -200
- package/dist/cjs/cli.js +12 -8
- package/dist/cjs/runtime/basepathRewrite.js +12 -8
- package/dist/cjs/runtime/dataMutation.js +9 -5
- package/dist/cjs/runtime/hooks.js +20 -19
- package/dist/cjs/runtime/hydrationBoundary.js +12 -7
- package/dist/cjs/runtime/index.js +66 -54
- package/dist/cjs/runtime/lifecycle.js +21 -91
- package/dist/cjs/runtime/loaderBridge.js +173 -0
- package/dist/cjs/runtime/outlet.js +58 -0
- package/dist/cjs/runtime/plugin.js +190 -110
- package/dist/cjs/runtime/plugin.node.js +25 -40
- package/dist/cjs/runtime/plugin.worker.js +9 -5
- package/dist/cjs/runtime/pluginCore.js +55 -0
- package/dist/cjs/runtime/prefetchLink.js +10 -6
- package/dist/cjs/runtime/register.js +56 -0
- package/dist/cjs/runtime/routeTree.js +30 -212
- package/dist/cjs/runtime/router.js +41 -0
- package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
- package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
- package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
- package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
- package/dist/cjs/runtime/rsc/client.js +9 -5
- package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
- package/dist/cjs/runtime/rsc/index.js +9 -5
- package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
- package/dist/cjs/runtime/rsc/server.js +9 -5
- package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
- package/dist/cjs/runtime/rsc/symbols.js +20 -15
- package/dist/cjs/runtime/state.js +45 -0
- package/dist/cjs/runtime/types.js +13 -9
- package/dist/cjs/runtime/utils.js +9 -11
- package/dist/cjs/runtime.js +9 -5
- package/dist/esm/cli/index.mjs +73 -28
- package/dist/esm/cli/routeSplitting.mjs +1 -30
- package/dist/esm/cli/tanstackTypes.mjs +100 -190
- package/dist/esm/runtime/hooks.mjs +1 -8
- package/dist/esm/runtime/hydrationBoundary.mjs +3 -2
- package/dist/esm/runtime/index.mjs +4 -2
- package/dist/esm/runtime/lifecycle.mjs +1 -82
- package/dist/esm/runtime/loaderBridge.mjs +114 -0
- package/dist/esm/runtime/outlet.mjs +17 -0
- package/dist/esm/runtime/plugin.mjs +183 -107
- package/dist/esm/runtime/plugin.node.mjs +19 -38
- package/dist/esm/runtime/pluginCore.mjs +14 -0
- package/dist/esm/runtime/prefetchLink.mjs +1 -1
- package/dist/esm/runtime/register.mjs +18 -0
- package/dist/esm/runtime/routeTree.mjs +15 -198
- package/dist/esm/runtime/router.mjs +2 -0
- package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
- package/dist/esm/runtime/state.mjs +7 -0
- package/dist/esm/runtime/utils.mjs +0 -6
- package/dist/esm-node/cli/index.mjs +73 -28
- package/dist/esm-node/cli/routeSplitting.mjs +1 -30
- package/dist/esm-node/cli/tanstackTypes.mjs +100 -190
- package/dist/esm-node/runtime/hooks.mjs +1 -8
- package/dist/esm-node/runtime/hydrationBoundary.mjs +3 -2
- package/dist/esm-node/runtime/index.mjs +4 -2
- package/dist/esm-node/runtime/lifecycle.mjs +1 -82
- package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
- package/dist/esm-node/runtime/outlet.mjs +18 -0
- package/dist/esm-node/runtime/plugin.mjs +183 -107
- package/dist/esm-node/runtime/plugin.node.mjs +19 -38
- package/dist/esm-node/runtime/pluginCore.mjs +15 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
- package/dist/esm-node/runtime/register.mjs +19 -0
- package/dist/esm-node/runtime/routeTree.mjs +15 -198
- package/dist/esm-node/runtime/router.mjs +3 -0
- package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
- package/dist/esm-node/runtime/state.mjs +8 -0
- package/dist/esm-node/runtime/utils.mjs +0 -6
- package/dist/types/cli/index.d.ts +11 -2
- package/dist/types/cli/routeSplitting.d.ts +6 -15
- package/dist/types/cli/tanstackTypes.d.ts +21 -1
- package/dist/types/runtime/hooks.d.ts +8 -33
- package/dist/types/runtime/index.d.ts +6 -3
- package/dist/types/runtime/lifecycle.d.ts +7 -22
- package/dist/types/runtime/loaderBridge.d.ts +48 -0
- package/dist/types/runtime/outlet.d.ts +2 -0
- package/dist/types/runtime/plugin.d.ts +1 -14
- package/dist/types/runtime/plugin.node.d.ts +1 -14
- package/dist/types/runtime/pluginCore.d.ts +21 -0
- package/dist/types/runtime/register.d.ts +9 -0
- package/dist/types/runtime/routeTree.d.ts +0 -2
- package/dist/types/runtime/router.d.ts +14 -0
- package/dist/types/runtime/state.d.ts +16 -0
- package/dist/types/runtime/types.d.ts +7 -53
- package/package.json +40 -38
- package/rstest.config.mts +6 -0
- package/src/cli/index.ts +146 -24
- package/src/cli/routeSplitting.ts +6 -44
- package/src/cli/tanstackTypes.ts +203 -208
- package/src/runtime/hooks.ts +10 -27
- package/src/runtime/hydrationBoundary.tsx +5 -5
- package/src/runtime/index.tsx +12 -14
- package/src/runtime/lifecycle.ts +16 -151
- package/src/runtime/loaderBridge.ts +257 -0
- package/src/runtime/outlet.tsx +48 -0
- package/src/runtime/plugin.node.tsx +28 -83
- package/src/runtime/plugin.tsx +350 -202
- package/src/runtime/pluginCore.ts +48 -0
- package/src/runtime/prefetchLink.tsx +1 -1
- package/src/runtime/register.ts +58 -0
- package/src/runtime/routeTree.ts +41 -365
- package/src/runtime/router.ts +15 -0
- package/src/runtime/rsc/payloadRouter.ts +45 -2
- package/src/runtime/ssr-shim.d.ts +1 -3
- package/src/runtime/state.ts +29 -0
- package/src/runtime/types.ts +20 -67
- package/src/runtime/utils.tsx +3 -7
- package/tests/router/cli.test.ts +373 -31
- package/tests/router/hooks.test.ts +26 -0
- package/tests/router/loaderBridge.test.ts +211 -0
- package/tests/router/packageSurface.test.ts +24 -0
- package/tests/router/prefetchLink.test.tsx +43 -7
- package/tests/router/register.test.ts +46 -0
- package/tests/router/routeTree.test.ts +176 -139
- package/tests/router/rsc.test.tsx +70 -0
- package/tests/router/tanstackTypes.test.ts +395 -7
- package/dist/cjs/runtime/DefaultNotFound.js +0 -47
- package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
- package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
- package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
- package/src/runtime/DefaultNotFound.tsx +0 -15
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -1,151 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
routes: RouteObject[];
|
|
18
|
-
runtimeContext: TInternalRuntimeContext;
|
|
19
|
-
basename?: string;
|
|
20
|
-
hydrationData?: unknown;
|
|
21
|
-
router?: unknown;
|
|
22
|
-
matches?: RouterRouteMatchSnapshot[];
|
|
23
|
-
cleanup?: () => void | Promise<void>;
|
|
24
|
-
serverSnapshot?: InternalRouterServerSnapshot;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type RouterSnapshotLike = Partial<InternalRouterServerSnapshot>;
|
|
28
|
-
|
|
29
|
-
function toHydrationScripts(state: {
|
|
30
|
-
hydrationScript?: string;
|
|
31
|
-
hydrationScripts?: string[];
|
|
32
|
-
}) {
|
|
33
|
-
if (state.hydrationScripts?.length) {
|
|
34
|
-
return state.hydrationScripts;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return state.hydrationScript ? [state.hydrationScript] : undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getMatchedRouteIdsFromMatches(matches?: RouterRouteMatchSnapshot[]) {
|
|
41
|
-
const routeIds = matches
|
|
42
|
-
?.map(match => match.assetRouteId ?? match.routeId)
|
|
43
|
-
.filter((routeId): routeId is string => typeof routeId === 'string');
|
|
44
|
-
|
|
45
|
-
return routeIds?.length ? routeIds : undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function createRouterServerSnapshot(
|
|
49
|
-
state: RouterSnapshotLike,
|
|
50
|
-
): InternalRouterServerSnapshot {
|
|
51
|
-
const hydrationScripts = toHydrationScripts(state);
|
|
52
|
-
const matchedRouteIds =
|
|
53
|
-
state.matchedRouteIds ?? getMatchedRouteIdsFromMatches(state.matches);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
...state,
|
|
57
|
-
...(hydrationScripts?.length
|
|
58
|
-
? {
|
|
59
|
-
hydrationScript: state.hydrationScript ?? hydrationScripts[0],
|
|
60
|
-
hydrationScripts,
|
|
61
|
-
}
|
|
62
|
-
: {}),
|
|
63
|
-
...(matchedRouteIds ? { matchedRouteIds } : {}),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function createRouterRuntimeState(
|
|
68
|
-
state: InternalRouterRuntimeState,
|
|
69
|
-
): InternalRouterRuntimeState {
|
|
70
|
-
const hasSnapshotState =
|
|
71
|
-
Boolean(state.serverSnapshot) ||
|
|
72
|
-
Boolean(state.hydrationScript) ||
|
|
73
|
-
Boolean(state.hydrationScripts?.length) ||
|
|
74
|
-
Boolean(state.matchedRouteIds?.length) ||
|
|
75
|
-
Boolean(state.matches?.length);
|
|
76
|
-
const serverSnapshot = state.serverSnapshot
|
|
77
|
-
? createRouterServerSnapshot({
|
|
78
|
-
...state.serverSnapshot,
|
|
79
|
-
framework: state.serverSnapshot.framework ?? state.framework,
|
|
80
|
-
basename: state.serverSnapshot.basename ?? state.basename,
|
|
81
|
-
hydrationScript:
|
|
82
|
-
state.serverSnapshot.hydrationScript ?? state.hydrationScript,
|
|
83
|
-
hydrationScripts:
|
|
84
|
-
state.serverSnapshot.hydrationScripts ?? state.hydrationScripts,
|
|
85
|
-
matchedRouteIds:
|
|
86
|
-
state.serverSnapshot.matchedRouteIds ?? state.matchedRouteIds,
|
|
87
|
-
matches: state.serverSnapshot.matches ?? state.matches,
|
|
88
|
-
})
|
|
89
|
-
: hasSnapshotState
|
|
90
|
-
? createRouterServerSnapshot({
|
|
91
|
-
framework: state.framework,
|
|
92
|
-
basename: state.basename,
|
|
93
|
-
hydrationScript: state.hydrationScript,
|
|
94
|
-
hydrationScripts: state.hydrationScripts,
|
|
95
|
-
matchedRouteIds: state.matchedRouteIds,
|
|
96
|
-
matches: state.matches,
|
|
97
|
-
})
|
|
98
|
-
: undefined;
|
|
99
|
-
const hydrationScripts = toHydrationScripts({
|
|
100
|
-
hydrationScript: state.hydrationScript ?? serverSnapshot?.hydrationScript,
|
|
101
|
-
hydrationScripts:
|
|
102
|
-
state.hydrationScripts ?? serverSnapshot?.hydrationScripts,
|
|
103
|
-
});
|
|
104
|
-
const matchedRouteIds =
|
|
105
|
-
state.matchedRouteIds ??
|
|
106
|
-
serverSnapshot?.matchedRouteIds ??
|
|
107
|
-
getMatchedRouteIdsFromMatches(state.matches);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
...state,
|
|
111
|
-
...(hydrationScripts?.length
|
|
112
|
-
? {
|
|
113
|
-
hydrationScript: state.hydrationScript ?? hydrationScripts[0],
|
|
114
|
-
hydrationScripts,
|
|
115
|
-
}
|
|
116
|
-
: {}),
|
|
117
|
-
...(matchedRouteIds ? { matchedRouteIds } : {}),
|
|
118
|
-
...(serverSnapshot ? { serverSnapshot } : {}),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function applyRouterRuntimeState(
|
|
123
|
-
runtimeContext: TInternalRuntimeContext,
|
|
124
|
-
state: InternalRouterRuntimeState,
|
|
125
|
-
) {
|
|
126
|
-
const normalized = createRouterRuntimeState(state);
|
|
127
|
-
const mutableRuntimeContext = runtimeContext as any;
|
|
128
|
-
mutableRuntimeContext.routerFramework = normalized.framework;
|
|
129
|
-
mutableRuntimeContext.routerInstance = normalized.instance;
|
|
130
|
-
mutableRuntimeContext.routerHydrationScript = normalized.hydrationScript;
|
|
131
|
-
mutableRuntimeContext.routerMatchedRouteIds = normalized.matchedRouteIds;
|
|
132
|
-
mutableRuntimeContext.routerRuntime = normalized;
|
|
133
|
-
if (normalized.serverSnapshot) {
|
|
134
|
-
mutableRuntimeContext.routerServerSnapshot = normalized.serverSnapshot;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return runtimeContext;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function applyRouterServerPrepareResult(
|
|
141
|
-
runtimeContext: TInternalRuntimeContext,
|
|
142
|
-
result: RouterServerPrepareResult,
|
|
143
|
-
) {
|
|
144
|
-
const state = createRouterRuntimeState({
|
|
145
|
-
...result.state,
|
|
146
|
-
cleanup: result.cleanup ?? result.state.cleanup,
|
|
147
|
-
serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
|
|
148
|
-
});
|
|
149
|
-
applyRouterRuntimeState(runtimeContext, state);
|
|
150
|
-
return runtimeContext;
|
|
151
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The router runtime state helpers are owned by @modern-js/runtime (the same
|
|
3
|
+
* implementation backs the built-in react-router provider and the SSR
|
|
4
|
+
* pipeline). This module only re-exports them so every router provider
|
|
5
|
+
* writes to the exact same runtime-context extension slot.
|
|
6
|
+
*/
|
|
7
|
+
export {
|
|
8
|
+
applyRouterRuntimeState,
|
|
9
|
+
applyRouterServerPrepareResult,
|
|
10
|
+
createRouterRuntimeState,
|
|
11
|
+
createRouterServerSnapshot,
|
|
12
|
+
getRouterRuntimeState,
|
|
13
|
+
getRouterServerSnapshot,
|
|
14
|
+
type RouterLifecycleContext,
|
|
15
|
+
type RouterLifecyclePhase,
|
|
16
|
+
} from '@modern-js/runtime/context';
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// @effect-diagnostics strictBooleanExpressions:off
|
|
2
|
+
/**
|
|
3
|
+
* Runtime bridge between Modern.js data loaders and TanStack Router.
|
|
4
|
+
*
|
|
5
|
+
* The generated `src/modern-tanstack/<entry>/router.gen.ts` files import these
|
|
6
|
+
* helpers instead of inlining them, so loader/redirect bugfixes ship with the
|
|
7
|
+
* package instead of requiring every app to regenerate its files. The
|
|
8
|
+
* hand-written route-tree builder (`routeTree.ts`) shares the response/redirect
|
|
9
|
+
* helpers for the same reason.
|
|
10
|
+
*/
|
|
11
|
+
import { notFound, redirect } from '@tanstack/react-router';
|
|
12
|
+
|
|
13
|
+
/** Router context shape used by the generated TanStack router types. */
|
|
14
|
+
export type ModernRouterContext = {
|
|
15
|
+
request?: Request;
|
|
16
|
+
requestContext?: unknown;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function isResponse(value: unknown): value is Response {
|
|
20
|
+
const record = value as { headers?: unknown; status?: unknown } | null;
|
|
21
|
+
return (
|
|
22
|
+
record != null &&
|
|
23
|
+
typeof record === 'object' &&
|
|
24
|
+
typeof record.status === 'number' &&
|
|
25
|
+
typeof record.headers === 'object'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
|
|
30
|
+
|
|
31
|
+
export function isRedirectResponse(res: Response): boolean {
|
|
32
|
+
return redirectStatusCodes.has(res.status);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* TanStack redirects are Response objects carrying the original redirect
|
|
37
|
+
* `options`. They must be re-thrown untouched — re-translating them through
|
|
38
|
+
* the Modern Response handling would lose `to`-based (internal) targets.
|
|
39
|
+
*/
|
|
40
|
+
export function isTanstackRedirect(value: unknown): boolean {
|
|
41
|
+
return (
|
|
42
|
+
isResponse(value) &&
|
|
43
|
+
typeof (value as { options?: unknown }).options === 'object'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isAbsoluteUrl(value: string): boolean {
|
|
48
|
+
try {
|
|
49
|
+
void new URL(value);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Re-throw a Modern.js `Location` redirect as a TanStack redirect.
|
|
58
|
+
*
|
|
59
|
+
* Prefers `to` for internal/relative redirects so the basepath rewrite can be
|
|
60
|
+
* applied; absolute (external) URLs go through `href` untouched.
|
|
61
|
+
*/
|
|
62
|
+
export function throwTanstackRedirect(location: string): never {
|
|
63
|
+
const target = location || '/';
|
|
64
|
+
if (isAbsoluteUrl(target)) {
|
|
65
|
+
throw redirect({ href: target });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw redirect({ to: target });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* React Router uses `*` for splat params, TanStack Router uses `_splat`.
|
|
73
|
+
* Modern loaders expect the React Router spelling.
|
|
74
|
+
*/
|
|
75
|
+
export function mapSplatParamsForModernLoader(
|
|
76
|
+
params: Record<string, string>,
|
|
77
|
+
hasSplat: boolean,
|
|
78
|
+
): Record<string, string> {
|
|
79
|
+
if (!hasSplat) {
|
|
80
|
+
return params;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { _splat, ...rest } = params as Record<string, string> & {
|
|
84
|
+
_splat?: string;
|
|
85
|
+
};
|
|
86
|
+
if (typeof _splat !== 'undefined') {
|
|
87
|
+
return { ...rest, '*': _splat };
|
|
88
|
+
}
|
|
89
|
+
return rest;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Static-data factory used by the generated router files: drops empty fields
|
|
94
|
+
* so route static data stays minimal.
|
|
95
|
+
*/
|
|
96
|
+
export function createRouteStaticData(opts: {
|
|
97
|
+
modernRouteId?: string;
|
|
98
|
+
modernRouteAction?: unknown;
|
|
99
|
+
modernRouteLoader?: unknown;
|
|
100
|
+
}): {
|
|
101
|
+
modernRouteId?: string;
|
|
102
|
+
modernRouteAction?: unknown;
|
|
103
|
+
modernRouteLoader?: unknown;
|
|
104
|
+
} {
|
|
105
|
+
const staticData: {
|
|
106
|
+
modernRouteId?: string;
|
|
107
|
+
modernRouteAction?: unknown;
|
|
108
|
+
modernRouteLoader?: unknown;
|
|
109
|
+
} = {};
|
|
110
|
+
|
|
111
|
+
if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
|
|
112
|
+
staticData.modernRouteId = opts.modernRouteId;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof opts.modernRouteLoader !== 'undefined') {
|
|
116
|
+
staticData.modernRouteLoader = opts.modernRouteLoader;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (typeof opts.modernRouteAction !== 'undefined') {
|
|
120
|
+
staticData.modernRouteAction = opts.modernRouteAction;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return staticData;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type LoaderLikeContext = {
|
|
127
|
+
abortController?: AbortController;
|
|
128
|
+
signal?: AbortSignal;
|
|
129
|
+
context?: ModernRouterContext;
|
|
130
|
+
location?:
|
|
131
|
+
| string
|
|
132
|
+
| {
|
|
133
|
+
publicHref?: string;
|
|
134
|
+
href?: string;
|
|
135
|
+
url?: { href?: string };
|
|
136
|
+
};
|
|
137
|
+
params?: Record<string, string>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function getLoaderSignal(ctx: LoaderLikeContext | undefined): AbortSignal {
|
|
141
|
+
const abortSignal = ctx?.abortController?.signal;
|
|
142
|
+
if (abortSignal instanceof AbortSignal) {
|
|
143
|
+
return abortSignal;
|
|
144
|
+
}
|
|
145
|
+
if (ctx?.signal instanceof AbortSignal) {
|
|
146
|
+
return ctx.signal;
|
|
147
|
+
}
|
|
148
|
+
return new AbortController().signal;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getLoaderHref(ctx: LoaderLikeContext | undefined): string {
|
|
152
|
+
if (typeof ctx?.location === 'string') {
|
|
153
|
+
return ctx.location;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const publicHref = ctx?.location?.publicHref;
|
|
157
|
+
if (typeof publicHref === 'string') {
|
|
158
|
+
return publicHref;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const href = ctx?.location?.href;
|
|
162
|
+
if (typeof href === 'string') {
|
|
163
|
+
return href;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const urlHref = ctx?.location?.url?.href;
|
|
167
|
+
return typeof urlHref === 'string' ? urlHref : '';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getLoaderParams(
|
|
171
|
+
ctx: LoaderLikeContext | undefined,
|
|
172
|
+
): Record<string, string> {
|
|
173
|
+
return typeof ctx?.params === 'object' && ctx.params !== null
|
|
174
|
+
? ctx.params
|
|
175
|
+
: {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleModernLoaderResult<LoaderResult>(
|
|
179
|
+
result: LoaderResult,
|
|
180
|
+
): LoaderResult {
|
|
181
|
+
if (isResponse(result)) {
|
|
182
|
+
if (isRedirectResponse(result)) {
|
|
183
|
+
const location = result.headers.get('Location') ?? '/';
|
|
184
|
+
throwTanstackRedirect(location);
|
|
185
|
+
}
|
|
186
|
+
if (result.status === 404) {
|
|
187
|
+
throw notFound();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function handleModernLoaderError(err: unknown): never {
|
|
195
|
+
if (isResponse(err)) {
|
|
196
|
+
if (isTanstackRedirect(err)) {
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
if (isRedirectResponse(err)) {
|
|
200
|
+
const location = err.headers.get('Location') ?? '/';
|
|
201
|
+
throwTanstackRedirect(location);
|
|
202
|
+
}
|
|
203
|
+
if (err.status === 404) {
|
|
204
|
+
throw notFound();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Wrap a Modern.js data loader (`page.data.ts` loader/action style) into a
|
|
213
|
+
* TanStack Router loader: builds a `Request` from the loader context, maps
|
|
214
|
+
* splat params, and translates Response redirects/404s into TanStack
|
|
215
|
+
* `redirect()`/`notFound()`.
|
|
216
|
+
*/
|
|
217
|
+
export function modernLoaderToTanstack<TLoader extends (args: any) => any>(
|
|
218
|
+
opts: { hasSplat: boolean },
|
|
219
|
+
modernLoader: TLoader,
|
|
220
|
+
): (ctx: unknown) => Promise<Awaited<ReturnType<TLoader>>> {
|
|
221
|
+
type LoaderResult = Awaited<ReturnType<TLoader>>;
|
|
222
|
+
|
|
223
|
+
return (rawCtx: unknown): Promise<LoaderResult> => {
|
|
224
|
+
const ctx = rawCtx as LoaderLikeContext | undefined;
|
|
225
|
+
try {
|
|
226
|
+
const signal = getLoaderSignal(ctx);
|
|
227
|
+
const baseRequest: Request | undefined =
|
|
228
|
+
ctx?.context?.request instanceof Request
|
|
229
|
+
? ctx.context.request
|
|
230
|
+
: undefined;
|
|
231
|
+
|
|
232
|
+
const href = getLoaderHref(ctx);
|
|
233
|
+
|
|
234
|
+
const request =
|
|
235
|
+
baseRequest !== undefined
|
|
236
|
+
? new Request(baseRequest, { signal })
|
|
237
|
+
: new Request(href, { signal });
|
|
238
|
+
|
|
239
|
+
const params = mapSplatParamsForModernLoader(
|
|
240
|
+
getLoaderParams(ctx),
|
|
241
|
+
opts.hasSplat,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return Promise.resolve(
|
|
245
|
+
(modernLoader as (args: unknown) => unknown)({
|
|
246
|
+
request,
|
|
247
|
+
params,
|
|
248
|
+
context: ctx?.context?.requestContext,
|
|
249
|
+
}) as LoaderResult,
|
|
250
|
+
)
|
|
251
|
+
.then((result: LoaderResult) => handleModernLoaderResult(result))
|
|
252
|
+
.catch(handleModernLoaderError);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
handleModernLoaderError(err);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Outlet as TanstackOutlet } from '@tanstack/react-router';
|
|
2
|
+
import {
|
|
3
|
+
createElement,
|
|
4
|
+
type ElementType,
|
|
5
|
+
memo,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
type RouteComponentProps = Record<string, unknown>;
|
|
10
|
+
type PreloadableComponent = ElementType<RouteComponentProps> & {
|
|
11
|
+
load?: () => Promise<unknown>;
|
|
12
|
+
preload?: () => Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
type WrappedPreloadableComponent = ((
|
|
15
|
+
props: RouteComponentProps,
|
|
16
|
+
) => ReactElement | null) & {
|
|
17
|
+
load?: () => Promise<unknown>;
|
|
18
|
+
preload?: () => Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Outlet = memo(function ModernTanstackOutlet() {
|
|
22
|
+
return <TanstackOutlet />;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export function withModernRouteMatchContext(
|
|
26
|
+
component: unknown,
|
|
27
|
+
_routeId: string,
|
|
28
|
+
): unknown {
|
|
29
|
+
if (component === null || component === undefined) {
|
|
30
|
+
return component;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const Component = component as ElementType<RouteComponentProps>;
|
|
34
|
+
const WrappedRouteComponent: WrappedPreloadableComponent = props =>
|
|
35
|
+
createElement(Component, props);
|
|
36
|
+
|
|
37
|
+
const preloadable = component as PreloadableComponent;
|
|
38
|
+
if (typeof preloadable.load === 'function') {
|
|
39
|
+
WrappedRouteComponent.load = preloadable.load.bind(preloadable);
|
|
40
|
+
}
|
|
41
|
+
if (typeof preloadable.preload === 'function') {
|
|
42
|
+
WrappedRouteComponent.preload = preloadable.preload.bind(preloadable);
|
|
43
|
+
} else if (typeof preloadable.load === 'function') {
|
|
44
|
+
WrappedRouteComponent.preload = WrappedRouteComponent.load;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return WrappedRouteComponent;
|
|
48
|
+
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off newPromise:off strictBooleanExpressions:off unnecessaryArrowBlock:off
|
|
2
2
|
/// <reference path="./ssr-shim.d.ts" />
|
|
3
3
|
|
|
4
|
-
import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
|
|
5
|
-
import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
|
|
6
4
|
import {
|
|
7
5
|
getGlobalEnableRsc,
|
|
8
|
-
getGlobalLayoutApp,
|
|
9
|
-
getGlobalRoutes,
|
|
10
6
|
InternalRuntimeContext,
|
|
11
7
|
type ServerPayload,
|
|
12
8
|
type TInternalRuntimeContext,
|
|
13
9
|
} from '@modern-js/runtime/context';
|
|
14
|
-
import { merge } from '@modern-js/runtime-utils/merge';
|
|
15
10
|
import {
|
|
16
11
|
createRequestContext,
|
|
17
12
|
type RequestContext,
|
|
18
13
|
storage,
|
|
19
14
|
} from '@modern-js/runtime-utils/node';
|
|
20
|
-
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
21
15
|
import { time } from '@modern-js/runtime-utils/time';
|
|
22
16
|
import { LOADER_REPORTER_NAME } from '@modern-js/utils/universal/constants';
|
|
23
17
|
import {
|
|
@@ -26,25 +20,23 @@ import {
|
|
|
26
20
|
createRouter,
|
|
27
21
|
RouterProvider,
|
|
28
22
|
} from '@tanstack/react-router';
|
|
29
|
-
import { attachRouterServerSsrUtils } from '@tanstack/router-core/ssr/server';
|
|
30
23
|
import type React from 'react';
|
|
31
24
|
import { useContext } from 'react';
|
|
32
25
|
import { createModernBasepathRewrite } from './basepathRewrite';
|
|
33
|
-
import {
|
|
34
|
-
modifyRoutes as modifyRoutesHook,
|
|
35
|
-
onAfterCreateRouter as onAfterCreateRouterHook,
|
|
36
|
-
onAfterHydrateRouter as onAfterHydrateRouterHook,
|
|
37
|
-
onBeforeCreateRouter as onBeforeCreateRouterHook,
|
|
38
|
-
onBeforeCreateRoutes as onBeforeCreateRoutesHook,
|
|
39
|
-
onBeforeHydrateRouter as onBeforeHydrateRouterHook,
|
|
40
|
-
type RouterExtendsHooks,
|
|
41
|
-
} from './hooks';
|
|
26
|
+
import { routerProviderRegistryHooks } from './hooks';
|
|
42
27
|
import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
|
|
43
28
|
import {
|
|
44
29
|
applyRouterServerPrepareResult,
|
|
45
30
|
createRouterServerSnapshot,
|
|
31
|
+
getRouterRuntimeState,
|
|
46
32
|
type RouterLifecycleContext,
|
|
47
33
|
} from './lifecycle';
|
|
34
|
+
import {
|
|
35
|
+
getFinalRouteConfig,
|
|
36
|
+
getMergedRouterConfig,
|
|
37
|
+
type TanstackRouterPluginAPI,
|
|
38
|
+
type TanstackRouterRuntimePlugin,
|
|
39
|
+
} from './pluginCore';
|
|
48
40
|
import {
|
|
49
41
|
createRouteTreeFromRouteObjects,
|
|
50
42
|
getModernRouteIdsFromMatches,
|
|
@@ -65,25 +57,6 @@ type ModernTanstackRouterContext = {
|
|
|
65
57
|
requestContext: RequestContext<Record<string, unknown>>;
|
|
66
58
|
};
|
|
67
59
|
|
|
68
|
-
type TanstackRouterRuntimeConfig = {
|
|
69
|
-
plugins?: TanstackRouterRuntimePlugin[];
|
|
70
|
-
router?: Partial<RouterConfig>;
|
|
71
|
-
[key: string]: unknown;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
type TanstackRouterRuntimeExtends = Required<
|
|
75
|
-
RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>
|
|
76
|
-
> & {
|
|
77
|
-
extendHooks: RouterExtendsHooks;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
|
|
81
|
-
|
|
82
|
-
type TanstackRouterRuntimePlugin = Plugin<
|
|
83
|
-
TanstackRouterPluginAPI,
|
|
84
|
-
TInternalRuntimeContext
|
|
85
|
-
>;
|
|
86
|
-
|
|
87
60
|
const setTanstackRscServerPayload = (payload: ServerPayload) => {
|
|
88
61
|
const storageContext = storage.useContext?.() as
|
|
89
62
|
| { serverPayload?: ServerPayload }
|
|
@@ -169,7 +142,8 @@ function isReactLazyRouteComponent(
|
|
|
169
142
|
component: unknown,
|
|
170
143
|
): component is ReactLazyRouteComponent {
|
|
171
144
|
return (
|
|
172
|
-
|
|
145
|
+
component !== null &&
|
|
146
|
+
component !== undefined &&
|
|
173
147
|
typeof component === 'object' &&
|
|
174
148
|
typeof (component as ReactLazyRouteComponent)._init === 'function' &&
|
|
175
149
|
'_payload' in component
|
|
@@ -299,20 +273,6 @@ function createGetSsrHref(request: Request): string {
|
|
|
299
273
|
return `${url.pathname}${url.search}${url.hash}`;
|
|
300
274
|
}
|
|
301
275
|
|
|
302
|
-
function stripSyntheticNotFoundRoute(routes: RouteObject[]): RouteObject[] {
|
|
303
|
-
return routes
|
|
304
|
-
.filter(route => !(route.path === '*' && !route.id && !route.loader))
|
|
305
|
-
.map(route => {
|
|
306
|
-
if (!route.children?.length) {
|
|
307
|
-
return route;
|
|
308
|
-
}
|
|
309
|
-
return {
|
|
310
|
-
...route,
|
|
311
|
-
children: stripSyntheticNotFoundRoute(route.children),
|
|
312
|
-
};
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
276
|
function collectRouterErrors(
|
|
317
277
|
tanstackRouter: AnyRouter,
|
|
318
278
|
): Record<string, unknown> | undefined {
|
|
@@ -339,40 +299,34 @@ function collectRouterErrors(
|
|
|
339
299
|
return Object.keys(errors).length > 0 ? errors : undefined;
|
|
340
300
|
}
|
|
341
301
|
|
|
302
|
+
async function attachServerSsrUtils(router: TanstackRouterWithServerSsr) {
|
|
303
|
+
const { attachRouterServerSsrUtils } = await import(
|
|
304
|
+
'@tanstack/router-core/ssr/server'
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
attachRouterServerSsrUtils({
|
|
308
|
+
router,
|
|
309
|
+
manifest: undefined,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
342
313
|
export const tanstackRouterPlugin = (
|
|
343
314
|
userConfig: Partial<RouterConfig> = {},
|
|
344
315
|
): TanstackRouterRuntimePlugin => {
|
|
345
316
|
const plugin: TanstackRouterRuntimePlugin = {
|
|
346
317
|
name: '@modern-js/plugin-router-tanstack',
|
|
347
|
-
registryHooks:
|
|
348
|
-
modifyRoutes: modifyRoutesHook,
|
|
349
|
-
onAfterCreateRouter: onAfterCreateRouterHook,
|
|
350
|
-
onAfterHydrateRouter: onAfterHydrateRouterHook,
|
|
351
|
-
onBeforeCreateRouter: onBeforeCreateRouterHook,
|
|
352
|
-
onBeforeCreateRoutes: onBeforeCreateRoutesHook,
|
|
353
|
-
onBeforeHydrateRouter: onBeforeHydrateRouterHook,
|
|
354
|
-
},
|
|
318
|
+
registryHooks: routerProviderRegistryHooks,
|
|
355
319
|
setup: (api: TanstackRouterPluginAPI) => {
|
|
356
320
|
api.onBeforeRender(async (context, interrupt) => {
|
|
357
|
-
const
|
|
358
|
-
router?: Partial<RouterConfig>;
|
|
359
|
-
};
|
|
360
|
-
const mergedConfig = merge(
|
|
361
|
-
pluginConfig.router || {},
|
|
362
|
-
userConfig,
|
|
363
|
-
) as RouterConfig;
|
|
321
|
+
const mergedConfig = getMergedRouterConfig(api, userConfig);
|
|
364
322
|
const serializationAdapters = getGlobalEnableRsc()
|
|
365
323
|
? (await import('./rsc/server')).getTanstackRscSerializationAdapters()
|
|
366
324
|
: undefined;
|
|
367
325
|
const enableRsc = getGlobalEnableRsc();
|
|
368
326
|
|
|
369
|
-
const { basename = '',
|
|
327
|
+
const { basename = '', createRoutes } = mergedConfig;
|
|
370
328
|
|
|
371
|
-
const finalRouteConfig =
|
|
372
|
-
routes: getGlobalRoutes(),
|
|
373
|
-
globalApp: getGlobalLayoutApp(),
|
|
374
|
-
...routesConfig,
|
|
375
|
-
};
|
|
329
|
+
const finalRouteConfig = getFinalRouteConfig(mergedConfig);
|
|
376
330
|
|
|
377
331
|
if (!finalRouteConfig.routes && !createRoutes) {
|
|
378
332
|
return;
|
|
@@ -387,12 +341,7 @@ export const tanstackRouterPlugin = (
|
|
|
387
341
|
routesConfig: finalRouteConfig,
|
|
388
342
|
ssrMode: context.ssrContext?.mode,
|
|
389
343
|
}) || [];
|
|
390
|
-
const
|
|
391
|
-
? routeObjects
|
|
392
|
-
: stripSyntheticNotFoundRoute(routeObjects);
|
|
393
|
-
const modifiedRouteObjects = hooks.modifyRoutes.call(
|
|
394
|
-
normalizedRouteObjects,
|
|
395
|
-
);
|
|
344
|
+
const modifiedRouteObjects = hooks.modifyRoutes.call(routeObjects);
|
|
396
345
|
|
|
397
346
|
if (!modifiedRouteObjects.length) {
|
|
398
347
|
return;
|
|
@@ -457,10 +406,7 @@ export const tanstackRouterPlugin = (
|
|
|
457
406
|
const serverRouter =
|
|
458
407
|
tanstackRouter as unknown as TanstackRouterWithServerSsr;
|
|
459
408
|
|
|
460
|
-
|
|
461
|
-
router: serverRouter,
|
|
462
|
-
manifest: undefined,
|
|
463
|
-
});
|
|
409
|
+
await attachServerSsrUtils(serverRouter);
|
|
464
410
|
|
|
465
411
|
const end = time();
|
|
466
412
|
|
|
@@ -559,8 +505,7 @@ export const tanstackRouterPlugin = (
|
|
|
559
505
|
const context = useContext(
|
|
560
506
|
InternalRuntimeContext,
|
|
561
507
|
) as unknown as TInternalRuntimeContext;
|
|
562
|
-
const router =
|
|
563
|
-
context.routerInstance ?? context.routerRuntime?.instance;
|
|
508
|
+
const router = getRouterRuntimeState(context)?.instance;
|
|
564
509
|
if (!router) {
|
|
565
510
|
return App ? <App {...props} /> : null;
|
|
566
511
|
}
|