@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.98 → 3.4.0-ultramodern.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/cjs/cli/index.js +80 -34
  2. package/dist/cjs/cli/routeSplitting.js +13 -41
  3. package/dist/cjs/cli/tanstackTypes.js +114 -200
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  6. package/dist/cjs/runtime/dataMutation.js +9 -5
  7. package/dist/cjs/runtime/hooks.js +20 -19
  8. package/dist/cjs/runtime/hydrationBoundary.js +12 -7
  9. package/dist/cjs/runtime/index.js +66 -54
  10. package/dist/cjs/runtime/lifecycle.js +21 -91
  11. package/dist/cjs/runtime/loaderBridge.js +173 -0
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +190 -110
  14. package/dist/cjs/runtime/plugin.node.js +25 -40
  15. package/dist/cjs/runtime/plugin.worker.js +9 -5
  16. package/dist/cjs/runtime/pluginCore.js +55 -0
  17. package/dist/cjs/runtime/prefetchLink.js +10 -6
  18. package/dist/cjs/runtime/register.js +56 -0
  19. package/dist/cjs/runtime/routeTree.js +30 -212
  20. package/dist/cjs/runtime/router.js +41 -0
  21. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  22. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  23. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  24. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  25. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  26. package/dist/cjs/runtime/rsc/client.js +9 -5
  27. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  28. package/dist/cjs/runtime/rsc/index.js +9 -5
  29. package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
  30. package/dist/cjs/runtime/rsc/server.js +9 -5
  31. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  32. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  33. package/dist/cjs/runtime/state.js +45 -0
  34. package/dist/cjs/runtime/types.js +13 -9
  35. package/dist/cjs/runtime/utils.js +9 -11
  36. package/dist/cjs/runtime.js +9 -5
  37. package/dist/esm/cli/index.mjs +73 -28
  38. package/dist/esm/cli/routeSplitting.mjs +1 -30
  39. package/dist/esm/cli/tanstackTypes.mjs +100 -190
  40. package/dist/esm/runtime/hooks.mjs +1 -8
  41. package/dist/esm/runtime/hydrationBoundary.mjs +3 -2
  42. package/dist/esm/runtime/index.mjs +4 -2
  43. package/dist/esm/runtime/lifecycle.mjs +1 -82
  44. package/dist/esm/runtime/loaderBridge.mjs +114 -0
  45. package/dist/esm/runtime/outlet.mjs +17 -0
  46. package/dist/esm/runtime/plugin.mjs +183 -107
  47. package/dist/esm/runtime/plugin.node.mjs +19 -38
  48. package/dist/esm/runtime/pluginCore.mjs +14 -0
  49. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  50. package/dist/esm/runtime/register.mjs +18 -0
  51. package/dist/esm/runtime/routeTree.mjs +15 -198
  52. package/dist/esm/runtime/router.mjs +2 -0
  53. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  54. package/dist/esm/runtime/state.mjs +7 -0
  55. package/dist/esm/runtime/utils.mjs +0 -6
  56. package/dist/esm-node/cli/index.mjs +73 -28
  57. package/dist/esm-node/cli/routeSplitting.mjs +1 -30
  58. package/dist/esm-node/cli/tanstackTypes.mjs +100 -190
  59. package/dist/esm-node/runtime/hooks.mjs +1 -8
  60. package/dist/esm-node/runtime/hydrationBoundary.mjs +3 -2
  61. package/dist/esm-node/runtime/index.mjs +4 -2
  62. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  63. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  64. package/dist/esm-node/runtime/outlet.mjs +18 -0
  65. package/dist/esm-node/runtime/plugin.mjs +183 -107
  66. package/dist/esm-node/runtime/plugin.node.mjs +19 -38
  67. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  68. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  69. package/dist/esm-node/runtime/register.mjs +19 -0
  70. package/dist/esm-node/runtime/routeTree.mjs +15 -198
  71. package/dist/esm-node/runtime/router.mjs +3 -0
  72. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  73. package/dist/esm-node/runtime/state.mjs +8 -0
  74. package/dist/esm-node/runtime/utils.mjs +0 -6
  75. package/dist/types/cli/index.d.ts +11 -2
  76. package/dist/types/cli/routeSplitting.d.ts +6 -15
  77. package/dist/types/cli/tanstackTypes.d.ts +21 -1
  78. package/dist/types/runtime/hooks.d.ts +8 -33
  79. package/dist/types/runtime/index.d.ts +6 -3
  80. package/dist/types/runtime/lifecycle.d.ts +7 -22
  81. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  82. package/dist/types/runtime/outlet.d.ts +2 -0
  83. package/dist/types/runtime/plugin.d.ts +1 -14
  84. package/dist/types/runtime/plugin.node.d.ts +1 -14
  85. package/dist/types/runtime/pluginCore.d.ts +21 -0
  86. package/dist/types/runtime/register.d.ts +9 -0
  87. package/dist/types/runtime/routeTree.d.ts +0 -2
  88. package/dist/types/runtime/router.d.ts +14 -0
  89. package/dist/types/runtime/state.d.ts +16 -0
  90. package/dist/types/runtime/types.d.ts +7 -53
  91. package/package.json +40 -38
  92. package/rstest.config.mts +6 -0
  93. package/src/cli/index.ts +146 -24
  94. package/src/cli/routeSplitting.ts +6 -44
  95. package/src/cli/tanstackTypes.ts +203 -208
  96. package/src/runtime/hooks.ts +10 -27
  97. package/src/runtime/hydrationBoundary.tsx +5 -5
  98. package/src/runtime/index.tsx +12 -14
  99. package/src/runtime/lifecycle.ts +16 -151
  100. package/src/runtime/loaderBridge.ts +257 -0
  101. package/src/runtime/outlet.tsx +48 -0
  102. package/src/runtime/plugin.node.tsx +28 -83
  103. package/src/runtime/plugin.tsx +350 -202
  104. package/src/runtime/pluginCore.ts +48 -0
  105. package/src/runtime/prefetchLink.tsx +1 -1
  106. package/src/runtime/register.ts +58 -0
  107. package/src/runtime/routeTree.ts +41 -365
  108. package/src/runtime/router.ts +15 -0
  109. package/src/runtime/rsc/payloadRouter.ts +45 -2
  110. package/src/runtime/ssr-shim.d.ts +1 -3
  111. package/src/runtime/state.ts +29 -0
  112. package/src/runtime/types.ts +20 -67
  113. package/src/runtime/utils.tsx +3 -7
  114. package/tests/router/cli.test.ts +373 -31
  115. package/tests/router/hooks.test.ts +26 -0
  116. package/tests/router/loaderBridge.test.ts +211 -0
  117. package/tests/router/packageSurface.test.ts +24 -0
  118. package/tests/router/prefetchLink.test.tsx +43 -7
  119. package/tests/router/register.test.ts +46 -0
  120. package/tests/router/routeTree.test.ts +176 -139
  121. package/tests/router/rsc.test.tsx +70 -0
  122. package/tests/router/tanstackTypes.test.ts +395 -7
  123. package/dist/cjs/runtime/DefaultNotFound.js +0 -47
  124. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  125. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  126. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  127. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -1,17 +1,4 @@
