@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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 +12 -0
- package/dist/cjs/cli/routeMetadata.js +131 -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 +27 -60
- package/dist/cjs/runtime/basepathRewrite.js +13 -6
- package/dist/cjs/runtime/lifecycle.js +6 -9
- package/dist/cjs/runtime/loaderBridge.js +10 -4
- package/dist/cjs/runtime/navigation.js +102 -0
- package/dist/cjs/runtime/plugin.js +7 -2
- package/dist/cjs/runtime/plugin.node.js +86 -64
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +0 -2
- package/dist/cjs/runtime/routeTree/construction.js +19 -59
- package/dist/cjs/runtime/routeTree/loaders.js +1 -1
- package/dist/cjs/runtime/routeTree/paths.js +0 -6
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/ssrManagedTags.js +2 -12
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/cjs/runtime/utils.js +3 -0
- package/dist/cjs/shared/routeDescriptor.js +60 -0
- package/dist/esm/cli/artifacts.mjs +13 -2
- package/dist/esm/cli/index.mjs +12 -9
- package/dist/esm/cli/routeMetadata.mjs +71 -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 +28 -61
- package/dist/esm/runtime/basepathRewrite.mjs +10 -3
- 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 +7 -2
- package/dist/esm/runtime/plugin.node.mjs +88 -66
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +0 -2
- package/dist/esm/runtime/routeTree/construction.mjs +19 -59
- package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm/runtime/utils.mjs +3 -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 +12 -9
- package/dist/esm-node/cli/routeMetadata.mjs +72 -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 +28 -61
- package/dist/esm-node/runtime/basepathRewrite.mjs +10 -3
- 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 +7 -2
- package/dist/esm-node/runtime/plugin.node.mjs +88 -66
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +0 -2
- package/dist/esm-node/runtime/routeTree/construction.mjs +19 -59
- 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/router.mjs +27 -11
- package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/utils.mjs +3 -0
- package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
- package/dist/types/cli/artifacts.d.ts +13 -5
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/cli/routeMetadata.d.ts +18 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
- package/dist/types/runtime/basepathRewrite.d.ts +2 -1
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/loaderBridge.d.ts +1 -0
- package/dist/types/runtime/navigation.d.ts +4 -0
- package/dist/types/runtime/register.d.ts +1 -9
- package/dist/types/runtime/routeTree/paths.d.ts +0 -2
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
- package/dist/types/runtime/ssrTypes.d.ts +0 -7
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/dist/types/shared/routeDescriptor.d.ts +26 -0
- package/package.json +22 -19
- package/rstest.config.mts +2 -24
- package/src/cli/artifacts.ts +53 -6
- package/src/cli/index.ts +11 -2
- package/src/cli/routeMetadata.ts +121 -0
- package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
- package/src/cli/tanstackTypes/shared.ts +0 -14
- package/src/cli/tanstackTypes/source.ts +42 -116
- package/src/runtime/basepathRewrite.ts +11 -2
- package/src/runtime/lifecycle.ts +14 -4
- package/src/runtime/loaderBridge.ts +9 -9
- package/src/runtime/navigation.ts +78 -0
- package/src/runtime/plugin.node.tsx +120 -93
- package/src/runtime/plugin.tsx +11 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -12
- package/src/runtime/routeTree/construction.ts +32 -110
- package/src/runtime/routeTree/loaders.ts +2 -4
- package/src/runtime/routeTree/paths.ts +0 -6
- package/src/runtime/router.ts +25 -40
- package/src/runtime/ssrManagedTags.ts +0 -17
- package/src/runtime/ssrTypes.ts +0 -7
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/src/runtime/utils.tsx +4 -1
- package/src/shared/routeDescriptor.ts +46 -0
- package/tests/router/cli.test.ts +148 -824
- package/tests/router/clientNavigation.ts +13 -0
- package/tests/router/dataMutation.test.tsx +17 -85
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +139 -27
- package/tests/router/loaderBridge.test.ts +0 -25
- package/tests/router/localisedIdentity.test.tsx +100 -0
- package/tests/router/prefetchLink.test.tsx +46 -21
- package/tests/router/prefetchLinkPreload.test.tsx +11 -32
- package/tests/router/routeHooks.test.ts +42 -105
- package/tests/router/routeMetadata.test.ts +171 -0
- package/tests/router/routeTree.test.ts +144 -836
- package/tests/router/rsc.test.tsx +131 -166
- package/tests/router/serverPlugin.test.ts +193 -1
- package/tests/router/tanstackTypes.test.ts +218 -963
- package/dist/esm/rslib-runtime~1.mjs +0 -18
- package/dist/esm-node/rslib-runtime~0.mjs +0 -19
- package/tests/realm-isolation.test.ts +0 -114
- package/tests/router/fastDefaults.test.ts +0 -32
- package/tests/router/fixtures/legacyRuntimeRouter.ts +0 -10
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -40
- package/tests/router/hydrationBoundary.test.tsx +0 -23
- package/tests/router/packageSurface.test.ts +0 -24
- package/tests/router/preloadRedirect.test.ts +0 -64
- package/tests/router/register.test.ts +0 -8
- package/tests/router/routerCompatibility.test.ts +0 -239
- package/tests/router/rscDecoderSecurity.test.ts +0 -113
- package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
package/src/cli/artifacts.ts
CHANGED
|
@@ -5,6 +5,12 @@ import type { AppToolsContext } from '@modern-js/app-tools';
|
|
|
5
5
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
6
6
|
import { fs } from '@modern-js/utils';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
findRouteMetaFiles,
|
|
10
|
+
ROUTE_METADATA_MANIFEST_FILE_NAME,
|
|
11
|
+
ROUTE_METADATA_MANIFEST_HEADER,
|
|
12
|
+
renderRouteMetadataManifest,
|
|
13
|
+
} from './routeMetadata';
|
|
8
14
|
import {
|
|
9
15
|
collectCanonicalRoutesForEntry,
|
|
10
16
|
generateTanstackRouterTypesSourceForEntry,
|
|
@@ -193,16 +199,52 @@ export async function writeTanstackRouterTypesForEntries(opts: {
|
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
* Writes `src/routes/ultramodern-route-metadata.ts` from the `route.meta.ts`
|
|
203
|
+
* files below `src/routes`. Reads only the file system, never the app config.
|
|
204
|
+
*/
|
|
205
|
+
export async function writeRouteMetadataManifest(opts: {
|
|
206
|
+
appDirectory: string;
|
|
207
|
+
}): Promise<void> {
|
|
208
|
+
const routesDirectory = path.join(opts.appDirectory, 'src', 'routes');
|
|
209
|
+
const manifestPath = path.join(
|
|
210
|
+
routesDirectory,
|
|
211
|
+
ROUTE_METADATA_MANIFEST_FILE_NAME,
|
|
212
|
+
);
|
|
213
|
+
const manifest = renderRouteMetadataManifest(
|
|
214
|
+
await findRouteMetaFiles(routesDirectory),
|
|
215
|
+
);
|
|
216
|
+
if (manifest !== null) {
|
|
217
|
+
await writeFileIfChanged(manifestPath, manifest);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const previous = (await fs.pathExists(manifestPath))
|
|
221
|
+
? await fs.readFile(manifestPath, 'utf-8')
|
|
222
|
+
: null;
|
|
223
|
+
// No valid manifest exists without routes (its namespace comes from them),
|
|
224
|
+
// and the app config imports it, so the app cannot load in this state.
|
|
225
|
+
if (previous?.startsWith(`${ROUTE_METADATA_MANIFEST_HEADER}\n`)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`[plugin-tanstack] ${manifestPath} is generated from route.meta.ts files, but none remain below ${routesDirectory}. ` +
|
|
228
|
+
'Restore at least one route.meta.ts that exports routeMeta, or delete the manifest together with its imports.',
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Headless regeneration of the route metadata manifest and of `router.gen.ts` /
|
|
235
|
+
* `register.gen.d.ts` for a generated app, without running a full dev/build.
|
|
236
|
+
* Drives the app-tools analyze `onPrepare` (which fires `generateEntryCode`)
|
|
237
|
+
* and returns before any bundling — the app's own registered plugin-tanstack
|
|
238
|
+
* writes the router artifacts via its `generateEntryCode` subscriber. Reuses
|
|
239
|
+
* the exact hook dev/build use, so there is no scanning drift.
|
|
201
240
|
*/
|
|
202
241
|
export async function generateTanstackRouteArtifacts(opts: {
|
|
203
242
|
appDirectory: string;
|
|
204
243
|
version?: string;
|
|
205
244
|
}): Promise<void> {
|
|
245
|
+
// The app config imports the manifest. Writing it first means a manifest
|
|
246
|
+
// that still imports a deleted route.meta.ts cannot fail the config load.
|
|
247
|
+
await writeRouteMetadataManifest(opts);
|
|
206
248
|
const [{ createRunOptions }, { cli }, utils] = await Promise.all([
|
|
207
249
|
import('@modern-js/app-tools/cli/run'),
|
|
208
250
|
import('@modern-js/plugin/cli'),
|
|
@@ -251,5 +293,10 @@ export async function generateTanstackRouteArtifacts(opts: {
|
|
|
251
293
|
internalPlugins: (utils as { INTERNAL_RUNTIME_PLUGINS: unknown })
|
|
252
294
|
.INTERNAL_RUNTIME_PLUGINS as never,
|
|
253
295
|
});
|
|
254
|
-
await cli.init({ ...runOptions, command: 'build' });
|
|
296
|
+
const { appContext } = await cli.init({ ...runOptions, command: 'build' });
|
|
297
|
+
// Preparation owns the artifact writes; there is no dev server or bundler
|
|
298
|
+
// phase after it. Release plugin resources before the headless caller exits.
|
|
299
|
+
// Failed initialization propagates unchanged: it has not returned a context
|
|
300
|
+
// whose lifecycle this caller can close.
|
|
301
|
+
await appContext.hooks.onBeforeExit.call();
|
|
255
302
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import type {
|
|
4
4
|
AppNormalizedConfig,
|
|
5
5
|
AppTools,
|
|
6
|
-
AppToolsContext,
|
|
7
6
|
CliPlugin,
|
|
8
7
|
} from '@modern-js/app-tools';
|
|
9
8
|
import type { CLIPluginAPI } from '@modern-js/plugin';
|
|
@@ -15,7 +14,6 @@ import type {
|
|
|
15
14
|
} from '@modern-js/types';
|
|
16
15
|
import {
|
|
17
16
|
filterRoutesForServer,
|
|
18
|
-
fs,
|
|
19
17
|
NESTED_ROUTE_SPEC_FILE,
|
|
20
18
|
} from '@modern-js/utils';
|
|
21
19
|
import { writeTanstackRouterTypesForEntries } from './artifacts';
|
|
@@ -26,6 +24,7 @@ import {
|
|
|
26
24
|
|
|
27
25
|
export {
|
|
28
26
|
generateTanstackRouteArtifacts,
|
|
27
|
+
writeRouteMetadataManifest,
|
|
29
28
|
writeTanstackRegisterFile,
|
|
30
29
|
writeTanstackRouterTypesForEntries,
|
|
31
30
|
} from './artifacts';
|
|
@@ -132,6 +131,16 @@ export function tanstackRouterPlugin(
|
|
|
132
131
|
name: '@modern-js/plugin-tanstack',
|
|
133
132
|
required: ['@modern-js/runtime'],
|
|
134
133
|
setup: api => {
|
|
134
|
+
const { srcDirectory, serverCompileExcludedFiles = [] } =
|
|
135
|
+
api.getAppContext();
|
|
136
|
+
api.updateAppContext({
|
|
137
|
+
serverCompileExcludedFiles: [
|
|
138
|
+
...new Set([
|
|
139
|
+
...serverCompileExcludedFiles,
|
|
140
|
+
path.join(srcDirectory, generatedDirName, 'register.gen.d.ts'),
|
|
141
|
+
]),
|
|
142
|
+
],
|
|
143
|
+
});
|
|
135
144
|
const nestedRoutesForServer: Record<string, unknown> = {};
|
|
136
145
|
|
|
137
146
|
const isTanstackEntrypoint = (entrypoint: Entrypoint) => {
|
|
@@ -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) => 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
|
+
}
|
|
@@ -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,24 +1,22 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
AppNormalizedConfig,
|
|
5
|
-
AppTools,
|
|
6
|
-
AppToolsContext,
|
|
7
|
-
} from '@modern-js/app-tools';
|
|
3
|
+
import type { AppToolsContext } from '@modern-js/app-tools';
|
|
8
4
|
import { makeLegalIdentifier } from '@modern-js/runtime/cli';
|
|
9
5
|
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
10
6
|
import { upath as path } from '@modern-js/utils';
|
|
11
7
|
|
|
8
|
+
import {
|
|
9
|
+
describeRouteTree,
|
|
10
|
+
type RouteDescriptor,
|
|
11
|
+
} from '../../shared/routeDescriptor';
|
|
12
|
+
|
|
12
13
|
import {
|
|
13
14
|
createRouteStaticDataSnippet,
|
|
14
|
-
isIndexRoute,
|
|
15
|
-
isPathlessLayout,
|
|
16
15
|
normalizeRelativeImport,
|
|
17
16
|
pickModernLoaderModule,
|
|
18
17
|
pickRouteSearchContractModules,
|
|
19
18
|
quote,
|
|
20
19
|
resolveFileNoExt,
|
|
21
|
-
toTanstackPath,
|
|
22
20
|
} from './shared';
|
|
23
21
|
|
|
24
22
|
type RouteForCli = NestedRouteForCli | PageRoute;
|
|
@@ -53,13 +51,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
53
51
|
entryName,
|
|
54
52
|
);
|
|
55
53
|
|
|
56
|
-
const rootModern = routes
|
|
57
|
-
const extras = r as RouteExtras;
|
|
58
|
-
return r && extras.type === 'nested' && extras.isRoot;
|
|
59
|
-
}) as NestedRouteForCli | undefined;
|
|
60
|
-
|
|
61
|
-
const rootExtras = rootModern as RouteExtras | undefined;
|
|
62
|
-
const topLevel = rootModern ? rootExtras?.children || [] : routes;
|
|
54
|
+
const { root: rootModern, children: topLevel } = describeRouteTree(routes);
|
|
63
55
|
|
|
64
56
|
const imports: string[] = [];
|
|
65
57
|
const statements: string[] = [];
|
|
@@ -90,15 +82,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
90
82
|
// value carries the internal `@_modern_js_src` alias, which the app's
|
|
91
83
|
// tsconfig does not map — the generated file must use relative
|
|
92
84
|
// imports.
|
|
93
|
-
const
|
|
94
|
-
if (!
|
|
85
|
+
const relImport = await resolveRouteImport(componentPath);
|
|
86
|
+
if (!relImport) {
|
|
95
87
|
return null;
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
const relImport = normalizeRelativeImport(
|
|
99
|
-
path.relative(outDir, resolvedNoExt),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
90
|
const componentName = `component_${componentIndex++}`;
|
|
103
91
|
imports.push(`import ${componentName} from ${quote(relImport)};`);
|
|
104
92
|
return componentName;
|
|
@@ -109,7 +97,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
109
97
|
return pendingImportName;
|
|
110
98
|
};
|
|
111
99
|
|
|
112
|
-
const
|
|
100
|
+
const resolveRouteImport = async (aliasedNoExtPath: string) => {
|
|
113
101
|
const prefix = `${appContext.internalSrcAlias}/`;
|
|
114
102
|
let absNoExt: string;
|
|
115
103
|
if (aliasedNoExtPath.startsWith(prefix)) {
|
|
@@ -122,7 +110,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
122
110
|
absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
|
|
113
|
+
const resolvedNoExt = await resolveFileNoExt(absNoExt);
|
|
114
|
+
return resolvedNoExt
|
|
115
|
+
? normalizeRelativeImport(path.relative(outDir, resolvedNoExt))
|
|
116
|
+
: null;
|
|
126
117
|
};
|
|
127
118
|
|
|
128
119
|
const getImportNamesForLoader = async (
|
|
@@ -141,15 +132,11 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
141
132
|
};
|
|
142
133
|
}
|
|
143
134
|
|
|
144
|
-
const
|
|
145
|
-
if (!
|
|
135
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
136
|
+
if (!relImport) {
|
|
146
137
|
return null;
|
|
147
138
|
}
|
|
148
139
|
|
|
149
|
-
const relImport = normalizeRelativeImport(
|
|
150
|
-
path.relative(outDir, resolvedNoExt),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
140
|
const importName = `loader_${loaderIndex++}`;
|
|
154
141
|
const actionName = hasAction
|
|
155
142
|
? importName.replace(/^loader_/, 'action_')
|
|
@@ -180,14 +167,10 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
180
167
|
return existing;
|
|
181
168
|
}
|
|
182
169
|
|
|
183
|
-
const
|
|
184
|
-
if (!
|
|
170
|
+
const relImport = await resolveRouteImport(aliasedNoExtPath);
|
|
171
|
+
if (!relImport) {
|
|
185
172
|
return null;
|
|
186
173
|
}
|
|
187
|
-
|
|
188
|
-
const relImport = normalizeRelativeImport(
|
|
189
|
-
path.relative(outDir, resolvedNoExt),
|
|
190
|
-
);
|
|
191
174
|
const importName =
|
|
192
175
|
exportName === 'validateSearch'
|
|
193
176
|
? `validateSearch_${validateSearchIndex++}`
|
|
@@ -215,15 +198,8 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
215
198
|
return reserveRouteVarName(`route_${base}`);
|
|
216
199
|
};
|
|
217
200
|
|
|
218
|
-
const
|
|
219
|
-
parentVar: string;
|
|
220
|
-
route: NestedRouteForCli | PageRoute;
|
|
221
|
-
}): Promise<string> => {
|
|
222
|
-
const { parentVar, route } = opts;
|
|
223
|
-
|
|
224
|
-
const varName = createRouteVarName(route);
|
|
201
|
+
const createRouteOptions = async (route: RouteForCli, hasSplat: boolean) => {
|
|
225
202
|
const routeExtras = route as RouteExtras;
|
|
226
|
-
|
|
227
203
|
const loaderInfo = pickModernLoaderModule(route);
|
|
228
204
|
const routeAction = routeExtras.action;
|
|
229
205
|
const loaderImports = loaderInfo
|
|
@@ -249,10 +225,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
249
225
|
)
|
|
250
226
|
: null;
|
|
251
227
|
|
|
252
|
-
const
|
|
253
|
-
const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
|
|
254
|
-
|
|
255
|
-
const routeOpts: string[] = [`getParentRoute: () => ${parentVar},`];
|
|
228
|
+
const routeOpts: string[] = [];
|
|
256
229
|
|
|
257
230
|
const componentName = await getImportNameForComponent(
|
|
258
231
|
routeExtras._component,
|
|
@@ -261,14 +234,6 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
261
234
|
routeOpts.push(`component: ${componentName},`);
|
|
262
235
|
}
|
|
263
236
|
|
|
264
|
-
if (isPathlessLayout(route)) {
|
|
265
|
-
const id = routeExtras.id;
|
|
266
|
-
routeOpts.push(`id: ${quote(id || 'pathless')},`);
|
|
267
|
-
} else {
|
|
268
|
-
const p = isIndexRoute(route) ? '/' : toTanstackPath(rawPath || '');
|
|
269
|
-
routeOpts.push(`path: ${quote(p)},`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
237
|
if (loaderName) {
|
|
273
238
|
routeOpts.push(
|
|
274
239
|
`loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
|
|
@@ -290,7 +255,22 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
290
255
|
routeOpts.push(staticDataSnippet);
|
|
291
256
|
}
|
|
292
257
|
|
|
293
|
-
|
|
258
|
+
return routeOpts;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const buildRoute = async (
|
|
262
|
+
descriptor: RouteDescriptor<RouteForCli>,
|
|
263
|
+
parentVar: string,
|
|
264
|
+
): Promise<string> => {
|
|
265
|
+
const { source: route, location, hasSplat, children } = descriptor;
|
|
266
|
+
const varName = createRouteVarName(route);
|
|
267
|
+
const routeOpts = [
|
|
268
|
+
`getParentRoute: () => ${parentVar},`,
|
|
269
|
+
...Object.entries(location).map(
|
|
270
|
+
([key, value]) => `${key}: ${quote(value)},`,
|
|
271
|
+
),
|
|
272
|
+
...(await createRouteOptions(route, hasSplat)),
|
|
273
|
+
];
|
|
294
274
|
const hasChildren = Boolean(children && children.length > 0);
|
|
295
275
|
const routeCtorVarName = hasChildren
|
|
296
276
|
? reserveRouteVarName(`${varName}__base`)
|
|
@@ -303,9 +283,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
303
283
|
if (children && children.length > 0) {
|
|
304
284
|
const childVars: string[] = [];
|
|
305
285
|
for (const child of children) {
|
|
306
|
-
childVars.push(
|
|
307
|
-
await buildRoute({ parentVar: routeCtorVarName, route: child }),
|
|
308
|
-
);
|
|
286
|
+
childVars.push(await buildRoute(child, routeCtorVarName));
|
|
309
287
|
}
|
|
310
288
|
statements.push(
|
|
311
289
|
`const ${varName} = ${routeCtorVarName}.addChildren([${childVars.join(', ')}]);`,
|
|
@@ -315,60 +293,14 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
|
|
|
315
293
|
return varName;
|
|
316
294
|
};
|
|
317
295
|
|
|
318
|
-
const rootLoaderInfo = rootModern ? pickModernLoaderModule(rootModern) : null;
|
|
319
|
-
const rootAction = rootExtras?.action;
|
|
320
|
-
const rootLoaderImports = rootLoaderInfo?.loaderPath
|
|
321
|
-
? await getImportNamesForLoader(
|
|
322
|
-
rootLoaderInfo.loaderPath,
|
|
323
|
-
rootLoaderInfo.inline,
|
|
324
|
-
Boolean(
|
|
325
|
-
rootLoaderInfo.inline && rootAction === rootLoaderInfo.loaderPath,
|
|
326
|
-
),
|
|
327
|
-
)
|
|
328
|
-
: null;
|
|
329
|
-
const rootLoaderName = rootLoaderImports?.loaderName || null;
|
|
330
|
-
const rootActionName = rootLoaderImports?.actionName || null;
|
|
331
|
-
const rootSearchContractInfo = rootModern
|
|
332
|
-
? pickRouteSearchContractModules(rootModern)
|
|
333
|
-
: null;
|
|
334
|
-
const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
|
|
335
|
-
? await getImportNameForSearchContract(
|
|
336
|
-
rootSearchContractInfo.validateSearchPath,
|
|
337
|
-
'validateSearch',
|
|
338
|
-
)
|
|
339
|
-
: null;
|
|
340
|
-
const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
|
|
341
|
-
? await getImportNameForSearchContract(
|
|
342
|
-
rootSearchContractInfo.loaderDepsPath,
|
|
343
|
-
'loaderDeps',
|
|
344
|
-
)
|
|
345
|
-
: null;
|
|
346
|
-
|
|
347
296
|
const topLevelVars: string[] = [];
|
|
348
297
|
for (const route of topLevel) {
|
|
349
|
-
topLevelVars.push(await buildRoute(
|
|
298
|
+
topLevelVars.push(await buildRoute(route, 'rootRoute'));
|
|
350
299
|
}
|
|
351
300
|
|
|
352
|
-
const rootOpts
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
rootExtras?._component,
|
|
356
|
-
);
|
|
357
|
-
if (rootComponentName) {
|
|
358
|
-
rootOpts.push(`component: ${rootComponentName},`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (rootLoaderName) {
|
|
362
|
-
rootOpts.push(
|
|
363
|
-
`loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
if (rootValidateSearchName) {
|
|
367
|
-
rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
|
|
368
|
-
}
|
|
369
|
-
if (rootLoaderDepsName) {
|
|
370
|
-
rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
|
|
371
|
-
}
|
|
301
|
+
const rootOpts = rootModern
|
|
302
|
+
? await createRouteOptions(rootModern, false)
|
|
303
|
+
: [];
|
|
372
304
|
|
|
373
305
|
const routerGenTs = `// This file is auto-generated by Modern.js. Do not edit manually.
|
|
374
306
|
|
|
@@ -386,13 +318,7 @@ ${imports.join('\n')}
|
|
|
386
318
|
|
|
387
319
|
export const rootRoute = createRootRouteWithContext<ModernRouterContext>()({
|
|
388
320
|
${rootOpts.join('\n ')}
|
|
389
|
-
|
|
390
|
-
createRouteStaticDataSnippet({
|
|
391
|
-
modernRouteId: rootExtras?.id,
|
|
392
|
-
loaderName: rootLoaderName,
|
|
393
|
-
actionName: rootActionName,
|
|
394
|
-
}) || ''
|
|
395
|
-
}
|
|
321
|
+
|
|
396
322
|
});
|
|
397
323
|
|
|
398
324
|
${statements.join('\n\n')}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// @effect-diagnostics strictBooleanExpressions:off
|
|
2
|
+
import { createLocalisedRouteRewrite } from '@modern-js/i18n-runtime-extensions';
|
|
3
|
+
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
4
|
+
import { composeRewrites } from '@tanstack/react-router';
|
|
5
|
+
|
|
2
6
|
function normalizeBasepath(basepath: string): string {
|
|
3
7
|
if (!basepath) {
|
|
4
8
|
return '/';
|
|
@@ -15,10 +19,12 @@ function normalizeBasepath(basepath: string): string {
|
|
|
15
19
|
export function createModernBasepathRewrite(
|
|
16
20
|
basepath: string,
|
|
17
21
|
caseSensitive = false,
|
|
22
|
+
routes: RouteObject[] = [],
|
|
18
23
|
) {
|
|
24
|
+
const localisedRewrite = createLocalisedRouteRewrite(routes);
|
|
19
25
|
const normalizedBasepath = normalizeBasepath(basepath);
|
|
20
26
|
if (normalizedBasepath === '/') {
|
|
21
|
-
return
|
|
27
|
+
return localisedRewrite;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
const normalizedBasepathWithSlash = `${normalizedBasepath}/`;
|
|
@@ -29,7 +35,7 @@ export function createModernBasepathRewrite(
|
|
|
29
35
|
? normalizedBasepathWithSlash
|
|
30
36
|
: normalizedBasepathWithSlash.toLowerCase();
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
const basepathRewrite = {
|
|
33
39
|
input: ({ url }: { url: URL }) => {
|
|
34
40
|
const pathname = caseSensitive
|
|
35
41
|
? url.pathname
|
|
@@ -57,4 +63,7 @@ export function createModernBasepathRewrite(
|
|
|
57
63
|
return url;
|
|
58
64
|
},
|
|
59
65
|
};
|
|
66
|
+
return localisedRewrite
|
|
67
|
+
? composeRewrites([basepathRewrite, localisedRewrite])
|
|
68
|
+
: basepathRewrite;
|
|
60
69
|
}
|
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The router runtime state helpers are owned by @modern-js/runtime (the same
|
|
2
|
+
* The router runtime state helpers are owned by @modern-js/runtime-extensions (the same
|
|
3
3
|
* implementation backs the built-in react-router provider and the SSR
|
|
4
4
|
* pipeline). This module only re-exports them so every router provider
|
|
5
5
|
* writes to the exact same runtime-context extension slot.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import type { RouterLifecycleContext as NativeRouterLifecycleContext } from '@modern-js/runtime/context';
|
|
9
|
+
import type {
|
|
10
|
+
InternalRouterServerSnapshot,
|
|
11
|
+
RouterRouteMatchSnapshot,
|
|
12
|
+
} from '@modern-js/runtime-extensions/router-state';
|
|
13
|
+
|
|
14
|
+
export type RouterLifecycleContext = NativeRouterLifecycleContext & {
|
|
15
|
+
matches?: RouterRouteMatchSnapshot[];
|
|
16
|
+
cleanup?: () => void | Promise<void>;
|
|
17
|
+
serverSnapshot?: InternalRouterServerSnapshot;
|
|
18
|
+
};
|
|
7
19
|
export {
|
|
8
20
|
applyRouterRuntimeState,
|
|
9
21
|
applyRouterServerPrepareResult,
|
|
10
|
-
createRouterRuntimeState,
|
|
11
22
|
createRouterServerSnapshot,
|
|
12
23
|
getRouterRuntimeState,
|
|
13
24
|
getRouterServerSnapshot,
|
|
14
|
-
type RouterLifecycleContext,
|
|
15
25
|
type RouterLifecyclePhase,
|
|
16
|
-
} from '@modern-js/runtime/
|
|
26
|
+
} from '@modern-js/runtime-extensions/router-state';
|