@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.10 → 3.9.0-ultramodern.12

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 (68) hide show
  1. package/dist/cjs/cli/artifacts.js +2 -1
  2. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
  3. package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
  4. package/dist/cjs/cli/tanstackTypes/source.js +28 -63
  5. package/dist/cjs/runtime/lifecycle.js +0 -3
  6. package/dist/cjs/runtime/loaderBridge.js +10 -4
  7. package/dist/cjs/runtime/navigation.js +102 -0
  8. package/dist/cjs/runtime/plugin.js +6 -1
  9. package/dist/cjs/runtime/plugin.node.js +82 -64
  10. package/dist/cjs/runtime/routeTree/construction.js +19 -61
  11. package/dist/cjs/runtime/routeTree/loaders.js +1 -1
  12. package/dist/cjs/runtime/routeTree/paths.js +0 -6
  13. package/dist/cjs/runtime/utils.js +1 -0
  14. package/dist/cjs/shared/routeDescriptor.js +60 -0
  15. package/dist/esm/cli/artifacts.mjs +2 -1
  16. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  17. package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
  18. package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
  19. package/dist/esm/runtime/lifecycle.mjs +1 -1
  20. package/dist/esm/runtime/loaderBridge.mjs +8 -5
  21. package/dist/esm/runtime/navigation.mjs +64 -0
  22. package/dist/esm/runtime/plugin.mjs +6 -1
  23. package/dist/esm/runtime/plugin.node.mjs +83 -65
  24. package/dist/esm/runtime/routeTree/construction.mjs +19 -61
  25. package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
  26. package/dist/esm/runtime/routeTree/paths.mjs +1 -4
  27. package/dist/esm/runtime/utils.mjs +1 -0
  28. package/dist/esm/shared/routeDescriptor.mjs +22 -0
  29. package/dist/esm-node/cli/artifacts.mjs +2 -1
  30. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  31. package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
  32. package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
  33. package/dist/esm-node/runtime/lifecycle.mjs +1 -1
  34. package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
  35. package/dist/esm-node/runtime/navigation.mjs +65 -0
  36. package/dist/esm-node/runtime/plugin.mjs +6 -1
  37. package/dist/esm-node/runtime/plugin.node.mjs +83 -65
  38. package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
  39. package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
  40. package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
  41. package/dist/esm-node/runtime/utils.mjs +1 -0
  42. package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
  43. package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
  44. package/dist/types/runtime/lifecycle.d.ts +1 -1
  45. package/dist/types/runtime/loaderBridge.d.ts +1 -0
  46. package/dist/types/runtime/navigation.d.ts +4 -0
  47. package/dist/types/runtime/routeTree/paths.d.ts +0 -2
  48. package/dist/types/shared/routeDescriptor.d.ts +26 -0
  49. package/package.json +12 -12
  50. package/src/cli/artifacts.ts +6 -1
  51. package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
  52. package/src/cli/tanstackTypes/shared.ts +0 -14
  53. package/src/cli/tanstackTypes/source.ts +43 -119
  54. package/src/runtime/lifecycle.ts +0 -1
  55. package/src/runtime/loaderBridge.ts +9 -9
  56. package/src/runtime/navigation.ts +78 -0
  57. package/src/runtime/plugin.node.tsx +111 -89
  58. package/src/runtime/plugin.tsx +6 -0
  59. package/src/runtime/routeTree/construction.ts +32 -112
  60. package/src/runtime/routeTree/loaders.ts +2 -4
  61. package/src/runtime/routeTree/paths.ts +0 -6
  62. package/src/runtime/utils.tsx +1 -0
  63. package/src/shared/routeDescriptor.ts +46 -0
  64. package/tests/router/generateRouteArtifacts.test.ts +36 -2
  65. package/tests/router/localisedIdentity.test.tsx +1 -0
  66. package/tests/router/prefetchLink.test.tsx +29 -0
  67. package/tests/router/serverPlugin.test.ts +145 -0
  68. package/tests/router/tanstackTypes.test.ts +120 -2
