@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.9 → 3.9.3-ultramodern.2

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 (112) hide show
  1. package/dist/cjs/cli/artifacts.js +15 -1
  2. package/dist/cjs/cli/index.js +13 -2
  3. package/dist/cjs/cli/routeMetadata.js +131 -0
  4. package/dist/cjs/cli/rscRouteLayer.js +86 -0
  5. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
  6. package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
  7. package/dist/cjs/cli/tanstackTypes/source.js +28 -63
  8. package/dist/cjs/runtime/lifecycle.js +0 -3
  9. package/dist/cjs/runtime/loaderBridge.js +10 -4
  10. package/dist/cjs/runtime/navigation.js +102 -0
  11. package/dist/cjs/runtime/plugin.js +10 -1
  12. package/dist/cjs/runtime/plugin.node.js +89 -64
  13. package/dist/cjs/runtime/routeTree/construction.js +19 -61
  14. package/dist/cjs/runtime/routeTree/loaders.js +1 -1
  15. package/dist/cjs/runtime/routeTree/paths.js +0 -6
  16. package/dist/cjs/runtime/ssrManagedTags.js +2 -12
  17. package/dist/cjs/runtime/utils.js +1 -0
  18. package/dist/cjs/shared/routeDescriptor.js +88 -0
  19. package/dist/esm/cli/artifacts.mjs +13 -2
  20. package/dist/esm/cli/index.mjs +13 -11
  21. package/dist/esm/cli/routeMetadata.mjs +71 -0
  22. package/dist/esm/cli/rscRouteLayer.mjs +35 -0
  23. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  24. package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
  25. package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
  26. package/dist/esm/runtime/lifecycle.mjs +1 -1
  27. package/dist/esm/runtime/loaderBridge.mjs +8 -5
  28. package/dist/esm/runtime/navigation.mjs +64 -0
  29. package/dist/esm/runtime/plugin.mjs +10 -1
  30. package/dist/esm/runtime/plugin.node.mjs +91 -66
  31. package/dist/esm/runtime/routeTree/construction.mjs +19 -61
  32. package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
  33. package/dist/esm/runtime/routeTree/paths.mjs +1 -4
  34. package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
  35. package/dist/esm/runtime/utils.mjs +1 -0
  36. package/dist/esm/shared/routeDescriptor.mjs +50 -0
  37. package/dist/esm-node/cli/artifacts.mjs +13 -2
  38. package/dist/esm-node/cli/index.mjs +13 -11
  39. package/dist/esm-node/cli/routeMetadata.mjs +72 -0
  40. package/dist/esm-node/cli/rscRouteLayer.mjs +36 -0
  41. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  42. package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
  43. package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
  44. package/dist/esm-node/runtime/lifecycle.mjs +1 -1
  45. package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
  46. package/dist/esm-node/runtime/navigation.mjs +65 -0
  47. package/dist/esm-node/runtime/plugin.mjs +10 -1
  48. package/dist/esm-node/runtime/plugin.node.mjs +91 -66
  49. package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
  50. package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
  51. package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
  52. package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
  53. package/dist/esm-node/runtime/utils.mjs +1 -0
  54. package/dist/esm-node/shared/routeDescriptor.mjs +51 -0
  55. package/dist/types/cli/artifacts.d.ts +16 -7
  56. package/dist/types/cli/index.d.ts +1 -1
  57. package/dist/types/cli/routeMetadata.d.ts +18 -0
  58. package/dist/types/cli/rscRouteLayer.d.ts +11 -0
  59. package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
  60. package/dist/types/cli/tanstackTypes/source.d.ts +3 -2
  61. package/dist/types/runtime/lifecycle.d.ts +1 -1
  62. package/dist/types/runtime/loaderBridge.d.ts +1 -0
  63. package/dist/types/runtime/navigation.d.ts +4 -0
  64. package/dist/types/runtime/pluginShared.d.ts +5 -0
  65. package/dist/types/runtime/routeTree/paths.d.ts +0 -2
  66. package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
  67. package/dist/types/runtime/ssrTypes.d.ts +0 -7
  68. package/dist/types/shared/routeDescriptor.d.ts +26 -0
  69. package/package.json +32 -26
  70. package/src/cli/artifacts.ts +59 -8
  71. package/src/cli/index.ts +17 -4
  72. package/src/cli/routeMetadata.ts +121 -0
  73. package/src/cli/rscRouteLayer.ts +93 -0
  74. package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
  75. package/src/cli/tanstackTypes/shared.ts +0 -14
  76. package/src/cli/tanstackTypes/source.ts +48 -120
  77. package/src/runtime/lifecycle.ts +0 -1
  78. package/src/runtime/loaderBridge.ts +9 -9
  79. package/src/runtime/navigation.ts +78 -0
  80. package/src/runtime/plugin.node.tsx +121 -91
  81. package/src/runtime/plugin.tsx +10 -0
  82. package/src/runtime/pluginShared.ts +12 -0
  83. package/src/runtime/routeTree/construction.ts +32 -112
  84. package/src/runtime/routeTree/loaders.ts +2 -4
  85. package/src/runtime/routeTree/paths.ts +0 -6
  86. package/src/runtime/ssrManagedTags.ts +0 -17
  87. package/src/runtime/ssrTypes.ts +0 -7
  88. package/src/runtime/utils.tsx +1 -0
  89. package/src/shared/routeDescriptor.ts +102 -0
  90. package/dist/esm/rslib-runtime~1.mjs +0 -18
  91. package/dist/esm-node/rslib-runtime~0.mjs +0 -19
  92. package/rslib.config.mts +0 -4
  93. package/rstest.config.mts +0 -53
  94. package/tests/router/cli.test.ts +0 -420
  95. package/tests/router/clientHydration.test.tsx +0 -200
  96. package/tests/router/dataMutation.test.tsx +0 -428
  97. package/tests/router/fixtures/router-gen.oxlint.json +0 -5
  98. package/tests/router/flightSerialization.roundtrip.test.tsx +0 -140
  99. package/tests/router/generateRouteArtifacts.test.ts +0 -146
  100. package/tests/router/loaderBridge.test.ts +0 -207
  101. package/tests/router/localisedIdentity.test.tsx +0 -95
  102. package/tests/router/prefetchLink.test.tsx +0 -122
  103. package/tests/router/prefetchLinkPreload.test.tsx +0 -65
  104. package/tests/router/routeHooks.test.ts +0 -49
  105. package/tests/router/routeTree.test.ts +0 -240
  106. package/tests/router/rsc.test.tsx +0 -480
  107. package/tests/router/serverPlugin.test.ts +0 -157
  108. package/tests/router/slotUsageSanitizer.test.ts +0 -23
  109. package/tests/router/ssrPreload.test.ts +0 -30
  110. package/tests/router/tanstackTypes.test.ts +0 -384
  111. package/tsconfig.json +0 -12
  112. package/tsconfig.tsgo.json +0 -6