1
- import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
2
- import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
3
- import { type TInternalRuntimeContext } from '@modern-js/runtime/context';
4
- import { type RouterExtendsHooks } from './hooks';
1
+ import { type TanstackRouterRuntimePlugin } from './pluginCore';
5
2
  import { type RouterConfig } from './types';
6
- type TanstackRouterRuntimeConfig = {
7
- plugins?: TanstackRouterRuntimePlugin[];
8
- router?: Partial<RouterConfig>;
9
- [key: string]: unknown;
10
- };
11
- type TanstackRouterRuntimeExtends = Required<RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>> & {
12
- extendHooks: RouterExtendsHooks;
13
- };
14
- type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
15
- type TanstackRouterRuntimePlugin = Plugin<TanstackRouterPluginAPI, TInternalRuntimeContext>;
16
3
  export declare const tanstackRouterPlugin: (userConfig?: Partial<RouterConfig>) => TanstackRouterRuntimePlugin;
17
4
  export default tanstackRouterPlugin;
@@ -0,0 +1,21 @@
1
+ import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
2
+ import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
3
+ import { type TInternalRuntimeContext } from '@modern-js/runtime/context';
4
+ import type { RouterExtendsHooks } from './hooks';
5
+ import type { RouterConfig } from './types';
6
+ export type TanstackRouterRuntimeConfig = {
7
+ plugins?: TanstackRouterRuntimePlugin[];
8
+ router?: Partial<RouterConfig>;
9
+ [key: string]: unknown;
10
+ };
11
+ type TanstackRouterRuntimeExtends = Required<RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>> & {
12
+ extendHooks: RouterExtendsHooks;
13
+ };
14
+ export type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
15
+ export type TanstackRouterRuntimePlugin = Plugin<TanstackRouterPluginAPI, TInternalRuntimeContext>;
16
+ export declare function getMergedRouterConfig(api: TanstackRouterPluginAPI, userConfig: Partial<RouterConfig>): RouterConfig;
17
+ export declare function getFinalRouteConfig(mergedConfig: RouterConfig): {
18
+ routes: (import("@modern-js/types").NestedRoute<string | (() => JSX.Element)> | import("@modern-js/types").PageRoute)[] | undefined;
19
+ globalApp: import("react").ComponentClass<{}, any> | import("react").FunctionComponent<{}> | undefined;
20
+ };
21
+ export {};
@@ -0,0 +1,9 @@
1
+ import { Form, RouteActionResponseError, useFetcher } from './dataMutation';
2
+ export declare const tanstackRouterCompatBindings: {
3
+ Form: typeof Form;
4
+ Link: <TRouter extends import("@tanstack/router-core").AnyRouter = import("@tanstack/router-core").AnyRouter, const TFrom extends string = string, const TTo extends string | undefined = undefined, const TMaskFrom extends string = TFrom, const TMaskTo extends string = "">(props: import("./prefetchLink").LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) => import("react").ReactElement;
5
+ NavLink: <TRouter extends import("@tanstack/router-core").AnyRouter = import("@tanstack/router-core").AnyRouter, const TFrom extends string = string, const TTo extends string | undefined = undefined, const TMaskFrom extends string = TFrom, const TMaskTo extends string = "">(props: import("./prefetchLink").LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) => import("react").ReactElement;
6
+ Outlet: import("react").MemoExoticComponent<() => import("react").JSX.Element>;
7
+ RouteActionResponseError: typeof RouteActionResponseError;
8
+ useFetcher: typeof useFetcher;
9
+ };
@@ -1,11 +1,9 @@
1
1
  import type { RouteObject } from '@modern-js/runtime-utils/router';
