@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
|
@@ -10,35 +10,8 @@ export type TanstackRsbuildRouteSplittingProfile = {
|
|
|
10
10
|
splitRouteChunks: boolean;
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
|
-
modernRouteChunks: {
|
|
14
|
-
enabled: boolean;
|
|
15
|
-
owner: 'modern';
|
|
16
|
-
};
|
|
17
|
-
builderChunkSplit: {
|
|
18
|
-
owner: 'modern-rsbuild';
|
|
19
|
-
preserved: true;
|
|
20
|
-
};
|
|
21
|
-
tanstackStartRspackSplitter: {
|
|
22
|
-
compatible: boolean;
|
|
23
|
-
reason: string;
|
|
24
|
-
clientDeleteNodes: string[];
|
|
25
|
-
routeFactoryCalls: string[];
|
|
26
|
-
};
|
|
27
13
|
};
|
|
28
14
|
|
|
29
|
-
const TANSTACK_START_ROUTE_FACTORY_CALLS = [
|
|
30
|
-
'createFileRoute',
|
|
31
|
-
'createRootRoute',
|
|
32
|
-
'createRootRouteWithContext',
|
|
33
|
-
] as const;
|
|
34
|
-
|
|
35
|
-
const TANSTACK_START_ROUTE_FACTORY_REGEX =
|
|
36
|
-
/\b(createFileRoute|createRootRoute|createRootRouteWithContext)\s*(?:<|\()/;
|
|
37
|
-
|
|
38
|
-
export function isTanstackStartRouteModuleSource(source: string) {
|
|
39
|
-
return TANSTACK_START_ROUTE_FACTORY_REGEX.test(source);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
15
|
export function resolveTanstackRouteCodeSplittingEnabled(
|
|
43
16
|
option?: TanstackRouteCodeSplittingOption,
|
|
44
17
|
) {
|
|
@@ -49,6 +22,12 @@ export function resolveTanstackRouteCodeSplittingEnabled(
|
|
|
49
22
|
return option?.enabled ?? true;
|
|
50
23
|
}
|
|
51
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Route chunking for TanStack entries is owned by Modern's
|
|
27
|
+
* `output.splitRouteChunks`; the TanStack Start Rspack splitter does not
|
|
28
|
+
* apply because Modern generates TanStack route trees from Modern route
|
|
29
|
+
* metadata rather than TanStack file-route factory modules.
|
|
30
|
+
*/
|
|
52
31
|
export function createTanstackRsbuildRouteSplittingProfile(opts: {
|
|
53
32
|
routeCodeSplitting?: TanstackRouteCodeSplittingOption;
|
|
54
33
|
}): TanstackRsbuildRouteSplittingProfile {
|
|
@@ -60,22 +39,5 @@ export function createTanstackRsbuildRouteSplittingProfile(opts: {
|
|
|
60
39
|
),
|
|
61
40
|
},
|
|
62
41
|
},
|
|
63
|
-
modernRouteChunks: {
|
|
64
|
-
enabled: resolveTanstackRouteCodeSplittingEnabled(
|
|
65
|
-
opts.routeCodeSplitting,
|
|
66
|
-
),
|
|
67
|
-
owner: 'modern',
|
|
68
|
-
},
|
|
69
|
-
builderChunkSplit: {
|
|
70
|
-
owner: 'modern-rsbuild',
|
|
71
|
-
preserved: true,
|
|
72
|
-
},
|
|
73
|
-
tanstackStartRspackSplitter: {
|
|
74
|
-
compatible: false,
|
|
75
|
-
reason:
|
|
76
|
-
'TanStack Start Rsbuild route splitting is tied to TanStack file-route factory modules; Modern generates TanStack route trees from Modern route metadata and owns route chunking through output.splitRouteChunks.',
|
|
77
|
-
clientDeleteNodes: ['ssr', 'server', 'headers'],
|
|
78
|
-
routeFactoryCalls: [...TANSTACK_START_ROUTE_FACTORY_CALLS],
|
|
79
|
-
},
|
|
80
42
|
};
|
|
81
43
|
}
|
package/src/cli/tanstackTypes.ts
CHANGED
|
@@ -1,31 +1,10 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off nodeBuiltinImport:off strictBooleanExpressions:off
|
|
2
2
|
import type { AppToolsContext } from '@modern-js/app-tools';
|
|
3
|
+
import { getPathWithoutExt, makeLegalIdentifier } from '@modern-js/runtime/cli';
|
|
3
4
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
4
|
-
import { findExists, formatImportPath,
|
|
5
|
+
import { findExists, formatImportPath, slash } from '@modern-js/utils';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
|
|
7
|
-
const reservedWords =
|
|
8
|
-
'break case class catch const continue debugger default delete do else export extends finally for function if import in instanceof let new return super switch this throw try typeof var void while with yield enum await implements package protected static interface private public';
|
|
9
|
-
const builtins =
|
|
10
|
-
'arguments Infinity NaN undefined null true false eval uneval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Symbol Error EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError Number Math Date String RegExp Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Float32Array Float64Array Map Set WeakMap WeakSet SIMD ArrayBuffer DataView JSON Promise Generator GeneratorFunction Reflect Proxy Intl';
|
|
11
|
-
const forbidList = new Set<string>(`${reservedWords} ${builtins}`.split(' '));
|
|
12
|
-
|
|
13
|
-
function makeLegalIdentifier(str: string) {
|
|
14
|
-
const identifier = str
|
|
15
|
-
.replace(/-(\w)/g, (_, letter) => letter.toUpperCase())
|
|
16
|
-
.replace(/[^$_a-zA-Z0-9]/g, '_');
|
|
17
|
-
|
|
18
|
-
if (/\d/.test(identifier[0]) || forbidList.has(identifier)) {
|
|
19
|
-
return `_${identifier}`;
|
|
20
|
-
}
|
|
21
|
-
return identifier || '_';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getPathWithoutExt(filename: string) {
|
|
25
|
-
const extname = path.extname(filename);
|
|
26
|
-
return extname ? filename.slice(0, -extname.length) : filename;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
8
|
const JS_OR_TS_EXTS = [
|
|
30
9
|
'.js',
|
|
31
10
|
'.jsx',
|
|
@@ -137,27 +116,154 @@ function createRouteStaticDataSnippet(opts: {
|
|
|
137
116
|
)}\n }),`;
|
|
138
117
|
}
|
|
139
118
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
119
|
+
const LOCALE_PARAM_SEGMENTS = new Set([
|
|
120
|
+
':lang',
|
|
121
|
+
':locale',
|
|
122
|
+
':language',
|
|
123
|
+
'$lang',
|
|
124
|
+
'$locale',
|
|
125
|
+
'$language',
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
type CanonicalAwareRoute = (NestedRouteForCli | PageRoute) & {
|
|
129
|
+
modernCanonicalPath?: string;
|
|
130
|
+
index?: boolean;
|
|
131
|
+
isRoot?: boolean;
|
|
132
|
+
children?: CanonicalAwareRoute[];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function paramsTypeForCanonicalPath(canonicalPath: string): string {
|
|
136
|
+
const fields: string[] = [];
|
|
137
|
+
|
|
138
|
+
for (const segment of canonicalPath.split('/')) {
|
|
139
|
+
if (!segment) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (segment === '*' || segment === '$') {
|
|
143
|
+
fields.push(`'_splat'?: string`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (segment.startsWith('{-$') && segment.endsWith('}')) {
|
|
147
|
+
fields.push(`${JSON.stringify(segment.slice(3, -1))}?: string`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (segment.startsWith('$')) {
|
|
151
|
+
fields.push(`${JSON.stringify(segment.slice(1))}: string`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (segment.startsWith(':')) {
|
|
155
|
+
const optional = segment.endsWith('?');
|
|
156
|
+
const name = segment.slice(1, optional ? undefined : segment.length);
|
|
157
|
+
fields.push(
|
|
158
|
+
`${JSON.stringify(optional ? name.slice(0, -1) : name)}${
|
|
159
|
+
optional ? '?' : ''
|
|
160
|
+
}: string`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return fields.length > 0
|
|
166
|
+
? `{ ${fields.join('; ')} }`
|
|
167
|
+
: 'Record<string, never>';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type CollectCanonicalRoutesOptions = {
|
|
171
|
+
/**
|
|
172
|
+
* Whether a leading `:lang`/`:locale`/`:language` route param may be
|
|
173
|
+
* treated as an i18n locale prefix. This MUST only be enabled when
|
|
174
|
+
* `@modern-js/plugin-i18n` is actually installed: the emitted
|
|
175
|
+
* `declare module '@modern-js/plugin-i18n/runtime'` augmentation breaks
|
|
176
|
+
* typechecking (TS2664) for apps that hand-roll a `/:lang/` param without
|
|
177
|
+
* the plugin. Routes carrying `modernCanonicalPath` metadata are always
|
|
178
|
+
* honored — only plugin-i18n produces them.
|
|
179
|
+
*/
|
|
180
|
+
localeParamHeuristic?: boolean;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Derive the canonical (language-agnostic) route map for an entry: the
|
|
185
|
+
* leading locale param is stripped and localized physical variants (routes
|
|
186
|
+
* carrying `modernCanonicalPath` metadata from `@modern-js/plugin-i18n`)
|
|
187
|
+
* collapse to their canonical pattern. Returns `null` when the entry has no
|
|
188
|
+
* i18n routing surface (no locale param and no localized variants), so plain
|
|
189
|
+
* TanStack apps never get a `@modern-js/plugin-i18n` module augmentation.
|
|
190
|
+
*/
|
|
191
|
+
export function collectCanonicalRoutesForEntry(
|
|
192
|
+
routes: (NestedRouteForCli | PageRoute)[],
|
|
193
|
+
options: CollectCanonicalRoutesOptions = {},
|
|
194
|
+
): Record<string, string> | null {
|
|
195
|
+
const { localeParamHeuristic = true } = options;
|
|
196
|
+
const canonicalParams = new Map<string, string>();
|
|
197
|
+
let hasI18nSurface = false;
|
|
198
|
+
|
|
199
|
+
const normalizeJoined = (joined: string): string => {
|
|
200
|
+
const collapsed = joined.replace(/\/+/g, '/');
|
|
201
|
+
const withLeading = collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
|
|
202
|
+
return withLeading.length > 1
|
|
203
|
+
? withLeading.replace(/\/+$/, '')
|
|
204
|
+
: withLeading;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const record = (canonicalPath: string) => {
|
|
208
|
+
const normalized = normalizeJoined(canonicalPath || '/');
|
|
209
|
+
const key = toTanstackPath(normalized);
|
|
210
|
+
if (!canonicalParams.has(key)) {
|
|
211
|
+
canonicalParams.set(key, paramsTypeForCanonicalPath(normalized));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const visit = (route: CanonicalAwareRoute, parentPath: string) => {
|
|
216
|
+
let currentPath = parentPath;
|
|
217
|
+
|
|
218
|
+
if (typeof route.modernCanonicalPath === 'string') {
|
|
219
|
+
hasI18nSurface = true;
|
|
220
|
+
currentPath = normalizeJoined(route.modernCanonicalPath);
|
|
221
|
+
} else if (typeof route.path === 'string' && route.path.length > 0) {
|
|
222
|
+
const segments = route.path
|
|
223
|
+
.replace(/\[(.+?)\]/g, ':$1')
|
|
224
|
+
.split('/')
|
|
225
|
+
.filter(Boolean);
|
|
226
|
+
if (
|
|
227
|
+
localeParamHeuristic &&
|
|
228
|
+
parentPath === '' &&
|
|
229
|
+
LOCALE_PARAM_SEGMENTS.has(segments[0])
|
|
230
|
+
) {
|
|
231
|
+
hasI18nSurface = true;
|
|
232
|
+
segments.shift();
|
|
233
|
+
}
|
|
234
|
+
currentPath = segments.length
|
|
235
|
+
? normalizeJoined(`${parentPath}/${segments.join('/')}`)
|
|
236
|
+
: parentPath;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const children = route.children;
|
|
240
|
+
if (children && children.length > 0) {
|
|
241
|
+
for (const child of children) {
|
|
242
|
+
visit(child, currentPath);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Leaf page or index route: a navigable target.
|
|
248
|
+
record(currentPath || '/');
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const rootModern = routes.find(
|
|
252
|
+
route => (route as CanonicalAwareRoute).isRoot,
|
|
253
|
+
) as CanonicalAwareRoute | undefined;
|
|
254
|
+
const topLevel = rootModern ? (rootModern.children ?? []) : routes;
|
|
255
|
+
|
|
256
|
+
for (const route of topLevel) {
|
|
257
|
+
visit(route as CanonicalAwareRoute, '');
|
|
152
258
|
}
|
|
153
259
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Heuristic: allow both single and double quotes, tolerate whitespace/newlines.
|
|
157
|
-
return /framework\s*:\s*['"]tanstack['"]/.test(content);
|
|
158
|
-
} catch {
|
|
159
|
-
return false;
|
|
260
|
+
if (!hasI18nSurface || canonicalParams.size === 0) {
|
|
261
|
+
return null;
|
|
160
262
|
}
|
|
263
|
+
|
|
264
|
+
return Object.fromEntries(
|
|
265
|
+
[...canonicalParams.entries()].sort(([a], [b]) => a.localeCompare(b)),
|
|
266
|
+
);
|
|
161
267
|
}
|
|
162
268
|
|
|
163
269
|
export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
@@ -193,13 +299,50 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
193
299
|
const statements: string[] = [];
|
|
194
300
|
|
|
195
301
|
const loaderImportMap = new Map<string, string>();
|
|
302
|
+
const componentImportMap = new Map<string, Promise<string | null>>();
|
|
196
303
|
const searchContractImportMap = new Map<string, string>();
|
|
197
304
|
const usedRouteVarNames = new Set<string>();
|
|
198
305
|
let loaderIndex = 0;
|
|
306
|
+
let componentIndex = 0;
|
|
199
307
|
let validateSearchIndex = 0;
|
|
200
308
|
let loaderDepsIndex = 0;
|
|
201
309
|
let routeIndex = 0;
|
|
202
310
|
|
|
311
|
+
const getImportNameForComponent = (
|
|
312
|
+
componentPath: unknown,
|
|
313
|
+
): Promise<string | null> => {
|
|
314
|
+
if (typeof componentPath !== 'string' || componentPath.length === 0) {
|
|
315
|
+
return Promise.resolve(null);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Cache the in-flight promise: sibling routes sharing a component module
|
|
319
|
+
// are generated concurrently and must reuse one import.
|
|
320
|
+
let pendingImportName = componentImportMap.get(componentPath);
|
|
321
|
+
if (!pendingImportName) {
|
|
322
|
+
pendingImportName = (async () => {
|
|
323
|
+
// Resolve through the same machinery as loaders: the raw `_component`
|
|
324
|
+
// value carries the internal `@_modern_js_src` alias, which the app's
|
|
325
|
+
// tsconfig does not map — the generated file must use relative
|
|
326
|
+
// imports.
|
|
327
|
+
const resolvedNoExt = await resolveRouteModuleNoExt(componentPath);
|
|
328
|
+
if (!resolvedNoExt) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const relImport = normalizeRelativeImport(
|
|
333
|
+
path.relative(outDir, resolvedNoExt),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const componentName = `component_${componentIndex++}`;
|
|
337
|
+
imports.push(`import ${componentName} from ${quote(relImport)};`);
|
|
338
|
+
return componentName;
|
|
339
|
+
})();
|
|
340
|
+
componentImportMap.set(componentPath, pendingImportName);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return pendingImportName;
|
|
344
|
+
};
|
|
345
|
+
|
|
203
346
|
const resolveRouteModuleNoExt = async (aliasedNoExtPath: string) => {
|
|
204
347
|
const prefix = `${appContext.internalSrcAlias}/`;
|
|
205
348
|
let absNoExt: string;
|
|
@@ -344,6 +487,13 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
344
487
|
|
|
345
488
|
const routeOpts: string[] = [`getParentRoute: () => ${parentVar},`];
|
|
346
489
|
|
|
490
|
+
const componentName = await getImportNameForComponent(
|
|
491
|
+
(route as any)._component,
|
|
492
|
+
);
|
|
493
|
+
if (componentName) {
|
|
494
|
+
routeOpts.push(`component: ${componentName},`);
|
|
495
|
+
}
|
|
496
|
+
|
|
347
497
|
if (isPathlessLayout(route)) {
|
|
348
498
|
const id = (route as any).id as string | undefined;
|
|
349
499
|
routeOpts.push(`id: ${quote(id || 'pathless')},`);
|
|
@@ -433,6 +583,14 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
433
583
|
);
|
|
434
584
|
|
|
435
585
|
const rootOpts: string[] = [];
|
|
586
|
+
|
|
587
|
+
const rootComponentName = await getImportNameForComponent(
|
|
588
|
+
(rootModern as any)?._component,
|
|
589
|
+
);
|
|
590
|
+
if (rootComponentName) {
|
|
591
|
+
rootOpts.push(`component: ${rootComponentName},`);
|
|
592
|
+
}
|
|
593
|
+
|
|
436
594
|
if (rootLoaderName) {
|
|
437
595
|
rootOpts.push(
|
|
438
596
|
`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
|
|
@@ -450,178 +608,15 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
450
608
|
|
|
451
609
|
import {
|
|
452
610
|
createMemoryHistory,
|
|
453
|
-
modernTanstackRouterFastDefaults,
|
|
454
611
|
createRootRouteWithContext,
|
|
455
612
|
createRoute,
|
|
456
613
|
createRouter,
|
|
457
|
-
|
|
458
|
-
|
|
614
|
+
createRouteStaticData,
|
|
615
|
+
type ModernRouterContext,
|
|
616
|
+
modernLoaderToTanstack,
|
|
617
|
+
modernTanstackRouterFastDefaults,
|
|
459
618
|
} from '@modern-js/plugin-tanstack/runtime';
|
|
460
619
|
|
|
461
|
-
type ModernRouterContext = {
|
|
462
|
-
request?: Request;
|
|
463
|
-
requestContext?: unknown;
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
function isResponse(value: unknown): value is Response {
|
|
467
|
-
return (
|
|
468
|
-
value != null &&
|
|
469
|
-
typeof value === 'object' &&
|
|
470
|
-
typeof (value as any).status === 'number' &&
|
|
471
|
-
typeof (value as any).headers === 'object'
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
|
|
476
|
-
function isRedirectResponse(res: Response) {
|
|
477
|
-
return redirectStatusCodes.has(res.status);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function throwTanstackRedirect(location: string) {
|
|
481
|
-
const target = location.length > 0 ? location : '/';
|
|
482
|
-
try {
|
|
483
|
-
void new URL(target);
|
|
484
|
-
throw redirect({ href: target });
|
|
485
|
-
} catch {
|
|
486
|
-
throw redirect({ to: target });
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function mapParamsForModernLoader(params: Record<string, string>, hasSplat: boolean) {
|
|
491
|
-
if (!hasSplat) {
|
|
492
|
-
return params;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const { _splat, ...rest } = params as any;
|
|
496
|
-
if (typeof _splat !== 'undefined') {
|
|
497
|
-
return { ...rest, '*': _splat };
|
|
498
|
-
}
|
|
499
|
-
return rest;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function createRouteStaticData(opts: {
|
|
503
|
-
modernRouteId?: string;
|
|
504
|
-
modernRouteAction?: unknown;
|
|
505
|
-
modernRouteLoader?: unknown;
|
|
506
|
-
}) {
|
|
507
|
-
const staticData: {
|
|
508
|
-
modernRouteId?: string;
|
|
509
|
-
modernRouteAction?: unknown;
|
|
510
|
-
modernRouteLoader?: unknown;
|
|
511
|
-
} = {};
|
|
512
|
-
|
|
513
|
-
if (typeof opts.modernRouteId === 'string' && opts.modernRouteId.length > 0) {
|
|
514
|
-
staticData.modernRouteId = opts.modernRouteId;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (typeof opts.modernRouteLoader !== 'undefined') {
|
|
518
|
-
staticData.modernRouteLoader = opts.modernRouteLoader;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (typeof opts.modernRouteAction !== 'undefined') {
|
|
522
|
-
staticData.modernRouteAction = opts.modernRouteAction;
|
|
523
|
-
}
|
|
524
|
-
|
|
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;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function modernLoaderToTanstack<TLoader extends (args: any) => any>(
|
|
591
|
-
opts: { hasSplat: boolean },
|
|
592
|
-
modernLoader: TLoader,
|
|
593
|
-
) {
|
|
594
|
-
type LoaderResult = Awaited<ReturnType<TLoader>>;
|
|
595
|
-
|
|
596
|
-
return (ctx: any): Promise<LoaderResult> => {
|
|
597
|
-
try {
|
|
598
|
-
const signal = getLoaderSignal(ctx);
|
|
599
|
-
const baseRequest: Request | undefined =
|
|
600
|
-
ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
|
|
601
|
-
|
|
602
|
-
const href = getLoaderHref(ctx);
|
|
603
|
-
|
|
604
|
-
const request = baseRequest !== undefined
|
|
605
|
-
? new Request(baseRequest, { signal })
|
|
606
|
-
: new Request(href, { signal });
|
|
607
|
-
|
|
608
|
-
const params = mapParamsForModernLoader(getLoaderParams(ctx), opts.hasSplat);
|
|
609
|
-
|
|
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);
|
|
619
|
-
} catch (err) {
|
|
620
|
-
handleModernLoaderError(err);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
|
|
625
620
|
${imports.join('\n')}
|
|
626
621
|
|
|
627
622
|
export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
|
package/src/runtime/hooks.ts
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
createSyncHook<(context: TRuntimeContext) => void>();
|
|
9
|
-
const onBeforeCreateRouter =
|
|
10
|
-
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
11
|
-
const onAfterCreateRouter =
|
|
12
|
-
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
13
|
-
const onBeforeHydrateRouter =
|
|
14
|
-
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
15
|
-
const onAfterHydrateRouter =
|
|
16
|
-
createSyncHook<(context: RouterLifecycleContext) => void>();
|
|
17
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The router hooks are owned by @modern-js/runtime — this module re-exports
|
|
3
|
+
* the canonical instances through the `/context` seam, so the TanStack
|
|
4
|
+
* provider taps and calls the exact same hooks the built-in router wrapper
|
|
5
|
+
* registers. Creating separate hook instances here would silently split the
|
|
6
|
+
* hook registry between the wrapper and this provider.
|
|
7
|
+
*/
|
|
18
8
|
export {
|
|
19
9
|
modifyRoutes,
|
|
20
10
|
onAfterCreateRouter,
|
|
@@ -22,13 +12,6 @@ export {
|
|
|
22
12
|
onBeforeCreateRouter,
|
|
23
13
|
onBeforeCreateRoutes,
|
|
24
14
|
onBeforeHydrateRouter,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
modifyRoutes: typeof modifyRoutes;
|
|
29
|
-
onBeforeCreateRoutes: typeof onBeforeCreateRoutes;
|
|
30
|
-
onBeforeCreateRouter: typeof onBeforeCreateRouter;
|
|
31
|
-
onAfterCreateRouter: typeof onAfterCreateRouter;
|
|
32
|
-
onBeforeHydrateRouter: typeof onBeforeHydrateRouter;
|
|
33
|
-
onAfterHydrateRouter: typeof onAfterHydrateRouter;
|
|
34
|
-
};
|
|
15
|
+
type RouterExtendsHooks,
|
|
16
|
+
routerProviderRegistryHooks,
|
|
17
|
+
} from '@modern-js/runtime/context';
|
|
@@ -4,9 +4,9 @@ export function wrapTanstackSsrHydrationBoundary(
|
|
|
4
4
|
routerContent: ReactElement,
|
|
5
5
|
shouldWrap: boolean,
|
|
6
6
|
) {
|
|
7
|
-
|
|
8
|
-
<Suspense fallback={null}>{routerContent}</Suspense
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
if (shouldWrap) {
|
|
8
|
+
return <Suspense fallback={null}>{routerContent}</Suspense>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return routerContent;
|
|
12
12
|
}
|
package/src/runtime/index.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import './register';
|
|
2
|
+
|
|
1
3
|
export * from '@tanstack/react-router';
|
|
2
|
-
export {
|
|
3
|
-
Outlet,
|
|
4
|
-
useLocation,
|
|
5
|
-
useMatch,
|
|
6
|
-
useMatches,
|
|
7
|
-
useNavigate,
|
|
8
|
-
useRouter,
|
|
9
|
-
} from '@tanstack/react-router';
|
|
10
4
|
export type {
|
|
11
5
|
Fetcher,
|
|
12
6
|
FetcherState,
|
|
@@ -19,6 +13,14 @@ export {
|
|
|
19
13
|
RouteActionResponseError,
|
|
20
14
|
useFetcher,
|
|
21
15
|
} from './dataMutation';
|
|
16
|
+
export type { ModernRouterContext } from './loaderBridge';
|
|
17
|
+
export {
|
|
18
|
+
createRouteStaticData,
|
|
19
|
+
isAbsoluteUrl,
|
|
20
|
+
modernLoaderToTanstack,
|
|
21
|
+
throwTanstackRedirect,
|
|
22
|
+
} from './loaderBridge';
|
|
23
|
+
export { Outlet } from './outlet';
|
|
22
24
|
export {
|
|
23
25
|
tanstackRouterPlugin,
|
|
24
26
|
tanstackRouterPlugin as default,
|
|
@@ -29,12 +31,8 @@ export type {
|
|
|
29
31
|
PrefetchBehavior,
|
|
30
32
|
} from './prefetchLink';
|
|
31
33
|
export { Link, NavLink } from './prefetchLink';
|
|
32
|
-
export type {
|
|
33
|
-
|
|
34
|
-
AnyRenderableServerComponent,
|
|
35
|
-
CompositeComponentProps,
|
|
36
|
-
} from './rsc/client';
|
|
37
|
-
export { CompositeComponent } from './rsc/client';
|
|
34
|
+
export type { TanstackRouterState } from './state';
|
|
35
|
+
export { getTanstackRouterState } from './state';
|
|
38
36
|
export type { RouterConfig } from './types';
|
|
39
37
|
export {
|
|
40
38
|
getModernTanstackRouterFastDefaults,
|