@@ -0,0 +1,65 @@
1
+ import "node:module";
2
+ import { Link } from "./prefetchLink.mjs";
3
+ function createTanstackNavigation(router) {
4
+ let location;
5
+ let matches;
6
+ let snapshot;
7
+ return {
8
+ Link: Link,
9
+ getSnapshot () {
10
+ const state = router.state;
11
+ if (location !== state.location || void 0 === matches || matches.length !== state.matches.length || matches.some((match, index)=>match.params !== state.matches[index]?.params)) {
12
+ location = state.location;
13
+ matches = state.matches;
14
+ snapshot = {
15
+ location: {
16
+ pathname: location.pathname,
17
+ search: location.searchStr,
18
+ hash: '' !== location.hash ? `#${location.hash.replace(/^#/, '')}` : ''
19
+ },
20
+ params: Object.assign({}, ...matches.map((match)=>match.params))
21
+ };
22
+ }
23
+ return snapshot;
24
+ },
25
+ subscribe (listener) {
26
+ const stops = [
27
+ 'onBeforeNavigate',
28
+ 'onBeforeLoad',
29
+ 'onLoad',
30
+ 'onResolved'
31
+ ].map((event)=>router.subscribe(event, listener));
32
+ return ()=>{
33
+ for (const stop of stops)stop();
34
+ };
35
+ },
36
+ navigate (href, options) {
37
+ return router.navigate({
38
+ to: href,
39
+ replace: options?.replace,
40
+ ...options?.state === void 0 ? {} : {
41
+ state: options.state
42
+ }
43
+ });
44
+ },
45
+ createLinkProps (target) {
46
+ const preload = void 0 !== target.preload ? target.preload : 'none' === target.prefetch ? false : target.prefetch;
47
+ return {
48
+ to: target.pathname,
49
+ ...void 0 !== target.search ? {
50
+ search: target.search
51
+ } : {},
52
+ ...void 0 !== target.hash && '' !== target.hash ? {
53
+ hash: target.hash
54
+ } : {},
55
+ ...void 0 === target.hashScrollIntoView ? {} : {
56
+ hashScrollIntoView: target.hashScrollIntoView
57
+ },
58
+ ...void 0 === preload ? {} : {
59
+ preload
60
+ }
61
+ };
62
+ }
63
+ };
64
+ }
65
+ export { createTanstackNavigation };
@@ -11,6 +11,7 @@ import { ModernRouterClient, getTanstackSsrHydrationPromise, hasTanstackSsrHydra
11
11
  import { routerProviderRegistryHooks } from "./hooks.mjs";
12
12
  import { wrapTanstackSsrHydrationBoundary } from "./hydrationBoundary.mjs";
13
13
  import { applyRouterRuntimeState } from "./lifecycle.mjs";
14
+ import { createTanstackNavigation } from "./navigation.mjs";
14
15
  import { createTanstackRouteObjects, getMergedRouterConfig, getTanstackRouteConfig, joinBasename } from "./pluginShared.mjs";
15
16
  import { Link } from "./prefetchLink.mjs";
16
17
  import { useMatches } from "./routeHooks.mjs";
@@ -143,10 +144,14 @@ const tanstackRouterPlugin = (userConfig = {})=>{
143
144
  runtimeContext
144
145
  ]);
145
146
  if (!router) return App ? /*#__PURE__*/ jsx(App, {}) : null;
147
+ const navigation = useMemo(()=>createTanstackNavigation(router), [
148
+ router
149
+ ]);
146
150
  const runtimeState = applyRouterRuntimeState(runtimeContext, {
147
151
  framework: 'tanstack',
148
152
  basename: _basename,
149
- instance: router
153
+ instance: router,
154
+ navigation
150
155
  });
151
156
  const lifecycleContext = {
152
157
  framework: 'tanstack',
@@ -9,7 +9,8 @@ import { useContext } from "react";
9
9
  import { createModernBasepathRewrite } from "./basepathRewrite.mjs";
10
10
  import { routerProviderRegistryHooks } from "./hooks.mjs";
11
11
  import { wrapTanstackSsrHydrationBoundary } from "./hydrationBoundary.mjs";
12
- import { applyRouterServerPrepareResult, createRouterServerSnapshot, getRouterRuntimeState } from "./lifecycle.mjs";
12
+ import { applyRouterRuntimeState, applyRouterServerPrepareResult, getRouterRuntimeState, getRouterServerSnapshot } from "./lifecycle.mjs";
13
+ import { createTanstackNavigation } from "./navigation.mjs";
13
14
  import { createTanstackRouteObjects, getTanstackRouteConfig, joinBasename } from "./pluginShared.mjs";
14
15
  import { Link } from "./prefetchLink.mjs";
15
16
  import { createRouteTreeFromRouteObjects, getModernRouteIdsFromMatches } from "./routeTree/index.mjs";
@@ -88,76 +89,93 @@ const tanstackRouterPlugin = (userConfig = {})=>{
88
89
  });