2
- import type { NestedRoute, PageRoute } from '@modern-js/types';
3
2
  import type { AnyRouter, RootRoute as TanstackRootRoute } from '@tanstack/react-router';
4
3
  type ModernTanstackRootRoute = TanstackRootRoute;
5
4
  type RouteTreeOptions = {
6
5
  rscPayloadRouter?: boolean;
7
6
  };
8
- export declare function createRouteTreeFromModernRoutes(routes: Array<NestedRoute | PageRoute>, options?: RouteTreeOptions): ModernTanstackRootRoute;
9
7
  export declare function createRouteTreeFromRouteObjects(routes: RouteObject[], options?: RouteTreeOptions): ModernTanstackRootRoute;
10
8
  export declare function getModernRouteIdsFromMatches(router: AnyRouter): string[];
11
9
  export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Entry-injected router module for entrypoints that are NOT TanStack
3
+ * file-route entrypoints (e.g. apps configuring
4
+ * `router: { framework: 'tanstack', createRoutes }` in modern.runtime.ts).
5
+ *
6
+ * The CLI plugin injects `{ name: 'router', path: '<pkg>/runtime/router' }`
7
+ * for those entries, so the generated entry code value-imports `routerPlugin`
8
+ * from here. That single import both installs the framework-resolving router
9
+ * plugin of @modern-js/runtime AND registers the TanStack router provider
10
+ * (the './register' side effect) — the registration can therefore never be
11
+ * tree-shaken away from the entry that needs it.
12
+ */
13
+ import './register';
14
+ export { routerPlugin } from '@modern-js/runtime/router/internal';
@@ -0,0 +1,16 @@
1
+ import type { AnyRouter } from '@tanstack/react-router';
2
+ import type { InternalRouterRuntimeState } from './types';
3
+ /**
4
+ * Router runtime state as published by the TanStack router provider into the
5
+ * runtime-context extension slot.
6
+ */
7
+ export interface TanstackRouterState extends Omit<InternalRouterRuntimeState, 'framework' | 'instance'> {
8
+ framework: 'tanstack';
9
+ instance?: AnyRouter;
10
+ }
11
+ /**
12
+ * Typed accessor for the TanStack router state stored on a Modern.js runtime
13
+ * context. Returns `undefined` when the active router provider is not
14
+ * TanStack (e.g. react-router) or no router has been created yet.
15
+ */
16
+ export declare function getTanstackRouterState(context: object): TanstackRouterState | undefined;
@@ -1,23 +1,23 @@
1
- import type { RequestContext } from '@modern-js/runtime-utils/node';
1
+ import type { RouterFramework } from '@modern-js/runtime/context';
2
2
  import type { RouteObject } from '@modern-js/runtime-utils/router';
3
3
  import type { NestedRoute, PageRoute } from '@modern-js/types';
4
4
  import type React from 'react';
