@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.120 → 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.
Files changed (94) hide show
  1. package/dist/cjs/cli/index.js +47 -27
  2. package/dist/cjs/cli/routeSplitting.js +0 -32
  3. package/dist/cjs/cli/tanstackTypes.js +34 -199
  4. package/dist/cjs/runtime/hooks.js +11 -14
  5. package/dist/cjs/runtime/index.js +107 -319
  6. package/dist/cjs/runtime/lifecycle.js +12 -86
  7. package/dist/cjs/runtime/loaderBridge.js +173 -0
  8. package/dist/cjs/runtime/plugin.js +6 -30
  9. package/dist/cjs/runtime/plugin.node.js +7 -29
  10. package/dist/cjs/runtime/pluginCore.js +55 -0
  11. package/dist/cjs/runtime/register.js +56 -0
  12. package/dist/cjs/runtime/routeTree.js +10 -207
  13. package/dist/cjs/runtime/{DefaultNotFound.js → router.js} +5 -15
  14. package/dist/cjs/runtime/rsc/payloadRouter.js +35 -1
  15. package/dist/cjs/runtime/state.js +45 -0
  16. package/dist/cjs/runtime/utils.js +0 -5
  17. package/dist/esm/cli/index.mjs +52 -26
  18. package/dist/esm/cli/routeSplitting.mjs +1 -30
  19. package/dist/esm/cli/tanstackTypes.mjs +32 -194
  20. package/dist/esm/runtime/hooks.mjs +1 -8
  21. package/dist/esm/runtime/index.mjs +4 -2
  22. package/dist/esm/runtime/lifecycle.mjs +1 -82
  23. package/dist/esm/runtime/loaderBridge.mjs +114 -0
  24. package/dist/esm/runtime/plugin.mjs +8 -32
  25. package/dist/esm/runtime/plugin.node.mjs +10 -32
  26. package/dist/esm/runtime/pluginCore.mjs +14 -0
  27. package/dist/esm/runtime/register.mjs +18 -0
  28. package/dist/esm/runtime/routeTree.mjs +4 -198
  29. package/dist/esm/runtime/router.mjs +2 -0
  30. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  31. package/dist/esm/runtime/state.mjs +7 -0
  32. package/dist/esm/runtime/utils.mjs +0 -5
  33. package/dist/esm-node/cli/index.mjs +52 -26
  34. package/dist/esm-node/cli/routeSplitting.mjs +1 -30
  35. package/dist/esm-node/cli/tanstackTypes.mjs +32 -194
  36. package/dist/esm-node/runtime/hooks.mjs +1 -8
  37. package/dist/esm-node/runtime/index.mjs +4 -2
  38. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  39. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  40. package/dist/esm-node/runtime/plugin.mjs +8 -32
  41. package/dist/esm-node/runtime/plugin.node.mjs +10 -32
  42. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  43. package/dist/esm-node/runtime/register.mjs +19 -0
  44. package/dist/esm-node/runtime/routeTree.mjs +4 -198
  45. package/dist/esm-node/runtime/router.mjs +3 -0
  46. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  47. package/dist/esm-node/runtime/state.mjs +8 -0
  48. package/dist/esm-node/runtime/utils.mjs +0 -5
  49. package/dist/types/cli/index.d.ts +9 -2
  50. package/dist/types/cli/routeSplitting.d.ts +6 -15
  51. package/dist/types/cli/tanstackTypes.d.ts +13 -2
  52. package/dist/types/runtime/hooks.d.ts +8 -18
  53. package/dist/types/runtime/index.d.ts +6 -4
  54. package/dist/types/runtime/lifecycle.d.ts +7 -22
  55. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  56. package/dist/types/runtime/plugin.d.ts +1 -14
  57. package/dist/types/runtime/plugin.node.d.ts +1 -14
  58. package/dist/types/runtime/pluginCore.d.ts +21 -0
  59. package/dist/types/runtime/register.d.ts +9 -0
  60. package/dist/types/runtime/routeTree.d.ts +0 -2
  61. package/dist/types/runtime/router.d.ts +14 -0
  62. package/dist/types/runtime/state.d.ts +16 -0
  63. package/dist/types/runtime/types.d.ts +7 -53
  64. package/package.json +30 -28
  65. package/rstest.config.mts +6 -0
  66. package/src/cli/index.ts +111 -29
  67. package/src/cli/routeSplitting.ts +6 -44
  68. package/src/cli/tanstackTypes.ts +78 -214
  69. package/src/runtime/hooks.ts +10 -27
  70. package/src/runtime/index.tsx +12 -107
  71. package/src/runtime/lifecycle.ts +16 -151
  72. package/src/runtime/loaderBridge.ts +257 -0
  73. package/src/runtime/plugin.node.tsx +14 -77
  74. package/src/runtime/plugin.tsx +12 -72
  75. package/src/runtime/pluginCore.ts +48 -0
  76. package/src/runtime/register.ts +58 -0
  77. package/src/runtime/routeTree.ts +8 -370
  78. package/src/runtime/router.ts +15 -0
  79. package/src/runtime/rsc/payloadRouter.ts +45 -2
  80. package/src/runtime/state.ts +29 -0
  81. package/src/runtime/types.ts +20 -67
  82. package/src/runtime/utils.tsx +3 -6
  83. package/tests/router/cli.test.ts +297 -31
  84. package/tests/router/hooks.test.ts +26 -0
  85. package/tests/router/loaderBridge.test.ts +211 -0
  86. package/tests/router/packageSurface.test.ts +24 -0
  87. package/tests/router/register.test.ts +46 -0
  88. package/tests/router/routeTree.test.ts +65 -180
  89. package/tests/router/rsc.test.tsx +70 -0
  90. package/tests/router/tanstackTypes.test.ts +164 -6
  91. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  92. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  93. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  94. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -62,15 +62,109 @@ describe('tanstack router type generation', () => {
62
62
  );
63
63
  expect(routerGenTs).toContain('modernRouteLoader: loader_0');
64
64
  expect(routerGenTs).toContain('modernRouteAction: action_0');
65
- expect(routerGenTs).toContain('modernRouteId?: string;');
66
- expect(routerGenTs).not.toContain(
67
- 'return Object.keys(staticData).length > 0 ? staticData : undefined;',
68
- );
69
65
  expect(routerGenTs).toContain(
70
66
  "} from '@modern-js/plugin-tanstack/runtime';",
71
67
  );
72
68
  expect(routerGenTs).toContain('modernTanstackRouterFastDefaults,');
73
69
  expect(routerGenTs).toContain('...modernTanstackRouterFastDefaults,');
70
+
71
+ // The loader-bridge helpers are imported from the package runtime instead
72
+ // of being inlined into every generated file (bugfixes ship via package
73
+ // update, and the broken inline absolute-redirect handler is gone).
74
+ expect(routerGenTs).toContain('createRouteStaticData,');
75
+ expect(routerGenTs).toContain('modernLoaderToTanstack,');
76
+ expect(routerGenTs).toContain('type ModernRouterContext,');
77
+ expect(routerGenTs).not.toContain('function modernLoaderToTanstack');
78
+ expect(routerGenTs).not.toContain('function createRouteStaticData');
79
+ expect(routerGenTs).not.toContain('function throwTanstackRedirect');
80
+ });
81
+
82
+ test('emits resolvable relative component imports for routes carrying _component', async () => {
83
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
84
+ const srcDirectory = path.join(tempDir, 'src');
85
+ for (const componentFile of [
86
+ 'routes/layout.tsx',
87
+ 'routes/page.tsx',
88
+ 'routes/about/page.tsx',
89
+ ]) {
90
+ const componentPath = path.join(srcDirectory, componentFile);
91
+ await mkdir(path.dirname(componentPath), { recursive: true });
92
+ await writeFile(componentPath, 'export default () => null;');
93
+ }
94
+
95
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
96
+ appContext: {
97
+ srcDirectory,
98
+ internalSrcAlias: '@/_',
99
+ } as any,
100
+ entryName: 'index',
101
+ routes: [
102
+ {
103
+ type: 'nested',
104
+ id: 'layout',
105
+ isRoot: true,
106
+ _component: '@/_/routes/layout',
107
+ children: [
108
+ {
109
+ type: 'nested',
110
+ id: 'page',
111
+ index: true,
112
+ _component: '@/_/routes/page',
113
+ },
114
+ {
115
+ type: 'nested',
116
+ id: 'about/page',
117
+ path: 'about',
118
+ _component: '@/_/routes/about/page',
119
+ },
120
+ {
121
+ // Shares the page component module: the import must be reused.
122
+ type: 'nested',
123
+ id: 'about-alias/page',
124
+ path: 'about-alias',
125
+ _component: '@/_/routes/about/page',
126
+ },
127
+ {
128
+ // No _component: no component option may be emitted.
129
+ type: 'nested',
130
+ id: 'data-only/page',
131
+ path: 'data-only',
132
+ },
133
+ ],
134
+ },
135
+ ] as any,
136
+ });
137
+
138
+ // Children are emitted first, the root route component import last.
139
+ // Imports are relative (resolved like loader modules) — the raw
140
+ // `@/_` internal alias is not mapped by app tsconfigs.
141
+ expect(routerGenTs).toContain(
142
+ 'import component_0 from "../../routes/page";',
143
+ );
144
+ expect(routerGenTs).toContain(
145
+ 'import component_1 from "../../routes/about/page";',
146
+ );
147
+ expect(routerGenTs).toContain(
148
+ 'import component_2 from "../../routes/layout";',
149
+ );
150
+ expect(routerGenTs).not.toContain('@/_/routes');
151
+ // The shared module is imported exactly once.
152
+ expect(routerGenTs).not.toContain('component_3');
153
+ expect(
154
+ routerGenTs.match(/from "\.\.\/\.\.\/routes\/about\/page";/g),
155
+ ).toHaveLength(1);
156
+
157
+ expect(routerGenTs).toContain('component: component_0,');
158
+ // The shared component import is referenced by both aliased routes.
159
+ expect(routerGenTs.match(/component: component_1,/g)).toHaveLength(2);
160
+ // The root route gets its component option.
161
+ expect(routerGenTs).toContain('component: component_2,');
162
+
163
+ const dataOnlyRoute = routerGenTs
164
+ .split('const ')
165
+ .find(block => block.includes('path: "data-only",'));
166
+ expect(dataOnlyRoute).toBeDefined();
167
+ expect(dataOnlyRoute).not.toContain('component:');
74
168
  });