89
90
  const serverRouter = tanstackRouter;
90
91
  if (enableRsc) setTanstackRscRouter(serverRouter);
91
- await attachServerSsrUtils(serverRouter);
92
- const end = time();
93
- try {
94
- await tanstackRouter.load({
95
- sync: true
96
- });
97
- } finally{
98
- const cost = end();
99
- context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
100
- }
101
- const serverLoadResult = serverRouter._serverResult;
102
- if (!serverLoadResult) {
103
- try {
104
- serverRouter.serverSsr?.cleanup?.();
105
- } catch {}
106
- throw new Error('TanStack Router completed an SSR load without a server result.');
107
- }
108
- if ('redirect' === serverLoadResult.type) {
109
- const { redirect } = serverLoadResult;
110
- try {
111
- serverRouter.serverSsr?.cleanup?.();
112
- } catch {}
113
- return interrupt(isRSCNavigation ? handleTanstackRscRedirect(redirect.headers, _basename, redirect.status) : redirect);
114
- }
115
- const routerErrors = collectRouterErrors(tanstackRouter);
116
- if (routerErrors && 'clientRender' === loaderFailureMode) {
117
- context.ssrContext?.response?.status(200);
118
- try {
119
- serverRouter.serverSsr?.cleanup?.();
120
- } catch {}
121
- throw Object.values(routerErrors)[0];
122
- }
123
- await preloadMatchedRouteComponents(serverRouter);
124
- context.ssrContext?.response?.status(serverLoadResult.status);
125
- await serverRouter.serverSsr?.dehydrate?.();
126
- if (enableRsc) {
127
- if (isRSCNavigation) await waitForRouterSerialization(serverRouter);
128
- setTanstackRscServerPayload(createTanstackRscServerPayload(serverRouter, {
129
- omitClientLoaderData: isRSCNavigation
130
- }));
131
- }
132
- const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
133
- const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
134
- const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
135
- const routerServerSnapshot = createRouterServerSnapshot({
92
+ let disposed = false;
93
+ let prepared = false;
94
+ const onAbort = ()=>controller.abort(rawRequest.signal.reason);
95
+ const cleanup = ()=>{
96
+ if (disposed) return;
97
+ disposed = true;
98
+ rawRequest.signal.removeEventListener('abort', onAbort);
99
+ controller.abort();
100
+ serverRouter.serverSsr?.cleanup?.();
101
+ };
102
+ applyRouterRuntimeState(context, {
136
103
  framework: 'tanstack',
137
104
  basename: _basename,
138
- statusCode: serverLoadResult.status,
139
- errors: routerErrors,
140
- matchedRouteIds,
141
- hydrationScripts
105
+ instance: serverRouter,
106
+ cleanup
107
+ });
108
+ rawRequest.signal.addEventListener('abort', onAbort, {
109
+ once: true
142
110
  });
143
- const runtimeContext = applyRouterServerPrepareResult(context, {
144
- snapshot: routerServerSnapshot,
145
- cleanup: ()=>serverRouter.serverSsr?.cleanup?.(),
146
- state: {
111
+ try {
112
+ rawRequest.signal.throwIfAborted();
113
+ await attachServerSsrUtils(serverRouter);
114
+ rawRequest.signal.throwIfAborted();
115
+ const end = time();
116
+ try {
117
+ await tanstackRouter.load({
118
+ sync: true
119
+ });
120
+ } finally{
121
+ const cost = end();
122
+ context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
123
+ }
124
+ rawRequest.signal.throwIfAborted();
125
+ const serverLoadResult = serverRouter._serverResult;
126
+ if (!serverLoadResult) throw new Error('TanStack Router completed an SSR load without a server result.');
127
+ if ('redirect' === serverLoadResult.type) {
128
+ const { redirect } = serverLoadResult;
129
+ return interrupt(isRSCNavigation ? handleTanstackRscRedirect(redirect.headers, _basename, redirect.status) : redirect);
130
+ }
131
+ const routerErrors = collectRouterErrors(tanstackRouter);
132
+ if (routerErrors && 'clientRender' === loaderFailureMode) {
133
+ context.ssrContext?.response?.status(200);
134
+ throw Object.values(routerErrors)[0];
135
+ }
136
+ await preloadMatchedRouteComponents(serverRouter);
137
+ context.ssrContext?.response?.status(serverLoadResult.status);
138
+ await serverRouter.serverSsr?.dehydrate?.();
139
+ if (enableRsc) {
140
+ if (isRSCNavigation) await waitForRouterSerialization(serverRouter);
141
+ setTanstackRscServerPayload(createTanstackRscServerPayload(serverRouter, {
142
+ omitClientLoaderData: isRSCNavigation
143
+ }));
144
+ }
145
+ const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
146
+ const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
147
+ const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
148
+ const routerServerSnapshot = {
147
149
  framework: 'tanstack',
148
150
  basename: _basename,
149
- instance: serverRouter,
150
- hydrationScripts,
151
+ statusCode: serverLoadResult.status,
152
+ errors: routerErrors,
151
153
  matchedRouteIds,
152
- serverSnapshot: routerServerSnapshot
153
- }
154
- });
155
- hooks.onAfterCreateRouter.call({
156
- ...routerLifecycleContext,
157
- router: serverRouter,
158
- serverSnapshot: routerServerSnapshot,
159
- runtimeContext
160
- });
154
+ hydrationScripts
155
+ };
156
+ const runtimeContext = applyRouterServerPrepareResult(context, {
157
+ snapshot: routerServerSnapshot,
158
+ cleanup,
159
+ state: {
160
+ framework: 'tanstack',
161
+ basename: _basename,
162
+ instance: serverRouter,
163
+ navigation: createTanstackNavigation(tanstackRouter)
164
+ }
165
+ });
166
+ rawRequest.signal.throwIfAborted();
167
+ hooks.onAfterCreateRouter.call({
168
+ ...routerLifecycleContext,
169
+ router: serverRouter,
170
+ serverSnapshot: getRouterServerSnapshot(runtimeContext),
171
+ runtimeContext
172
+ });
173
+ prepared = true;
174
+ } finally{
175
+ if (!prepared) try {
176
+ cleanup();
177
+ } catch {}
178
+ }
161
179
  });