@@ -1,5 +1,6 @@
1
1
  import node_path from "node:path";
2
2
  import { fs } from "@modern-js/utils";
3
+ import { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, findRouteMetaFiles, renderRouteMetadataManifest } from "./routeMetadata.mjs";
3
4
  import { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
4
5
  const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
5
6
  async function writeFileIfChanged(filePath, content) {
@@ -77,7 +78,16 @@ async function writeTanstackRouterTypesForEntries(opts) {
77
78
  canonicalRoutes
78
79
  });
79
80
  }
81
+ async function writeRouteMetadataManifest(opts) {
82
+ const routesDirectory = node_path.join(opts.appDirectory, 'src', 'routes');
83
+ const manifestPath = node_path.join(routesDirectory, ROUTE_METADATA_MANIFEST_FILE_NAME);
84
+ const manifest = renderRouteMetadataManifest(await findRouteMetaFiles(routesDirectory));
85
+ if (null !== manifest) return void await writeFileIfChanged(manifestPath, manifest);
86
+ const previous = await fs.pathExists(manifestPath) ? await fs.readFile(manifestPath, 'utf-8') : null;
87
+ if (previous?.startsWith(`${ROUTE_METADATA_MANIFEST_HEADER}\n`)) throw new Error(`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.`);
88
+ }
80
89
  async function generateTanstackRouteArtifacts(opts) {
90
+ await writeRouteMetadataManifest(opts);
81
91
  const [{ createRunOptions }, { cli }, utils] = await Promise.all([
82
92
  import("@modern-js/app-tools/cli/run"),
83
93
  import("@modern-js/plugin/cli"),
@@ -95,9 +105,10 @@ async function generateTanstackRouteArtifacts(opts) {
95
105
  version: opts.version || process.env.MODERN_JS_VERSION || '0.0.0-routes-generate',
96
106
  internalPlugins: utils.INTERNAL_RUNTIME_PLUGINS
97
107
  });
98
- await cli.init({
108
+ const { appContext } = await cli.init({
99
109
  ...runOptions,
100
110
  command: 'build'
101
111
  });
112
+ await appContext.hooks.onBeforeExit.call();
102
113
  }
103
- export { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
114
+ export { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
@@ -1,20 +1,15 @@
1
1
  import node_path from "node:path";
2
2
  import { NESTED_ROUTE_SPEC_FILE, filterRoutesForServer } from "@modern-js/utils";
3
- import { generateTanstackRouteArtifacts, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
3
+ import { generateTanstackRouteArtifacts, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries } from "./artifacts.mjs";
4
4
  import { createTanstackRsbuildRouteSplittingProfile, resolveTanstackRouteCodeSplittingEnabled } from "./routeSplitting.mjs";
5
- import { __webpack_require__ } from "../rslib-runtime~1.mjs";
6
- __webpack_require__.add({
7
- 267 (module) {
8
- module.exports = require("@modern-js/runtime/cli");
9
- }
10
- });
5
+ import { TANSTACK_SERVER_ROUTES_FILE, createTanstackRscRouteLayerPlugin } from "./rscRouteLayer.mjs";
11
6
  const DEFAULT_ROUTES_DIR = 'routes';
12
7
  const DEFAULT_GENERATED_DIR_NAME = 'modern-tanstack';
13
8
  const ENTRYPOINTS_KEY = '@modern-js/plugin-tanstack';
14
9
  let runtimeRouterCli;
15
10
  function getRuntimeRouterCli() {
16
11
  if (runtimeRouterCli) return runtimeRouterCli;
17
- const cli = __webpack_require__(267);
12
+ const cli = require("@modern-js/runtime/cli");
18
13
  if (cli.handleGeneratorEntryCode && cli.getEntrypointRoutesDir && cli.getEntrypointRoutesOwner && cli.updateNestedRoutesSpec) {
19
14
  runtimeRouterCli = cli;
20
15
  return runtimeRouterCli;
@@ -96,6 +91,13 @@ function tanstackRouterPlugin(options = {}) {
96
91
  }));
97
92
  api.config(()=>({
98
93
  ...routeSplittingProfile.defaultConfig,
94
+ builderPlugins: [
95
+ createTanstackRscRouteLayerPlugin(()=>({
96
+ entryNames: api.getAppContext().entrypoints.filter(isTanstackEntrypoint).map((entrypoint)=>entrypoint.entryName),
97
+ internalDirectory: api.getAppContext().internalDirectory,
98
+ rsc: api.getNormalizedConfig().server.rsc
99
+ }))
100
+ ],
99
101
  source: {
100
102
  include: [
101
103
  /[\\/]node_modules[\\/]@tanstack[\\/]react-router[\\/]/,
@@ -122,7 +124,7 @@ function tanstackRouterPlugin(options = {}) {
122
124
  hydrateRscClientRoutes: true,
123
125
  includeRouteServerLoadersInSsrEntry: false,
124
126
  isolateRouteDataInRscLayer: true,
125
- serverRoutesFileName: 'tanstack-routes.server.js'
127
+ serverRoutesFileName: TANSTACK_SERVER_ROUTES_FILE
126
128
  });
127
129
  await writeTanstackRouterTypesForEntries({
128
130
  appContext: api.getAppContext(),
@@ -146,7 +148,7 @@ function tanstackRouterPlugin(options = {}) {
146
148
  hydrateRscClientRoutes: true,
147
149
  includeRouteServerLoadersInSsrEntry: false,
148
150
  isolateRouteDataInRscLayer: true,
149
- serverRoutesFileName: 'tanstack-routes.server.js'
151
+ serverRoutesFileName: TANSTACK_SERVER_ROUTES_FILE
150
152
  });
151
153
  await writeTanstackRouterTypesForEntries({
152
154
  appContext: api.getAppContext(),
@@ -182,4 +184,4 @@ function tanstackRouterPlugin(options = {}) {
182
184
  const src_cli = tanstackRouterPlugin;
183
185
  export { collectCanonicalRoutesForEntry, generateTanstackRouterTypesSourceForEntry } from "./tanstackTypes.mjs";
184
186
  export default src_cli;
185
- export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
187
+ export { createTanstackRsbuildRouteSplittingProfile, generateTanstackRouteArtifacts, resolveTanstackRouteCodeSplittingEnabled, tanstackRouterPlugin, writeRouteMetadataManifest, writeTanstackRegisterFile, writeTanstackRouterTypesForEntries };
@@ -0,0 +1,71 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import node_path from "node:path";
3
+ const ROUTE_META_FILE_NAME = 'route.meta.ts';
4
+ const ROUTE_METADATA_MANIFEST_FILE_NAME = 'ultramodern-route-metadata.ts';
5
+ const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
6
+ const PRINT_WIDTH = 120;
7
+ const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
8
+ ultramodernRouteMetadata
9
+ .filter((route: { canonicalPath: string }) => route.canonicalPath !== '/')
10
+ .map((route) => [route.canonicalPath, route.localisedPaths]),
11
+ );
12
+ export const ultramodernPublicRoutes = ultramodernRouteMetadata
13
+ .filter((route) => route.public && route.indexable)
14
+ .map((route) => ({
15
+ canonicalPath: route.canonicalPath,
16
+ descriptionKey: route.descriptionKey,
17
+ id: route.id,
18
+ localisedPaths: route.localisedPaths,
19
+ namespace: route.namespace,
20
+ ownerAppId: route.ownerAppId,
21
+ titleKey: route.titleKey,
22
+ ...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
23
+ }));
24
+ export const ultramodernRouteConfig = {
25
+ authoring: 'colocated-route-meta',
26
+ generatedManifest: true,
27
+ localisedUrls: ultramodernLocalisedUrls,
28
+ namespace: ultramodernRouteNamespace,
29
+ publicRoutes: ultramodernPublicRoutes,
30
+ routes: ultramodernRouteMetadata,
31
+ source: 'route-owned',
32
+ } as const;
33
+ `;
34
+ const compareCodeUnits = (left, right)=>left < right ? -1 : left > right ? 1 : 0;
35
+ const quoteSpecifier = (specifier)=>specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
36
+ function renderRouteMetadataManifest(routeMetaFiles) {
37
+ const specifiers = routeMetaFiles.map((file)=>`./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`).sort(compareCodeUnits);
38
+ if (0 === specifiers.length) return null;
39
+ const bindings = specifiers.map((_, index)=>`route${index}`);
40
+ const imports = specifiers.map((specifier, index)=>`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`);
41
+ const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
42
+ const routes = inlineRoutes.length <= PRINT_WIDTH ? inlineRoutes : `export const ultramodernRouteMetadata = [\n${bindings.map((binding)=>` ${binding},\n`).join('')}] as const;`;
43
+ return `${ROUTE_METADATA_MANIFEST_HEADER}
44
+ ${imports.join('\n')}
45
+
46
+ export const ultramodernRouteNamespace = route0.namespace;
47
+ ${routes}
48
+ ${MANIFEST_BODY}`;
49
+ }
50
+ async function findRouteMetaFiles(routesDirectory) {
51
+ const files = [];
52
+ const visit = async (relativeDirectory)=>{
53
+ const entries = await readdir(node_path.join(routesDirectory, relativeDirectory), {
54
+ withFileTypes: true
55
+ });
56
+ for (const entry of entries){
57
+ const relativePath = node_path.join(relativeDirectory, entry.name);
58
+ if (entry.isDirectory()) await visit(relativePath);
59
+ else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) files.push(relativePath);
60
+ }
61
+ };
62
+ try {
63
+ await visit('');
64
+ } catch (error) {
65
+ const { code, path: missingPath } = error;
66
+ if ('ENOENT' === code && missingPath === routesDirectory) return [];
67
+ throw error;
68
+ }
69
+ return files;
70
+ }
71
+ export { ROUTE_METADATA_MANIFEST_FILE_NAME, ROUTE_METADATA_MANIFEST_HEADER, ROUTE_META_FILE_NAME, findRouteMetaFiles, renderRouteMetadataManifest };
@@ -0,0 +1,35 @@
1
+ import node_path from "node:path";
2
+ const TANSTACK_SERVER_ROUTES_FILE = 'tanstack-routes.server.js';
3
+ function exactResource(...filenames) {
4
+ const escaped = filenames.map((filename)=>filename.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'));
5
+ return new RegExp(`^(?:${escaped.join('|')})(?:\\?.*)?$`, 'u');
6
+ }
7
+ function createTanstackRscRouteLayerPlugin(getContext) {
8
+ return {
9
+ name: 'modern:tanstack:rsc-route-layer',
10
+ setup (api) {
11
+ api.modifyRspackConfig({
12
+ order: 'post',
13
+ handler (config, { environment, rspack, target }) {
14
+ const { entryNames, internalDirectory, rsc } = getContext();
15
+ if (false === rsc || void 0 === rsc || 0 === entryNames.length) return;
16
+ const rscServerEnvironment = 'object' == typeof rsc ? rsc.environments?.server ?? 'server' : 'server';
17
+ if ('node' !== target && 'node' !== config.target && environment.name !== rscServerEnvironment) return;
18
+ config.module ??= {};
19
+ config.module.rules ??= [];
20
+ config.plugins ??= [];
21
+ for (const entryName of entryNames){
22
+ const routes = node_path.resolve(internalDirectory, entryName, 'routes.js');
23
+ const serverRoutes = node_path.resolve(internalDirectory, entryName, TANSTACK_SERVER_ROUTES_FILE);
24
+ config.plugins.push(new rspack.NormalModuleReplacementPlugin(exactResource(routes), serverRoutes));
25
+ config.module.rules.push({
26
+ resource: exactResource(routes, serverRoutes),
27
+ layer: 'server-side-rendering'
28
+ });
29
+ }
30
+ }
31
+ });
32
+ }
33
+ };
34
+ }
35
+ export { TANSTACK_SERVER_ROUTES_FILE, createTanstackRscRouteLayerPlugin };
@@ -1,3 +1,4 @@
1
+ import { describeRouteTree } from "../../shared/routeDescriptor.mjs";
1
2
  import { toTanstackPath } from "./shared.mjs";
2
3
  const LOCALE_PARAM_SEGMENTS = new Set([
3
4
  ':lang',
@@ -44,11 +45,13 @@ function collectCanonicalRoutesForEntry(routes, options = {}) {
44
45
  const key = toTanstackPath(normalized);
45
46
  if (!canonicalParams.has(key)) canonicalParams.set(key, paramsTypeForCanonicalPath(normalized));
46
47
  };
47
- const visit = (route, parentPath)=>{
48
+ const visit = (descriptor, parentPath)=>{
49
+ const { source: route, children } = descriptor;
48
50
  let currentPath = parentPath;
49
- if ('string' == typeof route.modernCanonicalPath) {
51
+ const canonicalPath = route.modernLocalisedRoute?.canonicalPath ?? route.modernCanonicalPath;
52
+ if ('string' == typeof canonicalPath) {
50
53
  hasI18nSurface = true;
51
- currentPath = normalizeJoined(route.modernCanonicalPath);
54
+ currentPath = canonicalPath ? normalizeJoined(canonicalPath) : '';
52
55
  } else if ('string' == typeof route.path && route.path.length > 0) {
53
56
  const segments = route.path.replace(/\[(.+?)\]/g, ':$1').split('/').filter(Boolean);
54
57
  if (localeParamHeuristic && '' === parentPath && LOCALE_PARAM_SEGMENTS.has(segments[0])) {
@@ -57,16 +60,13 @@ function collectCanonicalRoutesForEntry(routes, options = {}) {
57
60
  }
58
61
  currentPath = segments.length ? normalizeJoined(`${parentPath}/${segments.join('/')}`) : parentPath;
59
62
  }
60
- const children = route.children;
61
63
  if (children && children.length > 0) {
62
64
  for (const child of children)visit(child, currentPath);
63
65
  return;
64
66
  }
65
67
  record(currentPath || '/');
66
68
  };
67
- const rootModern = routes.find((route)=>route.isRoot);
68
- const topLevel = rootModern ? rootModern.children ?? [] : routes;
69
- for (const route of topLevel)visit(route, '');
69
+ for (const route of describeRouteTree(routes).children)visit(route, '');
70
70
  if (!hasI18nSurface || 0 === canonicalParams.size) return null;
71
71
  return Object.fromEntries([
72
72
  ...canonicalParams.entries()
@@ -42,14 +42,6 @@ function pickRouteSearchContractModules(route) {
42
42
  loaderDepsPath: 'string' == typeof loaderDepsPath ? loaderDepsPath : null
43
43
  };
44
44
  }
45
- function isPathlessLayout(route) {
46
- const extras = route;
47
- return 'nested' === extras.type && 'boolean' != typeof extras.index && void 0 === extras.path;
48
- }
49
- function isIndexRoute(route) {
50
- const extras = route;
51
- return 'nested' === extras.type && Boolean(extras.index);
52
- }
53
45
  function createRouteStaticDataSnippet(opts) {
54
46
  const staticData = createRouteStaticData({
55
47
  modernRouteId: opts.modernRouteId && quote(opts.modernRouteId),
@@ -60,4 +52,4 @@ function createRouteStaticDataSnippet(opts) {
60
52
  return entries.length > 0 ? `staticData: createRouteStaticData({\n ${entries.map(([key, value])=>`${key}: ${value},`).join('\n ')}\n }),` : null;
61
53
  }
62
54
  export { toTanstackPath } from "../../runtime/routeTree/paths.mjs";
63
- export { createRouteStaticDataSnippet, isIndexRoute, isPathlessLayout, normalizeRelativeImport, pickModernLoaderModule, pickRouteSearchContractModules, quote, resolveFileNoExt };
55
+ export { createRouteStaticDataSnippet, normalizeRelativeImport, pickModernLoaderModule, pickRouteSearchContractModules, quote, resolveFileNoExt };
@@ -1,17 +1,11 @@
1
- import { canonicaliseLocalisedRoutes } from "@modern-js/i18n-runtime-extensions";
2
1
  import { makeLegalIdentifier } from "@modern-js/runtime/cli";
3
2
  import { upath } from "@modern-js/utils";
4
- import { createRouteStaticDataSnippet, isIndexRoute, isPathlessLayout, normalizeRelativeImport, pickModernLoaderModule, pickRouteSearchContractModules, quote, resolveFileNoExt, toTanstackPath } from "./shared.mjs";
3
+ import { describeRouteTree } from "../../shared/routeDescriptor.mjs";
4
+ import { createRouteStaticDataSnippet, normalizeRelativeImport, pickModernLoaderModule, pickRouteSearchContractModules, quote, resolveFileNoExt } from "./shared.mjs";
5
5
  async function generateTanstackRouterTypesSourceForEntry(opts) {
6
- const { appContext, entryName, generatedDirName = 'modern-tanstack', routes: rawRoutes } = opts;
7
- const routes = canonicaliseLocalisedRoutes(rawRoutes);
6
+ const { appContext, entryName, generatedDirName = 'modern-tanstack', routes } = opts;
8
7
  const outDir = upath.join(appContext.srcDirectory, generatedDirName, entryName);
9
- const rootModern = routes.find((r)=>{
10
- const extras = r;
11
- return r && 'nested' === extras.type && extras.isRoot;
12
- });
13
- const rootExtras = rootModern;
14
- const topLevel = rootModern ? rootExtras?.children || [] : routes;
8
+ const { root: rootModern, children: topLevel } = describeRouteTree(routes);
15
9
  const imports = [];
16
10
  const statements = [];
17
11
  const loaderImportMap = new Map();
@@ -28,9 +22,8 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
28
22
  let pendingImportName = componentImportMap.get(componentPath);
29
23
  if (!pendingImportName) {
30
24
  pendingImportName = (async ()=>{
31
- const resolvedNoExt = await resolveRouteModuleNoExt(componentPath);
32
- if (!resolvedNoExt) return null;
33
- const relImport = normalizeRelativeImport(upath.relative(outDir, resolvedNoExt));
25
+ const relImport = await resolveRouteImport(componentPath);
26
+ if (!relImport) return null;
34
27
  const componentName = `component_${componentIndex++}`;
35
28
  imports.push(`import ${componentName} from ${quote(relImport)};`);
36
29
  return componentName;
@@ -39,14 +32,15 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
39
32
  }
40
33
  return pendingImportName;
41
34
  };
42
- const resolveRouteModuleNoExt = async (aliasedNoExtPath)=>{
35
+ const resolveRouteImport = async (aliasedNoExtPath)=>{
43
36
  const prefix = `${appContext.internalSrcAlias}/`;
44
37
  let absNoExt;
45
38
  if (aliasedNoExtPath.startsWith(prefix)) {
46
39
  const rel = aliasedNoExtPath.slice(prefix.length);
47
40
  absNoExt = upath.join(appContext.srcDirectory, rel);
48
41
  } else absNoExt = upath.isAbsolute(aliasedNoExtPath) ? aliasedNoExtPath : upath.join(appContext.srcDirectory, aliasedNoExtPath);
49
- return resolveFileNoExt(absNoExt);
42
+ const resolvedNoExt = await resolveFileNoExt(absNoExt);
43
+ return resolvedNoExt ? normalizeRelativeImport(upath.relative(outDir, resolvedNoExt)) : null;
50
44
  };
51
45
  const getImportNamesForLoader = async (aliasedNoExtPath, inline, hasAction)=>{
52
46
  const key = `${inline ? 'inline' : 'default'}:${hasAction ? 'action' : 'loader'}:${aliasedNoExtPath}`;
@@ -55,9 +49,8 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
55
49
  loaderName: existing,
56
50
  actionName: hasAction ? existing.replace(/^loader_/, 'action_') : null
57
51
  };
58
- const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
59
- if (!resolvedNoExt) return null;
60
- const relImport = normalizeRelativeImport(upath.relative(outDir, resolvedNoExt));
52
+ const relImport = await resolveRouteImport(aliasedNoExtPath);
53
+ if (!relImport) return null;
61
54
  const importName = `loader_${loaderIndex++}`;
62
55
  const actionName = hasAction ? importName.replace(/^loader_/, 'action_') : null;
63
56
  if (inline) {
@@ -77,9 +70,8 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
77
70
  const key = `${exportName}:${aliasedNoExtPath}`;
78
71
  const existing = searchContractImportMap.get(key);
79
72
  if (existing) return existing;
80
- const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
81
- if (!resolvedNoExt) return null;
82
- const relImport = normalizeRelativeImport(upath.relative(outDir, resolvedNoExt));
73
+ const relImport = await resolveRouteImport(aliasedNoExtPath);
74
+ if (!relImport) return null;
83
75
  const importName = 'validateSearch' === exportName ? `validateSearch_${validateSearchIndex++}` : `loaderDeps_${loaderDepsIndex++}`;
84
76
  imports.push(`import { ${exportName} as ${importName} } from ${quote(relImport)};`);
85
77
  searchContractImportMap.set(key, importName);
@@ -97,9 +89,7 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
97
89
  const base = id ? makeLegalIdentifier(id) : `r_${routeIndex++}`;
98
90
  return reserveRouteVarName(`route_${base}`);
99
91
  };
100
- const buildRoute = async (opts)=>{
101
- const { parentVar, route } = opts;
102
- const varName = createRouteVarName(route);
92
+ const createRouteOptions = async (route, hasSplat)=>{
103
93
  const routeExtras = route;
104
94
  const loaderInfo = pickModernLoaderModule(route);
105
95
  const routeAction = routeExtras.action;
@@ -109,20 +99,9 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
109
99
  const searchContractInfo = pickRouteSearchContractModules(route);
110
100
  const validateSearchName = searchContractInfo.validateSearchPath ? await getImportNameForSearchContract(searchContractInfo.validateSearchPath, 'validateSearch') : null;
111
101
  const loaderDepsName = searchContractInfo.loaderDepsPath ? await getImportNameForSearchContract(searchContractInfo.loaderDepsPath, 'loaderDeps') : null;
112
- const rawPath = routeExtras.path;
113
- const hasSplat = 'string' == typeof rawPath && rawPath.includes('*');
114
- const routeOpts = [
115
- `getParentRoute: () => ${parentVar},`
116
- ];
102
+ const routeOpts = [];
117
103
  const componentName = await getImportNameForComponent(routeExtras._component);
118
104
  if (componentName) routeOpts.push(`component: ${componentName},`);
119
- if (isPathlessLayout(route)) {
120
- const id = routeExtras.id;
121
- routeOpts.push(`id: ${quote(id || 'pathless')},`);
122
- } else {
123
- const p = isIndexRoute(route) ? '/' : toTanstackPath(rawPath || '');
124
- routeOpts.push(`path: ${quote(p)},`);
125
- }
126
105
  if (loaderName) routeOpts.push(`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`);
127
106
  if (validateSearchName) routeOpts.push(`validateSearch: ${validateSearchName},`);
128
107
  if (loaderDepsName) routeOpts.push(`loaderDeps: ${loaderDepsName},`);
@@ -132,39 +111,29 @@ async function generateTanstackRouterTypesSourceForEntry(opts) {
132
111
  actionName
133
112
  });
134
113
  if (staticDataSnippet) routeOpts.push(staticDataSnippet);
135
- const children = routeExtras.children;
114
+ return routeOpts;
115
+ };
116
+ const buildRoute = async (descriptor, parentVar)=>{
117
+ const { source: route, location, hasSplat, children } = descriptor;
118
+ const varName = createRouteVarName(route);
119
+ const routeOpts = [
120
+ `getParentRoute: () => ${parentVar},`,
121
+ ...Object.entries(location).map(([key, value])=>`${key}: ${quote(value)},`),
122
+ ...await createRouteOptions(route, hasSplat)
123
+ ];
136
124
  const hasChildren = Boolean(children && children.length > 0);
137
125
  const routeCtorVarName = hasChildren ? reserveRouteVarName(`${varName}__base`) : varName;
138
126
  statements.push(`const ${routeCtorVarName} = createRoute({\n ${routeOpts.join('\n ')}\n});`);
139
127
  if (children && children.length > 0) {
140
128
  const childVars = [];
141
- for (const child of children)childVars.push(await buildRoute({
142
- parentVar: routeCtorVarName,
143
- route: child
144
- }));
129
+ for (const child of children)childVars.push(await buildRoute(child, routeCtorVarName));
145
130
  statements.push(`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`);
146
131
  }
147
132
  return varName;
148
133
  };
149
- const rootLoaderInfo = rootModern ? pickModernLoaderModule(rootModern) : null;
150
- const rootAction = rootExtras?.action;
151
- const rootLoaderImports = rootLoaderInfo?.loaderPath ? await getImportNamesForLoader(rootLoaderInfo.loaderPath, rootLoaderInfo.inline, Boolean(rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath)) : null;
152
- const rootLoaderName = rootLoaderImports?.loaderName || null;
153
- const rootActionName = rootLoaderImports?.actionName || null;
154
- const rootSearchContractInfo = rootModern ? pickRouteSearchContractModules(rootModern) : null;
155
- const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath ? await getImportNameForSearchContract(rootSearchContractInfo.validateSearchPath, 'validateSearch') : null;
156
- const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath ? await getImportNameForSearchContract(rootSearchContractInfo.loaderDepsPath, 'loaderDeps') : null;
157
134
  const topLevelVars = [];
158
- for (const route of topLevel)topLevelVars.push(await buildRoute({
159
- parentVar: 'rootRoute',
160
- route
161
- }));
162
- const rootOpts = [];
163
- const rootComponentName = await getImportNameForComponent(rootExtras?._component);
164
- if (rootComponentName) rootOpts.push(`component: ${rootComponentName},`);
165
- if (rootLoaderName) rootOpts.push(`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`);
166
- if (rootValidateSearchName) rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
167
- if (rootLoaderDepsName) rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
135
+ for (const route of topLevel)topLevelVars.push(await buildRoute(route, 'rootRoute'));
136
+ const rootOpts = rootModern ? await createRouteOptions(rootModern, false) : [];
168
137
  const routerGenTs = `// This file is auto-generated by Modern.js. Do not edit manually.
169
138
 
170
139
  import {
@@ -181,11 +150,7 @@ ${imports.join('\n')}
181
150
 
182
151
  export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
183
152
  ${rootOpts.join('\n ')}
184
- ${createRouteStaticDataSnippet({
185
- modernRouteId: rootExtras?.id,
186
- loaderName: rootLoaderName,
187
- actionName: rootActionName
188
- }) || ''}
153
+
189
154
  });
190
155
 
191
156
  ${statements.join('\n\n')}
@@ -1 +1 @@
1
- export { applyRouterRuntimeState, applyRouterServerPrepareResult, createRouterRuntimeState, createRouterServerSnapshot, getRouterRuntimeState, getRouterServerSnapshot } from "@modern-js/runtime-extensions/router-state";
1
+ export { applyRouterRuntimeState, applyRouterServerPrepareResult, createRouterServerSnapshot, getRouterRuntimeState, getRouterServerSnapshot } from "@modern-js/runtime-extensions/router-state";
@@ -36,10 +36,13 @@ function mapSplatParamsForModernLoader(params, hasSplat) {
36
36
  return rest;
37
37
  }
38
38
  function getLoaderSignal(ctx) {
39
- const abortSignal = ctx?.abortController?.signal;
40
- if (abortSignal instanceof AbortSignal) return abortSignal;
41
- if (ctx?.signal instanceof AbortSignal) return ctx.signal;
42
- return new AbortController().signal;
39
+ const matchSignal = ctx?.abortController?.signal ?? ctx?.signal;
40
+ const requestSignal = ctx?.context?.request?.signal;
41
+ const signals = [
42
+ matchSignal,
43
+ requestSignal
44
+ ].filter((signal)=>signal instanceof AbortSignal);
45
+ return AbortSignal.any(signals);
43
46
  }
44
47
  function getLoaderHref(ctx) {
45
48
  if ('string' == typeof ctx?.location) return ctx.location;
@@ -101,4 +104,4 @@ function modernLoaderToTanstack(opts, modernLoader) {
101
104
  };
102
105
  }
103
106
  export { createRouteStaticData } from "../shared/routeStaticData.mjs";
104
- export { getLoaderHref, handleModernLoaderError, handleModernLoaderResult, isAbsoluteUrl, isRedirectResponse, isResponse, isTanstackRedirect, mapSplatParamsForModernLoader, modernLoaderToTanstack, throwTanstackRedirect };
107
+ export { getLoaderHref, getLoaderSignal, handleModernLoaderError, handleModernLoaderResult, isAbsoluteUrl, isRedirectResponse, isResponse, isTanstackRedirect, mapSplatParamsForModernLoader, modernLoaderToTanstack, throwTanstackRedirect };
@@ -0,0 +1,64 @@
1
+ import { Link } from "./prefetchLink.mjs";
2
+ function createTanstackNavigation(router) {
3
+ let location;
4
+ let matches;
5
+ let snapshot;
6
+ return {
7
+ Link: Link,
8
+ getSnapshot () {
9
+ const state = router.state;
10
+ if (location !== state.location || void 0 === matches || matches.length !== state.matches.length || matches.some((match, index)=>match.params !== state.matches[index]?.params)) {
11
+ location = state.location;
12
+ matches = state.matches;
13
+ snapshot = {
14
+ location: {
15
+ pathname: location.pathname,
16
+ search: location.searchStr,
17
+ hash: '' !== location.hash ? `#${location.hash.replace(/^#/, '')}` : ''
18
+ },
19
+ params: Object.assign({}, ...matches.map((match)=>match.params))
20
+ };
21
+ }
22
+ return snapshot;
23
+ },
24
+ subscribe (listener) {
25
+ const stops = [
26
+ 'onBeforeNavigate',
27
+ 'onBeforeLoad',
28
+ 'onLoad',
29
+ 'onResolved'
30
+ ].map((event)=>router.subscribe(event, listener));
31
+ return ()=>{
32
+ for (const stop of stops)stop();
33
+ };
34
+ },
35
+ navigate (href, options) {
36
+ return router.navigate({
37
+ to: href,
38
+ replace: options?.replace,
39
+ ...options?.state === void 0 ? {} : {
40
+ state: options.state
41
+ }
42
+ });
43
+ },
44
+ createLinkProps (target) {
45
+ const preload = void 0 !== target.preload ? target.preload : 'none' === target.prefetch ? false : target.prefetch;
46
+ return {
47
+ to: target.pathname,
48
+ ...void 0 !== target.search ? {
49
+ search: target.search
50
+ } : {},
51
+ ...void 0 !== target.hash && '' !== target.hash ? {
52
+ hash: target.hash
53
+ } : {},
54
+ ...void 0 === target.hashScrollIntoView ? {} : {
55
+ hashScrollIntoView: target.hashScrollIntoView
56
+ },
57
+ ...void 0 === preload ? {} : {
58
+ preload
59
+ }
60
+ };
61
+ }
62
+ };
63
+ }
64
+ export { createTanstackNavigation };
@@ -1,5 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { InternalRuntimeContext, getGlobalEnableRsc } from "@modern-js/runtime/context";
3
+ import { createRouterStatePlugin } from "@modern-js/runtime-extensions/router-state-plugin";
3
4
  import { normalizePathname } from "@modern-js/runtime-utils/url";
4
5
  import { RouterProvider, createBrowserHistory, createHashHistory, createRouter, useLocation, useNavigate, useRouter } from "@tanstack/react-router";
5
6
  import { useContext, useMemo } from "react";
@@ -10,6 +11,7 @@ import { ModernRouterClient, getTanstackSsrHydrationPromise, hasTanstackSsrHydra
10
11
  import { routerProviderRegistryHooks } from "./hooks.mjs";
11
12
  import { wrapTanstackSsrHydrationBoundary } from "./hydrationBoundary.mjs";
12
13
  import { applyRouterRuntimeState } from "./lifecycle.mjs";
14
+ import { createTanstackNavigation } from "./navigation.mjs";
13
15
  import { createTanstackRouteObjects, getMergedRouterConfig, getTanstackRouteConfig, joinBasename } from "./pluginShared.mjs";
14
16
  import { Link } from "./prefetchLink.mjs";
15
17
  import { useMatches } from "./routeHooks.mjs";
@@ -21,6 +23,9 @@ const tanstackRouterPlugin = (userConfig = {})=>{
21
23
  name: '@modern-js/plugin-router-tanstack',
22
24
  registryHooks: routerProviderRegistryHooks,
23
25
  setup: (api)=>{
26
+ createRouterStatePlugin({
27
+ registryHooks: routerProviderRegistryHooks
28
+ }).setup(api);
24
29
  const hooks = api.getHooks();
25
30
  let cachedRouteObjects;
26
31
  let cachedRouteTree = null;
@@ -142,10 +147,14 @@ const tanstackRouterPlugin = (userConfig = {})=>{
142
147
  runtimeContext
143
148
  ]);
144
149
  if (!router) return App ? /*#__PURE__*/ jsx(App, {}) : null;
150
+ const navigation = useMemo(()=>createTanstackNavigation(router), [
151
+ router
152
+ ]);
145
153
  const runtimeState = applyRouterRuntimeState(runtimeContext, {
146
154
  framework: 'tanstack',
147
155
  basename: _basename,
148
- instance: router
156
+ instance: router,
157
+ navigation
149
158
  });
150
159
  const lifecycleContext = {
151
160
  framework: 'tanstack',