75
169
 
76
170
  test('typechecks generated TanStack search contracts', async () => {
@@ -132,13 +226,17 @@ describe('tanstack router type generation', () => {
132
226
  ' options: TOptions;',
133
227
  ' addChildren<TChildren extends readonly unknown[]>(children: TChildren): Route<TOptions> & { children: TChildren };',
134
228
  ' };',
229
+ ' export type ModernRouterContext = {',
230
+ ' request?: Request;',
231
+ ' requestContext?: unknown;',
232
+ ' };',
135
233
  ' export function createMemoryHistory(options: unknown): unknown;',
136
234
  ' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
137
235
  ' export function createRootRouteWithContext<TContext>(): <TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
138
236
  ' export function createRoute<TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
139
237
  ' export function createRouter<TOptions extends Record<string, unknown>>(options: TOptions): TOptions;',
140
- ' export function notFound(): never;',
141
- ' export function redirect(options: unknown): never;',
238
+ ' export function createRouteStaticData(opts: { modernRouteId?: string; modernRouteAction?: unknown; modernRouteLoader?: unknown }): Record<string, unknown>;',
239
+ ' export function modernLoaderToTanstack<TLoader extends (args: never) => unknown>(opts: { hasSplat: boolean }, modernLoader: TLoader): (ctx: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
142
240
  '}',
143
241
  ].join('\n'),
144
242
  );
@@ -273,6 +371,66 @@ describe('collectCanonicalRoutesForEntry', () => {
273
371
  expect(result).toBeNull();
274
372
  });
275
373
 
374
+ test('ignores a leading :lang param when the locale-param heuristic is disabled (no plugin-i18n)', () => {
375
+ const routes = [
376
+ {
377
+ type: 'nested',
378
+ id: 'layout',
379
+ isRoot: true,
380
+ children: [
381
+ {
382
+ type: 'nested',
383
+ id: '(lang)/layout',
384
+ path: ':lang',
385
+ children: [
386
+ {
387
+ type: 'nested',
388
+ id: '(lang)/about/page',
389
+ path: 'about',
390
+ },
391
+ ],
392
+ },
393
+ ],
394
+ },
395
+ ] as any;
396
+
397
+ // Without plugin-i18n, a hand-rolled `/:lang/` param must NOT trigger the
398
+ // i18n surface (the emitted module augmentation would break typechecking).
399
+ expect(
400
+ collectCanonicalRoutesForEntry(routes, { localeParamHeuristic: false }),
401
+ ).toBeNull();
402
+ // With plugin-i18n installed the heuristic strips the locale prefix.
403
+ expect(
404
+ collectCanonicalRoutesForEntry(routes, { localeParamHeuristic: true }),
405
+ ).toEqual({
406
+ '/about': 'Record<string, never>',
407
+ });
408
+ });
409
+
410
+ test('still honors modernCanonicalPath metadata when the heuristic is disabled', () => {
411
+ const result = collectCanonicalRoutesForEntry(
412
+ [
413
+ {
414
+ type: 'nested',
415
+ id: 'layout',
416
+ isRoot: true,
417
+ children: [
418
+ {
419
+ type: 'nested',
420
+ id: '(lang)/products/(slug)/page',
421
+ path: 'products/:slug',
422
+ modernCanonicalPath: '/products/:slug',
423
+ },
424
+ ],
425
+ },
426
+ ] as any,
427
+ { localeParamHeuristic: false },
428
+ );
429
+
430
+ expect(result).not.toBeNull();
431
+ expect(result!['/products/$slug']).toBe('{ "slug": string }');
432
+ });
433
+
276
434
  test('strips leading :lang param and maps index under :lang to "/"', () => {
277
435
  const result = collectCanonicalRoutesForEntry([
278
436
  {
@@ -1,13 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import "react";
3
- const DefaultNotFound = ()=>/*#__PURE__*/ jsx("div", {
4
- style: {
5
- margin: '150px auto',
6
- textAlign: 'center',
7
- display: 'flex',
8
- alignItems: 'center',
9
- justifyContent: 'center'
10
- },
11
- children: "404"
12
- });
13
- export { DefaultNotFound };
@@ -1,14 +0,0 @@
1
- import "node:module";
2
- import { jsx } from "react/jsx-runtime";
3
- import "react";
4
- const DefaultNotFound = ()=>/*#__PURE__*/ jsx("div", {
5
- style: {
6
- margin: '150px auto',
7
- textAlign: 'center',
8
- display: 'flex',
9
- alignItems: 'center',
10
- justifyContent: 'center'
11
- },
12
- children: "404"
13
- });
14
- export { DefaultNotFound };
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const DefaultNotFound: () => React.JSX.Element;
@@ -1,15 +0,0 @@
1
- import React from 'react';
2
-
3
- export const DefaultNotFound = () => (
4
- <div
5
- style={{
6
- margin: '150px auto',
7
- textAlign: 'center',
8
- display: 'flex',
9
- alignItems: 'center',
10
- justifyContent: 'center',
11
- }}
12
- >
13
- 404
14
- </div>
15
- );