162
180
  api.wrapRoot((App)=>{
163
181
  const getRouteApp = ()=>{
@@ -1,24 +1,20 @@
1
1
  import "node:module";
2
- import { canonicaliseLocalisedRoutes } from "@modern-js/i18n-runtime-extensions";
3
2
  import { DefaultNotFound } from "@modern-js/runtime/context";
4
3
  import { rootRouteId } from "@tanstack/react-router";
4
+ import { describeRouteTree } from "../../shared/routeDescriptor.mjs";
5
5
  import { createRouteStaticData } from "../../shared/routeStaticData.mjs";
6
6
  import { withModernRouteMatchContext } from "../outlet.mjs";
7
7
  import { toErrorComponent, toPendingComponent, toRouteComponent } from "./components.mjs";
8
8
  import { createModernShouldReload, wrapRouteObjectLoader } from "./loaders.mjs";
9
- import { isRouteObjectPathlessLayout, toTanstackPath } from "./paths.mjs";
10
9
  import { mergeModernRouteHandle } from "./staticData.mjs";
11
10
  import { createTanstackRootRoute, createTanstackRoute, wrapRouteComponentWithModernContext } from "./tanstackRoutes.mjs";
12
- function createRouteFromRouteObject(opts) {
13
- const { options = {}, parent, pathlessFallbackId = 'pathless', routeObject } = opts;
11
+ function createRouteOptions(routeObject, options) {
14
12
  const modernRouteObject = routeObject;
15
13
  const revalidationState = {};
16
14
  const shouldRevalidate = modernRouteObject.shouldRevalidate;
17
15
  const shouldReload = createModernShouldReload(shouldRevalidate, revalidationState);
18
- const stableFallbackId = routeObject.id || modernRouteObject.file || pathlessFallbackId;
19
16
  const component = toRouteComponent(routeObject);
20
17
  const base = {
21
- getParentRoute: ()=>parent,
22
18
  component,
23
19
  pendingComponent: toPendingComponent(routeObject),
24
20
  errorComponent: toErrorComponent(routeObject),
@@ -39,67 +35,29 @@ function createRouteFromRouteObject(opts) {
39
35
  };
40
36
  if (modernRouteObject.inValidSSRRoute) base.ssr = false;
41
37
  if (shouldReload) base.shouldReload = shouldReload;
42
- if (isRouteObjectPathlessLayout(routeObject)) base.id = stableFallbackId;
43
- else base.path = routeObject.index ? '/' : toTanstackPath(routeObject.path || '');
44
- const route = createTanstackRoute(base);
45
- wrapRouteComponentWithModernContext(route, component, routeObject.id);
46
- const children = routeObject.children;
47
- if (children && children.length > 0) {
48
- const childRoutes = children.map((child, index)=>createRouteFromRouteObject({
49
- options,
50
- parent: route,
51
- pathlessFallbackId: `pathless-${index}`,
52
- routeObject: child
53
- }));
54
- route.addChildren(childRoutes);
55
- }
56
- return route;
38
+ return base;
57
39
  }
58
- function getRootLikeRouteObject(routes) {
59
- return routes.find((route)=>'/' === route.path && !route.index);
40
+ function createRouteFromDescriptor(descriptor, parent, options) {
41
+ const { source, location, children } = descriptor;
42
+ const routeOptions = createRouteOptions(source, options);
43
+ const route = createTanstackRoute({
44
+ ...routeOptions,
45
+ ...location,
46
+ getParentRoute: ()=>parent
47
+ });
48
+ wrapRouteComponentWithModernContext(route, routeOptions.component, source.id);
49
+ if (children.length) route.addChildren(children.map((child)=>createRouteFromDescriptor(child, route, options)));
50
+ return route;
60
51
  }
61
52
  function createRouteTreeFromRouteObjects(routes, options = {}) {
62
- routes = canonicaliseLocalisedRoutes(routes);
63
- const rootLikeRoute = getRootLikeRouteObject(routes);
64
- const rootRevalidationState = {};
65
- const rootShouldRevalidate = rootLikeRoute?.shouldRevalidate;
66
- const rootShouldReload = createModernShouldReload(rootShouldRevalidate, rootRevalidationState);
67
- const rootComponent = rootLikeRoute ? toRouteComponent(rootLikeRoute) : void 0;
53
+ const { root, children } = describeRouteTree(routes);
68
54
  const rootRouteOptions = {
69
- component: rootComponent,
70
- pendingComponent: rootLikeRoute ? toPendingComponent(rootLikeRoute) : void 0,
71
- errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : void 0,
72
- validateSearch: rootLikeRoute?.validateSearch,
73
- loaderDeps: rootLikeRoute?.loaderDeps,
74
- notFoundComponent: DefaultNotFound,
75
- staticData: createRouteStaticData({
76
- modernRouteId: rootLikeRoute?.id,
77
- modernRouteAction: rootLikeRoute?.action,
78
- modernRouteHandle: rootLikeRoute ? mergeModernRouteHandle(rootLikeRoute) : void 0,
79
- modernRouteHasAction: rootLikeRoute?.hasAction || Boolean(rootLikeRoute?.action),
80
- modernRouteHasClientLoader: rootLikeRoute?.hasClientLoader || void 0 !== rootLikeRoute?.clientData,
81
- modernRouteHasLoader: rootLikeRoute?.hasLoader || 'function' == typeof rootLikeRoute?.loader,
82
- modernRouteIsClientComponent: rootLikeRoute?.isClientComponent,
83
- modernRouteLoader: rootLikeRoute?.loader,
84
- modernRouteShouldRevalidate: rootShouldRevalidate
85
- }),
86
- loader: rootLikeRoute ? wrapRouteObjectLoader(rootLikeRoute, rootRevalidationState, options) : void 0
55
+ ...root ? createRouteOptions(root, options) : {},
56
+ notFoundComponent: DefaultNotFound
87
57
  };
88
- if (rootShouldReload) rootRouteOptions.shouldReload = rootShouldReload;
89
- if (rootLikeRoute?.inValidSSRRoute) rootRouteOptions.ssr = false;
90
58
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
91
- if (rootComponent) rootRoute.options.component = withModernRouteMatchContext(rootComponent, rootRouteId);
92
- const topLevel = rootLikeRoute ? [
93
- ...rootLikeRoute.children || [],
94
- ...routes.filter((route)=>route !== rootLikeRoute)
95
- ] : routes;
96
- const childRoutes = topLevel.map((routeObject, index)=>createRouteFromRouteObject({
97
- options,
98
- parent: rootRoute,
99
- pathlessFallbackId: `pathless-${index}`,
100
- routeObject
101
- }));
102
- rootRoute.addChildren(childRoutes);
59
+ if (rootRouteOptions.component) rootRoute.options.component = withModernRouteMatchContext(rootRouteOptions.component, rootRouteId);
60
+ rootRoute.addChildren(children.map((child)=>createRouteFromDescriptor(child, rootRoute, options)));
103
61
  return rootRoute;
104
62
  }
105
63
  function getModernRouteIdsFromMatches(router) {
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
- import { getLoaderHref, handleModernLoaderError, handleModernLoaderResult, mapSplatParamsForModernLoader } from "../loaderBridge.mjs";
2
+ import { getLoaderHref, getLoaderSignal, handleModernLoaderError, handleModernLoaderResult, mapSplatParamsForModernLoader } from "../loaderBridge.mjs";
3
3
  import { isTanstackRscPayloadNavigationEnabled, loadTanstackRscRouteData } from "../rsc/payloadRouter.mjs";
4
4
  function isModernDeferredData(value) {
5
5
  if (!value || 'object' != typeof value) return false;
@@ -50,7 +50,7 @@ function wrapRouteObjectLoader(route, revalidationState, options = {}) {
50
50
  if ('function' == typeof modernRoute.lazyImport) try {
51
51
  await modernRoute.lazyImport();
52
52
  } catch {}
53
- const signal = ctx?.abortController?.signal || ctx?.signal || new AbortController().signal;
53
+ const signal = getLoaderSignal(ctx);
54
54
  const baseRequest = ctx?.context?.request instanceof Request ? ctx.context.request : void 0;
55
55
  const href = getLoaderHref(ctx);
56
56
  const request = void 0 !== baseRequest ? new Request(baseRequest, {
@@ -11,7 +11,4 @@ function toTanstackPath(pathname) {
11
11
  return segment;
12
12
  }).join('/');
13
13
  }
14
- function isRouteObjectPathlessLayout(route) {
15
- return !route.path && !route.index;
16
- }
17
- export { isRouteObjectPathlessLayout, toTanstackPath };
14
+ export { toTanstackPath };
@@ -15,6 +15,7 @@ function withGlobalLayout(Component, globalApp) {
15
15
  function toTanstackRouteObject(route, globalApp) {
16
16
  if ('nested' === route.type) return {
17
17
  modernLocalisedRoute: Reflect.get(route, 'modernLocalisedRoute'),
18
+ isRoot: Reflect.get(route, 'isRoot'),
18
19
  path: route.path,
19
20
  id: route.id,
20
21
  loader: route.loader,
@@ -0,0 +1,23 @@
1
+ import "node:module";
2
+ import { toTanstackPath } from "../runtime/routeTree/paths.mjs";
3
+ function describeRouteTree(routes) {
4
+ const root = routes.find((route)=>true === route.isRoot || '/' === route.path && true !== route.index);
5
+ const describe = (route, index)=>({
6
+ source: route,
7
+ location: 0 === (route.path ?? '').length && true !== route.index ? {
8
+ id: route.id || route.file || `pathless-${index}`
9
+ } : {
10
+ path: true === route.index ? '/' : toTanstackPath(route.path || '')
11
+ },
12
+ hasSplat: Boolean(route.path?.includes('*')),
13
+ children: (route.children ?? []).map(describe)
14
+ });
15
+ return {
16
+ root,
17
+ children: (void 0 !== root ? [
18
+ ...root.children ?? [],
19
+ ...routes.filter((route)=>route !== root)
20
+ ] : routes).map(describe)
21
+ };
22
+ }
23
+ export { describeRouteTree };
@@ -11,8 +11,6 @@ export declare function pickRouteSearchContractModules(route: NestedRouteForCli
11
11
  validateSearchPath: string | null;
12
12
  loaderDepsPath: string | null;
13
13
  };
14
- export declare function isPathlessLayout(route: NestedRouteForCli | PageRoute): boolean;
15
- export declare function isIndexRoute(route: NestedRouteForCli | PageRoute): boolean;
16
14
  export declare function createRouteStaticDataSnippet(opts: {
17
15
  modernRouteId?: string;
18
16
  loaderName?: string | null;
@@ -11,4 +11,4 @@ export type RouterLifecycleContext = NativeRouterLifecycleContext & {
11
11
  cleanup?: () => void | Promise<void>;
12
12
  serverSnapshot?: InternalRouterServerSnapshot;
13
13
  };
14
- export { applyRouterRuntimeState, applyRouterServerPrepareResult, createRouterRuntimeState, createRouterServerSnapshot, getRouterRuntimeState, getRouterServerSnapshot, type RouterLifecyclePhase, } from '@modern-js/runtime-extensions/router-state';
14
+ export { applyRouterRuntimeState, applyRouterServerPrepareResult, createRouterServerSnapshot, getRouterRuntimeState, getRouterServerSnapshot, type RouterLifecyclePhase, } from '@modern-js/runtime-extensions/router-state';
@@ -39,6 +39,7 @@ type LoaderLikeContext = {
39
39
  };
40
40
  params?: Record<string, string>;
41
41
  };
42
+ export declare function getLoaderSignal(ctx: LoaderLikeContext | undefined): AbortSignal;
42
43
  export declare function getLoaderHref(ctx: LoaderLikeContext | undefined): string;
43
44
  export declare function handleModernLoaderResult<LoaderResult>(result: LoaderResult): LoaderResult;
44
45
  export declare function handleModernLoaderError(err: unknown): never;
@@ -0,0 +1,4 @@
1
+ import type { RouterNavigationCapability } from '@modern-js/runtime-extensions/router-state';
2
+ import type { AnyRouter } from '@tanstack/react-router';
3
+ /** Native TanStack navigation, exposed through the provider contract. */
4
+ export declare function createTanstackNavigation(router: AnyRouter): RouterNavigationCapability;
@@ -1,3 +1 @@
1
- import type { RouteObject } from '@modern-js/runtime-utils/router';
2
1
  export declare function toTanstackPath(pathname: string): string;
3
- export declare function isRouteObjectPathlessLayout(route: RouteObject): boolean;
@@ -0,0 +1,26 @@
1
+ type RouteShape = {
2
+ id?: string;
3
+ file?: string;
4
+ path?: string;
5
+ index?: boolean;
6
+ isRoot?: boolean;
7
+ children?: RouteShape[];
8
+ };
9
+ /** Browser-safe structure shared by the executable tree and concrete TS emitter. */
10
+ export type RouteDescriptor<T extends RouteShape> = {
11
+ source: T;
12
+ location: {
13
+ id: string;
14
+ path?: never;
15
+ } | {
16
+ path: string;
17
+ id?: never;
18
+ };
19
+ hasSplat: boolean;
20
+ children: RouteDescriptor<T>[];
21
+ };
22
+ export declare function describeRouteTree<T extends RouteShape>(routes: T[]): {
23
+ root: T | undefined;
24
+ children: RouteDescriptor<T>[];
25
+ };
26
+ export {};
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "tanstack-router"
20
20
  ],
21
- "version": "3.9.0-ultramodern.10",
21
+ "version": "3.9.0-ultramodern.12",
22
22
  "engines": {
23
23
  "node": ">=20"
24
24
  },
@@ -94,19 +94,19 @@
94
94
  }
95
95
  },
96
96
  "dependencies": {
97
- "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.10",
98
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.10",
99
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.10",
100
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.10",
101
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.10",
102
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.10",
97
+ "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.12",
98
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.12",
99
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.12",
100
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.12",
101
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.12",
102
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.12",
103
103
  "@swc/helpers": "^0.5.23",
104
104
  "@tanstack/react-router": "1.170.33",
105
105
  "@tanstack/router-core": "1.171.28"
106
106
  },
107
107
  "peerDependencies": {
108
- "@modern-js/app-tools": "3.9.0-ultramodern.10",
109
- "@modern-js/runtime": "3.9.0-ultramodern.10",
108
+ "@modern-js/app-tools": "3.9.0-ultramodern.12",
109
+ "@modern-js/runtime": "3.9.0-ultramodern.12",
110
110
  "react": "^19.2.8",
111
111
  "react-dom": "^19.2.8",
112
112
  "react-server-dom-rspack": "0.1.0"
@@ -117,9 +117,9 @@
117
117
  }
118
118
  },
119
119
  "devDependencies": {
120
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.10",
121
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.10",
122
- "@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.9.0-ultramodern.10",
120
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.12",
121
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.12",
122
+ "@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.9.0-ultramodern.12",
123
123
  "@rslib/core": "1.0.0",
124
124
  "@scripts/rstest-config": "2.66.0",
125
125
  "@tanstack/history": "1.162.2",
@@ -251,5 +251,10 @@ export async function generateTanstackRouteArtifacts(opts: {
251
251
  internalPlugins: (utils as { INTERNAL_RUNTIME_PLUGINS: unknown })
252
252
  .INTERNAL_RUNTIME_PLUGINS as never,
253
253
  });
254
- await cli.init({ ...runOptions, command: 'build' });
254
+ const { appContext } = await cli.init({ ...runOptions, command: 'build' });
255
+ // Preparation owns the artifact writes; there is no dev server or bundler
256
+ // phase after it. Release plugin resources before the headless caller exits.
257
+ // Failed initialization propagates unchanged: it has not returned a context
258
+ // whose lifecycle this caller can close.
259
+ await appContext.hooks.onBeforeExit.call();
255
260
  }
@@ -1,7 +1,10 @@
1
1
  // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
2
 
3
3
  import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
4
- import { MAIN_ENTRY_NAME } from '@modern-js/utils';
4
+ import {
5
+ describeRouteTree,
6
+ type RouteDescriptor,
7
+ } from '../../shared/routeDescriptor';
5
8
 
6
9
  import { toTanstackPath } from './shared';
7
10
 
@@ -16,6 +19,7 @@ const LOCALE_PARAM_SEGMENTS = new Set([
16
19
 
17
20
  type CanonicalAwareRoute = (NestedRouteForCli | PageRoute) & {
18
21
  modernCanonicalPath?: string;
22
+ modernLocalisedRoute?: { canonicalPath: string };
19
23
  index?: boolean;
20
24
  isRoot?: boolean;
21
25
  children?: CanonicalAwareRoute[];
@@ -101,12 +105,18 @@ export function collectCanonicalRoutesForEntry(
101
105
  }
102
106
  };
103
107
 
104
- const visit = (route: CanonicalAwareRoute, parentPath: string) => {
108
+ const visit = (
109
+ descriptor: RouteDescriptor<CanonicalAwareRoute>,
110
+ parentPath: string,
111
+ ) => {
112
+ const { source: route, children } = descriptor;
105
113
  let currentPath = parentPath;
106
114
 
107
- if (typeof route.modernCanonicalPath === 'string') {
115
+ const canonicalPath =
116
+ route.modernLocalisedRoute?.canonicalPath ?? route.modernCanonicalPath;
117
+ if (typeof canonicalPath === 'string') {
108
118
  hasI18nSurface = true;
109
- currentPath = normalizeJoined(route.modernCanonicalPath);
119
+ currentPath = canonicalPath ? normalizeJoined(canonicalPath) : '';
110
120
  } else if (typeof route.path === 'string' && route.path.length > 0) {
111
121
  const segments = route.path
112
122
  .replace(/\[(.+?)\]/g, ':$1')
@@ -125,7 +135,6 @@ export function collectCanonicalRoutesForEntry(
125
135
  : parentPath;
126
136
  }
127
137
 
128
- const children = route.children;
129
138
  if (children && children.length > 0) {
130
139
  for (const child of children) {
131
140
  visit(child, currentPath);
@@ -137,13 +146,9 @@ export function collectCanonicalRoutesForEntry(
137
146
  record(currentPath || '/');
138
147
  };
139
148
 
140
- const rootModern = routes.find(
141
- route => (route as CanonicalAwareRoute).isRoot,
142
- ) as CanonicalAwareRoute | undefined;
143
- const topLevel = rootModern ? (rootModern.children ?? []) : routes;
144
-
145
- for (const route of topLevel) {
146
- visit(route as CanonicalAwareRoute, '');
149
+ for (const route of describeRouteTree(routes as CanonicalAwareRoute[])
150
+ .children) {
151
+ visit(route, '');
147
152
  }
148
153
 
149
154
  if (!hasI18nSurface || canonicalParams.size === 0) {
@@ -71,20 +71,6 @@ export function pickRouteSearchContractModules(
71
71
  };
72
72
  }
73
73
 
74
- export function isPathlessLayout(route: NestedRouteForCli | PageRoute) {
75
- const extras = route as RouteExtras;
76
- return (
77
- extras.type === 'nested' &&
78
- typeof extras.index !== 'boolean' &&
79
- typeof extras.path === 'undefined'
80
- );
81
- }
82
-
83
- export function isIndexRoute(route: NestedRouteForCli | PageRoute) {
84
- const extras = route as RouteExtras;
85
- return extras.type === 'nested' && Boolean(extras.index);
86
- }
87
-
88
74
  export function createRouteStaticDataSnippet(opts: {
89
75
  modernRouteId?: string;
90
76
  loaderName?: string | null;