5
- export type BuiltInRouterFramework = 'react-router' | 'tanstack';
6
- export type RouterFramework = BuiltInRouterFramework | (string & {});
5
+ export type { BuiltInRouterFramework, InternalRouterRuntimeState, InternalRouterServerSnapshot, RouterFramework, RouterRouteMatchSnapshot, RouterServerPrepareResult, } from '@modern-js/runtime/context';
6
+ /**
7
+ * TanStack-specific router config. Unlike the react-router provider config,
8
+ * this intentionally has no `oldVersion`/`future` fields — those are
9
+ * react-router-only knobs with no meaning here.
10
+ */
7
11
  export type RouterConfig = {
8
12
  framework?: RouterFramework;
9
13
  routesConfig: {
10
14
  globalApp?: React.ComponentType<any>;
11
15
  routes?: (NestedRoute | PageRoute)[];
12
16
  };
13
- oldVersion?: boolean;
14
17
  serverBase?: string[];
15
18
  supportHtml5History?: boolean;
16
19
  basename?: string;
17
20
  createRoutes?: () => RouteObject[];
18
- future?: Partial<{
19
- v7_startTransition: boolean;
20
- }>;
21
21
  defaultStructuralSharing?: boolean;
22
22
  unstable_reloadOnURLMismatch?: boolean;
23
23
  };
@@ -27,49 +27,3 @@ export declare const modernTanstackRouterFastDefaults: {
27
27
  export declare const getModernTanstackRouterFastDefaults: (config?: Partial<Pick<RouterConfig, 'defaultStructuralSharing'>>) => {
28
28
  defaultStructuralSharing: boolean;
29
29
  };
30
- export interface RouterRouteMatchSnapshot {
31
- routeId: string;
32
- assetRouteId?: string;
33
- pathname?: string;
34
- params?: Record<string, string>;
35
- }
36
- export interface InternalRouterServerSnapshot {
37
- framework?: RouterFramework;
38
- basename?: string;
39
- statusCode?: number;
40
- errors?: Record<string, unknown>;
41
- routerData?: {
42
- loaderData?: Record<string, unknown>;
43
- errors?: Record<string, unknown>;
44
- };
45
- hydrationScript?: string;
46
- hydrationScripts?: string[];
47
- matchedRouteIds?: string[];
48
- matches?: RouterRouteMatchSnapshot[];
49
- }
50
- export interface InternalRouterRuntimeState {
51
- framework: RouterFramework;
52
- basename?: string;
53
- instance?: unknown;
54
- hydrationScript?: string;
55
- hydrationScripts?: string[];
56
- matchedRouteIds?: string[];
57
- matches?: RouterRouteMatchSnapshot[];
58
- serverSnapshot?: InternalRouterServerSnapshot;
59
- cleanup?: () => void | Promise<void>;
60
- }
61
- export interface RouterServerPrepareResult {
62
- state: InternalRouterRuntimeState;
63
- snapshot?: InternalRouterServerSnapshot;
64
- redirect?: Response;
65
- cleanup?: () => void | Promise<void>;
66
- }
67
- interface DataFunctionArgs<D = any> {
68
- request: Request;
69
- params: Record<string, string>;
70
- context?: D;
71
- }
72
- export type LoaderFunctionArgs<P extends Record<string, unknown> = Record<string, unknown>> = DataFunctionArgs<RequestContext<P>>;
73
- type DataFunctionValue = Response | NonNullable<unknown> | null;
74
- export type LoaderFunction = <P extends Record<string, unknown> = Record<string, unknown>>(args: LoaderFunctionArgs<P>) => Promise<DataFunctionValue> | DataFunctionValue;
75
- export {};
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "tanstack-router"
20
20
  ],
21
- "version": "3.2.0-ultramodern.98",
21
+ "version": "3.4.0-ultramodern.0",
22
22
  "engines": {
23
23
  "node": ">=20"
24
24
  },
@@ -49,17 +49,12 @@
49
49
  },
50
50
  "default": "./dist/esm/runtime/index.mjs"
51
51
  },
52
- "./runtime/rsc": {
53
- "types": "./dist/types/runtime/rsc/index.d.ts",
54
- "default": "./dist/esm/runtime/rsc/index.mjs"
55
- },
56
- "./runtime/rsc/client": {
57
- "types": "./dist/types/runtime/rsc/client.d.ts",
58
- "default": "./dist/esm/runtime/rsc/client.mjs"
59
- },
60
- "./runtime/rsc/server": {
61
- "types": "./dist/types/runtime/rsc/server.d.ts",
62
- "default": "./dist/esm/runtime/rsc/server.mjs"
52
+ "./runtime/router": {
53
+ "types": "./dist/types/runtime/router.d.ts",
54
+ "node": {
55
+ "module": "./dist/esm/runtime/router.mjs"
56
+ },
57
+ "default": "./dist/esm/runtime/router.mjs"
63
58
  }
64
59
  },
65
60
  "typesVersions": {
@@ -73,47 +68,54 @@
73
68
  "runtime": [
74
69
  "./dist/types/runtime/index.d.ts"
75
70
  ],
76
- "runtime/rsc": [
77
- "./dist/types/runtime/rsc/index.d.ts"
78
- ],
79
- "runtime/rsc/client": [
80
- "./dist/types/runtime/rsc/client.d.ts"
81
- ],
82
- "runtime/rsc/server": [
83
- "./dist/types/runtime/rsc/server.d.ts"
71
+ "runtime/router": [
72
+ "./dist/types/runtime/router.d.ts"
84
73
  ]
85
74
  }
86
75
  },
