@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.6 → 3.9.0-ultramodern.8
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/package.json +12 -12
- package/rstest.config.mts +0 -3
- package/tests/router/cli.test.ts +85 -860
- package/tests/router/dataMutation.test.tsx +36 -0
- package/tests/router/generateRouteArtifacts.test.ts +11 -11
- package/tests/router/loaderBridge.test.ts +52 -0
- package/tests/router/prefetchLink.test.tsx +0 -16
- package/tests/router/prefetchLinkPreload.test.tsx +12 -25
- package/tests/router/routeTree.test.ts +144 -624
- package/tests/router/rsc.test.tsx +14 -31
- package/tests/router/tanstackTypes.test.ts +92 -616
- package/tests/realm-isolation.test.ts +0 -104
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -23
- package/tests/router/rscDecoderSecurity.test.ts +0 -84
package/tests/router/cli.test.ts
CHANGED
|
@@ -1,19 +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 { mergeConfig } from '@modern-js/plugin/cli';
|
|
7
6
|
import { compile } from '@modern-js/server-utils';
|
|
8
7
|
import type { Entrypoint } from '@modern-js/types';
|
|
9
|
-
import { fs
|
|
10
|
-
import {
|
|
11
|
-
createTanstackRsbuildRouteSplittingProfile,
|
|
12
|
-
generateTanstackRouteArtifacts,
|
|
13
|
-
tanstackRouterPlugin,
|
|
14
|
-
writeTanstackRegisterFile,
|
|
15
|
-
writeTanstackRouterTypesForEntries,
|
|
16
|
-
} from '../../src/cli';
|
|
8
|
+
import { fs } from '@modern-js/utils';
|
|
9
|
+
import { tanstackRouterPlugin, writeTanstackRegisterFile } from '../../src/cli';
|
|
17
10
|
|
|
18
11
|
const execFileAsync = promisify(execFile);
|
|
19
12
|
const strictestTsconfigPath = path.resolve(
|
|
@@ -31,11 +24,14 @@ function createAppContextApi<T extends object>(initial: T) {
|
|
|
31
24
|
};
|
|
32
25
|
}
|
|
33
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
|
+
*/
|
|
34
31
|
async function typecheckGeneratedRegistration(options: {
|
|
35
32
|
entries: string[];
|
|
36
33
|
generatedDirName: string;
|
|
37
34
|
srcDirectory: string;
|
|
38
|
-
runtimeModule?: string;
|
|
39
35
|
i18nRuntimeModule?: string;
|
|
40
36
|
canonicalTypeChecks?: string[];
|
|
41
37
|
}) {
|
|
@@ -43,10 +39,10 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
43
39
|
entries,
|
|
44
40
|
generatedDirName,
|
|
45
41
|
srcDirectory,
|
|
46
|
-
runtimeModule = '@modern-js/plugin-tanstack/runtime',
|
|
47
42
|
i18nRuntimeModule,
|
|
48
43
|
canonicalTypeChecks = [],
|
|
49
44
|
} = options;
|
|
45
|
+
const runtimeModule = '@modern-js/plugin-tanstack/runtime';
|
|
50
46
|
const projectDirectory = path.dirname(srcDirectory);
|
|
51
47
|
|
|
52
48
|
for (const entry of entries) {
|
|
@@ -75,33 +71,28 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
75
71
|
' export function modernLoaderToTanstack<TLoader extends (...args: never[]) => unknown>(options: { hasSplat: boolean }, loader: TLoader): (context: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
|
|
76
72
|
' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
|
|
77
73
|
'}',
|
|
74
|
+
...(i18nRuntimeModule
|
|
75
|
+
? [
|
|
76
|
+
`declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
|
|
77
|
+
]
|
|
78
|
+
: []),
|
|
78
79
|
];
|
|
79
|
-
if (i18nRuntimeModule) {
|
|
80
|
-
runtimeDeclaration.push(
|
|
81
|
-
`declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
80
|
await fs.outputFile(
|
|
85
81
|
path.join(srcDirectory, 'generated-runtime-shim.d.ts'),
|
|
86
82
|
runtimeDeclaration.join('\n'),
|
|
87
83
|
);
|
|
88
84
|
|
|
89
|
-
const contractLines = [
|
|
90
|
-
`import type { Register } from '${runtimeModule}';`,
|
|
91
|
-
...entries.map(
|
|
92
|
-
(entry, index) =>
|
|
93
|
-
`import { router as router${index} } from './${generatedDirName}/${entry}/router.gen';`,
|
|
94
|
-
),
|
|
95
|
-
...entries.map(
|
|
96
|
-
(_, index) =>
|
|
97
|
-
`const registeredRouter${index}: Register['router'] = router${index};`,
|
|
98
|
-
),
|
|
99
|
-
...entries.map((_, index) => `void registeredRouter${index};`),
|
|
100
|
-
...canonicalTypeChecks,
|
|
101
|
-
];
|
|
102
85
|
await fs.outputFile(
|
|
103
86
|
path.join(srcDirectory, 'registration-contract.ts'),
|
|
104
|
-
|
|
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'),
|
|
105
96
|
);
|
|
106
97
|
await fs.outputJSON(path.join(projectDirectory, 'tsconfig.json'), {
|
|
107
98
|
extends: strictestTsconfigPath,
|
|
@@ -120,26 +111,16 @@ async function typecheckGeneratedRegistration(options: {
|
|
|
120
111
|
await execFileAsync(
|
|
121
112
|
process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
|
|
122
113
|
['-p', 'tsconfig.json'],
|
|
123
|
-
{
|
|
124
|
-
cwd: projectDirectory,
|
|
125
|
-
shell: process.platform === 'win32',
|
|
126
|
-
},
|
|
114
|
+
{ cwd: projectDirectory, shell: process.platform === 'win32' },
|
|
127
115
|
);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
|
|
130
|
-
error
|
|
131
|
-
|
|
132
|
-
'stdout' in error &&
|
|
133
|
-
typeof error.stdout === 'string'
|
|
134
|
-
) {
|
|
135
|
-
throw new Error(error.stdout, { cause: error });
|
|
136
|
-
}
|
|
137
|
-
throw error;
|
|
116
|
+
} catch (error: any) {
|
|
117
|
+
throw typeof error?.stdout === 'string'
|
|
118
|
+
? new Error(error.stdout, { cause: error })
|
|
119
|
+
: error;
|
|
138
120
|
}
|
|
139
121
|
}
|
|
140
122
|
|
|
141
123
|
const runtimeCliMocks = {
|
|
142
|
-
handleFileChange: rstest.fn(),
|
|
143
124
|
handleGeneratorEntryCode: rstest.fn(),
|
|
144
125
|
};
|
|
145
126
|
|
|
@@ -162,40 +143,14 @@ rstest.mock('@modern-js/runtime/cli', () => {
|
|
|
162
143
|
: null),
|
|
163
144
|
getEntrypointRoutesOwner: (entrypoint: any) =>
|
|
164
145
|
entrypoint.__modernRoutesOwner || null,
|
|
165
|
-
// Forward through
|
|
146
|
+
// Forward through an arrow: the mock factory is hoisted above the
|
|
166
147
|
// `runtimeCliMocks` initializer, so it must not dereference it eagerly.
|
|
167
|
-
handleFileChange: (...args: unknown[]) =>
|
|
168
|
-
runtimeCliMocks.handleFileChange(...args),
|
|
169
148
|
handleGeneratorEntryCode: (...args: unknown[]) =>
|
|
170
149
|
runtimeCliMocks.handleGeneratorEntryCode(...args),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
) =>
|
|
175
|
-
entrypoints.map(entrypoint => {
|
|
176
|
-
const routesEntry = path.join(entrypoint.absoluteEntryDir!, routesDir);
|
|
177
|
-
return {
|
|
178
|
-
...entrypoint,
|
|
179
|
-
nestedRoutesEntry: routesEntry,
|
|
180
|
-
[routesDirMetaKey]: routesDir,
|
|
181
|
-
};
|
|
182
|
-
}),
|
|
183
|
-
isRouteEntry: (dir: string, routesDir = 'routes') => {
|
|
184
|
-
const routesEntry = path.join(dir, routesDir);
|
|
185
|
-
return fs.existsSync(routesEntry) ? routesEntry : false;
|
|
186
|
-
},
|
|
187
|
-
updateNestedRoutesSpec: async (
|
|
188
|
-
specPath: string,
|
|
189
|
-
nextRoutes: Record<string, unknown>,
|
|
190
|
-
) => {
|
|
191
|
-
const existingRoutes = fs.existsSync(specPath)
|
|
192
|
-
? await fs.readJSON(specPath)
|
|
193
|
-
: {};
|
|
194
|
-
await fs.outputJSON(specPath, {
|
|
195
|
-
...existingRoutes,
|
|
196
|
-
...nextRoutes,
|
|
197
|
-
});
|
|
198
|
-
},
|
|
150
|
+
handleFileChange: async () => {},
|
|
151
|
+
handleModifyEntrypoints: async (entrypoints: Entrypoint[]) => entrypoints,
|
|
152
|
+
isRouteEntry: () => false,
|
|
153
|
+
updateNestedRoutesSpec: async () => {},
|
|
199
154
|
};
|
|
200
155
|
});
|
|
201
156
|
|
|
@@ -203,30 +158,21 @@ describe('tanstack router cli plugin', () => {
|
|
|
203
158
|
let tempDir: string | undefined;
|
|
204
159
|
|
|
205
160
|
afterEach(async () => {
|
|
206
|
-
runtimeCliMocks.handleFileChange.mockReset();
|
|
207
161
|
runtimeCliMocks.handleGeneratorEntryCode.mockReset();
|
|
208
|
-
|
|
209
162
|
if (tempDir) {
|
|
210
163
|
await rm(tempDir, { recursive: true, force: true });
|
|
211
164
|
tempDir = undefined;
|
|
212
165
|
}
|
|
213
166
|
});
|
|
214
167
|
|
|
215
|
-
test
|
|
216
|
-
{ sourceDirectory: 'src', generatedDirName: undefined },
|
|
217
|
-
{ sourceDirectory: 'client', generatedDirName: 'routing-types' },
|
|
218
|
-
])('excludes its generated declaration from server roots only when installed: %s', async ({
|
|
219
|
-
sourceDirectory,
|
|
220
|
-
generatedDirName,
|
|
221
|
-
}) => {
|
|
168
|
+
test('excludes its generated declaration from server roots only when installed', async () => {
|
|
222
169
|
tempDir = await fs.realpath(
|
|
223
170
|
await mkdtemp(path.join(tmpdir(), 'tanstack-server-roots-')),
|
|
224
171
|
);
|
|
225
|
-
const srcDirectory = path.join(tempDir,
|
|
226
|
-
const generatedDirectory = generatedDirName ?? 'modern-tanstack';
|
|
172
|
+
const srcDirectory = path.join(tempDir, 'src');
|
|
227
173
|
const declaration = path.join(
|
|
228
174
|
srcDirectory,
|
|
229
|
-
|
|
175
|
+
'modern-tanstack',
|
|
230
176
|
'register.gen.d.ts',
|
|
231
177
|
);
|
|
232
178
|
const apiDirectory = path.join(tempDir, 'api');
|
|
@@ -244,7 +190,7 @@ describe('tanstack router cli plugin', () => {
|
|
|
244
190
|
noEmitOnError: true,
|
|
245
191
|
skipLibCheck: false,
|
|
246
192
|
},
|
|
247
|
-
include: ['api',
|
|
193
|
+
include: ['api', 'src'],
|
|
248
194
|
});
|
|
249
195
|
await fs.outputFile(
|
|
250
196
|
path.join(apiDirectory, 'index.ts'),
|
|
@@ -255,14 +201,10 @@ describe('tanstack router cli plugin', () => {
|
|
|
255
201
|
"import type { Missing } from 'missing-browser-only-types';\nexport const page: Missing = undefined;\n",
|
|
256
202
|
);
|
|
257
203
|
await fs.outputFile(
|
|
258
|
-
path.join(srcDirectory,
|
|
204
|
+
path.join(srcDirectory, 'modern-tanstack', 'main/router.gen.ts'),
|
|
259
205
|
"import { page } from '../../routes/page';\nexport const router = { page };\n",
|
|
260
206
|
);
|
|
261
|
-
await writeTanstackRegisterFile({
|
|
262
|
-
entries: ['main'],
|
|
263
|
-
srcDirectory,
|
|
264
|
-
generatedDirName,
|
|
265
|
-
});
|
|
207
|
+
await writeTanstackRegisterFile({ entries: ['main'], srcDirectory });
|
|
266
208
|
|
|
267
209
|
await expect(compile(tempDir, {}, compileOptions)).rejects.toThrow(
|
|
268
210
|
/missing-browser-only-types/,
|
|
@@ -283,270 +225,28 @@ describe('tanstack router cli plugin', () => {
|
|
|
283
225
|
modifyFileSystemRoutes: () => {},
|
|
284
226
|
onBeforeGenerateRoutes: () => {},
|
|
285
227
|
};
|
|
286
|
-
tanstackRouterPlugin(
|
|
228
|
+
tanstackRouterPlugin().setup!(api as never);
|
|
287
229
|
const excludeFiles = api.getAppContext().serverCompileExcludedFiles;
|
|
288
230
|
expect(excludeFiles).toEqual([...previousExclusions, declaration]);
|
|
289
|
-
expect(previousExclusions).toEqual([
|
|
290
|
-
path.join(tempDir, 'other-client.d.ts'),
|
|
291
|
-
]);
|
|
292
231
|
await compile(tempDir, {}, { ...compileOptions, excludeFiles });
|
|
293
232
|
expect(
|
|
294
233
|
await fs.pathExists(path.join(compileOptions.distDir, 'api/index.js')),
|
|
295
234
|
).toBe(true);
|
|
296
|
-
expect(
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test('typechecks plugin-owned routers and register metadata under strictest', async () => {
|
|
302
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
303
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
304
|
-
await mkdir(srcDirectory, { recursive: true });
|
|
305
|
-
|
|
306
|
-
await writeTanstackRouterTypesForEntries({
|
|
307
|
-
appContext: {
|
|
308
|
-
srcDirectory,
|
|
309
|
-
internalSrcAlias: '@/_',
|
|
310
|
-
entrypoints: [
|
|
311
|
-
{ entryName: 'dashboard', isMainEntry: false },
|
|
312
|
-
{ entryName: 'main', isMainEntry: true },
|
|
313
|
-
],
|
|
314
|
-
} as any,
|
|
315
|
-
generatedDirName: 'generated-router',
|
|
316
|
-
routesByEntry: {
|
|
317
|
-
dashboard: [
|
|
318
|
-
{
|
|
319
|
-
type: 'nested',
|
|
320
|
-
id: 'layout',
|
|
321
|
-
isRoot: true,
|
|
322
|
-
children: [{ type: 'nested', id: 'page', index: true }],
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
main: [
|
|
326
|
-
{
|
|
327
|
-
type: 'nested',
|
|
328
|
-
id: 'layout',
|
|
329
|
-
isRoot: true,
|
|
330
|
-
children: [{ type: 'nested', id: 'page', index: true }],
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
await typecheckGeneratedRegistration({
|
|
337
|
-
entries: ['main', 'dashboard'],
|
|
338
|
-
generatedDirName: 'generated-router',
|
|
339
|
-
srcDirectory,
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test('typechecks register metadata for custom entry lists without routes', async () => {
|
|
344
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
345
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
346
|
-
|
|
347
|
-
await writeTanstackRegisterFile({
|
|
348
|
-
entries: ['main'],
|
|
349
|
-
generatedDirName: 'tanstack',
|
|
350
|
-
srcDirectory,
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
await typecheckGeneratedRegistration({
|
|
354
|
-
entries: ['main'],
|
|
355
|
-
generatedDirName: 'tanstack',
|
|
356
|
-
srcDirectory,
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test('exposes canonical route params through plugin-i18n types', async () => {
|
|
361
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
362
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
363
|
-
|
|
364
|
-
await writeTanstackRegisterFile({
|
|
365
|
-
entries: ['main'],
|
|
366
|
-
generatedDirName: 'tanstack',
|
|
367
|
-
srcDirectory,
|
|
368
|
-
canonicalRoutes: {
|
|
369
|
-
'/': 'Record<string, never>',
|
|
370
|
-
'/products/$slug': '{ "slug": string }',
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
await typecheckGeneratedRegistration({
|
|
375
|
-
entries: ['main'],
|
|
376
|
-
generatedDirName: 'tanstack',
|
|
377
|
-
srcDirectory,
|
|
378
|
-
i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
|
|
379
|
-
canonicalTypeChecks: [
|
|
380
|
-
"import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
|
|
381
|
-
"const rootParams: UltramodernCanonicalRoutes['/'] = {};",
|
|
382
|
-
"const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
|
|
383
|
-
'void rootParams;',
|
|
384
|
-
'void productParams;',
|
|
385
|
-
],
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
test('typechecks a plain TanStack register without plugin-i18n installed', async () => {
|
|
390
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
391
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
392
|
-
|
|
393
|
-
await writeTanstackRegisterFile({
|
|
394
|
-
entries: ['main'],
|
|
395
|
-
generatedDirName: 'tanstack',
|
|
396
|
-
srcDirectory,
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
await typecheckGeneratedRegistration({
|
|
400
|
-
entries: ['main'],
|
|
401
|
-
generatedDirName: 'tanstack',
|
|
402
|
-
srcDirectory,
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test('typechecks custom runtime module augmentations', async () => {
|
|
407
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
408
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
409
|
-
|
|
410
|
-
await writeTanstackRegisterFile({
|
|
411
|
-
entries: ['main'],
|
|
412
|
-
generatedDirName: 'tanstack',
|
|
413
|
-
runtimeModule: '@my-org/tanstack/runtime',
|
|
414
|
-
srcDirectory,
|
|
415
|
-
canonicalRoutes: {
|
|
416
|
-
'/talks': 'Record<string, never>',
|
|
417
|
-
},
|
|
418
|
-
i18nRuntimeModule: '@my-org/i18n/runtime',
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
await typecheckGeneratedRegistration({
|
|
422
|
-
entries: ['main'],
|
|
423
|
-
generatedDirName: 'tanstack',
|
|
424
|
-
srcDirectory,
|
|
425
|
-
runtimeModule: '@my-org/tanstack/runtime',
|
|
426
|
-
i18nRuntimeModule: '@my-org/i18n/runtime',
|
|
427
|
-
canonicalTypeChecks: [
|
|
428
|
-
"import type { UltramodernCanonicalRoutes } from '@my-org/i18n/runtime';",
|
|
429
|
-
"const talksParams: UltramodernCanonicalRoutes['/talks'] = {};",
|
|
430
|
-
'void talksParams;',
|
|
431
|
-
],
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
test('claims custom routes, injects runtime plugin, and merges route specs', async () => {
|
|
436
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
437
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
438
|
-
const distDirectory = path.join(tempDir, 'dist');
|
|
439
|
-
const entryDir = path.join(srcDirectory, 'main');
|
|
440
|
-
const viewsDir = path.join(entryDir, 'views');
|
|
441
|
-
await mkdir(viewsDir, { recursive: true });
|
|
442
|
-
|
|
443
|
-
const taps: Record<string, any> = {};
|
|
444
|
-
const api = {
|
|
445
|
-
...createAppContextApi({
|
|
446
|
-
srcDirectory,
|
|
447
|
-
distDirectory,
|
|
448
|
-
metaName: 'modern-js',
|
|
449
|
-
serverRoutes: [{ entryName: 'main', urlPath: '/dashboard' }],
|
|
450
|
-
}),
|
|
451
|
-
_internalRuntimePlugins: (tap: any) => {
|
|
452
|
-
taps.internalRuntimePlugins = tap;
|
|
453
|
-
},
|
|
454
|
-
checkEntryPoint: (tap: any) => {
|
|
455
|
-
taps.checkEntryPoint = tap;
|
|
456
|
-
},
|
|
457
|
-
config: (tap: any) => {
|
|
458
|
-
taps.config = tap;
|
|
459
|
-
},
|
|
460
|
-
modifyEntrypoints: (tap: any) => {
|
|
461
|
-
taps.modifyEntrypoints = tap;
|
|
462
|
-
},
|
|
463
|
-
generateEntryCode: (tap: any) => {
|
|
464
|
-
taps.generateEntryCode = tap;
|
|
465
|
-
},
|
|
466
|
-
onFileChanged: (tap: any) => {
|
|
467
|
-
taps.onFileChanged = tap;
|
|
468
|
-
},
|
|
469
|
-
modifyFileSystemRoutes: (tap: any) => {
|
|
470
|
-
taps.modifyFileSystemRoutes = tap;
|
|
471
|
-
},
|
|
472
|
-
onBeforeGenerateRoutes: (tap: any) => {
|
|
473
|
-
taps.onBeforeGenerateRoutes = tap;
|
|
474
|
-
},
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
tanstackRouterPlugin({ routesDir: 'views' }).setup!(api as any);
|
|
478
|
-
|
|
479
|
-
expect(taps.checkEntryPoint({ path: entryDir, entry: false })).toEqual({
|
|
480
|
-
path: entryDir,
|
|
481
|
-
entry: viewsDir,
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
const { entrypoints } = await taps.modifyEntrypoints({
|
|
485
|
-
entrypoints: [
|
|
486
|
-
{
|
|
487
|
-
entryName: 'main',
|
|
488
|
-
entry: entryDir,
|
|
489
|
-
absoluteEntryDir: entryDir,
|
|
490
|
-
isAutoMount: true,
|
|
491
|
-
isMainEntry: true,
|
|
492
|
-
} as Entrypoint,
|
|
493
|
-
],
|
|
494
|
-
});
|
|
495
|
-
const [entrypoint] = entrypoints;
|
|
496
|
-
expect(entrypoint.nestedRoutesEntry).toBe(viewsDir);
|
|
497
|
-
|
|
498
|
-
expect(
|
|
499
|
-
taps.internalRuntimePlugins({ entrypoint, plugins: [] }).plugins,
|
|
500
|
-
).toEqual([
|
|
501
|
-
{
|
|
502
|
-
name: 'tanstackRouter',
|
|
503
|
-
path: '@modern-js/plugin-tanstack/runtime',
|
|
504
|
-
config: { serverBase: ['/dashboard'] },
|
|
505
|
-
},
|
|
506
|
-
]);
|
|
507
|
-
|
|
508
|
-
expect(taps.config()).toMatchObject({
|
|
509
|
-
output: {
|
|
510
|
-
splitRouteChunks: true,
|
|
511
|
-
},
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const specPath = path.join(distDirectory, NESTED_ROUTE_SPEC_FILE);
|
|
515
|
-
await fs.outputJSON(specPath, {
|
|
516
|
-
existing: [{ id: 'keep-me' }],
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
await taps.modifyFileSystemRoutes({
|
|
520
|
-
entrypoint,
|
|
521
|
-
routes: [
|
|
522
|
-
{
|
|
523
|
-
id: 'main-route',
|
|
524
|
-
type: 'nested',
|
|
525
|
-
origin: 'file-system',
|
|
526
|
-
},
|
|
527
|
-
],
|
|
528
|
-
});
|
|
529
|
-
await taps.onBeforeGenerateRoutes({ entrypoint, code: '' });
|
|
530
|
-
|
|
531
|
-
expect(await fs.readJSON(specPath)).toEqual({
|
|
532
|
-
existing: [{ id: 'keep-me' }],
|
|
533
|
-
main: [
|
|
534
|
-
{
|
|
535
|
-
id: 'main-route',
|
|
536
|
-
type: 'nested',
|
|
537
|
-
origin: 'file-system',
|
|
538
|
-
},
|
|
539
|
-
],
|
|
540
|
-
});
|
|
235
|
+
expect(await fs.pathExists(path.join(compileOptions.distDir, 'src'))).toBe(
|
|
236
|
+
false,
|
|
237
|
+
);
|
|
541
238
|
});
|
|
542
239
|
|
|
543
|
-
test('
|
|
240
|
+
test('installs the TanStack router only for entrypoints it owns', () => {
|
|
544
241
|
const taps: Record<string, any> = {};
|
|
545
242
|
const api = {
|
|
546
243
|
...createAppContextApi({
|
|
547
244
|
srcDirectory: '/tmp/app/src',
|
|
548
245
|
metaName: 'modern-js',
|
|
549
|
-
serverRoutes: [
|
|
246
|
+
serverRoutes: [
|
|
247
|
+
{ entryName: 'custom', urlPath: '/' },
|
|
248
|
+
{ entryName: 'home', urlPath: '/' },
|
|
249
|
+
],
|
|
550
250
|
}),
|
|
551
251
|
_internalRuntimePlugins: (tap: any) => {
|
|
552
252
|
taps.internalRuntimePlugins = tap;
|
|
@@ -560,170 +260,70 @@ describe('tanstack router cli plugin', () => {
|
|
|
560
260
|
onBeforeGenerateRoutes: () => {},
|
|
561
261
|
};
|
|
562
262
|
|
|
563
|
-
tanstackRouterPlugin().setup!(api as any);
|
|
263
|
+
tanstackRouterPlugin({ routesDir: 'ts-routes' }).setup!(api as any);
|
|
564
264
|
|
|
565
265
|
// Custom entry without a routes dir (e.g. `createRoutes` in
|
|
566
|
-
// modern.runtime.ts): installing the plugin is the explicit opt-in,
|
|
567
|
-
//
|
|
568
|
-
//
|
|
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.
|
|
569
269
|
const customEntrypoint = {
|
|
570
270
|
entryName: 'custom',
|
|
571
271
|
isAutoMount: true,
|
|
572
272
|
} as Entrypoint;
|
|
273
|
+
const tanstackRouterEntry = {
|
|
274
|
+
name: 'router',
|
|
275
|
+
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
276
|
+
config: { serverBase: ['/'] },
|
|
277
|
+
};
|
|
573
278
|
expect(
|
|
574
279
|
taps.internalRuntimePlugins({ entrypoint: customEntrypoint, plugins: [] })
|
|
575
280
|
.plugins,
|
|
576
|
-
).toEqual([
|
|
577
|
-
{
|
|
578
|
-
name: 'router',
|
|
579
|
-
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
580
|
-
config: { serverBase: ['/'] },
|
|
581
|
-
},
|
|
582
|
-
]);
|
|
281
|
+
).toEqual([tanstackRouterEntry]);
|
|
583
282
|
|
|
584
283
|
// If the built-in router CLI already installed the internal router for
|
|
585
284
|
// this custom entry (explicit `runtime.router` config), only the module
|
|
586
|
-
// path is redirected
|
|
587
|
-
|
|
588
|
-
const existingRouterPlugin = {
|
|
285
|
+
// path is redirected — pushing a second entry installs two routers.
|
|
286
|
+
const builtInPlugin = {
|
|
589
287
|
name: 'router',
|
|
590
288
|
path: '@modern-js/runtime/router/internal',
|
|
591
289
|
config: { serverBase: ['/'] },
|
|
592
290
|
};
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
name: 'router',
|
|
600
|
-
path: '@modern-js/plugin-tanstack/runtime/router',
|
|
601
|
-
config: { serverBase: ['/'] },
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
test('leaves built-in and foreign-owned route entrypoints to their own router', () => {
|
|
606
|
-
const taps: Record<string, any> = {};
|
|
607
|
-
const api = {
|
|
608
|
-
...createAppContextApi({
|
|
609
|
-
srcDirectory: '/tmp/app/src',
|
|
610
|
-
metaName: 'modern-js',
|
|
611
|
-
serverRoutes: [{ entryName: 'home', urlPath: '/' }],
|
|
612
|
-
}),
|
|
613
|
-
_internalRuntimePlugins: (tap: any) => {
|
|
614
|
-
taps.internalRuntimePlugins = tap;
|
|
615
|
-
},
|
|
616
|
-
checkEntryPoint: () => {},
|
|
617
|
-
config: () => {},
|
|
618
|
-
modifyEntrypoints: () => {},
|
|
619
|
-
generateEntryCode: () => {},
|
|
620
|
-
onFileChanged: () => {},
|
|
621
|
-
modifyFileSystemRoutes: () => {},
|
|
622
|
-
onBeforeGenerateRoutes: () => {},
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
tanstackRouterPlugin({ routesDir: 'ts-routes' }).setup!(api as any);
|
|
291
|
+
expect(
|
|
292
|
+
taps.internalRuntimePlugins({
|
|
293
|
+
entrypoint: customEntrypoint,
|
|
294
|
+
plugins: [builtInPlugin],
|
|
295
|
+
}).plugins,
|
|
296
|
+
).toEqual([tanstackRouterEntry]);
|
|
626
297
|
|
|
627
298
|
// A classic react-router file-route entry (src/<entry>/routes) living
|
|
628
299
|
// next to the TanStack entries: its internal router plugin must be left
|
|
629
|
-
// untouched — redirecting it
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
path: '@modern-js/runtime/router/internal',
|
|
643
|
-
config: { serverBase: ['/'] },
|
|
644
|
-
},
|
|
645
|
-
],
|
|
646
|
-
});
|
|
647
|
-
expect(builtInPlugins).toEqual([
|
|
648
|
-
{
|
|
649
|
-
name: 'router',
|
|
650
|
-
path: '@modern-js/runtime/router/internal',
|
|
651
|
-
config: { serverBase: ['/'] },
|
|
652
|
-
},
|
|
653
|
-
]);
|
|
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]);
|
|
654
313
|
|
|
655
314
|
// An entry tagged by another routes-owner plugin: nothing is pushed.
|
|
656
|
-
// The replaced sniffing path in @modern-js/runtime excluded
|
|
657
|
-
// plugin-owned entrypoints for the same reason — pushing a second
|
|
658
|
-
// `router` plugin can install two routers for one entry.
|
|
659
|
-
const foreignEntrypoint = {
|
|
660
|
-
entryName: 'home',
|
|
661
|
-
isAutoMount: true,
|
|
662
|
-
nestedRoutesEntry: '/tmp/app/src/home/acme-routes',
|
|
663
|
-
__modernRoutesDir: 'acme-routes',
|
|
664
|
-
__modernRoutesOwner: '@acme/plugin-file-router',
|
|
665
|
-
} as Entrypoint;
|
|
666
315
|
expect(
|
|
667
316
|
taps.internalRuntimePlugins({
|
|
668
|
-
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,
|
|
669
324
|
plugins: [],
|
|
670
325
|
}).plugins,
|
|
671
326
|
).toEqual([]);
|
|
672
|
-
|
|
673
|
-
// The built-in pages/ convention is foreign too.
|
|
674
|
-
const pagesEntrypoint = {
|
|
675
|
-
entryName: 'home',
|
|
676
|
-
isAutoMount: true,
|
|
677
|
-
pageRoutesEntry: '/tmp/app/src/home/pages',
|
|
678
|
-
} as Entrypoint;
|
|
679
|
-
expect(
|
|
680
|
-
taps.internalRuntimePlugins({ entrypoint: pagesEntrypoint, plugins: [] })
|
|
681
|
-
.plugins,
|
|
682
|
-
).toEqual([]);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
test('source.include covers the package dist and TanStack runtime deps without string surgery', () => {
|
|
686
|
-
const taps: Record<string, any> = {};
|
|
687
|
-
const api = {
|
|
688
|
-
...createAppContextApi({
|
|
689
|
-
srcDirectory: '/tmp/app/src',
|
|
690
|
-
serverRoutes: [],
|
|
691
|
-
}),
|
|
692
|
-
_internalRuntimePlugins: () => {},
|
|
693
|
-
checkEntryPoint: () => {},
|
|
694
|
-
config: (tap: any) => {
|
|
695
|
-
taps.config = tap;
|
|
696
|
-
},
|
|
697
|
-
modifyEntrypoints: () => {},
|
|
698
|
-
generateEntryCode: () => {},
|
|
699
|
-
onFileChanged: () => {},
|
|
700
|
-
modifyFileSystemRoutes: () => {},
|
|
701
|
-
onBeforeGenerateRoutes: () => {},
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
tanstackRouterPlugin().setup!(api as any);
|
|
705
|
-
|
|
706
|
-
const include = taps.config().source.include as Array<RegExp | string>;
|
|
707
|
-
const regexes = include.filter(
|
|
708
|
-
(entry): entry is RegExp => entry instanceof RegExp,
|
|
709
|
-
);
|
|
710
|
-
for (const dep of ['react-router', 'router-core', 'react-store']) {
|
|
711
|
-
const sample = `/repo/node_modules/@tanstack/${dep}/dist/esm/index.js`;
|
|
712
|
-
expect(regexes.some(regex => regex.test(sample))).toBe(true);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const stringEntries = include.filter(
|
|
716
|
-
(entry): entry is string => typeof entry === 'string',
|
|
717
|
-
);
|
|
718
|
-
expect(stringEntries).toHaveLength(1);
|
|
719
|
-
// The include must point at the package root (two levels above the cli
|
|
720
|
-
// build dir) so dist/esm, dist/esm-node and dist/cjs are all covered —
|
|
721
|
-
// the old `.replace('cjs', 'esm')` never matched the bundled dist/esm
|
|
722
|
-
// runtime when the CLI was loaded through the ESM condition.
|
|
723
|
-
expect(stringEntries[0]).toBe(path.resolve(__dirname, '..', '..'));
|
|
724
|
-
expect(stringEntries[0]).not.toMatch(
|
|
725
|
-
/[\\/]dist[\\/]esm(?:-node)?(?:[\\/]|$)/,
|
|
726
|
-
);
|
|
727
327
|
});
|
|
728
328
|
|
|
729
329
|
test('emits the plugin-i18n augmentation only when plugin-i18n is registered', async () => {
|
|
@@ -817,379 +417,4 @@ describe('tanstack router cli plugin', () => {
|
|
|
817
417
|
{ name: '@modern-js/plugin-i18n' },
|
|
818
418
|
]);
|
|
819
419
|
});
|
|
820
|
-
|
|
821
|
-
test('generates plugin-owned TanStack route files through core route generation', async () => {
|
|
822
|
-
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
823
|
-
const srcDirectory = path.join(tempDir, 'src');
|
|
824
|
-
const entryDir = path.join(srcDirectory, 'main');
|
|
825
|
-
const viewsDir = path.join(entryDir, 'views');
|
|
826
|
-
await mkdir(path.join(viewsDir, 'mf'), { recursive: true });
|
|
827
|
-
await fs.outputFile(
|
|
828
|
-
path.join(viewsDir, 'mf', 'page.data.ts'),
|
|
829
|
-
[
|
|
830
|
-
'export const loader = () => ({ count: 0 });',
|
|
831
|
-
'export const action = () => Response.json({ count: 1 });',
|
|
832
|
-
].join('\n'),
|
|
833
|
-
);
|
|
834
|
-
|
|
835
|
-
const entrypoint = {
|
|
836
|
-
entryName: 'main',
|
|
837
|
-
entry: entryDir,
|
|
838
|
-
absoluteEntryDir: entryDir,
|
|
839
|
-
isAutoMount: true,
|
|
840
|
-
isMainEntry: true,
|
|
841
|
-
nestedRoutesEntry: viewsDir,
|
|
842
|
-
__modernRoutesDir: 'views',
|
|
843
|
-
} as Entrypoint;
|
|
844
|
-
runtimeCliMocks.handleGeneratorEntryCode.mockResolvedValue({
|
|
845
|
-
main: [
|
|
846
|
-
{
|
|
847
|
-
type: 'nested',
|
|
848
|
-
id: 'layout',
|
|
849
|
-
isRoot: true,
|
|
850
|
-
children: [
|
|
851
|
-
{
|
|
852
|
-
type: 'nested',
|
|
853
|
-
id: 'mf/page',
|
|
854
|
-
path: 'mf',
|
|
855
|
-
data: '@/_/main/views/mf/page.data',
|
|
856
|
-
action: '@/_/main/views/mf/page.data',
|
|
857
|
-
},
|
|
858
|
-
],
|
|
859
|
-
},
|
|
860
|
-
],
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
const taps: Record<string, any> = {};
|
|
864
|
-
const api = {
|
|
865
|
-
...createAppContextApi({
|
|
866
|
-
srcDirectory,
|
|
867
|
-
internalSrcAlias: '@/_',
|
|
868
|
-
entrypoints: [entrypoint],
|
|
869
|
-
}),
|
|
870
|
-
_internalRuntimePlugins: () => {},
|
|
871
|
-
checkEntryPoint: (tap: any) => {
|
|
872
|
-
taps.checkEntryPoint = tap;
|
|
873
|
-
},
|
|
874
|
-
config: (tap: any) => {
|
|
875
|
-
taps.config = tap;
|
|
876
|
-
},
|
|
877
|
-
modifyEntrypoints: (tap: any) => {
|
|
878
|
-
taps.modifyEntrypoints = tap;
|
|
879
|
-
},
|
|
880
|
-
generateEntryCode: (tap: any) => {
|
|
881
|
-
taps.generateEntryCode = tap;
|
|
882
|
-
},
|
|
883
|
-
onFileChanged: (tap: any) => {
|
|
884
|
-
taps.onFileChanged = tap;
|
|
885
|
-
},
|
|
886
|
-
modifyFileSystemRoutes: (tap: any) => {
|
|
887
|
-
taps.modifyFileSystemRoutes = tap;
|
|
888
|
-
},
|
|
889
|
-
onBeforeGenerateRoutes: (tap: any) => {
|
|
890
|
-
taps.onBeforeGenerateRoutes = tap;
|
|
891
|
-
},
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
tanstackRouterPlugin({
|
|
895
|
-
generatedDirName: 'tanstack-generated',
|
|
896
|
-
routesDir: 'views',
|
|
897
|
-
}).setup!(api as any);
|
|
898
|
-
|
|
899
|
-
await taps.generateEntryCode({ entrypoints: [entrypoint] });
|
|
900
|
-
|
|
901
|
-
expect(runtimeCliMocks.handleGeneratorEntryCode).toHaveBeenCalledWith(
|
|
902
|
-
api,
|
|
903
|
-
[entrypoint],
|
|
904
|
-
{
|
|
905
|
-
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
906
|
-
hydrateRscClientRoutes: true,
|
|
907
|
-
includeRouteServerLoadersInSsrEntry: false,
|
|
908
|
-
isolateRouteDataInRscLayer: true,
|
|
909
|
-
serverRoutesFileName: 'tanstack-routes.server.js',
|
|
910
|
-
},
|
|
911
|
-
);
|
|
912
|
-
|
|
913
|
-
await typecheckGeneratedRegistration({
|
|
914
|
-
entries: ['main'],
|
|
915
|
-
generatedDirName: 'tanstack-generated',
|
|
916
|
-
srcDirectory,
|
|
917
|
-
});
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
test('regenerates plugin-owned TanStack files for scoped file changes', async () => {
|
|
921
|
-
const regenerateEvent = {
|
|
922
|
-
eventType: 'add',
|
|
923
|
-
filename: 'src/main/views/page.tsx',
|
|
924
|
-
};
|
|
925
|
-
const entrypoint = {
|
|
926
|
-
entryName: 'main',
|
|
927
|
-
__modernRoutesDir: 'views',
|
|
928
|
-
} as any as Entrypoint;
|
|
929
|
-
const api = {
|
|
930
|
-
...createAppContextApi({
|
|
931
|
-
srcDirectory: '/tmp/app/src',
|
|
932
|
-
internalSrcAlias: '@/_',
|
|
933
|
-
entrypoints: [entrypoint],
|
|
934
|
-
}),
|
|
935
|
-
_internalRuntimePlugins: () => {},
|
|
936
|
-
checkEntryPoint: () => {},
|
|
937
|
-
config: () => {},
|
|
938
|
-
modifyEntrypoints: () => {},
|
|
939
|
-
generateEntryCode: () => {},
|
|
940
|
-
onFileChanged: (tap: any) => {
|
|
941
|
-
api.onFileChangedTap = tap;
|
|
942
|
-
},
|
|
943
|
-
modifyFileSystemRoutes: () => {},
|
|
944
|
-
onBeforeGenerateRoutes: () => {},
|
|
945
|
-
onFileChangedTap: undefined as any,
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
tanstackRouterPlugin({ routesDir: 'views' }).setup!(api as any);
|
|
949
|
-
|
|
950
|
-
runtimeCliMocks.handleFileChange.mockImplementationOnce(
|
|
951
|
-
async (_api, _event, options) => {
|
|
952
|
-
expect(options.entrypointsKey).toBe('@modern-js/plugin-tanstack');
|
|
953
|
-
expect(options.includeEntry(entrypoint)).toBe(true);
|
|
954
|
-
expect(
|
|
955
|
-
options.includeEntry({
|
|
956
|
-
...entrypoint,
|
|
957
|
-
__modernRoutesDir: 'routes',
|
|
958
|
-
}),
|
|
959
|
-
).toBe(false);
|
|
960
|
-
expect(typeof options.regenerate).toBe('function');
|
|
961
|
-
},
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
await api.onFileChangedTap(regenerateEvent);
|
|
965
|
-
|
|
966
|
-
expect(runtimeCliMocks.handleFileChange).toHaveBeenCalledWith(
|
|
967
|
-
api,
|
|
968
|
-
regenerateEvent,
|
|
969
|
-
expect.objectContaining({
|
|
970
|
-
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
971
|
-
}),
|
|
972
|
-
);
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
test('can opt out of Modern-owned route code splitting', async () => {
|
|
976
|
-
const taps: Record<string, any> = {};
|
|
977
|
-
const api = {
|
|
978
|
-
...createAppContextApi({
|
|
979
|
-
srcDirectory: '/tmp/app/src',
|
|
980
|
-
serverRoutes: [],
|
|
981
|
-
}),
|
|
982
|
-
_internalRuntimePlugins: () => {},
|
|
983
|
-
checkEntryPoint: () => {},
|
|
984
|
-
config: (tap: any) => {
|
|
985
|
-
taps.config = tap;
|
|
986
|
-
},
|
|
987
|
-
modifyEntrypoints: () => {},
|
|
988
|
-
generateEntryCode: () => {},
|
|
989
|
-
onFileChanged: () => {},
|
|
990
|
-
modifyFileSystemRoutes: () => {},
|
|
991
|
-
onBeforeGenerateRoutes: () => {},
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
tanstackRouterPlugin({ routeCodeSplitting: false }).setup!(api as any);
|
|
995
|
-
|
|
996
|
-
expect(taps.config()).toMatchObject({
|
|
997
|
-
output: {
|
|
998
|
-
splitRouteChunks: false,
|
|
999
|
-
},
|
|
1000
|
-
});
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
test('route splitting profile carries only the rsbuild config production consumes', () => {
|
|
1004
|
-
expect(createTanstackRsbuildRouteSplittingProfile({})).toEqual({
|
|
1005
|
-
defaultConfig: {
|
|
1006
|
-
output: {
|
|
1007
|
-
splitRouteChunks: true,
|
|
1008
|
-
},
|
|
1009
|
-
},
|
|
1010
|
-
});
|
|
1011
|
-
expect(
|
|
1012
|
-
createTanstackRsbuildRouteSplittingProfile({ routeCodeSplitting: false }),
|
|
1013
|
-
).toEqual({
|
|
1014
|
-
defaultConfig: {
|
|
1015
|
-
output: {
|
|
1016
|
-
splitRouteChunks: false,
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
});
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
test('preserves user-selected route and builder chunk splitting modes', () => {
|
|
1023
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
1024
|
-
{},
|
|
1025
|
-
).defaultConfig;
|
|
1026
|
-
const chunkSplits = [
|
|
1027
|
-
{ strategy: 'split-by-module' },
|
|
1028
|
-
{ strategy: 'split-by-experience' },
|
|
1029
|
-
{ strategy: 'all-in-one' },
|
|
1030
|
-
{ strategy: 'single-vendor' },
|
|
1031
|
-
{ strategy: 'split-by-size', minSize: 10_000, maxSize: 60_000 },
|
|
1032
|
-
{
|
|
1033
|
-
strategy: 'custom',
|
|
1034
|
-
splitChunks: {
|
|
1035
|
-
chunks: 'all',
|
|
1036
|
-
cacheGroups: {
|
|
1037
|
-
tractors: {
|
|
1038
|
-
name: 'tractors',
|
|
1039
|
-
test: /tractors/u,
|
|
1040
|
-
},
|
|
1041
|
-
},
|
|
1042
|
-
},
|
|
1043
|
-
},
|
|
1044
|
-
];
|
|
1045
|
-
|
|
1046
|
-
for (const chunkSplit of chunkSplits) {
|
|
1047
|
-
expect(
|
|
1048
|
-
mergeConfig([
|
|
1049
|
-
pluginDefaults,
|
|
1050
|
-
{
|
|
1051
|
-
output: {
|
|
1052
|
-
splitRouteChunks: false,
|
|
1053
|
-
},
|
|
1054
|
-
performance: {
|
|
1055
|
-
chunkSplit,
|
|
1056
|
-
},
|
|
1057
|
-
splitChunks: false,
|
|
1058
|
-
},
|
|
1059
|
-
]),
|
|
1060
|
-
).toMatchObject({
|
|
1061
|
-
output: {
|
|
1062
|
-
splitRouteChunks: false,
|
|
1063
|
-
},
|
|
1064
|
-
performance: {
|
|
1065
|
-
chunkSplit,
|
|
1066
|
-
},
|
|
1067
|
-
splitChunks: false,
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
const pageSplitWithManualAsyncChunks = mergeConfig([
|
|
1072
|
-
pluginDefaults,
|
|
1073
|
-
{
|
|
1074
|
-
performance: {
|
|
1075
|
-
chunkSplit: {
|
|
1076
|
-
strategy: 'custom',
|
|
1077
|
-
splitChunks: {
|
|
1078
|
-
chunks: 'async',
|
|
1079
|
-
},
|
|
1080
|
-
},
|
|
1081
|
-
},
|
|
1082
|
-
},
|
|
1083
|
-
]);
|
|
1084
|
-
|
|
1085
|
-
expect(pageSplitWithManualAsyncChunks).toMatchObject({
|
|
1086
|
-
output: {
|
|
1087
|
-
splitRouteChunks: true,
|
|
1088
|
-
},
|
|
1089
|
-
performance: {
|
|
1090
|
-
chunkSplit: {
|
|
1091
|
-
strategy: 'custom',
|
|
1092
|
-
splitChunks: {
|
|
1093
|
-
chunks: 'async',
|
|
1094
|
-
},
|
|
1095
|
-
},
|
|
1096
|
-
},
|
|
1097
|
-
});
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
test('keeps custom cache group details intact', () => {
|
|
1101
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
1102
|
-
{},
|
|
1103
|
-
).defaultConfig;
|
|
1104
|
-
|
|
1105
|
-
const mergedConfig = mergeConfig([
|
|
1106
|
-
pluginDefaults,
|
|
1107
|
-
{
|
|
1108
|
-
performance: {
|
|
1109
|
-
chunkSplit: {
|
|
1110
|
-
strategy: 'custom',
|
|
1111
|
-
splitChunks: {
|
|
1112
|
-
chunks: 'all',
|
|
1113
|
-
cacheGroups: {
|
|
1114
|
-
tractors: {
|
|
1115
|
-
name: 'tractors',
|
|
1116
|
-
test: /tractors/u,
|
|
1117
|
-
},
|
|
1118
|
-
},
|
|
1119
|
-
},
|
|
1120
|
-
},
|
|
1121
|
-
},
|
|
1122
|
-
},
|
|
1123
|
-
]);
|
|
1124
|
-
|
|
1125
|
-
expect(
|
|
1126
|
-
(
|
|
1127
|
-
mergedConfig as {
|
|
1128
|
-
performance?: {
|
|
1129
|
-
chunkSplit?: {
|
|
1130
|
-
splitChunks?: {
|
|
1131
|
-
cacheGroups?: {
|
|
1132
|
-
tractors?: {
|
|
1133
|
-
test?: RegExp;
|
|
1134
|
-
};
|
|
1135
|
-
};
|
|
1136
|
-
};
|
|
1137
|
-
};
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
).performance?.chunkSplit?.splitChunks?.cacheGroups?.tractors?.test,
|
|
1141
|
-
).toEqual(/tractors/u);
|
|
1142
|
-
expect(mergedConfig).toMatchObject({
|
|
1143
|
-
output: {
|
|
1144
|
-
splitRouteChunks: true,
|
|
1145
|
-
},
|
|
1146
|
-
performance: {
|
|
1147
|
-
chunkSplit: {
|
|
1148
|
-
strategy: 'custom',
|
|
1149
|
-
splitChunks: {
|
|
1150
|
-
chunks: 'all',
|
|
1151
|
-
cacheGroups: {
|
|
1152
|
-
tractors: {
|
|
1153
|
-
name: 'tractors',
|
|
1154
|
-
},
|
|
1155
|
-
},
|
|
1156
|
-
},
|
|
1157
|
-
},
|
|
1158
|
-
},
|
|
1159
|
-
});
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
test('plugin opt-out can still combine with manual builder chunking', () => {
|
|
1163
|
-
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile({
|
|
1164
|
-
routeCodeSplitting: false,
|
|
1165
|
-
}).defaultConfig;
|
|
1166
|
-
|
|
1167
|
-
expect(
|
|
1168
|
-
mergeConfig([
|
|
1169
|
-
pluginDefaults,
|
|
1170
|
-
{
|
|
1171
|
-
performance: {
|
|
1172
|
-
chunkSplit: {
|
|
1173
|
-
strategy: 'single-vendor',
|
|
1174
|
-
},
|
|
1175
|
-
},
|
|
1176
|
-
},
|
|
1177
|
-
]),
|
|
1178
|
-
).toMatchObject({
|
|
1179
|
-
output: {
|
|
1180
|
-
splitRouteChunks: false,
|
|
1181
|
-
},
|
|
1182
|
-
performance: {
|
|
1183
|
-
chunkSplit: {
|
|
1184
|
-
strategy: 'single-vendor',
|
|
1185
|
-
},
|
|
1186
|
-
},
|
|
1187
|
-
});
|
|
1188
|
-
});
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
describe('generateTanstackRouteArtifacts export', () => {
|
|
1192
|
-
test('is exported as a function', () => {
|
|
1193
|
-
expect(typeof generateTanstackRouteArtifacts).toBe('function');
|
|
1194
|
-
});
|
|
1195
420
|
});
|