@bleedingdev/modern-js-plugin-tanstack 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10
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 +9 -0
- package/dist/cjs/cli/tanstackTypes/source.js +3 -1
- package/dist/cjs/runtime/basepathRewrite.js +13 -6
- package/dist/cjs/runtime/lifecycle.js +7 -7
- package/dist/cjs/runtime/plugin.js +1 -1
- package/dist/cjs/runtime/plugin.node.js +5 -1
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +0 -2
- package/dist/cjs/runtime/routeTree/construction.js +2 -0
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/cjs/runtime/utils.js +2 -0
- package/dist/esm/cli/index.mjs +13 -5
- package/dist/esm/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/plugin.mjs +1 -1
- package/dist/esm/runtime/plugin.node.mjs +5 -1
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +0 -2
- package/dist/esm/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm/runtime/utils.mjs +2 -0
- package/dist/esm-node/cli/index.mjs +15 -5
- package/dist/esm-node/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm-node/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/plugin.mjs +1 -1
- package/dist/esm-node/runtime/plugin.node.mjs +5 -1
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +0 -2
- package/dist/esm-node/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm-node/runtime/router.mjs +27 -11
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/utils.mjs +2 -0
- package/dist/types/cli/index.d.ts +5 -5
- package/dist/types/cli/tanstackTypes/shared.d.ts +1 -1
- package/dist/types/cli/tanstackTypes.d.ts +3 -3
- package/dist/types/cli.d.ts +2 -2
- package/dist/types/runtime/basepathRewrite.d.ts +2 -1
- package/dist/types/runtime/dataMutation.d.ts +3 -3
- package/dist/types/runtime/index.d.ts +14 -14
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/loaderBridge.d.ts +2 -2
- package/dist/types/runtime/plugin.d.ts +2 -2
- package/dist/types/runtime/plugin.node.d.ts +2 -2
- package/dist/types/runtime/plugin.worker.d.ts +1 -1
- package/dist/types/runtime/pluginShared.d.ts +2 -2
- package/dist/types/runtime/register.d.ts +1 -9
- package/dist/types/runtime/routeTree/construction.d.ts +1 -1
- package/dist/types/runtime/routeTree/index.d.ts +2 -2
- package/dist/types/runtime/routeTree/loaders.d.ts +1 -1
- package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +1 -1
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -1
- package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +1 -1
- package/dist/types/runtime/rsc/client.d.ts +4 -4
- package/dist/types/runtime/rsc/createRscProxy.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadFetch.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -2
- package/dist/types/runtime/rsc/payloadStorage.d.ts +1 -1
- package/dist/types/runtime/rsc/server.d.ts +3 -3
- package/dist/types/runtime/rsc/shared.d.ts +1 -1
- package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +1 -1
- package/dist/types/runtime/ssrManagedTags.d.ts +1 -1
- package/dist/types/runtime/ssrPreload.d.ts +1 -1
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/submitAction.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/dist/types/runtime/utils.d.ts +1 -1
- package/dist/types/runtime.d.ts +1 -1
- package/package.json +19 -16
- package/rstest.config.mts +1 -24
- package/src/cli/index.ts +10 -0
- package/src/cli/tanstackTypes/source.ts +3 -1
- package/src/runtime/basepathRewrite.ts +11 -2
- package/src/runtime/lifecycle.ts +14 -3
- package/src/runtime/plugin.node.tsx +9 -2
- package/src/runtime/plugin.tsx +5 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -12
- package/src/runtime/routeTree/construction.ts +2 -0
- package/src/runtime/router.ts +25 -40
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/src/runtime/utils.tsx +3 -1
- package/tests/router/cli.test.ts +148 -824
- package/tests/router/dataMutation.test.tsx +17 -85
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +68 -28
- package/tests/router/loaderBridge.test.ts +0 -25
- package/tests/router/localisedIdentity.test.tsx +95 -0
- package/tests/router/prefetchLink.test.tsx +17 -21
- package/tests/router/prefetchLinkPreload.test.tsx +11 -31
- package/tests/router/routeHooks.test.ts +41 -105
- package/tests/router/routeTree.test.ts +144 -836
- package/tests/router/rsc.test.tsx +131 -166
- package/tests/router/serverPlugin.test.ts +43 -1
- package/tests/router/tanstackTypes.test.ts +109 -973
- package/tests/realm-isolation.test.ts +0 -114
- package/tests/router/fastDefaults.test.ts +0 -32
- package/tests/router/fixtures/legacyRuntimeRouter.ts +0 -10
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -40
- package/tests/router/hydrationBoundary.test.tsx +0 -23
- package/tests/router/packageSurface.test.ts +0 -24
- package/tests/router/preloadRedirect.test.ts +0 -64
- package/tests/router/register.test.ts +0 -8
- package/tests/router/routerCompatibility.test.ts +0 -239
- package/tests/router/rscDecoderSecurity.test.ts +0 -113
- package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
- /package/dist/esm/{rslib-runtime.mjs → rslib-runtime~1.mjs} +0 -0
- /package/dist/esm-node/{rslib-runtime.mjs → rslib-runtime~0.mjs} +0 -0
package/tests/router/cli.test.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import {
|
|
6
|
+
import { compile } from '@modern-js/server-utils';
|
|
7
7
|
import type { Entrypoint } from '@modern-js/types';
|
|
8
|
-
import { fs
|
|
9
|
-
import {
|
|
10
|
-
createTanstackRsbuildRouteSplittingProfile,
|
|
11
|
-
generateTanstackRouteArtifacts,
|
|
12
|
-
tanstackRouterPlugin,
|
|
13
|
-
writeTanstackRegisterFile,
|
|
14
|
-
writeTanstackRouterTypesForEntries,
|
|
15
|
-
} from '../../src/cli';
|
|
8
|
+
import { fs } from '@modern-js/utils';
|
|
9
|
+
import { tanstackRouterPlugin, writeTanstackRegisterFile } from '../../src/cli';
|
|
16
10
|
|
|
17
11
|
const execFileAsync = promisify(execFile);
|
|
18
12
|
const strictestTsconfigPath = path.resolve(
|
|
@@ -20,11 +14,24 @@ const strictestTsconfigPath = path.resolve(
|
|
|
20
14
|
'../../node_modules/@tsconfig/strictest/tsconfig.json',
|
|
21
15
|
);
|
|
22
16
|
|
|
17
|
+
function createAppContextApi<T extends object>(initial: T) {
|
|
18
|
+
let context: T & { serverCompileExcludedFiles?: string[] } = { ...initial };
|
|
19
|
+
return {
|
|
20
|
+
getAppContext: () => context,
|
|
21
|
+
updateAppContext: (update: Partial<typeof context>) => {
|
|
22
|
+
context = { ...context, ...update };
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compiles the generated registration output with `tsgo` under
|
|
29
|
+
* @tsconfig/strictest — the way a consumer's own type check would see it.
|
|
30
|
+
*/
|
|
23
31
|
async function typecheckGeneratedRegistration(options: {
|
|
24
32
|
entries: string[];
|
|
25
33
|
generatedDirName: string;
|
|
26
34
|
srcDirectory: string;
|
|
27
|
-
runtimeModule?: string;
|
|
28
35
|
i18nRuntimeModule?: string;
|
|
29
36
|
canonicalTypeChecks?: string[];
|
|
30
37
|
}) {
|
|
@@ -32,10 +39,10 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
32
39
|
entries,
|
|
33
40
|
generatedDirName,
|
|
34
41
|
srcDirectory,
|
|
35
|
-
runtimeModule = '@modern-js/plugin-tanstack/runtime',
|
|
36
42
|
i18nRuntimeModule,
|
|
37
43
|
canonicalTypeChecks = [],
|
|
38
44
|
} = options;
|
|
45
|
+
const runtimeModule = '@modern-js/plugin-tanstack/runtime';
|
|
39
46
|
const projectDirectory = path.dirname(srcDirectory);
|
|
40
47
|
|
|
41
48
|
for (const entry of entries) {
|
|
@@ -64,33 +71,28 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
64
71
|
' export function modernLoaderToTanstack<TLoader extends (...args: never[]) => unknown>(options: { hasSplat: boolean }, loader: TLoader): (context: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
|
|
65
72
|
' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
|
|
66
73
|
'}',
|
|
74
|
+
...(i18nRuntimeModule
|
|
75
|
+
? [
|
|
76
|
+
`declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
|
|
77
|
+
]
|
|
78
|
+
: []),
|
|
67
79
|
];
|
|
68
|
-
if (i18nRuntimeModule) {
|
|
69
|
-
runtimeDeclaration.push(
|
|
70
|
-
`declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
80
|
await fs.outputFile(
|
|
74
81
|
path.join(srcDirectory, 'generated-runtime-shim.d.ts'),
|
|
75
82
|
runtimeDeclaration.join('\n'),
|
|
76
83
|
);
|
|
77
84
|
|
|
78
|
-
const contractLines = [
|
|
79
|
-
`import type { Register } from '${runtimeModule}';`,
|
|
80
|
-
...entries.map(
|
|
81
|
-
(entry, index) =>
|
|
82
|
-
`import { router as router${index} } from './${generatedDirName}/${entry}/router.gen';`,
|
|
83
|
-
),
|
|
84
|
-
...entries.map(
|
|
85
|
-
(_, index) =>
|
|
86
|
-
`const registeredRouter${index}: Register['router'] = router${index};`,
|
|
87
|
-
),
|
|
88
|
-
...entries.map((_, index) => `void registeredRouter${index};`),
|
|
89
|
-
...canonicalTypeChecks,
|
|
90
|
-
];
|
|
91
85
|
await fs.outputFile(
|
|
92
86
|
path.join(srcDirectory, 'registration-contract.ts'),
|
|
93
|
-
|
|
87
|
+
[
|
|
88
|
+
`import type { Register } from '${runtimeModule}';`,
|
|
89
|
+
...entries.flatMap((entry, index) => [
|
|
90
|
+
`import { router as router${index} } from './${generatedDirName}/${entry}/router.gen';`,
|
|
91
|
+
`const registeredRouter${index}: Register['router'] = router${index};`,
|
|
92
|
+
`void registeredRouter${index};`,
|
|
93
|
+
]),
|
|
94
|
+
...canonicalTypeChecks,
|
|
95
|
+
].join('\n'),
|
|
94
96
|
);
|
|
95
97
|
await fs.outputJSON(path.join(projectDirectory, 'tsconfig.json'), {
|
|
96
98
|
extends: strictestTsconfigPath,
|
|
@@ -109,26 +111,16 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
109
111
|
await execFileAsync(
|
|
110
112
|
process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
|
|
111
113
|
['-p', 'tsconfig.json'],
|
|
112
|
-
{
|
|
113
|
-
cwd: projectDirectory,
|
|
114
|
-
shell: process.platform === 'win32',
|
|
115
|
-
},
|
|
114
|
+
{ cwd: projectDirectory, shell: process.platform === 'win32' },
|
|
116
115
|
);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
|
|
119
|
-
error
|
|
120
|
-
|
|
121
|
-
'stdout' in error &&
|
|
122
|
-
typeof error.stdout === 'string'
|
|
123
|
-
) {
|
|
124
|
-
throw new Error(error.stdout, { cause: error });
|
|
125
|
-
}
|
|
126
|
-
throw error;
|
|
116
|
+
} catch (error: any) {
|
|
117
|
+
throw typeof error?.stdout === 'string'
|
|
118
|
+
? new Error(error.stdout, { cause: error })
|
|
119
|
+
: error;
|
|
127
120
|
}
|
|
128
121
|
}
|
|
129
122
|
|
|
130
123
|
const runtimeCliMocks = {
|
|
131
|
-
handleFileChange: rstest.fn(),
|
|
132
124
|
handleGeneratorEntryCode: rstest.fn(),
|
|
133
125
|
};
|
|
134
126
|
|
|
@@ -151,40 +143,14 @@ rstest.mock('@modern-js/runtime/cli', () => {
|
|
|
151
143
|
: null),
|
|
152
144
|
getEntrypointRoutesOwner: (entrypoint: any) =>
|
|
153
145
|
entrypoint.__modernRoutesOwner || null,
|
|
154
|
-
// Forward through
|
|
146
|
+
// Forward through an arrow: the mock factory is hoisted above the
|
|
155
147
|
// `runtimeCliMocks` initializer, so it must not dereference it eagerly.
|
|
156
|
-
handleFileChange: (...args: unknown[]) =>
|
|
157
|
-
runtimeCliMocks.handleFileChange(...args),
|
|
158
148
|
handleGeneratorEntryCode: (...args: unknown[]) =>
|
|
159
149
|
runtimeCliMocks.handleGeneratorEntryCode(...args),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
) =>
|
|
164
|
-
entrypoints.map(entrypoint => {
|
|
165
|
-
const routesEntry = path.join(entrypoint.absoluteEntryDir!, routesDir);
|
|
166
|
-
return {
|
|
167
|
-
...entrypoint,
|
|
168
|
-
nestedRoutesEntry: routesEntry,
|
|
169
|
-
[routesDirMetaKey]: routesDir,
|
|
170
|
-
};
|
|
171
|
-
}),
|
|
172
|
-
isRouteEntry: (dir: string, routesDir = 'routes') => {
|
|
173
|
-
const routesEntry = path.join(dir, routesDir);
|
|
174
|
-
return fs.existsSync(routesEntry) ? routesEntry : false;
|
|
175
|
-
},
|
|
176
|
-
updateNestedRoutesSpec: async (
|
|
177
|
-
specPath: string,
|
|
178
|
-
nextRoutes: Record<string, unknown>,
|
|
179
|
-
) => {
|
|
180
|
-
const existingRoutes = fs.existsSync(specPath)
|
|
181
|
-
? await fs.readJSON(specPath)
|
|
182
|
-
: {};
|
|
183
|
-
await fs.outputJSON(specPath, {
|
|
184
|
-
...existingRoutes,
|
|
185
|
-
...nextRoutes,
|
|
186
|
-
});
|
|
187
|
-
},
|
|
150
|
+
handleFileChange: async () => {},
|
|
151
|
+
handleModifyEntrypoints: async (entrypoints: Entrypoint[]) => entrypoints,
|
|
152
|
+
isRouteEntry: () => false,
|
|
153
|
+
updateNestedRoutesSpec: async () => {},
|
|
188
154
|
};
|
|
189
155
|
});
|
|
190
156
|
|
|
@@ -192,264 +158,95 @@ describe('tanstack router cli plugin', () => {
|
|
|
192
158
|
let tempDir: string | undefined;
|
|
193
159
|
|
|
194
160
|
afterEach(async () => {
|
|
195
|
-
runtimeCliMocks.handleFileChange.mockReset();
|
|
196
161
|
runtimeCliMocks.handleGeneratorEntryCode.mockReset();
|
|
197
|
-
|
|
198
162
|
if (tempDir) {
|
|
199
163
|
await rm(tempDir, { recursive: true, force: true });
|
|
200
164
|
tempDir = undefined;
|
|
201
165
|
}
|
|
202
166
|
});
|
|
203
167
|
|
|
204
|
-
test('
|
|
205
|
-
tempDir = await
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
await writeTanstackRouterTypesForEntries({
|
|
210
|
-
appContext: {
|
|
211
|
-
srcDirectory,
|
|
212
|
-
internalSrcAlias: '@/_',
|
|
213
|
-
entrypoints: [
|
|
214
|
-
{ entryName: 'dashboard', isMainEntry: false },
|
|
215
|
-
{ entryName: 'main', isMainEntry: true },
|
|
216
|
-
],
|
|
217
|
-
} as any,
|
|
218
|
-
generatedDirName: 'generated-router',
|
|
219
|
-
routesByEntry: {
|
|
220
|
-
dashboard: [
|
|
221
|
-
{
|
|
222
|
-
type: 'nested',
|
|
223
|
-
id: 'layout',
|
|
224
|
-
isRoot: true,
|
|
225
|
-
children: [{ type: 'nested', id: 'page', index: true }],
|
|
226
|
-
},
|
|
227
|
-
],
|
|
228
|
-
main: [
|
|
229
|
-
{
|
|
230
|
-
type: 'nested',
|
|
231
|
-
id: 'layout',
|
|
232
|
-
isRoot: true,
|
|
233
|
-
children: [{ type: 'nested', id: 'page', index: true }],
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
await typecheckGeneratedRegistration({
|
|
240
|
-
entries: ['main', 'dashboard'],
|
|
241
|
-
generatedDirName: 'generated-router',
|
|
242
|
-
srcDirectory,
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('typechecks register metadata for custom entry lists without routes', async () => {
|
|
247
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
248
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
249
|
-
|
|
250
|
-
await writeTanstackRegisterFile({
|
|
251
|
-
entries: ['main'],
|
|
252
|
-
generatedDirName: 'tanstack',
|
|
253
|
-
srcDirectory,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
await typecheckGeneratedRegistration({
|
|
257
|
-
entries: ['main'],
|
|
258
|
-
generatedDirName: 'tanstack',
|
|
259
|
-
srcDirectory,
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test('exposes canonical route params through plugin-i18n types', async () => {
|
|
264
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
265
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
266
|
-
|
|
267
|
-
await writeTanstackRegisterFile({
|
|
268
|
-
entries: ['main'],
|
|
269
|
-
generatedDirName: 'tanstack',
|
|
270
|
-
srcDirectory,
|
|
271
|
-
canonicalRoutes: {
|
|
272
|
-
'/': 'Record<string, never>',
|
|
273
|
-
'/products/$slug': '{ "slug": string }',
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
await typecheckGeneratedRegistration({
|
|
278
|
-
entries: ['main'],
|
|
279
|
-
generatedDirName: 'tanstack',
|
|
280
|
-
srcDirectory,
|
|
281
|
-
i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
|
|
282
|
-
canonicalTypeChecks: [
|
|
283
|
-
"import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
|
|
284
|
-
"const rootParams: UltramodernCanonicalRoutes['/'] = {};",
|
|
285
|
-
"const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
|
|
286
|
-
'void rootParams;',
|
|
287
|
-
'void productParams;',
|
|
288
|
-
],
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test('typechecks a plain TanStack register without plugin-i18n installed', async () => {
|
|
293
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
294
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
295
|
-
|
|
296
|
-
await writeTanstackRegisterFile({
|
|
297
|
-
entries: ['main'],
|
|
298
|
-
generatedDirName: 'tanstack',
|
|
299
|
-
srcDirectory,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
await typecheckGeneratedRegistration({
|
|
303
|
-
entries: ['main'],
|
|
304
|
-
generatedDirName: 'tanstack',
|
|
305
|
-
srcDirectory,
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test('typechecks custom runtime module augmentations', async () => {
|
|
310
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
168
|
+
test('excludes its generated declaration from server roots only when installed', async () => {
|
|
169
|
+
tempDir = await fs.realpath(
|
|
170
|
+
await mkdtemp(path.join(tmpdir(), 'tanstack-server-roots-')),
|
|
171
|
+
);
|
|
311
172
|
const srcDirectory = path.join(tempDir, 'src');
|
|
312
|
-
|
|
313
|
-
await writeTanstackRegisterFile({
|
|
314
|
-
entries: ['main'],
|
|
315
|
-
generatedDirName: 'tanstack',
|
|
316
|
-
runtimeModule: '@my-org/tanstack/runtime',
|
|
173
|
+
const declaration = path.join(
|
|
317
174
|
srcDirectory,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
175
|
+
'modern-tanstack',
|
|
176
|
+
'register.gen.d.ts',
|
|
177
|
+
);
|
|
178
|
+
const apiDirectory = path.join(tempDir, 'api');
|
|
179
|
+
const compileOptions = {
|
|
180
|
+
sourceDirs: [apiDirectory],
|
|
181
|
+
distDir: path.join(tempDir, 'dist'),
|
|
182
|
+
tsconfigPath: path.join(tempDir, 'tsconfig.json'),
|
|
183
|
+
throwErrorInsteadOfExit: true,
|
|
184
|
+
};
|
|
185
|
+
await fs.outputJSON(compileOptions.tsconfigPath, {
|
|
186
|
+
compilerOptions: {
|
|
187
|
+
module: 'commonjs',
|
|
188
|
+
target: 'ES2022',
|
|
189
|
+
types: [],
|
|
190
|
+
noEmitOnError: true,
|
|
191
|
+
skipLibCheck: false,
|
|
192
|
+
},
|
|
193
|
+
include: ['api', 'src'],
|
|
335
194
|
});
|
|
336
|
-
|
|
195
|
+
await fs.outputFile(
|
|
196
|
+
path.join(apiDirectory, 'index.ts'),
|
|
197
|
+
'export const server = true;\n',
|
|
198
|
+
);
|
|
199
|
+
await fs.outputFile(
|
|
200
|
+
path.join(srcDirectory, 'routes/page.tsx'),
|
|
201
|
+
"import type { Missing } from 'missing-browser-only-types';\nexport const page: Missing = undefined;\n",
|
|
202
|
+
);
|
|
203
|
+
await fs.outputFile(
|
|
204
|
+
path.join(srcDirectory, 'modern-tanstack', 'main/router.gen.ts'),
|
|
205
|
+
"import { page } from '../../routes/page';\nexport const router = { page };\n",
|
|
206
|
+
);
|
|
207
|
+
await writeTanstackRegisterFile({ entries: ['main'], srcDirectory });
|
|
337
208
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const distDirectory = path.join(tempDir, 'dist');
|
|
342
|
-
const entryDir = path.join(srcDirectory, 'main');
|
|
343
|
-
const viewsDir = path.join(entryDir, 'views');
|
|
344
|
-
await mkdir(viewsDir, { recursive: true });
|
|
209
|
+
await expect(compile(tempDir, {}, compileOptions)).rejects.toThrow(
|
|
210
|
+
/missing-browser-only-types/,
|
|
211
|
+
);
|
|
345
212
|
|
|
346
|
-
const
|
|
213
|
+
const previousExclusions = [path.join(tempDir, 'other-client.d.ts')];
|
|
347
214
|
const api = {
|
|
348
|
-
|
|
215
|
+
...createAppContextApi({
|
|
349
216
|
srcDirectory,
|
|
350
|
-
|
|
351
|
-
metaName: 'modern-js',
|
|
352
|
-
serverRoutes: [{ entryName: 'main', urlPath: '/dashboard' }],
|
|
217
|
+
serverCompileExcludedFiles: previousExclusions,
|
|
353
218
|
}),
|
|
354
|
-
_internalRuntimePlugins: (
|
|
355
|
-
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
},
|
|
363
|
-
modifyEntrypoints: (tap: any) => {
|
|
364
|
-
taps.modifyEntrypoints = tap;
|
|
365
|
-
},
|
|
366
|
-
generateEntryCode: (tap: any) => {
|
|
367
|
-
taps.generateEntryCode = tap;
|
|
368
|
-
},
|
|
369
|
-
onFileChanged: (tap: any) => {
|
|
370
|
-
taps.onFileChanged = tap;
|
|
371
|
-
},
|
|
372
|
-
modifyFileSystemRoutes: (tap: any) => {
|
|
373
|
-
taps.modifyFileSystemRoutes = tap;
|
|
374
|
-
},
|
|
375
|
-
onBeforeGenerateRoutes: (tap: any) => {
|
|
376
|
-
taps.onBeforeGenerateRoutes = tap;
|
|
377
|
-
},
|
|
219
|
+
_internalRuntimePlugins: () => {},
|
|
220
|
+
checkEntryPoint: () => {},
|
|
221
|
+
config: () => {},
|
|
222
|
+
modifyEntrypoints: () => {},
|
|
223
|
+
generateEntryCode: () => {},
|
|
224
|
+
onFileChanged: () => {},
|
|
225
|
+
modifyFileSystemRoutes: () => {},
|
|
226
|
+
onBeforeGenerateRoutes: () => {},
|
|
378
227
|
};
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
path: entryDir,
|
|
384
|
-
entry: viewsDir,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const { entrypoints } = await taps.modifyEntrypoints({
|
|
388
|
-
entrypoints: [
|
|
389
|
-
{
|
|
390
|
-
entryName: 'main',
|
|
391
|
-
entry: entryDir,
|
|
392
|
-
absoluteEntryDir: entryDir,
|
|
393
|
-
isAutoMount: true,
|
|
394
|
-
isMainEntry: true,
|
|
395
|
-
} as Entrypoint,
|
|
396
|
-
],
|
|
397
|
-
});
|
|
398
|
-
const [entrypoint] = entrypoints;
|
|
399
|
-
expect(entrypoint.nestedRoutesEntry).toBe(viewsDir);
|
|
400
|
-
|
|
228
|
+
tanstackRouterPlugin().setup!(api as never);
|
|
229
|
+
const excludeFiles = api.getAppContext().serverCompileExcludedFiles;
|
|
230
|
+
expect(excludeFiles).toEqual([...previousExclusions, declaration]);
|
|
231
|
+
await compile(tempDir, {}, { ...compileOptions, excludeFiles });
|
|
401
232
|
expect(
|
|
402
|
-
|
|
403
|
-
).
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
config: { serverBase: ['/dashboard'] },
|
|
408
|
-
},
|
|
409
|
-
]);
|
|
410
|
-
|
|
411
|
-
expect(taps.config()).toMatchObject({
|
|
412
|
-
output: {
|
|
413
|
-
splitRouteChunks: true,
|
|
414
|
-
},
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
const specPath = path.join(distDirectory, NESTED_ROUTE_SPEC_FILE);
|
|
418
|
-
await fs.outputJSON(specPath, {
|
|
419
|
-
existing: [{ id: 'keep-me' }],
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
await taps.modifyFileSystemRoutes({
|
|
423
|
-
entrypoint,
|
|
424
|
-
routes: [
|
|
425
|
-
{
|
|
426
|
-
id: 'main-route',
|
|
427
|
-
type: 'nested',
|
|
428
|
-
origin: 'file-system',
|
|
429
|
-
},
|
|
430
|
-
],
|
|
431
|
-
});
|
|
432
|
-
await taps.onBeforeGenerateRoutes({ entrypoint, code: '' });
|
|
433
|
-
|
|
434
|
-
expect(await fs.readJSON(specPath)).toEqual({
|
|
435
|
-
existing: [{ id: 'keep-me' }],
|
|
436
|
-
main: [
|
|
437
|
-
{
|
|
438
|
-
id: 'main-route',
|
|
439
|
-
type: 'nested',
|
|
440
|
-
origin: 'file-system',
|
|
441
|
-
},
|
|
442
|
-
],
|
|
443
|
-
});
|
|
233
|
+
await fs.pathExists(path.join(compileOptions.distDir, 'api/index.js')),
|
|
234
|
+
).toBe(true);
|
|
235
|
+
expect(await fs.pathExists(path.join(compileOptions.distDir, 'src'))).toBe(
|
|
236
|
+
false,
|
|
237
|
+
);
|
|
444
238
|
});
|
|
445
239
|
|
|
446
|
-
test('
|
|
240
|
+
test('installs the TanStack router only for entrypoints it owns', () => {
|
|
447
241
|
const taps: Record<string, any> = {};
|
|
448
242
|
const api = {
|
|
449
|
-
|
|
243
|
+
...createAppContextApi({
|
|
450
244
|
srcDirectory: '/tmp/app/src',
|
|
451
245
|
metaName: 'modern-js',
|
|
452
|
-
serverRoutes: [
|
|
246
|
+
serverRoutes: [
|
|
247
|
+
{ entryName: 'custom', urlPath: '/' },
|
|
248
|
+
{ entryName: 'home', urlPath: '/' },
|
|
249
|
+
],
|
|
453
250
|
}),
|
|
454
251
|
_internalRuntimePlugins: (tap: any) => {
|
|
455
252
|
taps.internalRuntimePlugins = tap;
|
|
@@ -463,168 +260,70 @@ describe('tanstack router cli plugin', () => {
|
|
|
463
260
|
onBeforeGenerateRoutes: () => {},
|
|
464
261
|
};
|
|
465
262
|
|
|
466
|
-
tanstackRouterPlugin().setup!(api as any);
|
|
263
|
+
tanstackRouterPlugin({ routesDir: 'ts-routes' }).setup!(api as any);
|
|
467
264
|
|
|
468
265
|
// Custom entry without a routes dir (e.g. `createRoutes` in
|
|
469
|
-
// modern.runtime.ts): installing the plugin is the explicit opt-in,
|
|
470
|
-
//
|
|
471
|
-
//
|
|
266
|
+
// modern.runtime.ts): installing the plugin is the explicit opt-in, so
|
|
267
|
+
// the wrapper plus the provider registration is injected through the
|
|
268
|
+
// package's own runtime/router module.
|
|
472
269
|
const customEntrypoint = {
|
|
473
270
|
entryName: 'custom',
|
|
474
271
|
isAutoMount: true,
|
|
475
272
|
} as Entrypoint;
|
|
273
|
+
const tanstackRouterEntry = {
|
|
274
|
+
name: 'router',
|
|
275
|
+
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
276
|
+
config: { serverBase: ['/'] },
|
|
277
|
+
};
|
|
476
278
|
expect(
|
|
477
279
|
taps.internalRuntimePlugins({ entrypoint: customEntrypoint, plugins: [] })
|
|
478
280
|
.plugins,
|
|
479
|
-
).toEqual([
|
|
480
|
-
{
|
|
481
|
-
name: 'router',
|
|
482
|
-
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
483
|
-
config: { serverBase: ['/'] },
|
|
484
|
-
},
|
|
485
|
-
]);
|
|
281
|
+
).toEqual([tanstackRouterEntry]);
|
|
486
282
|
|
|
487
283
|
// If the built-in router CLI already installed the internal router for
|
|
488
284
|
// this custom entry (explicit `runtime.router` config), only the module
|
|
489
|
-
// path is redirected
|
|
490
|
-
|
|
491
|
-
const existingRouterPlugin = {
|
|
285
|
+
// path is redirected — pushing a second entry installs two routers.
|
|
286
|
+
const builtInPlugin = {
|
|
492
287
|
name: 'router',
|
|
493
288
|
path: '@modern-js/runtime/router/internal',
|
|
494
289
|
config: { serverBase: ['/'] },
|
|
495
290
|
};
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
name: 'router',
|
|
503
|
-
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
504
|
-
config: { serverBase: ['/'] },
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
test('leaves built-in and foreign-owned route entrypoints to their own router', () => {
|
|
509
|
-
const taps: Record<string, any> = {};
|
|
510
|
-
const api = {
|
|
511
|
-
getAppContext: () => ({
|
|
512
|
-
srcDirectory: '/tmp/app/src',
|
|
513
|
-
metaName: 'modern-js',
|
|
514
|
-
serverRoutes: [{ entryName: 'home', urlPath: '/' }],
|
|
515
|
-
}),
|
|
516
|
-
_internalRuntimePlugins: (tap: any) => {
|
|
517
|
-
taps.internalRuntimePlugins = tap;
|
|
518
|
-
},
|
|
519
|
-
checkEntryPoint: () => {},
|
|
520
|
-
config: () => {},
|
|
521
|
-
modifyEntrypoints: () => {},
|
|
522
|
-
generateEntryCode: () => {},
|
|
523
|
-
onFileChanged: () => {},
|
|
524
|
-
modifyFileSystemRoutes: () => {},
|
|
525
|
-
onBeforeGenerateRoutes: () => {},
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
tanstackRouterPlugin({ routesDir: 'ts-routes' }).setup!(api as any);
|
|
291
|
+
expect(
|
|
292
|
+
taps.internalRuntimePlugins({
|
|
293
|
+
entrypoint: customEntrypoint,
|
|
294
|
+
plugins: [builtInPlugin],
|
|
295
|
+
}).plugins,
|
|
296
|
+
).toEqual([tanstackRouterEntry]);
|
|
529
297
|
|
|
530
298
|
// A classic react-router file-route entry (src/<entry>/routes) living
|
|
531
299
|
// next to the TanStack entries: its internal router plugin must be left
|
|
532
|
-
// untouched — redirecting it
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
path: '@modern-js/runtime/router/internal',
|
|
546
|
-
config: { serverBase: ['/'] },
|
|
547
|
-
},
|
|
548
|
-
],
|
|
549
|
-
});
|
|
550
|
-
expect(builtInPlugins).toEqual([
|
|
551
|
-
{
|
|
552
|
-
name: 'router',
|
|
553
|
-
path: '@modern-js/runtime/router/internal',
|
|
554
|
-
config: { serverBase: ['/'] },
|
|
555
|
-
},
|
|
556
|
-
]);
|
|
300
|
+
// untouched — redirecting it would pull @tanstack/react-router into a
|
|
301
|
+
// pure react-router bundle.
|
|
302
|
+
expect(
|
|
303
|
+
taps.internalRuntimePlugins({
|
|
304
|
+
entrypoint: {
|
|
305
|
+
entryName: 'home',
|
|
306
|
+
isAutoMount: true,
|
|
307
|
+
nestedRoutesEntry: '/tmp/app/src/home/routes',
|
|
308
|
+
__modernRoutesDir: 'routes',
|
|
309
|
+
} as Entrypoint,
|
|
310
|
+
plugins: [builtInPlugin],
|
|
311
|
+
}).plugins,
|
|
312
|
+
).toEqual([builtInPlugin]);
|
|
557
313
|
|
|
558
314
|
// An entry tagged by another routes-owner plugin: nothing is pushed.
|
|
559
|
-
// The replaced sniffing path in @modern-js/runtime excluded
|
|
560
|
-
// plugin-owned entrypoints for the same reason — pushing a second
|
|
561
|
-
// `router` plugin can install two routers for one entry.
|
|
562
|
-
const foreignEntrypoint = {
|
|
563
|
-
entryName: 'home',
|
|
564
|
-
isAutoMount: true,
|
|
565
|
-
nestedRoutesEntry: '/tmp/app/src/home/acme-routes',
|
|
566
|
-
__modernRoutesDir: 'acme-routes',
|
|
567
|
-
__modernRoutesOwner: '@acme/plugin-file-router',
|
|
568
|
-
} as Entrypoint;
|
|
569
315
|
expect(
|
|
570
316
|
taps.internalRuntimePlugins({
|
|
571
|
-
entrypoint:
|
|
317
|
+
entrypoint: {
|
|
318
|
+
entryName: 'home',
|
|
319
|
+
isAutoMount: true,
|
|
320
|
+
nestedRoutesEntry: '/tmp/app/src/home/acme-routes',
|
|
321
|
+
__modernRoutesDir: 'acme-routes',
|
|
322
|
+
__modernRoutesOwner: '@acme/plugin-file-router',
|
|
323
|
+
} as Entrypoint,
|
|
572
324
|
plugins: [],
|
|
573
325
|
}).plugins,
|
|
574
326
|
).toEqual([]);
|
|
575
|
-
|
|
576
|
-
// The built-in pages/ convention is foreign too.
|
|
577
|
-
const pagesEntrypoint = {
|
|
578
|
-
entryName: 'home',
|
|
579
|
-
isAutoMount: true,
|
|
580
|
-
pageRoutesEntry: '/tmp/app/src/home/pages',
|
|
581
|
-
} as Entrypoint;
|
|
582
|
-
expect(
|
|
583
|
-
taps.internalRuntimePlugins({ entrypoint: pagesEntrypoint, plugins: [] })
|
|
584
|
-
.plugins,
|
|
585
|
-
).toEqual([]);
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
test('source.include covers the package dist and TanStack runtime deps without string surgery', () => {
|
|
589
|
-
const taps: Record<string, any> = {};
|
|
590
|
-
const api = {
|
|
591
|
-
getAppContext: () => ({
|
|
592
|
-
srcDirectory: '/tmp/app/src',
|
|
593
|
-
serverRoutes: [],
|
|
594
|
-
}),
|
|
595
|
-
_internalRuntimePlugins: () => {},
|
|
596
|
-
checkEntryPoint: () => {},
|
|
597
|
-
config: (tap: any) => {
|
|
598
|
-
taps.config = tap;
|
|
599
|
-
},
|
|
600
|
-
modifyEntrypoints: () => {},
|
|
601
|
-
generateEntryCode: () => {},
|
|
602
|
-
onFileChanged: () => {},
|
|
603
|
-
modifyFileSystemRoutes: () => {},
|
|
604
|
-
onBeforeGenerateRoutes: () => {},
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
tanstackRouterPlugin().setup!(api as any);
|
|
608
|
-
|
|
609
|
-
const include = taps.config().source.include as Array<RegExp | string>;
|
|
610
|
-
const regexes = include.filter(
|
|
611
|
-
(entry): entry is RegExp => entry instanceof RegExp,
|
|
612
|
-
);
|
|
613
|
-
for (const dep of ['react-router', 'router-core', 'react-store']) {
|
|
614
|
-
const sample = `/repo/node_modules/@tanstack/${dep}/dist/esm/index.js`;
|
|
615
|
-
expect(regexes.some(regex => regex.test(sample))).toBe(true);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
const stringEntries = include.filter(
|
|
619
|
-
(entry): entry is string => typeof entry === 'string',
|
|
620
|
-
);
|
|
621
|
-
expect(stringEntries).toHaveLength(1);
|
|
622
|
-
// The include must point at the package root (two levels above the cli
|
|
623
|
-
// build dir) so dist/esm, dist/esm-node and dist/cjs are all covered —
|
|
624
|
-
// the old `.replace('cjs', 'esm')` never matched the bundled dist/esm
|
|
625
|
-
// runtime when the CLI was loaded through the ESM condition.
|
|
626
|
-
expect(stringEntries[0]).toBe(path.resolve(__dirname, '..', '..'));
|
|
627
|
-
expect(stringEntries[0]).not.toContain('esm');
|
|
628
327
|
});
|
|
629
328
|
|
|
630
329
|
test('emits the plugin-i18n augmentation only when plugin-i18n is registered', async () => {
|
|
@@ -666,7 +365,7 @@ describe('tanstack router cli plugin', () => {
|
|
|
666
365
|
|
|
667
366
|
const taps: Record<string, any> = {};
|
|
668
367
|
const api = {
|
|
669
|
-
|
|
368
|
+
...createAppContextApi({
|
|
670
369
|
srcDirectory,
|
|
671
370
|
internalSrcAlias: '@/_',
|
|
672
371
|
entrypoints: [entrypoint],
|
|
@@ -718,379 +417,4 @@ describe('tanstack router cli plugin', () => {
|
|
|
718
417
|
{ name: '@modern-js/plugin-i18n' },
|
|
719
418
|
]);
|
|
720
419
|
});
|
|
721
|
-
|
|
722
|
-
test('generates plugin-owned TanStack route files through core route generation', async () => {
|
|
723
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
724
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
725
|
-
const entryDir = path.join(srcDirectory, 'main');
|
|
726
|
-
const viewsDir = path.join(entryDir, 'views');
|
|
727
|
-
await mkdir(path.join(viewsDir, 'mf'), { recursive: true });
|
|
728
|
-
await fs.outputFile(
|
|
729
|
-
path.join(viewsDir, 'mf', 'page.data.ts'),
|
|
730
|
-
[
|
|
731
|
-
'export const loader = () => ({ count: 0 });',
|
|
732
|
-
'export const action = () => Response.json({ count: 1 });',
|
|
733
|
-
].join('\n'),
|
|
734
|
-
);
|
|
735
|
-
|
|
736
|
-
const entrypoint = {
|
|
737
|
-
entryName: 'main',
|
|
738
|
-
entry: entryDir,
|
|
739
|
-
absoluteEntryDir: entryDir,
|
|
740
|
-
isAutoMount: true,
|
|
741
|
-
isMainEntry: true,
|
|
742
|
-
nestedRoutesEntry: viewsDir,
|
|
743
|
-
__modernRoutesDir: 'views',
|
|
744
|
-
} as Entrypoint;
|
|
745
|
-
runtimeCliMocks.handleGeneratorEntryCode.mockResolvedValue({
|
|
746
|
-
main: [
|
|
747
|
-
{
|
|
748
|
-
type: 'nested',
|
|
749
|
-
id: 'layout',
|
|
750
|
-
isRoot: true,
|
|
751
|
-
children: [
|
|
752
|
-
{
|
|
753
|
-
type: 'nested',
|
|
754
|
-
id: 'mf/page',
|
|
755
|
-
path: 'mf',
|
|
756
|
-
data: '@/_/main/views/mf/page.data',
|
|
757
|
-
action: '@/_/main/views/mf/page.data',
|
|
758
|
-
},
|
|
759
|
-
],
|
|
760
|
-
},
|
|
761
|
-
],
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
const taps: Record<string, any> = {};
|
|
765
|
-
const api = {
|
|
766
|
-
getAppContext: () => ({
|
|
767
|
-
srcDirectory,
|
|
768
|
-
internalSrcAlias: '@/_',
|
|
769
|
-
entrypoints: [entrypoint],
|
|
770
|
-
}),
|
|
771
|
-
_internalRuntimePlugins: () => {},
|
|
772
|
-
checkEntryPoint: (tap: any) => {
|
|
773
|
-
taps.checkEntryPoint = tap;
|
|
774
|
-
},
|
|
775
|
-
config: (tap: any) => {
|
|
776
|
-
taps.config = tap;
|
|
777
|
-
},
|
|
778
|
-
modifyEntrypoints: (tap: any) => {
|
|
779
|
-
taps.modifyEntrypoints = tap;
|
|
780
|
-
},
|
|
781
|
-
generateEntryCode: (tap: any) => {
|
|
782
|
-
taps.generateEntryCode = tap;
|
|
783
|
-
},
|
|
784
|
-
onFileChanged: (tap: any) => {
|
|
785
|
-
taps.onFileChanged = tap;
|
|
786
|
-
},
|
|
787
|
-
modifyFileSystemRoutes: (tap: any) => {
|
|
788
|
-
taps.modifyFileSystemRoutes = tap;
|
|
789
|
-
},
|
|
790
|
-
onBeforeGenerateRoutes: (tap: any) => {
|
|
791
|
-
taps.onBeforeGenerateRoutes = tap;
|
|
792
|
-
},
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
tanstackRouterPlugin({
|
|
796
|
-
generatedDirName: 'tanstack-generated',
|
|
797
|
-
routesDir: 'views',
|
|
798
|
-
}).setup!(api as any);
|
|
799
|
-
|
|
800
|
-
await taps.generateEntryCode({ entrypoints: [entrypoint] });
|
|
801
|
-
|
|
802
|
-
expect(runtimeCliMocks.handleGeneratorEntryCode).toHaveBeenCalledWith(
|
|
803
|
-
api,
|
|
804
|
-
[entrypoint],
|
|
805
|
-
{
|
|
806
|
-
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
807
|
-
hydrateRscClientRoutes: true,
|
|
808
|
-
includeRouteServerLoadersInSsrEntry: false,
|
|
809
|
-
isolateRouteDataInRscLayer: true,
|
|
810
|
-
serverRoutesFileName: 'tanstack-routes.server.js',
|
|
811
|
-
},
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
await typecheckGeneratedRegistration({
|
|
815
|
-
entries: ['main'],
|
|
816
|
-
generatedDirName: 'tanstack-generated',
|
|
817
|
-
srcDirectory,
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
test('regenerates plugin-owned TanStack files for scoped file changes', async () => {
|
|
822
|
-
const regenerateEvent = {
|
|
823
|
-
eventType: 'add',
|
|
824
|
-
filename: 'src/main/views/page.tsx',
|
|
825
|
-
};
|
|
826
|
-
const entrypoint = {
|
|
827
|
-
entryName: 'main',
|
|
828
|
-
__modernRoutesDir: 'views',
|
|
829
|
-
} as any as Entrypoint;
|
|
830
|
-
const api = {
|
|
831
|
-
getAppContext: () => ({
|
|
832
|
-
srcDirectory: '/tmp/app/src',
|
|
833
|
-
internalSrcAlias: '@/_',
|
|
834
|
-
entrypoints: [entrypoint],
|
|
835
|
-
}),
|
|
836
|
-
_internalRuntimePlugins: () => {},
|
|
837
|
-
checkEntryPoint: () => {},
|
|
838
|
-
config: () => {},
|
|
839
|
-
modifyEntrypoints: () => {},
|
|
840
|
-
generateEntryCode: () => {},
|
|
841
|
-
onFileChanged: (tap: any) => {
|
|
842
|
-
api.onFileChangedTap = tap;
|
|
843
|
-
},
|
|
844
|
-
modifyFileSystemRoutes: () => {},
|
|
845
|
-
onBeforeGenerateRoutes: () => {},
|
|
846
|
-
onFileChangedTap: undefined as any,
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
tanstackRouterPlugin({ routesDir: 'views' }).setup!(api as any);
|
|
850
|
-
|
|
851
|
-
runtimeCliMocks.handleFileChange.mockImplementationOnce(
|
|
852
|
-
async (_api, _event, options) => {
|
|
853
|
-
expect(options.entrypointsKey).toBe('@modern-js/plugin-tanstack');
|
|
854
|
-
expect(options.includeEntry(entrypoint)).toBe(true);
|
|
855
|
-
expect(
|
|
856
|
-
options.includeEntry({
|
|
857
|
-
...entrypoint,
|
|
858
|
-
__modernRoutesDir: 'routes',
|
|
859
|
-
}),
|
|
860
|
-
).toBe(false);
|
|
861
|
-
expect(typeof options.regenerate).toBe('function');
|
|
862
|
-
},
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
await api.onFileChangedTap(regenerateEvent);
|
|
866
|
-
|
|
867
|
-
expect(runtimeCliMocks.handleFileChange).toHaveBeenCalledWith(
|
|
868
|
-
api,
|
|
869
|
-
regenerateEvent,
|
|
870
|
-
expect.objectContaining({
|
|
871
|
-
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
872
|
-
}),
|
|
873
|
-
);
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
test('can opt out of Modern-owned route code splitting', async () => {
|
|
877
|
-
const taps: Record<string, any> = {};
|
|
878
|
-
const api = {
|
|
879
|
-
getAppContext: () => ({
|
|
880
|
-
srcDirectory: '/tmp/app/src',
|
|
881
|
-
serverRoutes: [],
|
|
882
|
-
}),
|
|
883
|
-
_internalRuntimePlugins: () => {},
|
|
884
|
-
checkEntryPoint: () => {},
|
|
885
|
-
config: (tap: any) => {
|
|
886
|
-
taps.config = tap;
|
|
887
|
-
},
|
|
888
|
-
modifyEntrypoints: () => {},
|
|
889
|
-
generateEntryCode: () => {},
|
|
890
|
-
onFileChanged: () => {},
|
|
891
|
-
modifyFileSystemRoutes: () => {},
|
|
892
|
-
onBeforeGenerateRoutes: () => {},
|
|
893
|
-
};
|
|
894
|
-
|
|
895
|
-
tanstackRouterPlugin({ routeCodeSplitting: false }).setup!(api as any);
|
|
896
|
-
|
|
897
|
-
expect(taps.config()).toMatchObject({
|
|
898
|
-
output: {
|
|
899
|
-
splitRouteChunks: false,
|
|
900
|
-
},
|
|
901
|
-
});
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
test('route splitting profile carries only the rsbuild config production consumes', () => {
|
|
905
|
-
expect(createTanstackRsbuildRouteSplittingProfile({})).toEqual({
|
|
906
|
-
defaultConfig: {
|
|
907
|
-
output: {
|
|
908
|
-
splitRouteChunks: true,
|
|
909
|
-
},
|
|
910
|
-
},
|
|
911
|
-
});
|
|
912
|
-
expect(
|
|
913
|
-
createTanstackRsbuildRouteSplittingProfile({ routeCodeSplitting: false }),
|
|
914
|
-
).toEqual({
|
|
915
|
-
defaultConfig: {
|
|
916
|
-
output: {
|
|
917
|
-
splitRouteChunks: false,
|
|
918
|
-
},
|
|
919
|
-
},
|
|
920
|
-
});
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
test('preserves user-selected route and builder chunk splitting modes', () => {
|
|
924
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
925
|
-
{},
|
|
926
|
-
).defaultConfig;
|
|
927
|
-
const chunkSplits = [
|
|
928
|
-
{ strategy: 'split-by-module' },
|
|
929
|
-
{ strategy: 'split-by-experience' },
|
|
930
|
-
{ strategy: 'all-in-one' },
|
|
931
|
-
{ strategy: 'single-vendor' },
|
|
932
|
-
{ strategy: 'split-by-size', minSize: 10_000, maxSize: 60_000 },
|
|
933
|
-
{
|
|
934
|
-
strategy: 'custom',
|
|
935
|
-
splitChunks: {
|
|
936
|
-
chunks: 'all',
|
|
937
|
-
cacheGroups: {
|
|
938
|
-
tractors: {
|
|
939
|
-
name: 'tractors',
|
|
940
|
-
test: /tractors/u,
|
|
941
|
-
},
|
|
942
|
-
},
|
|
943
|
-
},
|
|
944
|
-
},
|
|
945
|
-
];
|
|
946
|
-
|
|
947
|
-
for (const chunkSplit of chunkSplits) {
|
|
948
|
-
expect(
|
|
949
|
-
mergeConfig([
|
|
950
|
-
pluginDefaults,
|
|
951
|
-
{
|
|
952
|
-
output: {
|
|
953
|
-
splitRouteChunks: false,
|
|
954
|
-
},
|
|
955
|
-
performance: {
|
|
956
|
-
chunkSplit,
|
|
957
|
-
},
|
|
958
|
-
splitChunks: false,
|
|
959
|
-
},
|
|
960
|
-
]),
|
|
961
|
-
).toMatchObject({
|
|
962
|
-
output: {
|
|
963
|
-
splitRouteChunks: false,
|
|
964
|
-
},
|
|
965
|
-
performance: {
|
|
966
|
-
chunkSplit,
|
|
967
|
-
},
|
|
968
|
-
splitChunks: false,
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const pageSplitWithManualAsyncChunks = mergeConfig([
|
|
973
|
-
pluginDefaults,
|
|
974
|
-
{
|
|
975
|
-
performance: {
|
|
976
|
-
chunkSplit: {
|
|
977
|
-
strategy: 'custom',
|
|
978
|
-
splitChunks: {
|
|
979
|
-
chunks: 'async',
|
|
980
|
-
},
|
|
981
|
-
},
|
|
982
|
-
},
|
|
983
|
-
},
|
|
984
|
-
]);
|
|
985
|
-
|
|
986
|
-
expect(pageSplitWithManualAsyncChunks).toMatchObject({
|
|
987
|
-
output: {
|
|
988
|
-
splitRouteChunks: true,
|
|
989
|
-
},
|
|
990
|
-
performance: {
|
|
991
|
-
chunkSplit: {
|
|
992
|
-
strategy: 'custom',
|
|
993
|
-
splitChunks: {
|
|
994
|
-
chunks: 'async',
|
|
995
|
-
},
|
|
996
|
-
},
|
|
997
|
-
},
|
|
998
|
-
});
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
test('keeps custom cache group details intact', () => {
|
|
1002
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
1003
|
-
{},
|
|
1004
|
-
).defaultConfig;
|
|
1005
|
-
|
|
1006
|
-
const mergedConfig = mergeConfig([
|
|
1007
|
-
pluginDefaults,
|
|
1008
|
-
{
|
|
1009
|
-
performance: {
|
|
1010
|
-
chunkSplit: {
|
|
1011
|
-
strategy: 'custom',
|
|
1012
|
-
splitChunks: {
|
|
1013
|
-
chunks: 'all',
|
|
1014
|
-
cacheGroups: {
|
|
1015
|
-
tractors: {
|
|
1016
|
-
name: 'tractors',
|
|
1017
|
-
test: /tractors/u,
|
|
1018
|
-
},
|
|
1019
|
-
},
|
|
1020
|
-
},
|
|
1021
|
-
},
|
|
1022
|
-
},
|
|
1023
|
-
},
|
|
1024
|
-
]);
|
|
1025
|
-
|
|
1026
|
-
expect(
|
|
1027
|
-
(
|
|
1028
|
-
mergedConfig as {
|
|
1029
|
-
performance?: {
|
|
1030
|
-
chunkSplit?: {
|
|
1031
|
-
splitChunks?: {
|
|
1032
|
-
cacheGroups?: {
|
|
1033
|
-
tractors?: {
|
|
1034
|
-
test?: RegExp;
|
|
1035
|
-
};
|
|
1036
|
-
};
|
|
1037
|
-
};
|
|
1038
|
-
};
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
).performance?.chunkSplit?.splitChunks?.cacheGroups?.tractors?.test,
|
|
1042
|
-
).toEqual(/tractors/u);
|
|
1043
|
-
expect(mergedConfig).toMatchObject({
|
|
1044
|
-
output: {
|
|
1045
|
-
splitRouteChunks: true,
|
|
1046
|
-
},
|
|
1047
|
-
performance: {
|
|
1048
|
-
chunkSplit: {
|
|
1049
|
-
strategy: 'custom',
|
|
1050
|
-
splitChunks: {
|
|
1051
|
-
chunks: 'all',
|
|
1052
|
-
cacheGroups: {
|
|
1053
|
-
tractors: {
|
|
1054
|
-
name: 'tractors',
|
|
1055
|
-
},
|
|
1056
|
-
},
|
|
1057
|
-
},
|
|
1058
|
-
},
|
|
1059
|
-
},
|
|
1060
|
-
});
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
test('plugin opt-out can still combine with manual builder chunking', () => {
|
|
1064
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile({
|
|
1065
|
-
routeCodeSplitting: false,
|
|
1066
|
-
}).defaultConfig;
|
|
1067
|
-
|
|
1068
|
-
expect(
|
|
1069
|
-
mergeConfig([
|
|
1070
|
-
pluginDefaults,
|
|
1071
|
-
{
|
|
1072
|
-
performance: {
|
|
1073
|
-
chunkSplit: {
|
|
1074
|
-
strategy: 'single-vendor',
|
|
1075
|
-
},
|
|
1076
|
-
},
|
|
1077
|
-
},
|
|
1078
|
-
]),
|
|
1079
|
-
).toMatchObject({
|
|
1080
|
-
output: {
|
|
1081
|
-
splitRouteChunks: false,
|
|
1082
|
-
},
|
|
1083
|
-
performance: {
|
|
1084
|
-
chunkSplit: {
|
|
1085
|
-
strategy: 'single-vendor',
|
|
1086
|
-
},
|
|
1087
|
-
},
|
|
1088
|
-
});
|
|
1089
|
-
});
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
describe('generateTanstackRouteArtifacts export', () => {
|
|
1093
|
-
test('is exported as a function', () => {
|
|
1094
|
-
expect(typeof generateTanstackRouteArtifacts).toBe('function');
|
|
1095
|
-
});
|
|
1096
420
|
});
|