87
76
  "dependencies": {
88
77
  "@swc/helpers": "^0.5.23",
89
- "@tanstack/react-router": "1.170.11",
90
- "@tanstack/router-core": "1.171.9",
91
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.2.0-ultramodern.98",
92
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.98",
93
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.98",
94
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.98"
78
+ "@tanstack/react-router": "1.170.16",
79
+ "@tanstack/router-core": "1.171.13",
80
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.4.0-ultramodern.0",
81
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.4.0-ultramodern.0",
82
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.4.0-ultramodern.0",
83
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.4.0-ultramodern.0"
95
84
  },
96
85
  "peerDependencies": {
97
- "@modern-js/runtime": "3.2.0-ultramodern.98",
98
- "react": "^19.2.6",
99
- "react-dom": "^19.2.6"
86
+ "@modern-js/runtime": "3.4.0-ultramodern.0",
87
+ "react": "^19.2.7",
88
+ "react-dom": "^19.2.7"
100
89
  },
101
90
  "devDependencies": {
102
- "@rslib/core": "0.21.5",
91
+ "@rslib/core": "0.23.0",
103
92
  "@tanstack/history": "1.162.0",
104
93
  "@testing-library/dom": "^10.4.1",
105
94
  "@testing-library/react": "^16.3.2",
106
- "@types/node": "^25.9.1",
107
- "@types/react": "^19.2.15",
95
+ "@types/node": "^26.0.0",
96
+ "@types/react": "^19.2.17",
108
97
  "@types/react-dom": "^19.2.3",
109
- "@typescript/native-preview": "7.0.0-dev.20260527.2",
110
- "react": "^19.2.6",
111
- "react-dom": "^19.2.6",
112
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.2.0-ultramodern.98",
98
+ "@typescript/native-preview": "7.0.0-dev.20260624.1",
99
+ "react": "^19.2.7",
100
+ "react-dom": "^19.2.7",
101
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.4.0-ultramodern.0",
113
102
  "@scripts/rstest-config": "2.66.0",
114
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.2.0-ultramodern.98"
103
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.4.0-ultramodern.0"
115
104
  },
