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