@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121
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 +89 -31
- package/dist/cjs/cli/routeSplitting.js +55 -0
- package/dist/cjs/cli/tanstackTypes.js +172 -170
- 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 +48 -0
- package/dist/cjs/runtime/index.js +79 -35
- 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 +195 -114
- package/dist/cjs/runtime/plugin.node.js +45 -45
- package/dist/cjs/runtime/plugin.worker.js +53 -0
- 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 +74 -207
- 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 +31 -1
- package/dist/cjs/runtime/utils.js +9 -10
- package/dist/cjs/runtime.js +9 -5
- package/dist/esm/cli/index.mjs +75 -27
- package/dist/esm/cli/routeSplitting.mjs +14 -0
- package/dist/esm/cli/tanstackTypes.mjs +158 -160
- package/dist/esm/runtime/hooks.mjs +1 -8
- package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
- package/dist/esm/runtime/index.mjs +5 -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 +191 -114
- package/dist/esm/runtime/plugin.node.mjs +40 -44
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- 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 +59 -193
- 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/types.mjs +7 -0
- package/dist/esm/runtime/utils.mjs +0 -5
- package/dist/esm-node/cli/index.mjs +75 -27
- package/dist/esm-node/cli/routeSplitting.mjs +15 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +158 -160
- package/dist/esm-node/runtime/hooks.mjs +1 -8
- package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
- package/dist/esm-node/runtime/index.mjs +5 -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 +191 -114
- package/dist/esm-node/runtime/plugin.node.mjs +40 -44
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- 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 +59 -193
- 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/types.mjs +7 -0
- package/dist/esm-node/runtime/utils.mjs +0 -5
- package/dist/types/cli/index.d.ts +14 -1
- package/dist/types/cli/routeSplitting.d.ts +20 -0
- package/dist/types/cli/tanstackTypes.d.ts +21 -1
- package/dist/types/runtime/hooks.d.ts +8 -33
- package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
- package/dist/types/runtime/index.d.ts +8 -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 +2 -15
- package/dist/types/runtime/plugin.node.d.ts +2 -15
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- 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 +14 -53
- package/package.json +42 -40
- package/rstest.config.mts +6 -0
- package/src/cli/index.ts +162 -23
- package/src/cli/routeSplitting.ts +43 -0
- package/src/cli/tanstackTypes.ts +331 -187
- package/src/runtime/hooks.ts +10 -27
- package/src/runtime/hydrationBoundary.tsx +12 -0
- package/src/runtime/index.tsx +17 -7
- 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 +72 -85
- package/src/runtime/plugin.tsx +361 -206
- package/src/runtime/plugin.worker.tsx +4 -0
- 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 +163 -354
- 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 +32 -66
- package/src/runtime/utils.tsx +3 -6
- package/tests/router/cli.test.ts +586 -5
- package/tests/router/fastDefaults.test.ts +25 -0
- package/tests/router/hooks.test.ts +26 -0
- package/tests/router/hydrationBoundary.test.tsx +23 -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 +381 -81
- package/tests/router/rsc.test.tsx +70 -0
- package/tests/router/tanstackTypes.test.ts +573 -1
- 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
package/tests/router/cli.test.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { mergeConfig } from '@modern-js/plugin/cli';
|
|
4
5
|
import type { Entrypoint } from '@modern-js/types';
|
|
5
6
|
import { fs, NESTED_ROUTE_SPEC_FILE } from '@modern-js/utils';
|
|
6
7
|
import {
|
|
8
|
+
createTanstackRsbuildRouteSplittingProfile,
|
|
7
9
|
tanstackRouterPlugin,
|
|
8
10
|
writeTanstackRegisterFile,
|
|
9
11
|
writeTanstackRouterTypesForEntries,
|
|
@@ -16,16 +18,29 @@ const runtimeCliMocks = {
|
|
|
16
18
|
|
|
17
19
|
rstest.mock('@modern-js/runtime/cli', () => {
|
|
18
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
|
+
};
|
|
19
26
|
|
|
20
27
|
return {
|
|
21
28
|
__esModule: true,
|
|
29
|
+
getPathWithoutExt: actualCli.getPathWithoutExt,
|
|
30
|
+
makeLegalIdentifier: actualCli.makeLegalIdentifier,
|
|
22
31
|
getEntrypointRoutesDir: (entrypoint: any) =>
|
|
23
32
|
entrypoint[routesDirMetaKey] ||
|
|
24
33
|
(entrypoint.nestedRoutesEntry
|
|
25
34
|
? path.basename(entrypoint.nestedRoutesEntry)
|
|
26
35
|
: null),
|
|
27
|
-
|
|
28
|
-
|
|
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),
|
|
29
44
|
handleModifyEntrypoints: async (
|
|
30
45
|
entrypoints: Entrypoint[],
|
|
31
46
|
routesDir = 'routes',
|
|
@@ -120,6 +135,82 @@ describe('tanstack router cli plugin', () => {
|
|
|
120
135
|
);
|
|
121
136
|
});
|
|
122
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
|
+
|
|
123
214
|
test('claims custom routes, injects runtime plugin, and merges route specs', async () => {
|
|
124
215
|
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
125
216
|
const srcDirectory = path.join(tempDir, 'src');
|
|
@@ -193,6 +284,12 @@ describe('tanstack router cli plugin', () => {
|
|
|
193
284
|
},
|
|
194
285
|
]);
|
|
195
286
|
|
|
287
|
+
expect(taps.config()).toMatchObject({
|
|
288
|
+
output: {
|
|
289
|
+
splitRouteChunks: true,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
196
293
|
const specPath = path.join(distDirectory, NESTED_ROUTE_SPEC_FILE);
|
|
197
294
|
await fs.outputJSON(specPath, {
|
|
198
295
|
existing: [{ id: 'keep-me' }],
|
|
@@ -222,6 +319,278 @@ describe('tanstack router cli plugin', () => {
|
|
|
222
319
|
});
|
|
223
320
|
});
|
|
224
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
|
+
|
|
225
594
|
test('generates plugin-owned TanStack route files through core route generation', async () => {
|
|
226
595
|
tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
|
|
227
596
|
const srcDirectory = path.join(tempDir, 'src');
|
|
@@ -307,9 +676,6 @@ describe('tanstack router cli plugin', () => {
|
|
|
307
676
|
[entrypoint],
|
|
308
677
|
{
|
|
309
678
|
entrypointsKey: '@modern-js/plugin-tanstack',
|
|
310
|
-
generateCodeOptions: {
|
|
311
|
-
enableTanstackTypes: false,
|
|
312
|
-
},
|
|
313
679
|
},
|
|
314
680
|
);
|
|
315
681
|
|
|
@@ -383,4 +749,219 @@ describe('tanstack router cli plugin', () => {
|
|
|
383
749
|
}),
|
|
384
750
|
);
|
|
385
751
|
});
|
|
752
|
+
|
|
753
|
+
test('can opt out of Modern-owned route code splitting', async () => {
|
|
754
|
+
const taps: Record<string, any> = {};
|
|
755
|
+
const api = {
|
|
756
|
+
getAppContext: () => ({
|
|
757
|
+
srcDirectory: '/tmp/app/src',
|
|
758
|
+
serverRoutes: [],
|
|
759
|
+
}),
|
|
760
|
+
_internalRuntimePlugins: () => {},
|
|
761
|
+
checkEntryPoint: () => {},
|
|
762
|
+
config: (tap: any) => {
|
|
763
|
+
taps.config = tap;
|
|
764
|
+
},
|
|
765
|
+
modifyEntrypoints: () => {},
|
|
766
|
+
generateEntryCode: () => {},
|
|
767
|
+
onFileChanged: () => {},
|
|
768
|
+
modifyFileSystemRoutes: () => {},
|
|
769
|
+
onBeforeGenerateRoutes: () => {},
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
tanstackRouterPlugin({ routeCodeSplitting: false }).setup!(api as any);
|
|
773
|
+
|
|
774
|
+
expect(taps.config()).toMatchObject({
|
|
775
|
+
output: {
|
|
776
|
+
splitRouteChunks: false,
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
test('route splitting profile carries only the rsbuild config production consumes', () => {
|
|
782
|
+
expect(createTanstackRsbuildRouteSplittingProfile({})).toEqual({
|
|
783
|
+
defaultConfig: {
|
|
784
|
+
output: {
|
|
785
|
+
splitRouteChunks: true,
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
expect(
|
|
790
|
+
createTanstackRsbuildRouteSplittingProfile({ routeCodeSplitting: false }),
|
|
791
|
+
).toEqual({
|
|
792
|
+
defaultConfig: {
|
|
793
|
+
output: {
|
|
794
|
+
splitRouteChunks: false,
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
test('preserves user-selected route and builder chunk splitting modes', () => {
|
|
801
|
+
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
802
|
+
{},
|
|
803
|
+
).defaultConfig;
|
|
804
|
+
const chunkSplits = [
|
|
805
|
+
{ strategy: 'split-by-module' },
|
|
806
|
+
{ strategy: 'split-by-experience' },
|
|
807
|
+
{ strategy: 'all-in-one' },
|
|
808
|
+
{ strategy: 'single-vendor' },
|
|
809
|
+
{ strategy: 'split-by-size', minSize: 10_000, maxSize: 60_000 },
|
|
810
|
+
{
|
|
811
|
+
strategy: 'custom',
|
|
812
|
+
splitChunks: {
|
|
813
|
+
chunks: 'all',
|
|
814
|
+
cacheGroups: {
|
|
815
|
+
tractors: {
|
|
816
|
+
name: 'tractors',
|
|
817
|
+
test: /tractors/u,
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
for (const chunkSplit of chunkSplits) {
|
|
825
|
+
expect(
|
|
826
|
+
mergeConfig([
|
|
827
|
+
pluginDefaults,
|
|
828
|
+
{
|
|
829
|
+
output: {
|
|
830
|
+
splitRouteChunks: false,
|
|
831
|
+
},
|
|
832
|
+
performance: {
|
|
833
|
+
chunkSplit,
|
|
834
|
+
},
|
|
835
|
+
splitChunks: false,
|
|
836
|
+
},
|
|
837
|
+
]),
|
|
838
|
+
).toMatchObject({
|
|
839
|
+
output: {
|
|
840
|
+
splitRouteChunks: false,
|
|
841
|
+
},
|
|
842
|
+
performance: {
|
|
843
|
+
chunkSplit,
|
|
844
|
+
},
|
|
845
|
+
splitChunks: false,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const pageSplitWithManualAsyncChunks = mergeConfig([
|
|
850
|
+
pluginDefaults,
|
|
851
|
+
{
|
|
852
|
+
performance: {
|
|
853
|
+
chunkSplit: {
|
|
854
|
+
strategy: 'custom',
|
|
855
|
+
splitChunks: {
|
|
856
|
+
chunks: 'async',
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
]);
|
|
862
|
+
|
|
863
|
+
expect(pageSplitWithManualAsyncChunks).toMatchObject({
|
|
864
|
+
output: {
|
|
865
|
+
splitRouteChunks: true,
|
|
866
|
+
},
|
|
867
|
+
performance: {
|
|
868
|
+
chunkSplit: {
|
|
869
|
+
strategy: 'custom',
|
|
870
|
+
splitChunks: {
|
|
871
|
+
chunks: 'async',
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test('keeps custom cache group details intact', () => {
|
|
879
|
+
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
|
|
880
|
+
{},
|
|
881
|
+
).defaultConfig;
|
|
882
|
+
|
|
883
|
+
const mergedConfig = mergeConfig([
|
|
884
|
+
pluginDefaults,
|
|
885
|
+
{
|
|
886
|
+
performance: {
|
|
887
|
+
chunkSplit: {
|
|
888
|
+
strategy: 'custom',
|
|
889
|
+
splitChunks: {
|
|
890
|
+
chunks: 'all',
|
|
891
|
+
cacheGroups: {
|
|
892
|
+
tractors: {
|
|
893
|
+
name: 'tractors',
|
|
894
|
+
test: /tractors/u,
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
]);
|
|
902
|
+
|
|
903
|
+
expect(
|
|
904
|
+
(
|
|
905
|
+
mergedConfig as {
|
|
906
|
+
performance?: {
|
|
907
|
+
chunkSplit?: {
|
|
908
|
+
splitChunks?: {
|
|
909
|
+
cacheGroups?: {
|
|
910
|
+
tractors?: {
|
|
911
|
+
test?: RegExp;
|
|
912
|
+
};
|
|
913
|
+
};
|
|
914
|
+
};
|
|
915
|
+
};
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
).performance?.chunkSplit?.splitChunks?.cacheGroups?.tractors?.test,
|
|
919
|
+
).toEqual(/tractors/u);
|
|
920
|
+
expect(mergedConfig).toMatchObject({
|
|
921
|
+
output: {
|
|
922
|
+
splitRouteChunks: true,
|
|
923
|
+
},
|
|
924
|
+
performance: {
|
|
925
|
+
chunkSplit: {
|
|
926
|
+
strategy: 'custom',
|
|
927
|
+
splitChunks: {
|
|
928
|
+
chunks: 'all',
|
|
929
|
+
cacheGroups: {
|
|
930
|
+
tractors: {
|
|
931
|
+
name: 'tractors',
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
test('plugin opt-out can still combine with manual builder chunking', () => {
|
|
941
|
+
const pluginDefaults = createTanstackRsbuildRouteSplittingProfile({
|
|
942
|
+
routeCodeSplitting: false,
|
|
943
|
+
}).defaultConfig;
|
|
944
|
+
|
|
945
|
+
expect(
|
|
946
|
+
mergeConfig([
|
|
947
|
+
pluginDefaults,
|
|
948
|
+
{
|
|
949
|
+
performance: {
|
|
950
|
+
chunkSplit: {
|
|
951
|
+
strategy: 'single-vendor',
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
]),
|
|
956
|
+
).toMatchObject({
|
|
957
|
+
output: {
|
|
958
|
+
splitRouteChunks: false,
|
|
959
|
+
},
|
|
960
|
+
performance: {
|
|
961
|
+
chunkSplit: {
|
|
962
|
+
strategy: 'single-vendor',
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
});
|
|
966
|
+
});
|
|
386
967
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getModernTanstackRouterFastDefaults,
|
|
3
|
+
modernTanstackRouterFastDefaults,
|
|
4
|
+
} from '../../src/runtime/types';
|
|
5
|
+
|
|
6
|
+
describe('tanstack router fast defaults', () => {
|
|
7
|
+
test('enables structural sharing by default', () => {
|
|
8
|
+
expect(modernTanstackRouterFastDefaults).toEqual({
|
|
9
|
+
defaultStructuralSharing: true,
|
|
10
|
+
});
|
|
11
|
+
expect(getModernTanstackRouterFastDefaults()).toEqual({
|
|
12
|
+
defaultStructuralSharing: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('allows explicit structural sharing override', () => {
|
|
17
|
+
expect(
|
|
18
|
+
getModernTanstackRouterFastDefaults({
|
|
19
|
+
defaultStructuralSharing: false,
|
|
20
|
+
}),
|
|
21
|
+
).toEqual({
|
|
22
|
+
defaultStructuralSharing: false,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -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
|
+
});
|