@react-router/dev 0.0.0-experimental-e0b6ce9 → 0.0.0-experimental-de3b900
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/CHANGELOG.md +111 -0
- package/dist/cli/index.js +25 -2
- package/dist/config/default-rsc-entries/entry.client.tsx +2 -2
- package/dist/config.js +1 -1
- package/dist/routes.js +1 -1
- package/dist/vite/cloudflare.js +1 -1
- package/dist/vite.js +71 -4
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# `@react-router/dev`
|
|
2
2
|
|
|
3
|
+
## 7.9.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Update `valibot` dependency to `^1.1.0` ([#14379](https://github.com/remix-run/react-router/pull/14379))
|
|
8
|
+
|
|
9
|
+
- New (unstable) `useRoute` hook for accessing data from specific routes ([#14407](https://github.com/remix-run/react-router/pull/14407))
|
|
10
|
+
|
|
11
|
+
For example, let's say you have an `admin` route somewhere in your app and you want any child routes of `admin` to all have access to the `loaderData` and `actionData` from `admin.`
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// app/routes/admin.tsx
|
|
15
|
+
import { Outlet } from "react-router";
|
|
16
|
+
|
|
17
|
+
export const loader = () => ({ message: "Hello, loader!" });
|
|
18
|
+
|
|
19
|
+
export const action = () => ({ count: 1 });
|
|
20
|
+
|
|
21
|
+
export default function Component() {
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
{/* ... */}
|
|
25
|
+
<Outlet />
|
|
26
|
+
{/* ... */}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You might even want to create a reusable widget that all of the routes nested under `admin` could use:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { unstable_useRoute as useRoute } from "react-router";
|
|
36
|
+
|
|
37
|
+
export function AdminWidget() {
|
|
38
|
+
// How to get `message` and `count` from `admin` route?
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In framework mode, `useRoute` knows all your app's routes and gives you TS errors when invalid route IDs are passed in:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
export function AdminWidget() {
|
|
46
|
+
const admin = useRoute("routes/dmin");
|
|
47
|
+
// ^^^^^^^^^^^
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`useRoute` returns `undefined` if the route is not part of the current page:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
export function AdminWidget() {
|
|
55
|
+
const admin = useRoute("routes/admin");
|
|
56
|
+
if (!admin) {
|
|
57
|
+
throw new Error(`AdminWidget used outside of "routes/admin"`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Note: the `root` route is the exception since it is guaranteed to be part of the current page.
|
|
63
|
+
As a result, `useRoute` never returns `undefined` for `root`.
|
|
64
|
+
|
|
65
|
+
`loaderData` and `actionData` are marked as optional since they could be accessed before the `action` is triggered or after the `loader` threw an error:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
export function AdminWidget() {
|
|
69
|
+
const admin = useRoute("routes/admin");
|
|
70
|
+
if (!admin) {
|
|
71
|
+
throw new Error(`AdminWidget used outside of "routes/admin"`);
|
|
72
|
+
}
|
|
73
|
+
const { loaderData, actionData } = admin;
|
|
74
|
+
console.log(loaderData);
|
|
75
|
+
// ^? { message: string } | undefined
|
|
76
|
+
console.log(actionData);
|
|
77
|
+
// ^? { count: number } | undefined
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If instead of a specific route, you wanted access to the _current_ route's `loaderData` and `actionData`, you can call `useRoute` without arguments:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
export function AdminWidget() {
|
|
85
|
+
const currentRoute = useRoute();
|
|
86
|
+
currentRoute.loaderData;
|
|
87
|
+
currentRoute.actionData;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This usage is equivalent to calling `useLoaderData` and `useActionData`, but consolidates all route data access into one hook: `useRoute`.
|
|
92
|
+
|
|
93
|
+
Note: when calling `useRoute()` (without a route ID), TS has no way to know which route is the current route.
|
|
94
|
+
As a result, `loaderData` and `actionData` are typed as `unknown`.
|
|
95
|
+
If you want more type-safety, you can either narrow the type yourself with something like `zod` or you can refactor your app to pass down typed props to your `AdminWidget`:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
export function AdminWidget({
|
|
99
|
+
message,
|
|
100
|
+
count,
|
|
101
|
+
}: {
|
|
102
|
+
message: string;
|
|
103
|
+
count: number;
|
|
104
|
+
}) {
|
|
105
|
+
/* ... */
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- Updated dependencies:
|
|
110
|
+
- `react-router@7.9.4`
|
|
111
|
+
- `@react-router/node@7.9.4`
|
|
112
|
+
- `@react-router/serve@7.9.4`
|
|
113
|
+
|
|
3
114
|
## 7.9.3
|
|
4
115
|
|
|
5
116
|
### Patch Changes
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @react-router/dev v0.0.0-experimental-
|
|
3
|
+
* @react-router/dev v0.0.0-experimental-de3b900
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) Remix Software Inc.
|
|
6
6
|
*
|
|
@@ -980,9 +980,10 @@ function generateRoutes(ctx) {
|
|
|
980
980
|
interface Register {
|
|
981
981
|
pages: Pages
|
|
982
982
|
routeFiles: RouteFiles
|
|
983
|
+
routeModules: RouteModules
|
|
983
984
|
}
|
|
984
985
|
}
|
|
985
|
-
` + "\n\n" + generate(pagesType(allPages)).code + "\n\n" + generate(routeFilesType({ fileToRoutes, routeToPages })).code
|
|
986
|
+
` + "\n\n" + generate(pagesType(allPages)).code + "\n\n" + generate(routeFilesType({ fileToRoutes, routeToPages })).code + "\n\n" + generate(routeModulesType(ctx)).code
|
|
986
987
|
};
|
|
987
988
|
const allAnnotations = Array.from(fileToRoutes.entries()).filter(([file]) => isInAppDirectory(ctx, file)).map(
|
|
988
989
|
([file, routeIds]) => getRouteAnnotations({ ctx, file, routeIds, lineages })
|
|
@@ -1051,6 +1052,28 @@ function routeFilesType({
|
|
|
1051
1052
|
)
|
|
1052
1053
|
);
|
|
1053
1054
|
}
|
|
1055
|
+
function routeModulesType(ctx) {
|
|
1056
|
+
return t2.tsTypeAliasDeclaration(
|
|
1057
|
+
t2.identifier("RouteModules"),
|
|
1058
|
+
null,
|
|
1059
|
+
t2.tsTypeLiteral(
|
|
1060
|
+
Object.values(ctx.config.routes).map(
|
|
1061
|
+
(route) => t2.tsPropertySignature(
|
|
1062
|
+
t2.stringLiteral(route.id),
|
|
1063
|
+
t2.tsTypeAnnotation(
|
|
1064
|
+
isInAppDirectory(ctx, route.file) ? t2.tsTypeQuery(
|
|
1065
|
+
t2.tsImportType(
|
|
1066
|
+
t2.stringLiteral(
|
|
1067
|
+
`./${Path3.relative(ctx.rootDirectory, ctx.config.appDirectory)}/${route.file}`
|
|
1068
|
+
)
|
|
1069
|
+
)
|
|
1070
|
+
) : t2.tsUnknownKeyword()
|
|
1071
|
+
)
|
|
1072
|
+
)
|
|
1073
|
+
)
|
|
1074
|
+
)
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1054
1077
|
function isInAppDirectory(ctx, routeFile) {
|
|
1055
1078
|
const path9 = Path3.resolve(ctx.config.appDirectory, routeFile);
|
|
1056
1079
|
return path9.startsWith(ctx.config.appDirectory);
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
unstable_createCallServer as createCallServer,
|
|
13
13
|
unstable_getRSCStream as getRSCStream,
|
|
14
14
|
unstable_RSCHydratedRouter as RSCHydratedRouter,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
type unstable_RSCPayload as RSCPayload,
|
|
16
|
+
} from "react-router/dom";
|
|
17
17
|
|
|
18
18
|
setServerCallback(
|
|
19
19
|
createCallServer({
|
package/dist/config.js
CHANGED
package/dist/routes.js
CHANGED
package/dist/vite/cloudflare.js
CHANGED
package/dist/vite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @react-router/dev v0.0.0-experimental-
|
|
2
|
+
* @react-router/dev v0.0.0-experimental-de3b900
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -968,9 +968,10 @@ function generateRoutes(ctx) {
|
|
|
968
968
|
interface Register {
|
|
969
969
|
pages: Pages
|
|
970
970
|
routeFiles: RouteFiles
|
|
971
|
+
routeModules: RouteModules
|
|
971
972
|
}
|
|
972
973
|
}
|
|
973
|
-
` + "\n\n" + generate(pagesType(allPages)).code + "\n\n" + generate(routeFilesType({ fileToRoutes, routeToPages })).code
|
|
974
|
+
` + "\n\n" + generate(pagesType(allPages)).code + "\n\n" + generate(routeFilesType({ fileToRoutes, routeToPages })).code + "\n\n" + generate(routeModulesType(ctx)).code
|
|
974
975
|
};
|
|
975
976
|
const allAnnotations = Array.from(fileToRoutes.entries()).filter(([file]) => isInAppDirectory(ctx, file)).map(
|
|
976
977
|
([file, routeIds]) => getRouteAnnotations({ ctx, file, routeIds, lineages })
|
|
@@ -1039,6 +1040,28 @@ function routeFilesType({
|
|
|
1039
1040
|
)
|
|
1040
1041
|
);
|
|
1041
1042
|
}
|
|
1043
|
+
function routeModulesType(ctx) {
|
|
1044
|
+
return t2.tsTypeAliasDeclaration(
|
|
1045
|
+
t2.identifier("RouteModules"),
|
|
1046
|
+
null,
|
|
1047
|
+
t2.tsTypeLiteral(
|
|
1048
|
+
Object.values(ctx.config.routes).map(
|
|
1049
|
+
(route) => t2.tsPropertySignature(
|
|
1050
|
+
t2.stringLiteral(route.id),
|
|
1051
|
+
t2.tsTypeAnnotation(
|
|
1052
|
+
isInAppDirectory(ctx, route.file) ? t2.tsTypeQuery(
|
|
1053
|
+
t2.tsImportType(
|
|
1054
|
+
t2.stringLiteral(
|
|
1055
|
+
`./${Path3.relative(ctx.rootDirectory, ctx.config.appDirectory)}/${route.file}`
|
|
1056
|
+
)
|
|
1057
|
+
)
|
|
1058
|
+
) : t2.tsUnknownKeyword()
|
|
1059
|
+
)
|
|
1060
|
+
)
|
|
1061
|
+
)
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1042
1065
|
function isInAppDirectory(ctx, routeFile) {
|
|
1043
1066
|
const path9 = Path3.resolve(ctx.config.appDirectory, routeFile);
|
|
1044
1067
|
return path9.startsWith(ctx.config.appDirectory);
|
|
@@ -2526,7 +2549,7 @@ var getClientEntryChunk = (ctx, viteManifest) => {
|
|
|
2526
2549
|
invariant(chunk, `Chunk not found: ${filePath}`);
|
|
2527
2550
|
return chunk;
|
|
2528
2551
|
};
|
|
2529
|
-
var getReactRouterManifestBuildAssets = (ctx, viteConfig, viteManifest, entryFilePath, route) => {
|
|
2552
|
+
var getReactRouterManifestBuildAssets = (ctx, viteConfig, viteManifest, allDynamicCssFiles, entryFilePath, route) => {
|
|
2530
2553
|
let entryChunk = resolveChunk(ctx, viteManifest, entryFilePath);
|
|
2531
2554
|
invariant(entryChunk, `Chunk not found: ${entryFilePath}`);
|
|
2532
2555
|
let isRootRoute = Boolean(route && route.parentId === void 0);
|
|
@@ -2559,7 +2582,10 @@ var getReactRouterManifestBuildAssets = (ctx, viteConfig, viteManifest, entryFil
|
|
|
2559
2582
|
// in the manifest that isn't tied to any route file. If we want to render these
|
|
2560
2583
|
// styles correctly, we need to include them in the root route.
|
|
2561
2584
|
isRootRoute ? getCssCodeSplitDisabledFile(ctx, viteConfig, viteManifest) : null,
|
|
2562
|
-
chunks.flatMap((e) => e.css ?? []).map((href) =>
|
|
2585
|
+
chunks.flatMap((e) => e.css ?? []).map((href) => {
|
|
2586
|
+
let publicHref = `${ctx.publicPath}${href}`;
|
|
2587
|
+
return allDynamicCssFiles.has(href) ? `${publicHref}#` : publicHref;
|
|
2588
|
+
})
|
|
2563
2589
|
].flat(1).filter(isNonNullable)
|
|
2564
2590
|
)
|
|
2565
2591
|
};
|
|
@@ -2582,6 +2608,44 @@ function resolveDependantChunks(viteManifest, entryChunks) {
|
|
|
2582
2608
|
}
|
|
2583
2609
|
return Array.from(chunks);
|
|
2584
2610
|
}
|
|
2611
|
+
function getAllDynamicCssFiles(ctx, viteManifest) {
|
|
2612
|
+
let allDynamicCssFiles = /* @__PURE__ */ new Set();
|
|
2613
|
+
for (let route of Object.values(ctx.reactRouterConfig.routes)) {
|
|
2614
|
+
let routeFile = path6.join(ctx.reactRouterConfig.appDirectory, route.file);
|
|
2615
|
+
let entryChunk = resolveChunk(
|
|
2616
|
+
ctx,
|
|
2617
|
+
viteManifest,
|
|
2618
|
+
`${routeFile}${BUILD_CLIENT_ROUTE_QUERY_STRING}`
|
|
2619
|
+
);
|
|
2620
|
+
if (entryChunk) {
|
|
2621
|
+
let walk2 = function(chunk, isDynamicImportContext) {
|
|
2622
|
+
if (visitedChunks.has(chunk)) {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
visitedChunks.add(chunk);
|
|
2626
|
+
if (isDynamicImportContext && chunk.css) {
|
|
2627
|
+
for (let cssFile of chunk.css) {
|
|
2628
|
+
allDynamicCssFiles.add(cssFile);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (chunk.dynamicImports) {
|
|
2632
|
+
for (let dynamicImportKey of chunk.dynamicImports) {
|
|
2633
|
+
walk2(viteManifest[dynamicImportKey], true);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (chunk.imports) {
|
|
2637
|
+
for (let importKey of chunk.imports) {
|
|
2638
|
+
walk2(viteManifest[importKey], isDynamicImportContext);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
var walk = walk2;
|
|
2643
|
+
let visitedChunks = /* @__PURE__ */ new Set();
|
|
2644
|
+
walk2(entryChunk, false);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
return allDynamicCssFiles;
|
|
2648
|
+
}
|
|
2585
2649
|
function dedupe(array2) {
|
|
2586
2650
|
return [...new Set(array2)];
|
|
2587
2651
|
}
|
|
@@ -2871,10 +2935,12 @@ var reactRouterVitePlugin = () => {
|
|
|
2871
2935
|
let viteManifest = await loadViteManifest(
|
|
2872
2936
|
getClientBuildDirectory(ctx.reactRouterConfig)
|
|
2873
2937
|
);
|
|
2938
|
+
let allDynamicCssFiles = getAllDynamicCssFiles(ctx, viteManifest);
|
|
2874
2939
|
let entry = getReactRouterManifestBuildAssets(
|
|
2875
2940
|
ctx,
|
|
2876
2941
|
viteConfig2,
|
|
2877
2942
|
viteManifest,
|
|
2943
|
+
allDynamicCssFiles,
|
|
2878
2944
|
ctx.entryClientFilePath,
|
|
2879
2945
|
null
|
|
2880
2946
|
);
|
|
@@ -2926,6 +2992,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2926
2992
|
ctx,
|
|
2927
2993
|
viteConfig2,
|
|
2928
2994
|
viteManifest,
|
|
2995
|
+
allDynamicCssFiles,
|
|
2929
2996
|
`${routeFile}${BUILD_CLIENT_ROUTE_QUERY_STRING}`,
|
|
2930
2997
|
route
|
|
2931
2998
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-router/dev",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-de3b900",
|
|
4
4
|
"description": "Dev tools and CLI for React Router",
|
|
5
5
|
"homepage": "https://reactrouter.com",
|
|
6
6
|
"bugs": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"tinyglobby": "^0.2.14",
|
|
87
87
|
"valibot": "^1.1.0",
|
|
88
88
|
"vite-node": "^3.2.2",
|
|
89
|
-
"@react-router/node": "0.0.0-experimental-
|
|
89
|
+
"@react-router/node": "0.0.0-experimental-de3b900"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@types/babel__core": "^7.20.5",
|
|
@@ -109,16 +109,16 @@
|
|
|
109
109
|
"vite": "^6.1.0",
|
|
110
110
|
"wireit": "0.14.9",
|
|
111
111
|
"wrangler": "^4.23.0",
|
|
112
|
-
"
|
|
113
|
-
"react-router": "
|
|
112
|
+
"react-router": "^0.0.0-experimental-de3b900",
|
|
113
|
+
"@react-router/serve": "0.0.0-experimental-de3b900"
|
|
114
114
|
},
|
|
115
115
|
"peerDependencies": {
|
|
116
116
|
"@vitejs/plugin-rsc": "*",
|
|
117
117
|
"typescript": "^5.1.0",
|
|
118
118
|
"vite": "^5.1.0 || ^6.0.0 || ^7.0.0",
|
|
119
119
|
"wrangler": "^3.28.2 || ^4.0.0",
|
|
120
|
-
"@react-router/serve": "^0.0.0-experimental-
|
|
121
|
-
"react-router": "^0.0.0-experimental-
|
|
120
|
+
"@react-router/serve": "^0.0.0-experimental-de3b900",
|
|
121
|
+
"react-router": "^0.0.0-experimental-de3b900"
|
|
122
122
|
},
|
|
123
123
|
"peerDependenciesMeta": {
|
|
124
124
|
"@vitejs/plugin-rsc": {
|