@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.98 → 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
|
@@ -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: {
|
|
@@ -66,7 +65,6 @@ export function getRouteObjects(
|
|
|
66
65
|
id: route.id,
|
|
67
66
|
loader: route.loader,
|
|
68
67
|
action: route.action,
|
|
69
|
-
hasErrorBoundary: route.hasErrorBoundary,
|
|
70
68
|
shouldRevalidate: route.shouldRevalidate,
|
|
71
69
|
handle: {
|
|
72
70
|
...route.handle,
|
|
@@ -106,11 +104,9 @@ export function getRouteObjects(
|
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
|
|
107
|
+
// No synthetic `{ path: '*' }` 404 route here: TanStack Router handles
|
|
108
|
+
// not-found matches through the root route's `notFoundComponent`
|
|
109
|
+
// (see routeTree.ts), so the react-router style catch-all is unnecessary.
|
|
114
110
|
return routeObjects;
|
|
115
111
|
}
|
|
116
112
|
|
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',
|
|
@@ -123,6 +135,82 @@ describe('tanstack router cli plugin', () => {
|
|
|
123
135
|
);
|
|
124
136
|
});
|
|
125
137
|
|
|
138
|
+
test('writes plugin-i18n module augmentation when canonicalRoutes are provided', async () => {
|
|
139
|
+
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
140
|
+
const srcDirectory = path.join(tempDir, 'src');
|
|
141
|
+
|
|
142
|
+
await writeTanstackRegisterFile({
|
|
143
|
+
entries: ['main'],
|
|
144
|
+
generatedDirName: 'tanstack',
|
|
145
|
+
srcDirectory,
|
|
146
|
+
canonicalRoutes: {
|
|
147
|
+
'/': 'Record<string, never>',
|
|
148
|
+
'/products/$slug': '{ "slug": string }',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const register = await readFile(
|
|
153
|
+
path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
|
|
154
|
+
'utf-8',
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(register).toContain(
|
|
158
|
+
"declare module '@modern-js/plugin-i18n/runtime'",
|
|
159
|
+
);
|
|
160
|
+
expect(register).toContain('interface UltramodernCanonicalRoutes');
|
|
161
|
+
expect(register).toContain("'/': Record<string, never>;");
|
|
162
|
+
expect(register).toContain('\'/products/$slug\': { "slug": string };');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('does not emit plugin-i18n augmentation when canonicalRoutes is absent (back-compat)', async () => {
|
|
166
|
+
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
167
|
+
const srcDirectory = path.join(tempDir, 'src');
|
|
168
|
+
|
|
169
|
+
await writeTanstackRegisterFile({
|
|
170
|
+
entries: ['main'],
|
|
171
|
+
generatedDirName: 'tanstack',
|
|
172
|
+
srcDirectory,
|
|
173
|
+
// No canonicalRoutes provided at all — plain TanStack app
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const register = await readFile(
|
|
177
|
+
path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
|
|
178
|
+
'utf-8',
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(register).not.toContain('plugin-i18n');
|
|
182
|
+
expect(register).not.toContain('UltramodernCanonicalRoutes');
|
|
183
|
+
// But the standard TanStack runtime augmentation must still be present.
|
|
184
|
+
expect(register).toContain(
|
|
185
|
+
"declare module '@modern-js/plugin-tanstack/runtime'",
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('uses a custom i18nRuntimeModule when specified', async () => {
|
|
190
|
+
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
191
|
+
const srcDirectory = path.join(tempDir, 'src');
|
|
192
|
+
|
|
193
|
+
await writeTanstackRegisterFile({
|
|
194
|
+
entries: ['main'],
|
|
195
|
+
generatedDirName: 'tanstack',
|
|
196
|
+
srcDirectory,
|
|
197
|
+
canonicalRoutes: {
|
|
198
|
+
'/talks': 'Record<string, never>',
|
|
199
|
+
},
|
|
200
|
+
i18nRuntimeModule: '@my-org/i18n/runtime',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const register = await readFile(
|
|
204
|
+
path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
|
|
205
|
+
'utf-8',
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(register).toContain("declare module '@my-org/i18n/runtime'");
|
|
209
|
+
expect(register).not.toContain(
|
|
210
|
+
"declare module '@modern-js/plugin-i18n/runtime'",
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
126
214
|
test('claims custom routes, injects runtime plugin, and merges route specs', async () => {
|
|
127
215
|
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
128
216
|
const srcDirectory = path.join(tempDir, 'src');
|
|
@@ -231,6 +319,278 @@ describe('tanstack router cli plugin', () => {
|
|
|
231
319
|
});
|
|
232
320
|
});
|
|
233
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
|
+
|
|
234
594
|
test('generates plugin-owned TanStack route files through core route generation', async () => {
|
|
235
595
|
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
236
596
|
const srcDirectory = path.join(tempDir, 'src');
|
|
@@ -316,9 +676,6 @@ describe('tanstack router cli plugin', () => {
|
|
|
316
676
|
[entrypoint],
|
|
317
677
|
{
|
|
318
678
|
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
319
|
-
generateCodeOptions: {
|
|
320
|
-
enableTanstackTypes: false,
|
|
321
|
-
},
|
|
322
679
|
},
|
|
323
680
|
);
|
|
324
681
|
|
|
@@ -421,38 +778,23 @@ describe('tanstack router cli plugin', () => {
|
|
|
421
778
|
});
|
|
422
779
|
});
|
|
423
780
|
|
|
424
|
-
test('
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
expect(profile).toMatchObject({
|
|
781
|
+
test('route splitting profile carries only the rsbuild config production consumes', () => {
|
|
782
|
+
expect(createTanstackRsbuildRouteSplittingProfile({})).toEqual({
|
|
428
783
|
defaultConfig: {
|
|
429
784
|
output: {
|
|
430
785
|
splitRouteChunks: true,
|
|
431
786
|
},
|
|
432
787
|
},
|
|
433
|
-
modernRouteChunks: {
|
|
434
|
-
enabled: true,
|
|
435
|
-
owner: 'modern',
|
|
436
|
-
},
|
|
437
|
-
builderChunkSplit: {
|
|
438
|
-
owner: 'modern-rsbuild',
|
|
439
|
-
preserved: true,
|
|
440
|
-
},
|
|
441
|
-
tanstackStartRspackSplitter: {
|
|
442
|
-
compatible: false,
|
|
443
|
-
clientDeleteNodes: ['ssr', 'server', 'headers'],
|
|
444
|
-
},
|
|
445
788
|
});
|
|
446
789
|
expect(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
).toBe(false);
|
|
790
|
+
createTanstackRsbuildRouteSplittingProfile({ routeCodeSplitting: false }),
|
|
791
|
+
).toEqual({
|
|
792
|
+
defaultConfig: {
|
|
793
|
+
output: {
|
|
794
|
+
splitRouteChunks: false,
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
});
|
|
456
798
|
});
|
|
457
799
|
|
|
458
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
|
+
});
|