@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.
Files changed (136) hide show
  1. package/dist/cjs/cli/index.js +89 -31
  2. package/dist/cjs/cli/routeSplitting.js +55 -0
  3. package/dist/cjs/cli/tanstackTypes.js +172 -170
  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 +48 -0
  9. package/dist/cjs/runtime/index.js +79 -35
  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 +195 -114
  14. package/dist/cjs/runtime/plugin.node.js +45 -45
  15. package/dist/cjs/runtime/plugin.worker.js +53 -0
  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 +74 -207
  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 +31 -1
  35. package/dist/cjs/runtime/utils.js +9 -10
  36. package/dist/cjs/runtime.js +9 -5
  37. package/dist/esm/cli/index.mjs +75 -27
  38. package/dist/esm/cli/routeSplitting.mjs +14 -0
  39. package/dist/esm/cli/tanstackTypes.mjs +158 -160
  40. package/dist/esm/runtime/hooks.mjs +1 -8
  41. package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
  42. package/dist/esm/runtime/index.mjs +5 -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 +191 -114
  47. package/dist/esm/runtime/plugin.node.mjs +40 -44
  48. package/dist/esm/runtime/plugin.worker.mjs +1 -0
  49. package/dist/esm/runtime/pluginCore.mjs +14 -0
  50. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  51. package/dist/esm/runtime/register.mjs +18 -0
  52. package/dist/esm/runtime/routeTree.mjs +59 -193
  53. package/dist/esm/runtime/router.mjs +2 -0
  54. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  55. package/dist/esm/runtime/state.mjs +7 -0
  56. package/dist/esm/runtime/types.mjs +7 -0
  57. package/dist/esm/runtime/utils.mjs +0 -5
  58. package/dist/esm-node/cli/index.mjs +75 -27
  59. package/dist/esm-node/cli/routeSplitting.mjs +15 -0
  60. package/dist/esm-node/cli/tanstackTypes.mjs +158 -160
  61. package/dist/esm-node/runtime/hooks.mjs +1 -8
  62. package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
  63. package/dist/esm-node/runtime/index.mjs +5 -2
  64. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  65. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  66. package/dist/esm-node/runtime/outlet.mjs +18 -0
  67. package/dist/esm-node/runtime/plugin.mjs +191 -114
  68. package/dist/esm-node/runtime/plugin.node.mjs +40 -44
  69. package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
  70. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  71. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  72. package/dist/esm-node/runtime/register.mjs +19 -0
  73. package/dist/esm-node/runtime/routeTree.mjs +59 -193
  74. package/dist/esm-node/runtime/router.mjs +3 -0
  75. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  76. package/dist/esm-node/runtime/state.mjs +8 -0
  77. package/dist/esm-node/runtime/types.mjs +7 -0
  78. package/dist/esm-node/runtime/utils.mjs +0 -5
  79. package/dist/types/cli/index.d.ts +14 -1
  80. package/dist/types/cli/routeSplitting.d.ts +20 -0
  81. package/dist/types/cli/tanstackTypes.d.ts +21 -1
  82. package/dist/types/runtime/hooks.d.ts +8 -33
  83. package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
  84. package/dist/types/runtime/index.d.ts +8 -3
  85. package/dist/types/runtime/lifecycle.d.ts +7 -22
  86. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  87. package/dist/types/runtime/outlet.d.ts +2 -0
  88. package/dist/types/runtime/plugin.d.ts +2 -15
  89. package/dist/types/runtime/plugin.node.d.ts +2 -15
  90. package/dist/types/runtime/plugin.worker.d.ts +1 -0
  91. package/dist/types/runtime/pluginCore.d.ts +21 -0
  92. package/dist/types/runtime/register.d.ts +9 -0
  93. package/dist/types/runtime/routeTree.d.ts +0 -2
  94. package/dist/types/runtime/router.d.ts +14 -0
  95. package/dist/types/runtime/state.d.ts +16 -0
  96. package/dist/types/runtime/types.d.ts +14 -53
  97. package/package.json +42 -40
  98. package/rstest.config.mts +6 -0
  99. package/src/cli/index.ts +162 -23
  100. package/src/cli/routeSplitting.ts +43 -0
  101. package/src/cli/tanstackTypes.ts +331 -187
  102. package/src/runtime/hooks.ts +10 -27
  103. package/src/runtime/hydrationBoundary.tsx +12 -0
  104. package/src/runtime/index.tsx +17 -7
  105. package/src/runtime/lifecycle.ts +16 -151
  106. package/src/runtime/loaderBridge.ts +257 -0
  107. package/src/runtime/outlet.tsx +48 -0
  108. package/src/runtime/plugin.node.tsx +72 -85
  109. package/src/runtime/plugin.tsx +361 -206
  110. package/src/runtime/plugin.worker.tsx +4 -0
  111. package/src/runtime/pluginCore.ts +48 -0
  112. package/src/runtime/prefetchLink.tsx +1 -1
  113. package/src/runtime/register.ts +58 -0
  114. package/src/runtime/routeTree.ts +163 -354
  115. package/src/runtime/router.ts +15 -0
  116. package/src/runtime/rsc/payloadRouter.ts +45 -2
  117. package/src/runtime/ssr-shim.d.ts +1 -3
  118. package/src/runtime/state.ts +29 -0
  119. package/src/runtime/types.ts +32 -66
  120. package/src/runtime/utils.tsx +3 -6
  121. package/tests/router/cli.test.ts +586 -5
  122. package/tests/router/fastDefaults.test.ts +25 -0
  123. package/tests/router/hooks.test.ts +26 -0
  124. package/tests/router/hydrationBoundary.test.tsx +23 -0
  125. package/tests/router/loaderBridge.test.ts +211 -0
  126. package/tests/router/packageSurface.test.ts +24 -0
  127. package/tests/router/prefetchLink.test.tsx +43 -7
  128. package/tests/router/register.test.ts +46 -0
  129. package/tests/router/routeTree.test.ts +381 -81
  130. package/tests/router/rsc.test.tsx +70 -0
  131. package/tests/router/tanstackTypes.test.ts +573 -1
  132. package/dist/cjs/runtime/DefaultNotFound.js +0 -47
  133. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  134. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  135. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  136. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -1,13 +1,14 @@
