@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.120 → 3.2.0-ultramodern.122
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 +47 -27
- package/dist/cjs/cli/routeSplitting.js +0 -32
- package/dist/cjs/cli/tanstackTypes.js +34 -199
- package/dist/cjs/runtime/hooks.js +11 -14
- package/dist/cjs/runtime/index.js +107 -319
- package/dist/cjs/runtime/lifecycle.js +12 -86
- package/dist/cjs/runtime/loaderBridge.js +173 -0
- package/dist/cjs/runtime/plugin.js +6 -30
- package/dist/cjs/runtime/plugin.node.js +7 -29
- package/dist/cjs/runtime/pluginCore.js +55 -0
- package/dist/cjs/runtime/register.js +56 -0
- package/dist/cjs/runtime/routeTree.js +10 -207
- package/dist/cjs/runtime/{DefaultNotFound.js → router.js} +5 -15
- package/dist/cjs/runtime/rsc/payloadRouter.js +35 -1
- package/dist/cjs/runtime/state.js +45 -0
- package/dist/cjs/runtime/utils.js +0 -5
- package/dist/esm/cli/index.mjs +52 -26
- package/dist/esm/cli/routeSplitting.mjs +1 -30
- package/dist/esm/cli/tanstackTypes.mjs +32 -194
- package/dist/esm/runtime/hooks.mjs +1 -8
- 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/plugin.mjs +8 -32
- package/dist/esm/runtime/plugin.node.mjs +10 -32
- package/dist/esm/runtime/pluginCore.mjs +14 -0
- package/dist/esm/runtime/register.mjs +18 -0
- package/dist/esm/runtime/routeTree.mjs +4 -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 -5
- package/dist/esm-node/cli/index.mjs +52 -26
- package/dist/esm-node/cli/routeSplitting.mjs +1 -30
- package/dist/esm-node/cli/tanstackTypes.mjs +32 -194
- package/dist/esm-node/runtime/hooks.mjs +1 -8
- 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/plugin.mjs +8 -32
- package/dist/esm-node/runtime/plugin.node.mjs +10 -32
- package/dist/esm-node/runtime/pluginCore.mjs +15 -0
- package/dist/esm-node/runtime/register.mjs +19 -0
- package/dist/esm-node/runtime/routeTree.mjs +4 -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 -5
- package/dist/types/cli/index.d.ts +9 -2
- package/dist/types/cli/routeSplitting.d.ts +6 -15
- package/dist/types/cli/tanstackTypes.d.ts +13 -2
- package/dist/types/runtime/hooks.d.ts +8 -18
- package/dist/types/runtime/index.d.ts +6 -4
- package/dist/types/runtime/lifecycle.d.ts +7 -22
- package/dist/types/runtime/loaderBridge.d.ts +48 -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 +31 -29
- package/rstest.config.mts +6 -0
- package/src/cli/index.ts +111 -29
- package/src/cli/routeSplitting.ts +6 -44
- package/src/cli/tanstackTypes.ts +78 -214
- package/src/runtime/hooks.ts +10 -27
- package/src/runtime/index.tsx +12 -107
- package/src/runtime/lifecycle.ts +16 -151
- package/src/runtime/loaderBridge.ts +257 -0
- package/src/runtime/plugin.node.tsx +14 -77
- package/src/runtime/plugin.tsx +12 -72
- package/src/runtime/pluginCore.ts +48 -0
- package/src/runtime/register.ts +58 -0
- package/src/runtime/routeTree.ts +8 -370
- package/src/runtime/router.ts +15 -0
- package/src/runtime/rsc/payloadRouter.ts +45 -2
- package/src/runtime/state.ts +29 -0
- package/src/runtime/types.ts +20 -67
- package/src/runtime/utils.tsx +3 -6
- package/tests/router/cli.test.ts +297 -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/register.test.ts +46 -0
- package/tests/router/routeTree.test.ts +65 -180
- package/tests/router/rsc.test.tsx +70 -0
- package/tests/router/tanstackTypes.test.ts +164 -6
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// @effect-diagnostics asyncFunction:off globalFetch:off strictBooleanExpressions:off
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalFetch:off processEnv:off strictBooleanExpressions:off
|
|
2
2
|
import type { PayloadRoute, ServerPayload } from '@modern-js/runtime/context';
|
|
3
|
+
import { isRouteErrorResponse } from '@modern-js/runtime-utils/router';
|
|
3
4
|
import { notFound, redirect } from '@tanstack/react-router';
|
|
4
5
|
|
|
5
6
|
type PayloadDecoder = (stream: ReadableStream<Uint8Array>) => Promise<unknown>;
|
|
@@ -103,6 +104,48 @@ function toPayloadRoute(match: RouterMatchLike): PayloadRoute | undefined {
|
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
function shouldRedactServerError(status = 500) {
|
|
108
|
+
return (
|
|
109
|
+
status >= 500 &&
|
|
110
|
+
process.env.NODE_ENV !== 'development' &&
|
|
111
|
+
process.env.NODE_ENV !== 'test'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function serializePayloadError(error: unknown): unknown {
|
|
116
|
+
if (isRouteErrorResponse(error)) {
|
|
117
|
+
if (shouldRedactServerError(error.status)) {
|
|
118
|
+
return {
|
|
119
|
+
status: error.status,
|
|
120
|
+
statusText: 'Internal Server Error',
|
|
121
|
+
data: 'Unexpected Server Error',
|
|
122
|
+
__type: 'RouteErrorResponse',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { ...error, __type: 'RouteErrorResponse' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
if (shouldRedactServerError()) {
|
|
131
|
+
return {
|
|
132
|
+
message: 'Unexpected Server Error',
|
|
133
|
+
stack: undefined,
|
|
134
|
+
__type: 'Error',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
message: error.message,
|
|
140
|
+
stack: error.stack,
|
|
141
|
+
__type: 'Error',
|
|
142
|
+
...(error.name !== 'Error' ? { __subType: error.name } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return error;
|
|
147
|
+
}
|
|
148
|
+
|
|
106
149
|
export function createTanstackRscServerPayload(
|
|
107
150
|
router: TanstackPayloadRouterLike,
|
|
108
151
|
options: {
|
|
@@ -133,7 +176,7 @@ export function createTanstackRscServerPayload(
|
|
|
133
176
|
}
|
|
134
177
|
|
|
135
178
|
if (typeof match.error !== 'undefined') {
|
|
136
|
-
errors[payloadRoute.id] = match.error;
|
|
179
|
+
errors[payloadRoute.id] = serializePayloadError(match.error);
|
|
137
180
|
}
|
|
138
181
|
}
|
|
139
182
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getRouterRuntimeState } from '@modern-js/runtime/context';
|
|
2
|
+
import type { AnyRouter } from '@tanstack/react-router';
|
|
3
|
+
import type { InternalRouterRuntimeState } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Router runtime state as published by the TanStack router provider into the
|
|
7
|
+
* runtime-context extension slot.
|
|
8
|
+
*/
|
|
9
|
+
export interface TanstackRouterState
|
|
10
|
+
extends Omit<InternalRouterRuntimeState, 'framework' | 'instance'> {
|
|
11
|
+
framework: 'tanstack';
|
|
12
|
+
instance?: AnyRouter;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Typed accessor for the TanStack router state stored on a Modern.js runtime
|
|
17
|
+
* context. Returns `undefined` when the active router provider is not
|
|
18
|
+
* TanStack (e.g. react-router) or no router has been created yet.
|
|
19
|
+
*/
|
|
20
|
+
export function getTanstackRouterState(
|
|
21
|
+
context: object,
|
|
22
|
+
): TanstackRouterState | undefined {
|
|
23
|
+
const state = getRouterRuntimeState(context);
|
|
24
|
+
if (state === undefined || state.framework !== 'tanstack') {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return state as TanstackRouterState;
|
|
29
|
+
}
|
package/src/runtime/types.ts
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
RouterFramework,
|
|
3
|
+
// The router runtime state types are owned by @modern-js/runtime; they are
|
|
4
|
+
// imported through the `/context` seam instead of being copied here so
|
|
5
|
+
// upstream fixes propagate to this package automatically.
|
|
6
|
+
} from '@modern-js/runtime/context';
|
|
2
7
|
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
3
8
|
import type { NestedRoute, PageRoute } from '@modern-js/types';
|
|
4
9
|
import type React from 'react';
|
|
5
10
|
|
|
6
|
-
export type
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
export type {
|
|
12
|
+
BuiltInRouterFramework,
|
|
13
|
+
InternalRouterRuntimeState,
|
|
14
|
+
InternalRouterServerSnapshot,
|
|
15
|
+
RouterFramework,
|
|
16
|
+
RouterRouteMatchSnapshot,
|
|
17
|
+
RouterServerPrepareResult,
|
|
18
|
+
} from '@modern-js/runtime/context';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* TanStack-specific router config. Unlike the react-router provider config,
|
|
22
|
+
* this intentionally has no `oldVersion`/`future` fields — those are
|
|
23
|
+
* react-router-only knobs with no meaning here.
|
|
24
|
+
*/
|
|
9
25
|
export type RouterConfig = {
|
|
10
26
|
framework?: RouterFramework;
|
|
11
27
|
routesConfig: {
|
|
12
28
|
globalApp?: React.ComponentType<any>;
|
|
13
29
|
routes?: (NestedRoute | PageRoute)[];
|
|
14
30
|
};
|
|
15
|
-
oldVersion?: boolean;
|
|
16
31
|
serverBase?: string[];
|
|
17
32
|
supportHtml5History?: boolean;
|
|
18
33
|
basename?: string;
|
|
19
34
|
createRoutes?: () => RouteObject[];
|
|
20
|
-
future?: Partial<{
|
|
21
|
-
v7_startTransition: boolean;
|
|
22
|
-
}>;
|
|
23
35
|
defaultStructuralSharing?: boolean;
|
|
24
36
|
unstable_reloadOnURLMismatch?: boolean;
|
|
25
37
|
};
|
|
@@ -35,62 +47,3 @@ export const getModernTanstackRouterFastDefaults = (
|
|
|
35
47
|
config.defaultStructuralSharing ??
|
|
36
48
|
modernTanstackRouterFastDefaults.defaultStructuralSharing,
|
|
37
49
|
});
|
|
38
|
-
|
|
39
|
-
export interface RouterRouteMatchSnapshot {
|
|
40
|
-
routeId: string;
|
|
41
|
-
assetRouteId?: string;
|
|
42
|
-
pathname?: string;
|
|
43
|
-
params?: Record<string, string>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface InternalRouterServerSnapshot {
|
|
47
|
-
framework?: RouterFramework;
|
|
48
|
-
basename?: string;
|
|
49
|
-
statusCode?: number;
|
|
50
|
-
errors?: Record<string, unknown>;
|
|
51
|
-
routerData?: {
|
|
52
|
-
loaderData?: Record<string, unknown>;
|
|
53
|
-
errors?: Record<string, unknown>;
|
|
54
|
-
};
|
|
55
|
-
hydrationScript?: string;
|
|
56
|
-
hydrationScripts?: string[];
|
|
57
|
-
matchedRouteIds?: string[];
|
|
58
|
-
matches?: RouterRouteMatchSnapshot[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface InternalRouterRuntimeState {
|
|
62
|
-
framework: RouterFramework;
|
|
63
|
-
basename?: string;
|
|
64
|
-
instance?: unknown;
|
|
65
|
-
hydrationScript?: string;
|
|
66
|
-
hydrationScripts?: string[];
|
|
67
|
-
matchedRouteIds?: string[];
|
|
68
|
-
matches?: RouterRouteMatchSnapshot[];
|
|
69
|
-
serverSnapshot?: InternalRouterServerSnapshot;
|
|
70
|
-
cleanup?: () => void | Promise<void>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface RouterServerPrepareResult {
|
|
74
|
-
state: InternalRouterRuntimeState;
|
|
75
|
-
snapshot?: InternalRouterServerSnapshot;
|
|
76
|
-
redirect?: Response;
|
|
77
|
-
cleanup?: () => void | Promise<void>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface DataFunctionArgs<D = any> {
|
|
81
|
-
request: Request;
|
|
82
|
-
params: Record<string, string>;
|
|
83
|
-
context?: D;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type LoaderFunctionArgs<
|
|
87
|
-
P extends Record<string, unknown> = Record<string, unknown>,
|
|
88
|
-
> = DataFunctionArgs<RequestContext<P>>;
|
|
89
|
-
|
|
90
|
-
type DataFunctionValue = Response | NonNullable<unknown> | null;
|
|
91
|
-
|
|
92
|
-
export type LoaderFunction = <
|
|
93
|
-
P extends Record<string, unknown> = Record<string, unknown>,
|
|
94
|
-
>(
|
|
95
|
-
args: LoaderFunctionArgs<P>,
|
|
96
|
-
) => Promise<DataFunctionValue> | DataFunctionValue;
|
package/src/runtime/utils.tsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
3
3
|
import type { NestedRoute, PageRoute, SSRMode } from '@modern-js/types';
|
|
4
4
|
import React from 'react';
|
|
5
|
-
import { DefaultNotFound } from './DefaultNotFound';
|
|
6
5
|
|
|
7
6
|
type RouterConfig = {
|
|
8
7
|
routesConfig: {
|
|
@@ -106,11 +105,9 @@ export function getRouteObjects(
|
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
|
|
108
|
+
// No synthetic `{ path: '*' }` 404 route here: TanStack Router handles
|
|
109
|
+
// not-found matches through the root route's `notFoundComponent`
|
|
110
|
+
// (see routeTree.ts), so the react-router style catch-all is unnecessary.
|
|
114
111
|
return routeObjects;
|
|
115
112
|
}
|
|
116
113
|
|
package/tests/router/cli.test.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { Entrypoint } from '@modern-js/types';
|
|
|
6
6
|
import { fs, NESTED_ROUTE_SPEC_FILE } from '@modern-js/utils';
|
|
7
7
|
import {
|
|
8
8
|
createTanstackRsbuildRouteSplittingProfile,
|
|
9
|
-
isTanstackStartRouteModuleSource,
|
|
10
9
|
tanstackRouterPlugin,
|
|
11
10
|
writeTanstackRegisterFile,
|
|
12
11
|
writeTanstackRouterTypesForEntries,
|
|
@@ -19,16 +18,29 @@ const runtimeCliMocks = {
|
|
|
19
18
|
|
|
20
19
|
rstest.mock('@modern-js/runtime/cli', () => {
|
|
21
20
|
const routesDirMetaKey = '__modernRoutesDir';
|
|
21
|
+
// The codegen helpers are pure — forward to the real implementations.
|
|
22
|
+
const actualCli = rstest.requireActual('@modern-js/runtime/cli') as {
|
|
23
|
+
getPathWithoutExt: (filename: string) => string;
|
|
24
|
+
makeLegalIdentifier: (value: string) => string;
|
|
25
|
+
};
|
|
22
26
|
|
|
23
27
|
return {
|
|
24
28
|
__esModule: true,
|
|
29
|
+
getPathWithoutExt: actualCli.getPathWithoutExt,
|
|
30
|
+
makeLegalIdentifier: actualCli.makeLegalIdentifier,
|
|
25
31
|
getEntrypointRoutesDir: (entrypoint: any) =>
|
|
26
32
|
entrypoint[routesDirMetaKey] ||
|
|
27
33
|
(entrypoint.nestedRoutesEntry
|
|
28
34
|
? path.basename(entrypoint.nestedRoutesEntry)
|
|
29
35
|
: null),
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
getEntrypointRoutesOwner: (entrypoint: any) =>
|
|
37
|
+
entrypoint.__modernRoutesOwner || null,
|
|
38
|
+
// Forward through arrows: the mock factory is hoisted above the
|
|
39
|
+
// `runtimeCliMocks` initializer, so it must not dereference it eagerly.
|
|
40
|
+
handleFileChange: (...args: unknown[]) =>
|
|
41
|
+
runtimeCliMocks.handleFileChange(...args),
|
|
42
|
+
handleGeneratorEntryCode: (...args: unknown[]) =>
|
|
43
|
+
runtimeCliMocks.handleGeneratorEntryCode(...args),
|
|
32
44
|
handleModifyEntrypoints: async (
|
|
33
45
|
entrypoints: Entrypoint[],
|
|
34
46
|
routesDir = 'routes',
|
|
@@ -307,6 +319,278 @@ describe('tanstack router cli plugin', () => {
|
|
|
307
319
|
});
|
|
308
320
|
});
|
|
309
321
|
|
|
322
|
+
test('injects the framework-resolving router wrapper for non-file-route entrypoints', async () => {
|
|
323
|
+
const taps: Record<string, any> = {};
|
|
324
|
+
const api = {
|
|
325
|
+
getAppContext: () => ({
|
|
326
|
+
srcDirectory: '/tmp/app/src',
|
|
327
|
+
metaName: 'modern-js',
|
|
328
|
+
serverRoutes: [{ entryName: 'custom', urlPath: '/' }],
|
|
329
|
+
}),
|
|
330
|
+
_internalRuntimePlugins: (tap: any) => {
|
|
331
|
+
taps.internalRuntimePlugins = tap;
|
|
332
|
+
},
|
|
333
|
+
checkEntryPoint: () => {},
|
|
334
|
+
config: () => {},
|
|
335
|
+
modifyEntrypoints: () => {},
|
|
336
|
+
generateEntryCode: () => {},
|
|
337
|
+
onFileChanged: () => {},
|
|
338
|
+
modifyFileSystemRoutes: () => {},
|
|
339
|
+
onBeforeGenerateRoutes: () => {},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
tanstackRouterPlugin().setup!(api as any);
|
|
343
|
+
|
|
344
|
+
// Custom entry without a routes dir (e.g. `createRoutes` in
|
|
345
|
+
// modern.runtime.ts): installing the plugin is the explicit opt-in, no
|
|
346
|
+
// source sniffing — the wrapper plus the provider registration is
|
|
347
|
+
// injected through the package's own runtime/router module.
|
|
348
|
+
const customEntrypoint = {
|
|
349
|
+
entryName: 'custom',
|
|
350
|
+
isAutoMount: true,
|
|
351
|
+
} as Entrypoint;
|
|
352
|
+
expect(
|
|
353
|
+
taps.internalRuntimePlugins({ entrypoint: customEntrypoint, plugins: [] })
|
|
354
|
+
.plugins,
|
|
355
|
+
).toEqual([
|
|
356
|
+
{
|
|
357
|
+
name: 'router',
|
|
358
|
+
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
359
|
+
config: { serverBase: ['/'] },
|
|
360
|
+
},
|
|
361
|
+
]);
|
|
362
|
+
|
|
363
|
+
// If the built-in router CLI already installed the internal router for
|
|
364
|
+
// this custom entry (explicit `runtime.router` config), only the module
|
|
365
|
+
// path is redirected so the TanStack provider registration is
|
|
366
|
+
// value-imported with it.
|
|
367
|
+
const existingRouterPlugin = {
|
|
368
|
+
name: 'router',
|
|
369
|
+
path: '@modern-js/runtime/router/internal',
|
|
370
|
+
config: { serverBase: ['/'] },
|
|
371
|
+
};
|
|
372
|
+
const { plugins } = taps.internalRuntimePlugins({
|
|
373
|
+
entrypoint: customEntrypoint,
|
|
374
|
+
plugins: [existingRouterPlugin],
|
|
375
|
+
});
|
|
376
|
+
expect(plugins).toHaveLength(1);
|
|
377
|
+
expect(plugins[0]).toEqual({
|
|
378
|
+
name: 'router',
|
|
379
|
+
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
380
|
+
config: { serverBase: ['/'] },
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('leaves built-in and foreign-owned route entrypoints to their own router', () => {
|
|
385
|
+
const taps: Record<string, any> = {};
|
|
386
|
+
const api = {
|
|
387
|
+
getAppContext: () => ({
|
|
388
|
+
srcDirectory: '/tmp/app/src',
|
|
389
|
+
metaName: 'modern-js',
|
|
390
|
+
serverRoutes: [{ entryName: 'home', urlPath: '/' }],
|
|
391
|
+
}),
|
|
392
|
+
_internalRuntimePlugins: (tap: any) => {
|
|
393
|
+
taps.internalRuntimePlugins = tap;
|
|
394
|
+
},
|
|
395
|
+
checkEntryPoint: () => {},
|
|
396
|
+
config: () => {},
|
|
397
|
+
modifyEntrypoints: () => {},
|
|
398
|
+
generateEntryCode: () => {},
|
|
399
|
+
onFileChanged: () => {},
|
|
400
|
+
modifyFileSystemRoutes: () => {},
|
|
401
|
+
onBeforeGenerateRoutes: () => {},
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
tanstackRouterPlugin({ routesDir: 'ts-routes' }).setup!(api as any);
|
|
405
|
+
|
|
406
|
+
// A classic react-router file-route entry (src/<entry>/routes) living
|
|
407
|
+
// next to the TanStack entries: its internal router plugin must be left
|
|
408
|
+
// untouched — redirecting it through the TanStack wrapper would pull
|
|
409
|
+
// @tanstack/react-router into a pure react-router bundle.
|
|
410
|
+
const builtInEntrypoint = {
|
|
411
|
+
entryName: 'home',
|
|
412
|
+
isAutoMount: true,
|
|
413
|
+
nestedRoutesEntry: '/tmp/app/src/home/routes',
|
|
414
|
+
__modernRoutesDir: 'routes',
|
|
415
|
+
} as Entrypoint;
|
|
416
|
+
const { plugins: builtInPlugins } = taps.internalRuntimePlugins({
|
|
417
|
+
entrypoint: builtInEntrypoint,
|
|
418
|
+
plugins: [
|
|
419
|
+
{
|
|
420
|
+
name: 'router',
|
|
421
|
+
path: '@modern-js/runtime/router/internal',
|
|
422
|
+
config: { serverBase: ['/'] },
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
});
|
|
426
|
+
expect(builtInPlugins).toEqual([
|
|
427
|
+
{
|
|
428
|
+
name: 'router',
|
|
429
|
+
path: '@modern-js/runtime/router/internal',
|
|
430
|
+
config: { serverBase: ['/'] },
|
|
431
|
+
},
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
// An entry tagged by another routes-owner plugin: nothing is pushed.
|
|
435
|
+
// The replaced sniffing path in @modern-js/runtime excluded
|
|
436
|
+
// plugin-owned entrypoints for the same reason — pushing a second
|
|
437
|
+
// `router` plugin can install two routers for one entry.
|
|
438
|
+
const foreignEntrypoint = {
|
|
439
|
+
entryName: 'home',
|
|
440
|
+
isAutoMount: true,
|
|
441
|
+
nestedRoutesEntry: '/tmp/app/src/home/acme-routes',
|
|
442
|
+
__modernRoutesDir: 'acme-routes',
|
|
443
|
+
__modernRoutesOwner: '@acme/plugin-file-router',
|
|
444
|
+
} as Entrypoint;
|
|
445
|
+
expect(
|
|
446
|
+
taps.internalRuntimePlugins({
|
|
447
|
+
entrypoint: foreignEntrypoint,
|
|
448
|
+
plugins: [],
|
|
449
|
+
}).plugins,
|
|
450
|
+
).toEqual([]);
|
|
451
|
+
|
|
452
|
+
// The built-in pages/ convention is foreign too.
|
|
453
|
+
const pagesEntrypoint = {
|
|
454
|
+
entryName: 'home',
|
|
455
|
+
isAutoMount: true,
|
|
456
|
+
pageRoutesEntry: '/tmp/app/src/home/pages',
|
|
457
|
+
} as Entrypoint;
|
|
458
|
+
expect(
|
|
459
|
+
taps.internalRuntimePlugins({ entrypoint: pagesEntrypoint, plugins: [] })
|
|
460
|
+
.plugins,
|
|
461
|
+
).toEqual([]);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('source.include covers the package dist and TanStack runtime deps without string surgery', () => {
|
|
465
|
+
const taps: Record<string, any> = {};
|
|
466
|
+
const api = {
|
|
467
|
+
getAppContext: () => ({
|
|
468
|
+
srcDirectory: '/tmp/app/src',
|
|
469
|
+
serverRoutes: [],
|
|
470
|
+
}),
|
|
471
|
+
_internalRuntimePlugins: () => {},
|
|
472
|
+
checkEntryPoint: () => {},
|
|
473
|
+
config: (tap: any) => {
|
|
474
|
+
taps.config = tap;
|
|
475
|
+
},
|
|
476
|
+
modifyEntrypoints: () => {},
|
|
477
|
+
generateEntryCode: () => {},
|
|
478
|
+
onFileChanged: () => {},
|
|
479
|
+
modifyFileSystemRoutes: () => {},
|
|
480
|
+
onBeforeGenerateRoutes: () => {},
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
tanstackRouterPlugin().setup!(api as any);
|
|
484
|
+
|
|
485
|
+
const include = taps.config().source.include as Array<RegExp | string>;
|
|
486
|
+
const regexes = include.filter(
|
|
487
|
+
(entry): entry is RegExp => entry instanceof RegExp,
|
|
488
|
+
);
|
|
489
|
+
for (const dep of ['react-router', 'router-core', 'react-store']) {
|
|
490
|
+
const sample = `/repo/node_modules/@tanstack/${dep}/dist/esm/index.js`;
|
|
491
|
+
expect(regexes.some(regex => regex.test(sample))).toBe(true);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const stringEntries = include.filter(
|
|
495
|
+
(entry): entry is string => typeof entry === 'string',
|
|
496
|
+
);
|
|
497
|
+
expect(stringEntries).toHaveLength(1);
|
|
498
|
+
// The include must point at the package root (two levels above the cli
|
|
499
|
+
// build dir) so dist/esm, dist/esm-node and dist/cjs are all covered —
|
|
500
|
+
// the old `.replace('cjs', 'esm')` never matched the bundled dist/esm
|
|
501
|
+
// runtime when the CLI was loaded through the ESM condition.
|
|
502
|
+
expect(stringEntries[0]).toBe(path.resolve(__dirname, '..', '..'));
|
|
503
|
+
expect(stringEntries[0]).not.toContain('esm');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test('emits the plugin-i18n augmentation only when plugin-i18n is registered', async () => {
|
|
507
|
+
const langRoutes = [
|
|
508
|
+
{
|
|
509
|
+
type: 'nested',
|
|
510
|
+
id: 'layout',
|
|
511
|
+
isRoot: true,
|
|
512
|
+
children: [
|
|
513
|
+
{
|
|
514
|
+
type: 'nested',
|
|
515
|
+
id: '(lang)/layout',
|
|
516
|
+
path: ':lang',
|
|
517
|
+
children: [
|
|
518
|
+
{
|
|
519
|
+
type: 'nested',
|
|
520
|
+
id: '(lang)/about/page',
|
|
521
|
+
path: 'about',
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
const runGenerate = async (registeredPlugins: Array<{ name: string }>) => {
|
|
530
|
+
const dir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
531
|
+
const srcDirectory = path.join(dir, 'src');
|
|
532
|
+
const entrypoint = {
|
|
533
|
+
entryName: 'main',
|
|
534
|
+
isAutoMount: true,
|
|
535
|
+
isMainEntry: true,
|
|
536
|
+
nestedRoutesEntry: path.join(srcDirectory, 'routes'),
|
|
537
|
+
__modernRoutesDir: 'routes',
|
|
538
|
+
} as Entrypoint;
|
|
539
|
+
runtimeCliMocks.handleGeneratorEntryCode.mockResolvedValueOnce({
|
|
540
|
+
main: langRoutes,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const taps: Record<string, any> = {};
|
|
544
|
+
const api = {
|
|
545
|
+
getAppContext: () => ({
|
|
546
|
+
srcDirectory,
|
|
547
|
+
internalSrcAlias: '@/_',
|
|
548
|
+
entrypoints: [entrypoint],
|
|
549
|
+
plugins: registeredPlugins,
|
|
550
|
+
}),
|
|
551
|
+
_internalRuntimePlugins: () => {},
|
|
552
|
+
checkEntryPoint: () => {},
|
|
553
|
+
config: () => {},
|
|
554
|
+
modifyEntrypoints: () => {},
|
|
555
|
+
generateEntryCode: (tap: any) => {
|
|
556
|
+
taps.generateEntryCode = tap;
|
|
557
|
+
},
|
|
558
|
+
onFileChanged: () => {},
|
|
559
|
+
modifyFileSystemRoutes: () => {},
|
|
560
|
+
onBeforeGenerateRoutes: () => {},
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
tanstackRouterPlugin().setup!(api as any);
|
|
564
|
+
await taps.generateEntryCode({ entrypoints: [entrypoint] });
|
|
565
|
+
|
|
566
|
+
const register = await readFile(
|
|
567
|
+
path.join(srcDirectory, 'modern-tanstack', 'register.gen.d.ts'),
|
|
568
|
+
'utf-8',
|
|
569
|
+
);
|
|
570
|
+
await rm(dir, { recursive: true, force: true });
|
|
571
|
+
return register;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// A hand-rolled `/:lang/` app WITHOUT plugin-i18n must not get the
|
|
575
|
+
// augmentation — it would reference an unresolvable module (TS2664).
|
|
576
|
+
const withoutI18n = await runGenerate([{ name: '@modern-js/app-tools' }]);
|
|
577
|
+
expect(withoutI18n).not.toContain('plugin-i18n');
|
|
578
|
+
expect(withoutI18n).not.toContain('UltramodernCanonicalRoutes');
|
|
579
|
+
expect(withoutI18n).toContain(
|
|
580
|
+
"declare module '@modern-js/plugin-tanstack/runtime'",
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// With plugin-i18n registered the canonical route map is emitted.
|
|
584
|
+
const withI18n = await runGenerate([
|
|
585
|
+
{ name: '@modern-js/app-tools' },
|
|
586
|
+
{ name: '@modern-js/plugin-i18n' },
|
|
587
|
+
]);
|
|
588
|
+
expect(withI18n).toContain(
|
|
589
|
+
"declare module '@modern-js/plugin-i18n/runtime'",
|
|
590
|
+
);
|
|
591
|
+
expect(withI18n).toContain("'/about': Record<string, never>;");
|
|
592
|
+
});
|
|
593
|
+
|
|
310
594
|
test('generates plugin-owned TanStack route files through core route generation', async () => {
|
|
311
595
|
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
312
596
|
const srcDirectory = path.join(tempDir, 'src');
|
|
@@ -392,9 +676,6 @@ describe('tanstack router cli plugin', () => {
|
|
|
392
676
|
[entrypoint],
|
|
393
677
|
{
|
|
394
678
|
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
395
|
-
generateCodeOptions: {
|
|
396
|
-
enableTanstackTypes: false,
|
|
397
|
-
},
|
|
398
679
|
},
|
|
399
680
|
);
|
|
400
681
|
|
|
@@ -497,38 +778,23 @@ describe('tanstack router cli plugin', () => {
|
|
|
497
778
|
});
|
|
498
779
|
});
|
|
499
780
|
|
|
500
|
-
test('
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
expect(profile).toMatchObject({
|
|
781
|
+
test('route splitting profile carries only the rsbuild config production consumes', () => {
|
|
782
|
+
expect(createTanstackRsbuildRouteSplittingProfile({})).toEqual({
|
|
504
783
|
defaultConfig: {
|
|
505
784
|
output: {
|
|
506
785
|
splitRouteChunks: true,
|
|
507
786
|
},
|
|
508
787
|
},
|
|
509
|
-
modernRouteChunks: {
|
|
510
|
-
enabled: true,
|
|
511
|
-
owner: 'modern',
|
|
512
|
-
},
|
|
513
|
-
builderChunkSplit: {
|
|
514
|
-
owner: 'modern-rsbuild',
|
|
515
|
-
preserved: true,
|
|
516
|
-
},
|
|
517
|
-
tanstackStartRspackSplitter: {
|
|
518
|
-
compatible: false,
|
|
519
|
-
clientDeleteNodes: ['ssr', 'server', 'headers'],
|
|
520
|
-
},
|
|
521
788
|
});
|
|
522
789
|
expect(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
).toBe(false);
|
|
790
|
+
createTanstackRsbuildRouteSplittingProfile({ routeCodeSplitting: false }),
|
|
791
|
+
).toEqual({
|
|
792
|
+
defaultConfig: {
|
|
793
|
+
output: {
|
|
794
|
+
splitRouteChunks: false,
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
});
|
|
532
798
|
});
|
|
533
799
|
|
|
534
800
|
test('preserves user-selected route and builder chunk splitting modes', () => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
modifyRoutes as canonicalModifyRoutes,
|
|
3
|
+
routerProviderRegistryHooks,
|
|
4
|
+
} from '@modern-js/runtime/context';
|
|
5
|
+
import * as tanstackHooks from '../../src/runtime/hooks';
|
|
6
|
+
import { tanstackRouterPlugin as browserPlugin } from '../../src/runtime/plugin';
|
|
7
|
+
import { tanstackRouterPlugin as nodePlugin } from '../../src/runtime/plugin.node';
|
|
8
|
+
|
|
9
|
+
describe('tanstack router hooks single declaration source', () => {
|
|
10
|
+
test('re-exports the canonical hook instances owned by @modern-js/runtime', () => {
|
|
11
|
+
// Identity matters: separate instances would split the hook registry
|
|
12
|
+
// between the built-in router wrapper and this provider.
|
|
13
|
+
expect(tanstackHooks.modifyRoutes).toBe(canonicalModifyRoutes);
|
|
14
|
+
expect(tanstackHooks.routerProviderRegistryHooks).toBe(
|
|
15
|
+
routerProviderRegistryHooks,
|
|
16
|
+
);
|
|
17
|
+
expect(tanstackHooks.modifyRoutes).toBe(
|
|
18
|
+
routerProviderRegistryHooks.modifyRoutes,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('both runtime plugins register the canonical hook registry object', () => {
|
|
23
|
+
expect(browserPlugin().registryHooks).toBe(routerProviderRegistryHooks);
|
|
24
|
+
expect(nodePlugin().registryHooks).toBe(routerProviderRegistryHooks);
|
|
25
|
+
});
|
|
26
|
+
});
|