@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.9 → 3.9.3-ultramodern.1
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.
- package/dist/cjs/cli/artifacts.js +15 -1
- package/dist/cjs/cli/index.js +13 -2
- package/dist/cjs/cli/routeMetadata.js +131 -0
- package/dist/cjs/cli/rscRouteLayer.js +86 -0
- package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
- package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
- package/dist/cjs/cli/tanstackTypes/source.js +28 -63
- package/dist/cjs/runtime/lifecycle.js +0 -3
- package/dist/cjs/runtime/loaderBridge.js +10 -4
- package/dist/cjs/runtime/navigation.js +102 -0
- package/dist/cjs/runtime/plugin.js +10 -1
- package/dist/cjs/runtime/plugin.node.js +89 -64
- package/dist/cjs/runtime/routeTree/construction.js +19 -61
- package/dist/cjs/runtime/routeTree/loaders.js +1 -1
- package/dist/cjs/runtime/routeTree/paths.js +0 -6
- package/dist/cjs/runtime/ssrManagedTags.js +2 -12
- package/dist/cjs/runtime/utils.js +1 -0
- package/dist/cjs/shared/routeDescriptor.js +60 -0
- package/dist/esm/cli/artifacts.mjs +13 -2
- package/dist/esm/cli/index.mjs +13 -11
- package/dist/esm/cli/routeMetadata.mjs +71 -0
- package/dist/esm/cli/rscRouteLayer.mjs +35 -0
- package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/loaderBridge.mjs +8 -5
- package/dist/esm/runtime/navigation.mjs +64 -0
- package/dist/esm/runtime/plugin.mjs +10 -1
- package/dist/esm/runtime/plugin.node.mjs +91 -66
- package/dist/esm/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm/runtime/utils.mjs +1 -0
- package/dist/esm/shared/routeDescriptor.mjs +22 -0
- package/dist/esm-node/cli/artifacts.mjs +13 -2
- package/dist/esm-node/cli/index.mjs +13 -11
- package/dist/esm-node/cli/routeMetadata.mjs +72 -0
- package/dist/esm-node/cli/rscRouteLayer.mjs +36 -0
- package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
- package/dist/esm-node/runtime/navigation.mjs +65 -0
- package/dist/esm-node/runtime/plugin.mjs +10 -1
- package/dist/esm-node/runtime/plugin.node.mjs +91 -66
- package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm-node/runtime/utils.mjs +1 -0
- package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
- package/dist/types/cli/artifacts.d.ts +16 -7
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/cli/routeMetadata.d.ts +18 -0
- package/dist/types/cli/rscRouteLayer.d.ts +11 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
- package/dist/types/cli/tanstackTypes/source.d.ts +3 -2
- package/dist/types/runtime/lifecycle.d.ts +1 -1
- package/dist/types/runtime/loaderBridge.d.ts +1 -0
- package/dist/types/runtime/navigation.d.ts +4 -0
- package/dist/types/runtime/pluginShared.d.ts +5 -0
- package/dist/types/runtime/routeTree/paths.d.ts +0 -2
- package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
- package/dist/types/runtime/ssrTypes.d.ts +0 -7
- package/dist/types/shared/routeDescriptor.d.ts +26 -0
- package/package.json +32 -26
- package/src/cli/artifacts.ts +59 -8
- package/src/cli/index.ts +17 -4
- package/src/cli/routeMetadata.ts +121 -0
- package/src/cli/rscRouteLayer.ts +93 -0
- package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
- package/src/cli/tanstackTypes/shared.ts +0 -14
- package/src/cli/tanstackTypes/source.ts +48 -120
- package/src/runtime/lifecycle.ts +0 -1
- package/src/runtime/loaderBridge.ts +9 -9
- package/src/runtime/navigation.ts +78 -0
- package/src/runtime/plugin.node.tsx +121 -91
- package/src/runtime/plugin.tsx +10 -0
- package/src/runtime/pluginShared.ts +12 -0
- package/src/runtime/routeTree/construction.ts +32 -112
- package/src/runtime/routeTree/loaders.ts +2 -4
- package/src/runtime/routeTree/paths.ts +0 -6
- package/src/runtime/ssrManagedTags.ts +0 -17
- package/src/runtime/ssrTypes.ts +0 -7
- package/src/runtime/utils.tsx +1 -0
- package/src/shared/routeDescriptor.ts +46 -0
- package/dist/esm/rslib-runtime~1.mjs +0 -18
- package/dist/esm-node/rslib-runtime~0.mjs +0 -19
- package/rslib.config.mts +0 -4
- package/rstest.config.mts +0 -53
- package/tests/router/cli.test.ts +0 -420
- package/tests/router/clientHydration.test.tsx +0 -200
- package/tests/router/dataMutation.test.tsx +0 -428
- package/tests/router/fixtures/router-gen.oxlint.json +0 -5
- package/tests/router/flightSerialization.roundtrip.test.tsx +0 -140
- package/tests/router/generateRouteArtifacts.test.ts +0 -146
- package/tests/router/loaderBridge.test.ts +0 -207
- package/tests/router/localisedIdentity.test.tsx +0 -95
- package/tests/router/prefetchLink.test.tsx +0 -122
- package/tests/router/prefetchLinkPreload.test.tsx +0 -65
- package/tests/router/routeHooks.test.ts +0 -49
- package/tests/router/routeTree.test.ts +0 -240
- package/tests/router/rsc.test.tsx +0 -480
- package/tests/router/serverPlugin.test.ts +0 -157
- package/tests/router/slotUsageSanitizer.test.ts +0 -23
- package/tests/router/ssrPreload.test.ts +0 -30
- package/tests/router/tanstackTypes.test.ts +0 -384
- package/tsconfig.json +0 -12
- package/tsconfig.tsgo.json +0 -6
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off nodeBuiltinImport:off
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const ROUTE_META_FILE_NAME = 'route.meta.ts';
|
|
6
|
+
export const ROUTE_METADATA_MANIFEST_FILE_NAME =
|
|
7
|
+
'ultramodern-route-metadata.ts';
|
|
8
|
+
|
|
9
|
+
export const ROUTE_METADATA_MANIFEST_HEADER = `// @generated by @modern-js/plugin-tanstack from ${ROUTE_META_FILE_NAME} files. Run routes-generate; do not edit.`;
|
|
10
|
+
|
|
11
|
+
// The manifest is emitted formatted (oxfmt/Prettier style: print width 120,
|
|
12
|
+
// single quotes, trailing commas) and is generated output, so formatters
|
|
13
|
+
// ignore it like router.gen.ts; no formatter pass is needed to keep it stable.
|
|
14
|
+
const PRINT_WIDTH = 120;
|
|
15
|
+
|
|
16
|
+
const MANIFEST_BODY = `export const ultramodernLocalisedUrls = Object.fromEntries(
|
|
17
|
+
ultramodernRouteMetadata
|
|
18
|
+
.filter((route: { canonicalPath: string }) => route.canonicalPath !== '/')
|
|
19
|
+
.map((route) => [route.canonicalPath, route.localisedPaths]),
|
|
20
|
+
);
|
|
21
|
+
export const ultramodernPublicRoutes = ultramodernRouteMetadata
|
|
22
|
+
.filter((route) => route.public && route.indexable)
|
|
23
|
+
.map((route) => ({
|
|
24
|
+
canonicalPath: route.canonicalPath,
|
|
25
|
+
descriptionKey: route.descriptionKey,
|
|
26
|
+
id: route.id,
|
|
27
|
+
localisedPaths: route.localisedPaths,
|
|
28
|
+
namespace: route.namespace,
|
|
29
|
+
ownerAppId: route.ownerAppId,
|
|
30
|
+
titleKey: route.titleKey,
|
|
31
|
+
...('jsonLd' in route ? { jsonLd: route.jsonLd } : {}),
|
|
32
|
+
}));
|
|
33
|
+
export const ultramodernRouteConfig = {
|
|
34
|
+
authoring: 'colocated-route-meta',
|
|
35
|
+
generatedManifest: true,
|
|
36
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
37
|
+
namespace: ultramodernRouteNamespace,
|
|
38
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
39
|
+
routes: ultramodernRouteMetadata,
|
|
40
|
+
source: 'route-owned',
|
|
41
|
+
} as const;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const compareCodeUnits = (left: string, right: string) =>
|
|
45
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
46
|
+
|
|
47
|
+
const quoteSpecifier = (specifier: string) =>
|
|
48
|
+
specifier.includes("'") ? JSON.stringify(specifier) : `'${specifier}'`;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Renders the route metadata manifest for the `route.meta.ts` files of one
|
|
52
|
+
* routes directory. Each path is relative to that directory (either separator).
|
|
53
|
+
* Every `route.meta.ts` must export `routeMeta`; the manifest imports it
|
|
54
|
+
* statically, so the metadata is never evaluated while generating. Returns
|
|
55
|
+
* `null` when there are no route metadata files. Output depends only on the
|
|
56
|
+
* set of paths, never on their order or on the file system.
|
|
57
|
+
*/
|
|
58
|
+
export function renderRouteMetadataManifest(
|
|
59
|
+
routeMetaFiles: readonly string[],
|
|
60
|
+
): string | null {
|
|
61
|
+
const specifiers = routeMetaFiles
|
|
62
|
+
.map(file => `./${file.replaceAll('\\', '/').replace(/\.ts$/u, '')}`)
|
|
63
|
+
.sort(compareCodeUnits);
|
|
64
|
+
if (specifiers.length === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const bindings = specifiers.map((_, index) => `route${index}`);
|
|
68
|
+
const imports = specifiers.map(
|
|
69
|
+
(specifier, index) =>
|
|
70
|
+
`import { routeMeta as ${bindings[index]} } from ${quoteSpecifier(specifier)};`,
|
|
71
|
+
);
|
|
72
|
+
const inlineRoutes = `export const ultramodernRouteMetadata = [${bindings.join(', ')}] as const;`;
|
|
73
|
+
const routes =
|
|
74
|
+
inlineRoutes.length <= PRINT_WIDTH
|
|
75
|
+
? inlineRoutes
|
|
76
|
+
: `export const ultramodernRouteMetadata = [\n${bindings
|
|
77
|
+
.map(binding => ` ${binding},\n`)
|
|
78
|
+
.join('')}] as const;`;
|
|
79
|
+
|
|
80
|
+
return `${ROUTE_METADATA_MANIFEST_HEADER}
|
|
81
|
+
${imports.join('\n')}
|
|
82
|
+
|
|
83
|
+
export const ultramodernRouteNamespace = route0.namespace;
|
|
84
|
+
${routes}
|
|
85
|
+
${MANIFEST_BODY}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Lists the `route.meta.ts` files below `routesDirectory`, relative to it.
|
|
90
|
+
* Symbolic links are not followed: linked metadata belongs to the tree it
|
|
91
|
+
* lives in. A missing directory has no route metadata.
|
|
92
|
+
*/
|
|
93
|
+
export async function findRouteMetaFiles(
|
|
94
|
+
routesDirectory: string,
|
|
95
|
+
): Promise<string[]> {
|
|
96
|
+
const files: string[] = [];
|
|
97
|
+
const visit = async (relativeDirectory: string): Promise<void> => {
|
|
98
|
+
const entries = await readdir(
|
|
99
|
+
path.join(routesDirectory, relativeDirectory),
|
|
100
|
+
{ withFileTypes: true },
|
|
101
|
+
);
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
await visit(relativePath);
|
|
106
|
+
} else if (entry.isFile() && entry.name === ROUTE_META_FILE_NAME) {
|
|
107
|
+
files.push(relativePath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
await visit('');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const { code, path: missingPath } = error as NodeJS.ErrnoException;
|
|
115
|
+
if (code === 'ENOENT' && missingPath === routesDirectory) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @effect-diagnostics nodeBuiltinImport:off
|
|
2
|
+
// Rsbuild invokes this compiler hook synchronously with native filesystem paths.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { AppNormalizedConfig } from '@modern-js/app-tools/cli-config';
|
|
5
|
+
|
|
6
|
+
type BuilderPlugin = NonNullable<AppNormalizedConfig['builderPlugins']>[number];
|
|
7
|
+
type ModifyRspackConfig = Extract<
|
|
8
|
+
NonNullable<AppNormalizedConfig['tools']['rspack']>,
|
|
9
|
+
(...args: never[]) => unknown
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
export const TANSTACK_SERVER_ROUTES_FILE = 'tanstack-routes.server.js';
|
|
13
|
+
|
|
14
|
+
type RouteLayerContext = {
|
|
15
|
+
entryNames: readonly string[];
|
|
16
|
+
internalDirectory: string;
|
|
17
|
+
rsc: AppNormalizedConfig['server']['rsc'];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function exactResource(...filenames: string[]) {
|
|
21
|
+
const escaped = filenames.map(filename =>
|
|
22
|
+
filename.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'),
|
|
23
|
+
);
|
|
24
|
+
return new RegExp(`^(?:${escaped.join('|')})(?:\\?.*)?$`, 'u');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The HTML router consumes executable components, while route data stays in Flight. */
|
|
28
|
+
export function createTanstackRscRouteLayerPlugin(
|
|
29
|
+
getContext: () => RouteLayerContext,
|
|
30
|
+
): BuilderPlugin {
|
|
31
|
+
return {
|
|
32
|
+
name: 'modern:tanstack:rsc-route-layer',
|
|
33
|
+
setup(api) {
|
|
34
|
+
api.modifyRspackConfig({
|
|
35
|
+
order: 'post',
|
|
36
|
+
handler(
|
|
37
|
+
config: Parameters<ModifyRspackConfig>[0],
|
|
38
|
+
{ environment, rspack, target }: Parameters<ModifyRspackConfig>[1],
|
|
39
|
+
) {
|
|
40
|
+
const { entryNames, internalDirectory, rsc } = getContext();
|
|
41
|
+
if (rsc === false || rsc === undefined || entryNames.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const rscServerEnvironment =
|
|
45
|
+
typeof rsc === 'object'
|
|
46
|
+
? (rsc.environments?.server ?? 'server')
|
|
47
|
+
: 'server';
|
|
48
|
+
if (
|
|
49
|
+
target !== 'node' &&
|
|
50
|
+
config.target !== 'node' &&
|
|
51
|
+
environment.name !== rscServerEnvironment
|
|
52
|
+
) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
config.module ??= {};
|
|
57
|
+
config.module.rules ??= [];
|
|
58
|
+
config.plugins ??= [];
|
|
59
|
+
for (const entryName of entryNames) {
|
|
60
|
+
const routes = path.resolve(
|
|
61
|
+
internalDirectory,
|
|
62
|
+
entryName,
|
|
63
|
+
'routes.js',
|
|
64
|
+
);
|
|
65
|
+
const serverRoutes = path.resolve(
|
|
66
|
+
internalDirectory,
|
|
67
|
+
entryName,
|
|
68
|
+
TANSTACK_SERVER_ROUTES_FILE,
|
|
69
|
+
);
|
|
70
|
+
// runtime-global-context.js imports the browser route table in both
|
|
71
|
+
// compilers. Select the complete, loader-isolated table for HTML.
|
|
72
|
+
config.plugins.push(
|
|
73
|
+
new rspack.NormalModuleReplacementPlugin(
|
|
74
|
+
exactResource(routes),
|
|
75
|
+
serverRoutes,
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
// Match both names: native replacement can occur after Rspack has
|
|
79
|
+
// already selected rules and a layer for the original request.
|
|
80
|
+
// Avoid inheriting an RSC issuer's layer. Native use-server-entry
|
|
81
|
+
// processing returns client references in that layer, which ReactDOM
|
|
82
|
+
// must never invoke. Isolated __rsc_route_data__ retains its native
|
|
83
|
+
// explicit react-server-components rule and Flight serialization.
|
|
84
|
+
config.module.rules.push({
|
|
85
|
+
resource: exactResource(routes, serverRoutes),
|
|
86
|
+
layer: 'server-side-rendering',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -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 {
|
|
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 = (
|
|
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
|
-
|
|
115
|
+
const canonicalPath =
|
|
116
|
+
route.modernLocalisedRoute?.canonicalPath ?? route.modernCanonicalPath;
|
|
117
|
+
if (typeof canonicalPath === 'string') {
|
|
108
118
|
hasI18nSurface = true;
|
|
109
|
-
currentPath = normalizeJoined(
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
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;
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
AppTools,
|
|
6
|
-
AppToolsContext,
|
|
7
|
-
} from '@modern-js/app-tools';
|
|
8
|
-
import { canonicaliseLocalisedRoutes } from '@modern-js/i18n-runtime-extensions';
|
|
3
|
+
import type { AppTools } from '@modern-js/app-tools/cli-config';
|
|
4
|
+
import type { CLIPluginAPI } from '@modern-js/plugin';
|
|
9
5
|
import { makeLegalIdentifier } from '@modern-js/runtime/cli';
|
|
10
6
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
11
7
|
import { upath as path } from '@modern-js/utils';
|
|
12
8
|
|
|
9
|
+
import {
|
|
10
|
+
describeRouteTree,
|
|
11
|
+
type RouteDescriptor,
|
|
12
|
+
} from '../../shared/routeDescriptor';
|
|
13
|
+
|
|
13
14
|
import {
|
|
14
15
|
createRouteStaticDataSnippet,
|
|
15
|
-
isIndexRoute,
|
|
16
|
-
isPathlessLayout,
|
|
17
16
|
normalizeRelativeImport,
|
|
18
17
|
pickModernLoaderModule,
|
|
19
18
|
pickRouteSearchContractModules,
|
|
20
19
|
quote,
|
|
21
20
|
resolveFileNoExt,
|
|
22
|
-
toTanstackPath,
|
|
23
21
|
} from './shared';
|
|
24
22
|
|
|
25
23
|
type RouteForCli = NestedRouteForCli | PageRoute;
|
|
@@ -35,7 +33,10 @@ type RouteExtras = {
|
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
38
|
-
appContext:
|
|
36
|
+
appContext: Pick<
|
|
37
|
+
ReturnType<CLIPluginAPI<AppTools>['getAppContext']>,
|
|
38
|
+
'srcDirectory' | 'internalSrcAlias'
|
|
39
|
+
>;
|
|
39
40
|
entryName: string;
|
|
40
41
|
generatedDirName?: string;
|
|
41
42
|
routes: RouteForCli[];
|
|
@@ -46,22 +47,15 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
46
47
|
appContext,
|
|
47
48
|
entryName,
|
|
48
49
|
generatedDirName = 'modern-tanstack',
|
|
49
|
-
routes
|
|
50
|
+
routes,
|
|
50
51
|
} = opts;
|
|
51
|
-
const routes: RouteForCli[] = canonicaliseLocalisedRoutes(rawRoutes);
|
|
52
52
|
const outDir = path.join(
|
|
53
53
|
appContext.srcDirectory,
|
|
54
54
|
generatedDirName,
|
|
55
55
|
entryName,
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
const rootModern = routes
|
|
59
|
-
const extras = r as RouteExtras;
|
|
60
|
-
return r && extras.type === 'nested' && extras.isRoot;
|
|
61
|
-
}) as NestedRouteForCli | undefined;
|
|
62
|
-
|
|
63
|
-
const rootExtras = rootModern as RouteExtras | undefined;
|
|
64
|
-
const topLevel = rootModern ? rootExtras?.children || [] : routes;
|
|
58
|
+
const { root: rootModern, children: topLevel } = describeRouteTree(routes);
|
|
65
59
|
|
|
66
60
|
const imports: string[] = [];
|
|
67
61
|
const statements: string[] = [];
|
|
@@ -92,15 +86,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
92
86
|
// value carries the internal `@_modern_js_src` alias, which the app's
|
|
93
87
|
// tsconfig does not map — the generated file must use relative
|
|
94
88
|
// imports.
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
89
|
+
const relImport = await resolveRouteImport(componentPath);
|
|
90
|
+
if (!relImport) {
|
|
97
91
|
return null;
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
const relImport = normalizeRelativeImport(
|
|
101
|
-
path.relative(outDir, resolvedNoExt),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
94
|
const componentName = `component_${componentIndex++}`;
|
|
105
95
|
imports.push(`import ${componentName} from ${quote(relImport)};`);
|
|
106
96
|
return componentName;
|
|
@@ -111,7 +101,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
111
101
|
return pendingImportName;
|
|
112
102
|
};
|
|
113
103
|
|
|
114
|
-
const
|
|
104
|
+
const resolveRouteImport = async (aliasedNoExtPath: string) => {
|
|
115
105
|
const prefix = `${appContext.internalSrcAlias}/`;
|
|
116
106
|
let absNoExt: string;
|
|
117
107
|
if (aliasedNoExtPath.startsWith(prefix)) {
|
|
@@ -124,7 +114,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
124
114
|
absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
|
|
117
|
+
const resolvedNoExt = await resolveFileNoExt(absNoExt);
|
|
118
|
+
return resolvedNoExt
|
|
119
|
+
? normalizeRelativeImport(path.relative(outDir, resolvedNoExt))
|
|
120
|
+
: null;
|
|
128
121
|
};
|
|
129
122
|
|
|
130
123
|
const getImportNamesForLoader = async (
|
|
@@ -143,15 +136,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
143
136
|
};
|
|
144
137
|
}
|
|
145
138
|
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
139
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
140
|
+
if (!relImport) {
|
|
148
141
|
return null;
|
|
149
142
|
}
|
|
150
143
|
|
|
151
|
-
const relImport = normalizeRelativeImport(
|
|
152
|
-
path.relative(outDir, resolvedNoExt),
|
|
153
|
-
);
|
|
154
|
-
|
|
155
144
|
const importName = `loader_${loaderIndex++}`;
|
|
156
145
|
const actionName = hasAction
|
|
157
146
|
? importName.replace(/^loader_/, 'action_')
|
|
@@ -182,14 +171,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
182
171
|
return existing;
|
|
183
172
|
}
|
|
184
173
|
|
|
185
|
-
const
|
|
186
|
-
if (!
|
|
174
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
175
|
+
if (!relImport) {
|
|
187
176
|
return null;
|
|
188
177
|
}
|
|
189
|
-
|
|
190
|
-
const relImport = normalizeRelativeImport(
|
|
191
|
-
path.relative(outDir, resolvedNoExt),
|
|
192
|
-
);
|
|
193
178
|
const importName =
|
|
194
179
|
exportName === 'validateSearch'
|
|
195
180
|
? `validateSearch_${validateSearchIndex++}`
|
|
@@ -217,15 +202,8 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
217
202
|
return reserveRouteVarName(`route_${base}`);
|
|
218
203
|
};
|
|
219
204
|
|
|
220
|
-
const
|
|
221
|
-
parentVar: string;
|
|
222
|
-
route: NestedRouteForCli | PageRoute;
|
|
223
|
-
}): Promise<string> => {
|
|
224
|
-
const { parentVar, route } = opts;
|
|
225
|
-
|
|
226
|
-
const varName = createRouteVarName(route);
|
|
205
|
+
const createRouteOptions = async (route: RouteForCli, hasSplat: boolean) => {
|
|
227
206
|
const routeExtras = route as RouteExtras;
|
|
228
|
-
|
|
229
207
|
const loaderInfo = pickModernLoaderModule(route);
|
|
230
208
|
const routeAction = routeExtras.action;
|
|
231
209
|
const loaderImports = loaderInfo
|
|
@@ -251,10 +229,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
251
229
|
)
|
|
252
230
|
: null;
|
|
253
231
|
|
|
254
|
-
const
|
|
255
|
-
const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
|
|
256
|
-
|
|
257
|
-
const routeOpts: string[] = [`getParentRoute: () => ${parentVar},`];
|
|
232
|
+
const routeOpts: string[] = [];
|
|
258
233
|
|
|
259
234
|
const componentName = await getImportNameForComponent(
|
|
260
235
|
routeExtras._component,
|
|
@@ -263,14 +238,6 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
263
238
|
routeOpts.push(`component: ${componentName},`);
|
|
264
239
|
}
|
|
265
240
|
|
|
266
|
-
if (isPathlessLayout(route)) {
|
|
267
|
-
const id = routeExtras.id;
|
|
268
|
-
routeOpts.push(`id: ${quote(id || 'pathless')},`);
|
|
269
|
-
} else {
|
|
270
|
-
const p = isIndexRoute(route) ? '/' : toTanstackPath(rawPath || '');
|
|
271
|
-
routeOpts.push(`path: ${quote(p)},`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
241
|
if (loaderName) {
|
|
275
242
|
routeOpts.push(
|
|
276
243
|
`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
|
|
@@ -292,7 +259,22 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
292
259
|
routeOpts.push(staticDataSnippet);
|
|
293
260
|
}
|
|
294
261
|
|
|
295
|
-
|
|
262
|
+
return routeOpts;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const buildRoute = async (
|
|
266
|
+
descriptor: RouteDescriptor<RouteForCli>,
|
|
267
|
+
parentVar: string,
|
|
268
|
+
): Promise<string> => {
|
|
269
|
+
const { source: route, location, hasSplat, children } = descriptor;
|
|
270
|
+
const varName = createRouteVarName(route);
|
|
271
|
+
const routeOpts = [
|
|
272
|
+
`getParentRoute: () => ${parentVar},`,
|
|
273
|
+
...Object.entries(location).map(
|
|
274
|
+
([key, value]) => `${key}: ${quote(value)},`,
|
|
275
|
+
),
|
|
276
|
+
...(await createRouteOptions(route, hasSplat)),
|
|
277
|
+
];
|
|
296
278
|
const hasChildren = Boolean(children && children.length > 0);
|
|
297
279
|
const routeCtorVarName = hasChildren
|
|
298
280
|
? reserveRouteVarName(`${varName}__base`)
|
|
@@ -305,9 +287,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
305
287
|
if (children && children.length > 0) {
|
|
306
288
|
const childVars: string[] = [];
|
|
307
289
|
for (const child of children) {
|
|
308
|
-
childVars.push(
|
|
309
|
-
await buildRoute({ parentVar: routeCtorVarName, route: child }),
|
|
310
|
-
);
|
|
290
|
+
childVars.push(await buildRoute(child, routeCtorVarName));
|
|
311
291
|
}
|
|
312
292
|
statements.push(
|
|
313
293
|
`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
|
|
@@ -317,60 +297,14 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
317
297
|
return varName;
|
|
318
298
|
};
|
|
319
299
|
|
|
320
|
-
const rootLoaderInfo = rootModern ? pickModernLoaderModule(rootModern) : null;
|
|
321
|
-
const rootAction = rootExtras?.action;
|
|
322
|
-
const rootLoaderImports = rootLoaderInfo?.loaderPath
|
|
323
|
-
? await getImportNamesForLoader(
|
|
324
|
-
rootLoaderInfo.loaderPath,
|
|
325
|
-
rootLoaderInfo.inline,
|
|
326
|
-
Boolean(
|
|
327
|
-
rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath,
|
|
328
|
-
),
|
|
329
|
-
)
|
|
330
|
-
: null;
|
|
331
|
-
const rootLoaderName = rootLoaderImports?.loaderName || null;
|
|
332
|
-
const rootActionName = rootLoaderImports?.actionName || null;
|
|
333
|
-
const rootSearchContractInfo = rootModern
|
|
334
|
-
? pickRouteSearchContractModules(rootModern)
|
|
335
|
-
: null;
|
|
336
|
-
const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
|
|
337
|
-
? await getImportNameForSearchContract(
|
|
338
|
-
rootSearchContractInfo.validateSearchPath,
|
|
339
|
-
'validateSearch',
|
|
340
|
-
)
|
|
341
|
-
: null;
|
|
342
|
-
const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
|
|
343
|
-
? await getImportNameForSearchContract(
|
|
344
|
-
rootSearchContractInfo.loaderDepsPath,
|
|
345
|
-
'loaderDeps',
|
|
346
|
-
)
|
|
347
|
-
: null;
|
|
348
|
-
|
|
349
300
|
const topLevelVars: string[] = [];
|
|
350
301
|
for (const route of topLevel) {
|
|
351
|
-
topLevelVars.push(await buildRoute(
|
|
302
|
+
topLevelVars.push(await buildRoute(route, 'rootRoute'));
|
|
352
303
|
}
|
|
353
304
|
|
|
354
|
-
const rootOpts
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
rootExtras?._component,
|
|
358
|
-
);
|
|
359
|
-
if (rootComponentName) {
|
|
360
|
-
rootOpts.push(`component: ${rootComponentName},`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (rootLoaderName) {
|
|
364
|
-
rootOpts.push(
|
|
365
|
-
`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
if (rootValidateSearchName) {
|
|
369
|
-
rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
|
|
370
|
-
}
|
|
371
|
-
if (rootLoaderDepsName) {
|
|
372
|
-
rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
|
|
373
|
-
}
|
|
305
|
+
const rootOpts = rootModern
|
|
306
|
+
? await createRouteOptions(rootModern, false)
|
|
307
|
+
: [];
|
|
374
308
|
|
|
375
309
|
const routerGenTs = `// This file is auto-generated by Modern.js. Do not edit manually.
|
|
376
310
|
|
|
@@ -388,13 +322,7 @@ ${imports.join('\n')}
|
|
|
388
322
|
|
|
389
323
|
export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
|
|
390
324
|
${rootOpts.join('\n ')}
|
|
391
|
-
|
|
392
|
-
createRouteStaticDataSnippet({
|
|
393
|
-
modernRouteId: rootExtras?.id,
|
|
394
|
-
loaderName: rootLoaderName,
|
|
395
|
-
actionName: rootActionName,
|
|
396
|
-
}) || ''
|
|
397
|
-
}
|
|
325
|
+
|
|
398
326
|
});
|
|
399
327
|
|
|
400
328
|
${statements.join('\n\n')}
|
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -98,15 +98,15 @@ type LoaderLikeContext = {
|
|
|
98
98
|
params?: Record<string, string>;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
function getLoaderSignal(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
101
|
+
export function getLoaderSignal(
|
|
102
|
+
ctx: LoaderLikeContext | undefined,
|
|
103
|
+
): AbortSignal {
|
|
104
|
+
const matchSignal = ctx?.abortController?.signal ?? ctx?.signal;
|
|
105
|
+
const requestSignal = ctx?.context?.request?.signal;
|
|
106
|
+
const signals = [matchSignal, requestSignal].filter(
|
|
107
|
+
(signal): signal is AbortSignal => signal instanceof AbortSignal,
|
|
108
|
+
);
|
|
109
|
+
return AbortSignal.any(signals);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export function getLoaderHref(ctx: LoaderLikeContext | undefined): string {
|