116
- "sideEffects": false,
105
+ "sideEffects": [
106
+ "./src/runtime/register.ts",
107
+ "./src/runtime/index.tsx",
108
+ "./src/runtime/router.ts",
109
+ "./dist/esm/runtime/register.mjs",
110
+ "./dist/esm/runtime/index.mjs",
111
+ "./dist/esm/runtime/router.mjs",
112
+ "./dist/esm-node/runtime/register.mjs",
113
+ "./dist/esm-node/runtime/index.mjs",
114
+ "./dist/esm-node/runtime/router.mjs",
115
+ "./dist/cjs/runtime/register.js",
116
+ "./dist/cjs/runtime/index.js",
117
+ "./dist/cjs/runtime/router.js"
118
+ ],
117
119
  "publishConfig": {
118
120
  "registry": "https://registry.npmjs.org/",
119
121
  "access": "public"
package/rstest.config.mts CHANGED
@@ -24,6 +24,11 @@ export default {
24
24
  testEnvironment: 'node',
25
25
  include: [
26
26
  'tests/router/cli.test.ts',
27
+ 'tests/router/fastDefaults.test.ts',
28
+ 'tests/router/hooks.test.ts',
29
+ 'tests/router/loaderBridge.test.ts',
30
+ 'tests/router/packageSurface.test.ts',
31
+ 'tests/router/register.test.ts',
27
32
  'tests/router/rsc.test.tsx',
28
33
  'tests/router/tanstackTypes.test.ts',
29
34
  'tests/router/routeTree.test.ts',
@@ -35,6 +40,7 @@ export default {
35
40
  testEnvironment: 'happy-dom',
36
41
  include: [
37
42
  'tests/router/dataMutation.test.tsx',
43
+ 'tests/router/hydrationBoundary.test.tsx',
38
44
  'tests/router/prefetchLink.test.tsx',
39
45
  ],
40
46
  extends: commonConfig,
package/src/cli/index.ts CHANGED
@@ -23,8 +23,8 @@ import {
23
23
  type TanstackRouteCodeSplittingOption,
24
24
  } from './routeSplitting';
25
25
  import {
26
+ collectCanonicalRoutesForEntry,
26
27
  generateTanstackRouterTypesSourceForEntry,
27
- isTanstackRouterFrameworkEnabled,
28
28
  } from './tanstackTypes';
29
29
 
30
30
  export type {
@@ -33,12 +33,12 @@ export type {
33
33
  } from './routeSplitting';
34
34
  export {
35
35
  createTanstackRsbuildRouteSplittingProfile,
36
- isTanstackStartRouteModuleSource,
37
36
  resolveTanstackRouteCodeSplittingEnabled,
38
37
  } from './routeSplitting';
39
38
  export {
39
+ type CollectCanonicalRoutesOptions,
40
+ collectCanonicalRoutesForEntry,
40
41
  generateTanstackRouterTypesSourceForEntry,
41
- isTanstackRouterFrameworkEnabled,
42
42
  } from './tanstackTypes';
43
43
 
44
44
  const DEFAULT_ROUTES_DIR = 'routes';
@@ -53,6 +53,7 @@ export type TanstackRouterPluginOptions = {
53
53
 
54
54
  type RuntimeRouterCliHelpers = {
55
55
  getEntrypointRoutesDir: (entrypoint: Entrypoint) => string | null;
56
+ getEntrypointRoutesOwner: (entrypoint: Entrypoint) => string | null;
56
57
  handleFileChange: (
57
58
  api: CLIPluginAPI<AppTools>,
58
59
  event: unknown,
@@ -72,9 +73,6 @@ type RuntimeRouterCliHelpers = {
72
73
  entrypoints: Entrypoint[],
73
74
  options?: {
74
75
  entrypointsKey?: string;
75
- generateCodeOptions?: {
76
- enableTanstackTypes?: boolean;
77
- };
78
76
  },
79
77
  ) => Promise<Record<string, (NestedRouteForCli | PageRoute)[]>>;
80
78
  handleModifyEntrypoints: (
@@ -96,7 +94,11 @@ function getRuntimeRouterCli(): RuntimeRouterCliHelpers {
96
94
 
97
95
  const cli =
98
96
  require('@modern-js/runtime/cli') as Partial<RuntimeRouterCliHelpers>;
99
- if (cli.handleGeneratorEntryCode && cli.getEntrypointRoutesDir) {
97
+ if (
98
+ cli.handleGeneratorEntryCode &&
99
+ cli.getEntrypointRoutesDir &&
100
+ cli.getEntrypointRoutesOwner
101
+ ) {
100
102
  runtimeRouterCli = cli as RuntimeRouterCliHelpers;
101
103
  return runtimeRouterCli;
102
104
  }
@@ -124,6 +126,8 @@ async function writeFileIfChanged(filePath: string, content: string) {
124
126
  function createRegisterDtsContent(opts: {
125
127
  entries: string[];
126
128
  runtimeModule: string;
129
+ canonicalRoutes?: Record<string, string> | null;
130
+ i18nRuntimeModule?: string;
127
131
  }) {
128
132
  const importStatements = opts.entries
129
133
  .map(
@@ -135,6 +139,20 @@ function createRegisterDtsContent(opts: {
135
139
  .map((_, index) => `typeof router${index}`)
136
140
  .join(' | ');
137
141
 
142
+ const canonicalEntries = Object.entries(opts.canonicalRoutes ?? {});
143
+ const canonicalRoutesAugmentation =
144
+ canonicalEntries.length > 0
145
+ ? `
146
+ declare module '${opts.i18nRuntimeModule || '@modern-js/plugin-i18n/runtime'}' {
147
+ interface UltramodernCanonicalRoutes {
148
+ ${canonicalEntries
149
+ .map(([routePath, paramsType]) => ` '${routePath}': ${paramsType};`)
150
+ .join('\n')}
151
+ }
152
+ }
153
+ `
154
+ : '';
155
+
138
156
  return `// This file is auto-generated by Modern.js. Do not edit manually.
139
157
 
140
158
  ${importStatements}
@@ -144,7 +162,7 @@ declare module '${opts.runtimeModule}' {
144
162
  router: ${routerUnionType};
145
163
  }
146
164
  }
147
- `;
165
+ ${canonicalRoutesAugmentation}`;
148
166
  }
149
167
 
150
168
  export async function writeTanstackRegisterFile(opts: {
@@ -152,12 +170,16 @@ export async function writeTanstackRegisterFile(opts: {
152
170
  generatedDirName?: string;
153
171
  runtimeModule?: string;
154
172
  srcDirectory: string;
173
+ canonicalRoutes?: Record<string, string> | null;
174
+ i18nRuntimeModule?: string;
155
175
  }) {
156
176
  const {
157
177
  entries,
158
178
  generatedDirName = DEFAULT_GENERATED_DIR_NAME,
159
179
  runtimeModule = '@modern-js/plugin-tanstack/runtime',
160
180
  srcDirectory,
181
+ canonicalRoutes,
182
+ i18nRuntimeModule,
161
183
  } = opts;
162
184
 
163
185
  if (entries.length === 0) {
@@ -172,7 +194,12 @@ export async function writeTanstackRegisterFile(opts: {
172
194
 
173
195
  await writeFileIfChanged(
174
196
  registerDtsPath,
175
- createRegisterDtsContent({ entries, runtimeModule }),
197
+ createRegisterDtsContent({
198
+ entries,
199
+ runtimeModule,
200
+ canonicalRoutes,
201
+ i18nRuntimeModule,
202
+ }),
176
203
  );
177
204
  }
178
205
 
@@ -180,11 +207,19 @@ export async function writeTanstackRouterTypesForEntries(opts: {
180
207
  appContext: AppToolsContext;
181
208
  generatedDirName?: string;
182
209
  routesByEntry: Record<string, (NestedRouteForCli | PageRoute)[]>;
210
+ /**
211
+ * Whether `@modern-js/plugin-i18n` is actually installed in the app. The
212
+ * `register.gen.d.ts` augmentation of '@modern-js/plugin-i18n/runtime' is
213
+ * only emitted when this is true — otherwise apps with a hand-rolled
214
+ * `/:lang/` param would fail typechecking on an unresolvable module.
215
+ */
216
+ i18nPluginInstalled?: boolean;
183
217
  }) {
184
218
  const {
185
219
  appContext,
186
220
  generatedDirName = DEFAULT_GENERATED_DIR_NAME,
187
221
  routesByEntry,
222
+ i18nPluginInstalled = false,
188
223
  } = opts;
189
224
 
190
225
  const entryNames = Object.keys(routesByEntry);
@@ -225,10 +260,31 @@ export async function writeTanstackRouterTypesForEntries(opts: {
225
260
  return a.localeCompare(b);
226
261
  });
227
262
 
263
+ // Merge the canonical (language-agnostic) route maps of every entry so the
264
+ // typed i18n Link covers all routes the app can navigate to. Only relevant
265
+ // when plugin-i18n is actually installed — without it the emitted module
266
+ // augmentation would reference an unresolvable module.
267
+ let canonicalRoutes: Record<string, string> | null = null;
268
+ if (i18nPluginInstalled) {
269
+ for (const entryName of registerEntries) {
270
+ const entryCanonicalRoutes = collectCanonicalRoutesForEntry(
271
+ routesByEntry[entryName],
272
+ { localeParamHeuristic: true },
273
+ );
274
+ if (entryCanonicalRoutes) {
275
+ canonicalRoutes = {
276
+ ...entryCanonicalRoutes,
277
+ ...(canonicalRoutes ?? {}),
278
+ };
279
+ }
280
+ }
281
+ }
282
+
228
283
  await writeTanstackRegisterFile({
229
284
  entries: registerEntries,
230
285
  generatedDirName,
231
286
  srcDirectory: appContext.srcDirectory,
287
+ canonicalRoutes,
232
288
  });
233
289
  }
234
290
 
@@ -252,11 +308,38 @@ export function tanstackRouterPlugin(
252
308
  return getEntrypointRoutesDir(entrypoint) === routesDir;
253
309
  };
254
310
 
255
- api._internalRuntimePlugins(({ entrypoint, plugins }) => {
256
- if (!isTanstackEntrypoint(entrypoint as Entrypoint)) {
257
- return { entrypoint, plugins };
311
+ // Entrypoints claimed by another file-route convention — the built-in
312
+ // pages/ or routes/ entries of @modern-js/runtime's router plugin, or
313
+ // an entry tagged by a different routes-owner plugin. Their router
314
+ // runtime plugin must be left untouched: redirecting it through our
315
+ // wrapper would value-import the TanStack runtime into bundles that
316
+ // never use it, and pushing a second `router` plugin can install two
317
+ // routers for one entry.
318
+ const isForeignRouteEntrypoint = (entrypoint: Entrypoint) => {
319
+ const { getEntrypointRoutesDir, getEntrypointRoutesOwner } =
320
+ getRuntimeRouterCli();
321
+ if (getEntrypointRoutesOwner(entrypoint)) {
322
+ // Owned by some routes-owner plugin. TanStack-owned entries were
323
+ // already claimed by the isTanstackEntrypoint branch, so any owner
324
+ // seen here is foreign.
325
+ return true;
258
326
  }
327
+ if (entrypoint.pageRoutesEntry) {
328
+ return true;
329
+ }
330
+ return getEntrypointRoutesDir(entrypoint) !== null;
331
+ };
259
332
 
333
+ const isI18nPluginInstalled = () => {
334
+ const { plugins } = api.getAppContext() as {
335
+ plugins?: Array<{ name?: string }>;
336
+ };
337
+ return Boolean(
338
+ plugins?.some(plugin => plugin?.name === '@modern-js/plugin-i18n'),
339
+ );
340
+ };
341
+
342
+ api._internalRuntimePlugins(({ entrypoint, plugins }) => {
260
343
  const { metaName, serverRoutes } = api.getAppContext();
261
344
  const serverBase = serverRoutes
262
345
  .filter(
@@ -265,11 +348,44 @@ export function tanstackRouterPlugin(
265
348
  .map(route => route.urlPath)
266
349
  .sort((a, b) => (a.length - b.length > 0 ? -1 : 1));
267
350
 
268
- plugins.push({
269
- name: 'tanstackRouter',
270
- path: `@${metaName}/plugin-tanstack/runtime`,
271
- config: { serverBase },
272
- });
351
+ if (isTanstackEntrypoint(entrypoint as Entrypoint)) {
352
+ plugins.push({
353
+ name: 'tanstackRouter',
354
+ path: `@${metaName}/plugin-tanstack/runtime`,
355
+ config: { serverBase },
356
+ });
357
+
358
+ return { entrypoint, plugins };
359
+ }
360
+
361
+ // Entries owned by the built-in router (classic routes/ or pages/
362
+ // conventions) or by another routes-owner plugin keep their own
363
+ // router runtime plugin untouched.
364
+ if (isForeignRouteEntrypoint(entrypoint as Entrypoint)) {
365
+ return { entrypoint, plugins };
366
+ }
367
+
368
+ // True custom entry without any file-route convention (`createRoutes`
369
+ // apps, hand-rolled App entries, ...): having this plugin installed
370
+ // is the explicit signal that the app routes through the
371
+ // router-provider registry — no source sniffing of modern.runtime.ts.
372
+ // Inject the framework-resolving router plugin of @modern-js/runtime
373
+ // through our own runtime/router module, so the TanStack provider
374
+ // registration is value-imported together with it and can never be
375
+ // tree-shaken away from the entry.
376
+ const routerWrapperPath = `@${metaName}/plugin-tanstack/runtime/router`;
377
+ const existingRouterPlugin = plugins.find(
378
+ plugin => plugin.name === 'router',
379
+ );
380
+ if (existingRouterPlugin) {
381
+ existingRouterPlugin.path = routerWrapperPath;
382
+ } else {
383
+ plugins.push({
384
+ name: 'router',
385
+ path: routerWrapperPath,
386
+ config: { serverBase },
387
+ });
388
+ }
273
389
 
274
390
  return { entrypoint, plugins };
275
391
  });
@@ -284,8 +400,18 @@ export function tanstackRouterPlugin(
284
400
  ...routeSplittingProfile.defaultConfig,
285
401
  source: {
286
402
  include: [
403
+ // TanStack Router and its runtime deps ship modern syntax and
404
+ // must be down-leveled for the app's browser targets.
287
405
  /[\\/]node_modules[\\/]@tanstack[\\/]react-router[\\/]/,
288
- path.resolve(__dirname, '../runtime').replace('cjs', 'esm'),
406
+ /[\\/]node_modules[\\/]@tanstack[\\/]router-core[\\/]/,
407
+ /[\\/]node_modules[\\/]@tanstack[\\/]react-store[\\/]/,
408
+ // This package's own dist runtime, too. `__dirname` is
409
+ // dist/{cjs,esm-node}/cli (or src/cli in tests), so the package
410
+ // dist root is two levels up. Resolution-based — no string
411
+ // surgery: the old `.replace('cjs', 'esm')` corrupted workspace
412
+ // paths containing 'cjs' and, under the ESM CLI condition, never
413
+ // matched the dist/esm runtime that browsers actually bundle.
414
+ path.resolve(__dirname, '..', '..'),
289
415
  ],
290
416
  },
291
417
  }));
@@ -312,9 +438,6 @@ export function tanstackRouterPlugin(
312
438
  tanstackEntrypoints,
313
439
  {
314
440
  entrypointsKey: ENTRYPOINTS_KEY,
315
- generateCodeOptions: {
316
- enableTanstackTypes: false,
317
- },
318
441
  },
319
442
  );
320
443
 
@@ -322,6 +445,7 @@ export function tanstackRouterPlugin(
322
445
  appContext: api.getAppContext(),
323
446
  generatedDirName,
324
447
  routesByEntry,
448
+ i18nPluginInstalled: isI18nPluginInstalled(),
325
449
  });
326
450
  });
327
451
 
@@ -340,9 +464,6 @@ export function tanstackRouterPlugin(
340
464
  entrypoints,
341
465
  {
342
466
  entrypointsKey: ENTRYPOINTS_KEY,
343
- generateCodeOptions: {
344
- enableTanstackTypes: false,
345
- },
346
467
  },
347
468
  );
348
469
 
@@ -350,6 +471,7 @@ export function tanstackRouterPlugin(
350
471
  appContext: api.getAppContext(),
351
472
  generatedDirName,
352
473
  routesByEntry,
474
+ i18nPluginInstalled: isI18nPluginInstalled(),
353
475
  });
354
476
  },
355
477
  });