1
1
  import "node:module";
2
2
  import node_path from "node:path";
3
3
  import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer, fs } from "@modern-js/utils";
4
- import { generateTanstackRouterTypesSourceForEntry, isTanstackRouterFrameworkEnabled } from "./tanstackTypes.mjs";
4
+ import { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled } from "./routeSplitting.mjs";
5
+ import { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
5
6
  import { __webpack_require__ } from "../rslib-runtime.mjs";
6
7
  import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
7
8
  import { dirname as __rspack_dirname } from "node:path";
8
9
  import * as __rspack_external__modern_js_runtime_cli_401ee077 from "@modern-js/runtime/cli";
9
10
  __webpack_require__.add({
10
- "@modern-js/runtime/cli" (module) {
11
+ "@modern-js/runtime/cli?9b14" (module) {
11
12
  module.exports = __rspack_external__modern_js_runtime_cli_401ee077;
12
13
  }
13
14
  });
@@ -18,8 +19,8 @@ const ENTRYPOINTS_KEY = '@modern-js/plugin-tanstack';
18
19
  let runtimeRouterCli;
19
20
  function getRuntimeRouterCli() {
20
21
  if (runtimeRouterCli) return runtimeRouterCli;
21
- const cli = __webpack_require__("@modern-js/runtime/cli");
22
- if (cli.handleGeneratorEntryCode && cli.getEntrypointRoutesDir) {
22
+ const cli = __webpack_require__("@modern-js/runtime/cli?9b14");
23
+ if (cli.handleGeneratorEntryCode && cli.getEntrypointRoutesDir && cli.getEntrypointRoutesOwner) {
23
24
  runtimeRouterCli = cli;
24
25
  return runtimeRouterCli;
25
26
  }
@@ -35,6 +36,14 @@ async function writeFileIfChanged(filePath, content) {
35
36
  function createRegisterDtsContent(opts) {
36
37
  const importStatements = opts.entries.map((entryName, index)=>`import type { router as router${index} } from './${entryName}/router.gen';`).join('\n');
37
38
  const routerUnionType = opts.entries.map((_, index)=>`typeof router${index}`).join(' | ');
39
+ const canonicalEntries = Object.entries(opts.canonicalRoutes ?? {});
40
+ const canonicalRoutesAugmentation = canonicalEntries.length > 0 ? `
41
+ declare module '${opts.i18nRuntimeModule || '@modern-js/plugin-i18n/runtime'}' {
42
+ interface UltramodernCanonicalRoutes {
43
+ ${canonicalEntries.map(([routePath, paramsType])=>` '${routePath}': ${paramsType};`).join('\n')}
44
+ }
45
+ }
46
+ ` : '';
38
47
  return `// This file is auto-generated by Modern.js. Do not edit manually.
39
48
 
40
49
  ${importStatements}
@@ -44,19 +53,21 @@ declare module '${opts.runtimeModule}' {
44
53
  router: ${routerUnionType};
45
54
  }
46
55
  }
47
- `;
56
+ ${canonicalRoutesAugmentation}`;
48
57
  }
49
58
  async function writeTanstackRegisterFile(opts) {
50
- const { entries, generatedDirName = DEFAULT_GENERATED_DIR_NAME, runtimeModule = '@modern-js/plugin-tanstack/runtime', srcDirectory } = opts;
59
+ const { entries, generatedDirName = DEFAULT_GENERATED_DIR_NAME, runtimeModule = '@modern-js/plugin-tanstack/runtime', srcDirectory, canonicalRoutes, i18nRuntimeModule } = opts;
51
60
  if (0 === entries.length) return;
52
61
  const registerDtsPath = node_path.join(srcDirectory, generatedDirName, 'register.gen.d.ts');
53
62
  await writeFileIfChanged(registerDtsPath, createRegisterDtsContent({
54
63
  entries,
55
- runtimeModule
64
+ runtimeModule,
65
+ canonicalRoutes,
66
+ i18nRuntimeModule
56
67
  }));
57
68
  }
58
69
  async function writeTanstackRouterTypesForEntries(opts) {
59
- const { appContext, generatedDirName = DEFAULT_GENERATED_DIR_NAME, routesByEntry } = opts;
70
+ const { appContext, generatedDirName = DEFAULT_GENERATED_DIR_NAME, routesByEntry, i18nPluginInstalled = false } = opts;
60
71
  const entryNames = Object.keys(routesByEntry);
61
72
  await Promise.all(entryNames.map(async (entryName)=>{
62
73
  const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
@@ -73,15 +84,27 @@ async function writeTanstackRouterTypesForEntries(opts) {
73
84
  if (mainEntryName && b === mainEntryName) return 1;
74
85
  return a.localeCompare(b);
75
86
  });
87
+ let canonicalRoutes = null;
88
+ if (i18nPluginInstalled) for (const entryName of registerEntries){
89
+ const entryCanonicalRoutes = collectCanonicalRoutesForEntry(routesByEntry[entryName], {
90
+ localeParamHeuristic: true
91
+ });
92
+ if (entryCanonicalRoutes) canonicalRoutes = {
93
+ ...entryCanonicalRoutes,
94
+ ...canonicalRoutes ?? {}
95
+ };
96
+ }
76
97
  await writeTanstackRegisterFile({
77
98
  entries: registerEntries,
78
99
  generatedDirName,
79
- srcDirectory: appContext.srcDirectory
100
+ srcDirectory: appContext.srcDirectory,
101
+ canonicalRoutes
80
102
  });
81
103
  }
82
104
  function tanstackRouterPlugin(options = {}) {
83
105
  const routesDir = options.routesDir || DEFAULT_ROUTES_DIR;
84
106
  const generatedDirName = options.generatedDirName || DEFAULT_GENERATED_DIR_NAME;
107
+ const routeSplittingProfile = createTanstackRsbuildRouteSplittingProfile(options);
85
108
  return {
86
109
  name: '@modern-js/plugin-tanstack',
87
110
  required: [
@@ -93,16 +116,42 @@ function tanstackRouterPlugin(options = {}) {
93
116
  const { getEntrypointRoutesDir } = getRuntimeRouterCli();
94
117
  return getEntrypointRoutesDir(entrypoint) === routesDir;
95
118
  };
119
+ const isForeignRouteEntrypoint = (entrypoint)=>{
120
+ const { getEntrypointRoutesDir, getEntrypointRoutesOwner } = getRuntimeRouterCli();
121
+ if (getEntrypointRoutesOwner(entrypoint)) return true;
122
+ if (entrypoint.pageRoutesEntry) return true;
123
+ return null !== getEntrypointRoutesDir(entrypoint);
124
+ };
125
+ const isI18nPluginInstalled = ()=>{
126
+ const { plugins } = api.getAppContext();
127
+ return Boolean(plugins?.some((plugin)=>plugin?.name === '@modern-js/plugin-i18n'));
128
+ };
96
129
  api._internalRuntimePlugins(({ entrypoint, plugins })=>{
97
- if (!isTanstackEntrypoint(entrypoint)) return {
130
+ const { metaName, serverRoutes } = api.getAppContext();
131
+ const serverBase = serverRoutes.filter((route)=>route.entryName === entrypoint.entryName).map((route)=>route.urlPath).sort((a, b)=>a.length - b.length > 0 ? -1 : 1);
132
+ if (isTanstackEntrypoint(entrypoint)) {
133
+ plugins.push({
134
+ name: 'tanstackRouter',
135
+ path: `@${metaName}/plugin-tanstack/runtime`,
136
+ config: {
137
+ serverBase
138
+ }
139
+ });
140
+ return {
141
+ entrypoint,
142
+ plugins
143
+ };
144
+ }
145
+ if (isForeignRouteEntrypoint(entrypoint)) return {
98
146
  entrypoint,
99
147
  plugins
100
148
  };
101
- const { metaName, serverRoutes } = api.getAppContext();
102
- const serverBase = serverRoutes.filter((route)=>route.entryName === entrypoint.entryName).map((route)=>route.urlPath).sort((a, b)=>a.length - b.length > 0 ? -1 : 1);
103
- plugins.push({
104
- name: 'tanstackRouter',
105
- path: `@${metaName}/plugin-tanstack/runtime`,
149
+ const routerWrapperPath = `@${metaName}/plugin-tanstack/runtime/router`;
150
+ const existingRouterPlugin = plugins.find((plugin)=>'router' === plugin.name);
151
+ if (existingRouterPlugin) existingRouterPlugin.path = routerWrapperPath;
152
+ else plugins.push({
153
+ name: 'router',
154
+ path: routerWrapperPath,
106
155
  config: {
107
156
  serverBase
108
157
  }
@@ -117,10 +166,13 @@ function tanstackRouterPlugin(options = {}) {
117
166
  entry: entry || getRuntimeRouterCli().isRouteEntry(entryPath, routesDir)
118
167
  }));
119
168
  api.config(()=>({
169
+ ...routeSplittingProfile.defaultConfig,
120
170
  source: {
121
171
  include: [
122
172
  /[\\/]node_modules[\\/]@tanstack[\\/]react-router[\\/]/,
123
- node_path.resolve(cli_dirname, '../runtime').replace('cjs', 'esm')
173
+ /[\\/]node_modules[\\/]@tanstack[\\/]router-core[\\/]/,
174
+ /[\\/]node_modules[\\/]@tanstack[\\/]react-store[\\/]/,
175
+ node_path.resolve(cli_dirname, '..', '..')
124
176
  ]
125
177
  }
126
178
  }));
@@ -137,15 +189,13 @@ function tanstackRouterPlugin(options = {}) {
137
189
  if (0 === tanstackEntrypoints.length) return;
138
190
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
139
191
  const routesByEntry = await handleGeneratorEntryCode(api, tanstackEntrypoints, {
140
- entrypointsKey: ENTRYPOINTS_KEY,
141
- generateCodeOptions: {
142
- enableTanstackTypes: false
143
- }
192
+ entrypointsKey: ENTRYPOINTS_KEY
144
193
  });
145
194
  await writeTanstackRouterTypesForEntries({
146
195
  appContext: api.getAppContext(),
147
196
  generatedDirName,
148
- routesByEntry
197
+ routesByEntry,
198
+ i18nPluginInstalled: isI18nPluginInstalled()
149
199
  });
150
200
  });
151
201
  api.onFileChanged(async (event)=>{
@@ -159,15 +209,13 @@ function tanstackRouterPlugin(options = {}) {
159
209
  regenerate: async ({ api, entrypoints })=>{
160
210
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
161
211
  const routesByEntry = await handleGeneratorEntryCode(api, entrypoints, {
162
- entrypointsKey: ENTRYPOINTS_KEY,
163
- generateCodeOptions: {
164
- enableTanstackTypes: false
165
- }
212
+ entrypointsKey: ENTRYPOINTS_KEY
166
213
  });
167
214
  await writeTanstackRouterTypesForEntries({
168
215
  appContext: api.getAppContext(),
169
216
  generatedDirName,
170
- routesByEntry
217
+ routesByEntry,
218
+ i18nPluginInstalled: isI18nPluginInstalled()
171
219
  });
172
220
  }
173
221
  });
@@ -199,4 +247,4 @@ function tanstackRouterPlugin(options = {}) {
199
247
  }
200
248
  const src_cli = tanstackRouterPlugin;
201
249
  export default src_cli;
202
- export { generateTanstackRouterTypesSourceForEntry, isTanstackRouterFrameworkEnabled, tanstackRouterPlugin, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
250
+ export { collectCanonicalRoutesForEntry, createTanstackRsbuildRouteSplittingProfile, generateTanstackRouterTypesSourceForEntry, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
@@ -0,0 +1,15 @@
1
+ import "node:module";
2
+ function resolveTanstackRouteCodeSplittingEnabled(option) {
3
+ if ('boolean' == typeof option) return option;
4
+ return option?.enabled ?? true;
5
+ }
6
+ function createTanstackRsbuildRouteSplittingProfile(opts) {
7
+ return {
8
+ defaultConfig: {
9
+ output: {
10
+ splitRouteChunks: resolveTanstackRouteCodeSplittingEnabled(opts.routeCodeSplitting)
11
+ }
12
+ }
13
+ };
14
+ }
15
+ export { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled };
@@ -1,18 +1,7 @@
1
1
  import "node:module";
2
- import { findExists, formatImportPath, fs, slash } from "@modern-js/utils";
2
+ import { getPathWithoutExt, makeLegalIdentifier } from "@modern-js/runtime/cli";
3
+ import { findExists, formatImportPath, slash } from "@modern-js/utils";
3
4
  import path from "path";
4
- const reservedWords = 'break case class catch const continue debugger default delete do else export extends finally for function if import in instanceof let new return super switch this throw try typeof var void while with yield enum await implements package protected static interface private public';
5
- const builtins = 'arguments Infinity NaN undefined null true false eval uneval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Symbol Error EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError Number Math Date String RegExp Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Float32Array Float64Array Map Set WeakMap WeakSet SIMD ArrayBuffer DataView JSON Promise Generator GeneratorFunction Reflect Proxy Intl';
6
- const forbidList = new Set(`${reservedWords} ${builtins}`.split(' '));
7
- function makeLegalIdentifier(str) {
8
- const identifier = str.replace(/-(\w)/g, (_, letter)=>letter.toUpperCase()).replace(/[^$_a-zA-Z0-9]/g, '_');
9
- if (/\d/.test(identifier[0]) || forbidList.has(identifier)) return `_${identifier}`;
10
- return identifier || '_';
11
- }
12
- function getPathWithoutExt(filename) {
13
- const extname = path.extname(filename);
14
- return extname ? filename.slice(0, -extname.length) : filename;
15
- }
16
5
  const JS_OR_TS_EXTS = [
17
6
  '.js',
18
7
  '.jsx',
@@ -56,6 +45,14 @@ function pickModernLoaderModule(route) {
56
45
  inline
57
46
  };
58
47
  }
48
+ function pickRouteSearchContractModules(route) {
49
+ const validateSearchPath = route.validateSearch;
50
+ const loaderDepsPath = route.loaderDeps;
51
+ return {
52
+ validateSearchPath: 'string' == typeof validateSearchPath ? validateSearchPath : null,
53
+ loaderDepsPath: 'string' == typeof loaderDepsPath ? loaderDepsPath : null
54
+ };
55
+ }
59
56
  function isPathlessLayout(route) {
60
57
  return 'nested' === route.type && 'boolean' != typeof route.index && void 0 === route.path;
61
58
  }
@@ -70,16 +67,78 @@ function createRouteStaticDataSnippet(opts) {
70
67
  if (!staticDataLines.length) return null;
71
68
  return `staticData: createRouteStaticData({\n ${staticDataLines.join('\n ')}\n }),`;
72
69
  }
73
- async function isTanstackRouterFrameworkEnabled(appContext) {
74
- const runtimeConfigBase = path.join(appContext.srcDirectory, appContext.runtimeConfigFile);
75
- const runtimeConfigFile = findExists(JS_OR_TS_EXTS.map((ext)=>`${runtimeConfigBase}${ext}`));
76
- if (!runtimeConfigFile) return false;
77
- try {
78
- const content = await fs.readFile(runtimeConfigFile, 'utf-8');
79
- return /framework\s*:\s*['"]tanstack['"]/.test(content);
80
- } catch {
81
- return false;
70
+ const LOCALE_PARAM_SEGMENTS = new Set([
71
+ ':lang',
72
+ ':locale',
73
+ ':language',
74
+ '$lang',
75
+ '$locale',
76
+ '$language'
77
+ ]);
78
+ function paramsTypeForCanonicalPath(canonicalPath) {
79
+ const fields = [];
80
+ for (const segment of canonicalPath.split('/'))if (segment) {
81
+ if ('*' === segment || '$' === segment) {
82
+ fields.push("'_splat'?: string");
83
+ continue;
84
+ }
85
+ if (segment.startsWith('{-$') && segment.endsWith('}')) {
86
+ fields.push(`${JSON.stringify(segment.slice(3, -1))}?: string`);
87
+ continue;
88
+ }
89
+ if (segment.startsWith('$')) {
90
+ fields.push(`${JSON.stringify(segment.slice(1))}: string`);
91
+ continue;
92
+ }
93
+ if (segment.startsWith(':')) {
94
+ const optional = segment.endsWith('?');
95
+ const name = segment.slice(1, optional ? void 0 : segment.length);
96
+ fields.push(`${JSON.stringify(optional ? name.slice(0, -1) : name)}${optional ? '?' : ''}: string`);
97
+ }
82
98
  }
99
+ return fields.length > 0 ? `{ ${fields.join('; ')} }` : 'Record<string, never>';
100
+ }
101
+ function collectCanonicalRoutesForEntry(routes, options = {}) {
102
+ const { localeParamHeuristic = true } = options;
103
+ const canonicalParams = new Map();
104
+ let hasI18nSurface = false;
105
+ const normalizeJoined = (joined)=>{
106
+ const collapsed = joined.replace(/\/+/g, '/');
107
+ const withLeading = collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
108
+ return withLeading.length > 1 ? withLeading.replace(/\/+$/, '') : withLeading;
109
+ };
110
+ const record = (canonicalPath)=>{
111
+ const normalized = normalizeJoined(canonicalPath || '/');
112
+ const key = toTanstackPath(normalized);
113
+ if (!canonicalParams.has(key)) canonicalParams.set(key, paramsTypeForCanonicalPath(normalized));
114
+ };
115
+ const visit = (route, parentPath)=>{
116
+ let currentPath = parentPath;
117
+ if ('string' == typeof route.modernCanonicalPath) {
118
+ hasI18nSurface = true;
119
+ currentPath = normalizeJoined(route.modernCanonicalPath);
120
+ } else if ('string' == typeof route.path && route.path.length > 0) {
121
+ const segments = route.path.replace(/\[(.+?)\]/g, ':$1').split('/').filter(Boolean);
122
+ if (localeParamHeuristic && '' === parentPath && LOCALE_PARAM_SEGMENTS.has(segments[0])) {
123
+ hasI18nSurface = true;
124
+ segments.shift();
125
+ }
126
+ currentPath = segments.length ? normalizeJoined(`${parentPath}/${segments.join('/')}`) : parentPath;
127
+ }
128
+ const children = route.children;
129
+ if (children && children.length > 0) {
130
+ for (const child of children)visit(child, currentPath);
131
+ return;
132
+ }
133
+ record(currentPath || '/');
134
+ };
135
+ const rootModern = routes.find((route)=>route.isRoot);
136
+ const topLevel = rootModern ? rootModern.children ?? [] : routes;
137
+ for (const route of topLevel)visit(route, '');
138
+ if (!hasI18nSurface || 0 === canonicalParams.size) return null;
139
+ return Object.fromEntries([
140
+ ...canonicalParams.entries()
141
+ ].sort(([a], [b])=>a.localeCompare(b)));
83
142
  }
84
143
  async function generateTanstackRouterTypesSourceForEntry(opts) {
85
144
  const { appContext, entryName, generatedDirName = 'modern-tanstack', routes } = opts;
@@ -89,8 +148,39 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
89
148
  const imports = [];
90
149
  const statements = [];
91
150
  const loaderImportMap = new Map();
151
+ const componentImportMap = new Map();
152
+ const searchContractImportMap = new Map();
153
+ const usedRouteVarNames = new Set();
92
154
  let loaderIndex = 0;
155
+ let componentIndex = 0;
156
+ let validateSearchIndex = 0;
157
+ let loaderDepsIndex = 0;
93
158
  let routeIndex = 0;
159
+ const getImportNameForComponent = (componentPath)=>{
160
+ if ('string' != typeof componentPath || 0 === componentPath.length) return Promise.resolve(null);
161
+ let pendingImportName = componentImportMap.get(componentPath);
162
+ if (!pendingImportName) {
163
+ pendingImportName = (async ()=>{
164
+ const resolvedNoExt = await resolveRouteModuleNoExt(componentPath);
165
+ if (!resolvedNoExt) return null;
166
+ const relImport = normalizeRelativeImport(path.relative(outDir, resolvedNoExt));
167
+ const componentName = `component_${componentIndex++}`;
168
+ imports.push(`import ${componentName} from ${quote(relImport)};`);
169
+ return componentName;
170
+ })();
171
+ componentImportMap.set(componentPath, pendingImportName);
172
+ }
173
+ return pendingImportName;
174
+ };
175
+ const resolveRouteModuleNoExt = async (aliasedNoExtPath)=>{
176
+ const prefix = `${appContext.internalSrcAlias}/`;
177
+ let absNoExt;
178
+ if (aliasedNoExtPath.startsWith(prefix)) {
179
+ const rel = aliasedNoExtPath.slice(prefix.length);
180
+ absNoExt = path.join(appContext.srcDirectory, rel);
181
+ } else absNoExt = path.isAbsolute(aliasedNoExtPath) ? aliasedNoExtPath : path.join(appContext.srcDirectory, aliasedNoExtPath);
182
+ return resolveFileNoExt(absNoExt);
183
+ };
94
184
  const getImportNamesForLoader = async (aliasedNoExtPath, inline, hasAction)=>{
95
185
  const key = `${inline ? 'inline' : 'default'}:${hasAction ? 'action' : 'loader'}:${aliasedNoExtPath}`;
96
186
  const existing = loaderImportMap.get(key);
@@ -98,13 +188,7 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
98
188
  loaderName: existing,
99
189
  actionName: hasAction ? existing.replace(/^loader_/, 'action_') : null
100
190
  };
101
- const prefix = `${appContext.internalSrcAlias}/`;
102
- let absNoExt;
103
- if (aliasedNoExtPath.startsWith(prefix)) {
104
- const rel = aliasedNoExtPath.slice(prefix.length);
105
- absNoExt = path.join(appContext.srcDirectory, rel);
106
- } else absNoExt = path.isAbsolute(aliasedNoExtPath) ? aliasedNoExtPath : path.join(appContext.srcDirectory, aliasedNoExtPath);
107
- const resolvedNoExt = await resolveFileNoExt(absNoExt);
191
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
108
192
  if (!resolvedNoExt) return null;
109
193
  const relImport = normalizeRelativeImport(path.relative(outDir, resolvedNoExt));
110
194
  const importName = `loader_${loaderIndex++}`;
@@ -122,10 +206,29 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
122
206
  actionName
123
207
  };
124
208
  };
209
+ const getImportNameForSearchContract = async (aliasedNoExtPath, exportName)=>{
210
+ const key = `${exportName}:${aliasedNoExtPath}`;
211
+ const existing = searchContractImportMap.get(key);
212
+ if (existing) return existing;
213
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
214
+ if (!resolvedNoExt) return null;
215
+ const relImport = normalizeRelativeImport(path.relative(outDir, resolvedNoExt));
216
+ const importName = 'validateSearch' === exportName ? `validateSearch_${validateSearchIndex++}` : `loaderDeps_${loaderDepsIndex++}`;
217
+ imports.push(`import { ${exportName} as ${importName} } from ${quote(relImport)};`);
218
+ searchContractImportMap.set(key, importName);
219
+ return importName;
220
+ };
221
+ const reserveRouteVarName = (preferred)=>{
222
+ let candidate = preferred;
223
+ let suffix = 1;
224
+ while(usedRouteVarNames.has(candidate))candidate = `${preferred}_${suffix++}`;
225
+ usedRouteVarNames.add(candidate);
226
+ return candidate;
227
+ };
125
228
  const createRouteVarName = (route)=>{
126
229
  const id = route.id;
127
230
  const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
128
- return `route_${base}`;
231
+ return reserveRouteVarName(`route_${base}`);
129
232
  };
130
233
  const buildRoute = async (opts)=>{
131
234
  const { parentVar, route } = opts;
@@ -135,11 +238,16 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
135
238
  const loaderImports = loaderInfo ? await getImportNamesForLoader(loaderInfo.loaderPath, loaderInfo.inline, Boolean(loaderInfo.inline && routeAction === loaderInfo.loaderPath)) : null;
136
239
  const loaderName = loaderImports?.loaderName || null;
137
240
  const actionName = loaderImports?.actionName || null;
241
+ const searchContractInfo = pickRouteSearchContractModules(route);
242
+ const validateSearchName = searchContractInfo.validateSearchPath ? await getImportNameForSearchContract(searchContractInfo.validateSearchPath, 'validateSearch') : null;
243
+ const loaderDepsName = searchContractInfo.loaderDepsPath ? await getImportNameForSearchContract(searchContractInfo.loaderDepsPath, 'loaderDeps') : null;
138
244
  const rawPath = route.path;
139
245
  const hasSplat = 'string' == typeof rawPath && rawPath.includes('*');
140
246
  const routeOpts = [
141
247
  `getParentRoute: () => ${parentVar},`
142
248
  ];
249
+ const componentName = await getImportNameForComponent(route._component);
250
+ if (componentName) routeOpts.push(`component: ${componentName},`);
143
251
  if (isPathlessLayout(route)) {
144
252
  const id = route.id;
145
253
  routeOpts.push(`id: ${quote(id || 'pathless')},`);
@@ -148,20 +256,24 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
148
256
  routeOpts.push(`path: ${quote(p)},`);
149
257
  }
150
258
  if (loaderName) routeOpts.push(`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`);
259
+ if (validateSearchName) routeOpts.push(`validateSearch: ${validateSearchName},`);
260
+ if (loaderDepsName) routeOpts.push(`loaderDeps: ${loaderDepsName},`);
151
261
  const staticDataSnippet = createRouteStaticDataSnippet({
152
262
  modernRouteId: route.id,
153
263
  loaderName,
154
264
  actionName
155
265
  });
156
266
  if (staticDataSnippet) routeOpts.push(staticDataSnippet);
157
- statements.push(`const ${varName} = createRoute({\n ${routeOpts.join('\n ')}\n});`);
158
267
  const children = route.children;
268
+ const hasChildren = Boolean(children && children.length > 0);
269
+ const routeCtorVarName = hasChildren ? reserveRouteVarName(`${varName}__base`) : varName;
270
+ statements.push(`const ${routeCtorVarName} = createRoute({\n ${routeOpts.join('\n ')}\n});`);
159
271
  if (children && children.length > 0) {
160
272
  const childVars = await Promise.all(children.map((child)=>buildRoute({
161
- parentVar: varName,
273
+ parentVar: routeCtorVarName,
162
274
  route: child
163
275
  })));
164
- statements.push(`${varName}.addChildren([${childVars.join(', ')}]);`);
276
+ statements.push(`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`);
165
277
  }
166
278
  return varName;
167
279
  };
@@ -170,12 +282,19 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
170
282
  const rootLoaderImports = rootLoaderInfo?.loaderPath ? await getImportNamesForLoader(rootLoaderInfo.loaderPath, rootLoaderInfo.inline, Boolean(rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath)) : null;
171
283
  const rootLoaderName = rootLoaderImports?.loaderName || null;
172
284
  const rootActionName = rootLoaderImports?.actionName || null;
285
+ const rootSearchContractInfo = rootModern ? pickRouteSearchContractModules(rootModern) : null;
286
+ const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath ? await getImportNameForSearchContract(rootSearchContractInfo.validateSearchPath, 'validateSearch') : null;
287
+ const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath ? await getImportNameForSearchContract(rootSearchContractInfo.loaderDepsPath, 'loaderDeps') : null;
173
288
  const topLevelVars = await Promise.all(topLevel.map((route)=>buildRoute({
174
289
  parentVar: 'rootRoute',
175
290
  route
176
291
  })));
177
292
  const rootOpts = [];
293
+ const rootComponentName = await getImportNameForComponent(rootModern?._component);
294
+ if (rootComponentName) rootOpts.push(`component: ${rootComponentName},`);
178
295
  if (rootLoaderName) rootOpts.push(`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`);
296
+ if (rootValidateSearchName) rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
297
+ if (rootLoaderDepsName) rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
179
298
  const routerGenTs = `/* eslint-disable */
180
299
  // This file is auto-generated by Modern.js. Do not edit manually.
181
300
 
@@ -184,134 +303,12 @@ import {
184
303
  createRootRouteWithContext,
185
304
  createRoute,
186
305
  createRouter,
187
- notFound,
188
- redirect,
306
+ createRouteStaticData,
307
+ type ModernRouterContext,
308
+ modernLoaderToTanstack,
309
+ modernTanstackRouterFastDefaults,
189
310
  } from '@modern-js/plugin-tanstack/runtime';
190
311
 
191
- type ModernRouterContext = {
192
- request?: Request;
193
- requestContext?: unknown;
194
- };
195
-
196
- function isResponse(value: unknown): value is Response {
197
- return (
198
- value != null &&
199
- typeof value === 'object' &&
200
- typeof (value as any).status === 'number' &&
201
- typeof (value as any).headers === 'object'
202
- );
203
- }
204
-
205
- const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
206
- function isRedirectResponse(res: Response) {
207
- return redirectStatusCodes.has(res.status);
208
- }
209
-
210
- function throwTanstackRedirect(location: string) {
211
- const target = location || '/';
212
- try {
213
- void new URL(target);
214
- throw redirect({ href: target });
215
- } catch {
216
- throw redirect({ to: target });
217
- }
218
- }
219
-
220
- function mapParamsForModernLoader(params: Record<string, string>, hasSplat: boolean) {
221
- if (!hasSplat) {
222
- return params;
223
- }
224
-
225
- const { _splat, ...rest } = params as any;
226
- if (typeof _splat !== 'undefined') {
227
- return { ...rest, '*': _splat };
228
- }
229
- return rest;
230
- }
231
-
232
- function createRouteStaticData(opts: {
233
- modernRouteId?: string;
234
- modernRouteAction?: unknown;
235
- modernRouteLoader?: unknown;
236
- }) {
237
- const staticData: Record<string, unknown> = {};
238
-
239
- if (opts.modernRouteId) {
240
- staticData.modernRouteId = opts.modernRouteId;
241
- }
242
-
243
- if (opts.modernRouteLoader) {
244
- staticData.modernRouteLoader = opts.modernRouteLoader;
245
- }
246
-
247
- if (opts.modernRouteAction) {
248
- staticData.modernRouteAction = opts.modernRouteAction;
249
- }
250
-
251
- return Object.keys(staticData).length > 0 ? staticData : undefined;
252
- }
253
-
254
- function modernLoaderToTanstack<TLoader extends (args: any) => any>(
255
- opts: { hasSplat: boolean },
256
- modernLoader: TLoader,
257
- ) {
258
- type LoaderResult = Awaited<ReturnType<TLoader>>;
259
-
260
- return async (ctx: any): Promise<LoaderResult> => {
261
- try {
262
- const signal: AbortSignal =
263
- ctx?.abortController?.signal ||
264
- ctx?.signal ||
265
- new AbortController().signal;
266
- const baseRequest: Request | undefined =
267
- ctx?.context?.request instanceof Request ? ctx.context.request : undefined;
268
-
269
- const href =
270
- typeof ctx?.location === 'string'
271
- ? ctx.location
272
- : ctx?.location?.publicHref ||
273
- ctx?.location?.href ||
274
- ctx?.location?.url?.href ||
275
- '';
276
-
277
- const request = baseRequest
278
- ? new Request(baseRequest, { signal })
279
- : new Request(href, { signal });
280
-
281
- const params = mapParamsForModernLoader(ctx?.params || {}, opts.hasSplat);
282
-
283
- const result = await (modernLoader as any)({
284
- request,
285
- params,
286
- context: ctx?.context?.requestContext,
287
- });
288
-
289
- if (isResponse(result)) {
290
- if (isRedirectResponse(result)) {
291
- const location = result.headers.get('Location') || '/';
292
- throwTanstackRedirect(location);
293
- }
294
- if (result.status === 404) {
295
- throw notFound();
296
- }
297
- }
298
-
299
- return result as LoaderResult;
300
- } catch (err) {
301
- if (isResponse(err)) {
302
- if (isRedirectResponse(err)) {
303
- const location = err.headers.get('Location') || '/';
304
- throwTanstackRedirect(location);
305
- }
306
- if (err.status === 404) {
307
- throw notFound();
308
- }
309
- }
310
- throw err;
311
- }
312
- };
313
- }
314
-
315
312
  ${imports.join('\n')}
316
313
 
317
314
  export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
@@ -328,6 +325,7 @@ ${statements.join('\n\n')}
328
325
  export const routeTree = rootRoute.addChildren([${topLevelVars.join(', ')}]);
329
326
 
330
327
  export const router = createRouter({
328
+ ...modernTanstackRouterFastDefaults,
331
329
  routeTree,
332
330
  history: createMemoryHistory({
333
331
  initialEntries: ['/'],
@@ -339,4 +337,4 @@ export const router = createRouter({
339
337
  routerGenTs
340
338
  };
341
339
  }
342
- export { generateTanstackRouterTypesSourceForEntry, isTanstackRouterFrameworkEnabled };
340
+ export { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry };
@@ -1,9 +1,2 @@
1
1
  import "node:module";
2
- import { createSyncHook } from "@modern-js/plugin";
3
- const modifyRoutes = createSyncHook();
4
- const onBeforeCreateRoutes = createSyncHook();
5
- const onBeforeCreateRouter = createSyncHook();
6
- const onAfterCreateRouter = createSyncHook();
7
- const onBeforeHydrateRouter = createSyncHook();
8
- const onAfterHydrateRouter = createSyncHook();
9
- export { modifyRoutes, onAfterCreateRouter, onAfterHydrateRouter, onBeforeCreateRouter, onBeforeCreateRoutes, onBeforeHydrateRouter };
2
+ export { modifyRoutes, onAfterCreateRouter, onAfterHydrateRouter, onBeforeCreateRouter, onBeforeCreateRoutes, onBeforeHydrateRouter, routerProviderRegistryHooks } from "@modern-js/runtime/context";
@@ -0,0 +1,11 @@
1
+ import "node:module";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Suspense } from "react";
4
+ function wrapTanstackSsrHydrationBoundary(routerContent, shouldWrap) {
5
+ if (shouldWrap) return /*#__PURE__*/ jsx(Suspense, {
6
+ fallback: null,
7
+ children: routerContent
8
+ });
9
+ return routerContent;
10
+ }
11
+ export { wrapTanstackSsrHydrationBoundary };