diff --git a/CHANGELOG.md b/CHANGELOG.md index 173fe085a9..ee68b6aba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,78 +13,80 @@ We manage release notes in this file instead of the paginated Github Releases Pa Table of Contents - [React Router Releases](#react-router-releases) + - [v7.9.1](#v791) + - [Patch Changes](#patch-changes) - [v7.9.0](#v790) - [What's Changed](#whats-changed) - [Stable Middleware and Context APIs](#stable-middleware-and-context-apis) - [Minor Changes](#minor-changes) - - [Patch Changes](#patch-changes) + - [Patch Changes](#patch-changes-1) - [Unstable Changes](#unstable-changes) - [v7.8.2](#v782) - - [Patch Changes](#patch-changes-1) + - [Patch Changes](#patch-changes-2) - [Unstable Changes](#unstable-changes-1) - [v7.8.1](#v781) - - [Patch Changes](#patch-changes-2) + - [Patch Changes](#patch-changes-3) - [Unstable Changes](#unstable-changes-2) - [v7.8.0](#v780) - [What's Changed](#whats-changed-1) - [Consistently named `loaderData` values](#consistently-named-loaderdata-values) - [Improvements/fixes to the middleware APIs (unstable)](#improvementsfixes-to-the-middleware-apis-unstable) - [Minor Changes](#minor-changes-1) - - [Patch Changes](#patch-changes-3) + - [Patch Changes](#patch-changes-4) - [Unstable Changes](#unstable-changes-3) - [Changes by Package](#changes-by-package) - [v7.7.1](#v771) - - [Patch Changes](#patch-changes-4) + - [Patch Changes](#patch-changes-5) - [Unstable Changes](#unstable-changes-4) - [v7.7.0](#v770) - [What's Changed](#whats-changed-2) - [Unstable RSC APIs](#unstable-rsc-apis) - [Minor Changes](#minor-changes-2) - - [Patch Changes](#patch-changes-5) + - [Patch Changes](#patch-changes-6) - [Unstable Changes](#unstable-changes-5) - [Changes by Package](#changes-by-package-1) - [v7.6.3](#v763) - - [Patch Changes](#patch-changes-6) - - [v7.6.2](#v762) - [Patch Changes](#patch-changes-7) - - [v7.6.1](#v761) + - [v7.6.2](#v762) - [Patch Changes](#patch-changes-8) + - [v7.6.1](#v761) + - [Patch Changes](#patch-changes-9) - [Unstable Changes](#unstable-changes-6) - [v7.6.0](#v760) - [What's Changed](#whats-changed-3) - [`routeDiscovery` Config Option](#routediscovery-config-option) - [Automatic Types for Future Flags](#automatic-types-for-future-flags) - [Minor Changes](#minor-changes-3) - - [Patch Changes](#patch-changes-9) + - [Patch Changes](#patch-changes-10) - [Unstable Changes](#unstable-changes-7) - [Changes by Package](#changes-by-package-2) - [v7.5.3](#v753) - - [Patch Changes](#patch-changes-10) + - [Patch Changes](#patch-changes-11) - [v7.5.2](#v752) - [Security Notice](#security-notice) - - [Patch Changes](#patch-changes-11) - - [v7.5.1](#v751) - [Patch Changes](#patch-changes-12) + - [v7.5.1](#v751) + - [Patch Changes](#patch-changes-13) - [Unstable Changes](#unstable-changes-8) - [v7.5.0](#v750) - [What's Changed](#whats-changed-4) - [`route.lazy` Object API](#routelazy-object-api) - [Minor Changes](#minor-changes-4) - - [Patch Changes](#patch-changes-13) + - [Patch Changes](#patch-changes-14) - [Unstable Changes](#unstable-changes-9) - [Changes by Package](#changes-by-package-3) - [v7.4.1](#v741) - [Security Notice](#security-notice-1) - - [Patch Changes](#patch-changes-14) + - [Patch Changes](#patch-changes-15) - [Unstable Changes](#unstable-changes-10) - [v7.4.0](#v740) - [Minor Changes](#minor-changes-5) - - [Patch Changes](#patch-changes-15) + - [Patch Changes](#patch-changes-16) - [Unstable Changes](#unstable-changes-11) - [Changes by Package](#changes-by-package-4) - [v7.3.0](#v730) - [Minor Changes](#minor-changes-6) - - [Patch Changes](#patch-changes-16) + - [Patch Changes](#patch-changes-17) - [Unstable Changes](#unstable-changes-12) - [Client-side `context` (unstable)](#client-side-context-unstable) - [Middleware (unstable)](#middleware-unstable) @@ -97,28 +99,28 @@ We manage release notes in this file instead of the paginated Github Releases Pa - [Prerendering with a SPA Fallback](#prerendering-with-a-spa-fallback) - [Allow a root `loader` in SPA Mode](#allow-a-root-loader-in-spa-mode) - [Minor Changes](#minor-changes-7) - - [Patch Changes](#patch-changes-17) + - [Patch Changes](#patch-changes-18) - [Unstable Changes](#unstable-changes-13) - [Split Route Modules (unstable)](#split-route-modules-unstable) - [Changes by Package](#changes-by-package-6) - [v7.1.5](#v715) - - [Patch Changes](#patch-changes-18) - - [v7.1.4](#v714) - [Patch Changes](#patch-changes-19) - - [v7.1.3](#v713) + - [v7.1.4](#v714) - [Patch Changes](#patch-changes-20) - - [v7.1.2](#v712) + - [v7.1.3](#v713) - [Patch Changes](#patch-changes-21) - - [v7.1.1](#v711) + - [v7.1.2](#v712) - [Patch Changes](#patch-changes-22) + - [v7.1.1](#v711) + - [Patch Changes](#patch-changes-23) - [v7.1.0](#v710) - [Minor Changes](#minor-changes-8) - - [Patch Changes](#patch-changes-23) + - [Patch Changes](#patch-changes-24) - [Changes by Package](#changes-by-package-7) - [v7.0.2](#v702) - - [Patch Changes](#patch-changes-24) - - [v7.0.1](#v701) - [Patch Changes](#patch-changes-25) + - [v7.0.1](#v701) + - [Patch Changes](#patch-changes-26) - [v7.0.0](#v700) - [Breaking Changes](#breaking-changes) - [Package Restructuring](#package-restructuring) @@ -135,201 +137,201 @@ We manage release notes in this file instead of the paginated Github Releases Pa - [Major Changes (`react-router`)](#major-changes-react-router) - [Major Changes (`@react-router/*`)](#major-changes-react-router-1) - [Minor Changes](#minor-changes-9) - - [Patch Changes](#patch-changes-26) + - [Patch Changes](#patch-changes-27) - [Changes by Package](#changes-by-package-8) - [React Router v6 Releases](#react-router-v6-releases) - [v6.30.1](#v6301) - - [Patch Changes](#patch-changes-27) + - [Patch Changes](#patch-changes-28) - [v6.30.0](#v6300) - [Minor Changes](#minor-changes-10) - - [Patch Changes](#patch-changes-28) + - [Patch Changes](#patch-changes-29) - [v6.29.0](#v6290) - [Minor Changes](#minor-changes-11) - - [Patch Changes](#patch-changes-29) - - [v6.28.2](#v6282) - [Patch Changes](#patch-changes-30) - - [v6.28.1](#v6281) + - [v6.28.2](#v6282) - [Patch Changes](#patch-changes-31) + - [v6.28.1](#v6281) + - [Patch Changes](#patch-changes-32) - [v6.28.0](#v6280) - [What's Changed](#whats-changed-6) - [Minor Changes](#minor-changes-12) - - [Patch Changes](#patch-changes-32) + - [Patch Changes](#patch-changes-33) - [v6.27.0](#v6270) - [What's Changed](#whats-changed-7) - [Stabilized APIs](#stabilized-apis) - [Minor Changes](#minor-changes-13) - - [Patch Changes](#patch-changes-33) - - [v6.26.2](#v6262) - [Patch Changes](#patch-changes-34) - - [v6.26.1](#v6261) + - [v6.26.2](#v6262) - [Patch Changes](#patch-changes-35) + - [v6.26.1](#v6261) + - [Patch Changes](#patch-changes-36) - [v6.26.0](#v6260) - [Minor Changes](#minor-changes-14) - - [Patch Changes](#patch-changes-36) - - [v6.25.1](#v6251) - [Patch Changes](#patch-changes-37) + - [v6.25.1](#v6251) + - [Patch Changes](#patch-changes-38) - [v6.25.0](#v6250) - [What's Changed](#whats-changed-8) - [Stabilized `v7_skipActionErrorRevalidation`](#stabilized-v7_skipactionerrorrevalidation) - [Minor Changes](#minor-changes-15) - - [Patch Changes](#patch-changes-38) - - [v6.24.1](#v6241) - [Patch Changes](#patch-changes-39) + - [v6.24.1](#v6241) + - [Patch Changes](#patch-changes-40) - [v6.24.0](#v6240) - [What's Changed](#whats-changed-9) - [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war) - [Minor Changes](#minor-changes-16) - - [Patch Changes](#patch-changes-40) - - [v6.23.1](#v6231) - [Patch Changes](#patch-changes-41) + - [v6.23.1](#v6231) + - [Patch Changes](#patch-changes-42) - [v6.23.0](#v6230) - [What's Changed](#whats-changed-10) - [Data Strategy (unstable)](#data-strategy-unstable) - [Skip Action Error Revalidation (unstable)](#skip-action-error-revalidation-unstable) - [Minor Changes](#minor-changes-17) - [v6.22.3](#v6223) - - [Patch Changes](#patch-changes-42) - - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-43) - - [v6.22.1](#v6221) + - [v6.22.2](#v6222) - [Patch Changes](#patch-changes-44) + - [v6.22.1](#v6221) + - [Patch Changes](#patch-changes-45) - [v6.22.0](#v6220) - [What's Changed](#whats-changed-11) - [Core Web Vitals Technology Report Flag](#core-web-vitals-technology-report-flag) - [Minor Changes](#minor-changes-18) - - [Patch Changes](#patch-changes-45) - - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-46) - - [v6.21.2](#v6212) + - [v6.21.3](#v6213) - [Patch Changes](#patch-changes-47) - - [v6.21.1](#v6211) + - [v6.21.2](#v6212) - [Patch Changes](#patch-changes-48) + - [v6.21.1](#v6211) + - [Patch Changes](#patch-changes-49) - [v6.21.0](#v6210) - [What's Changed](#whats-changed-12) - [`future.v7_relativeSplatPath`](#futurev7_relativesplatpath) - [Partial Hydration](#partial-hydration) - [Minor Changes](#minor-changes-19) - - [Patch Changes](#patch-changes-49) - - [v6.20.1](#v6201) - [Patch Changes](#patch-changes-50) + - [v6.20.1](#v6201) + - [Patch Changes](#patch-changes-51) - [v6.20.0](#v6200) - [Minor Changes](#minor-changes-20) - - [Patch Changes](#patch-changes-51) + - [Patch Changes](#patch-changes-52) - [v6.19.0](#v6190) - [What's Changed](#whats-changed-13) - [`unstable_flushSync` API](#unstable_flushsync-api) - [Minor Changes](#minor-changes-21) - - [Patch Changes](#patch-changes-52) + - [Patch Changes](#patch-changes-53) - [v6.18.0](#v6180) - [What's Changed](#whats-changed-14) - [New Fetcher APIs](#new-fetcher-apis) - [Persistence Future Flag (`future.v7_fetcherPersist`)](#persistence-future-flag-futurev7_fetcherpersist) - [Minor Changes](#minor-changes-22) - - [Patch Changes](#patch-changes-53) + - [Patch Changes](#patch-changes-54) - [v6.17.0](#v6170) - [What's Changed](#whats-changed-15) - [View Transitions 🚀](#view-transitions-) - [Minor Changes](#minor-changes-23) - - [Patch Changes](#patch-changes-54) + - [Patch Changes](#patch-changes-55) - [v6.16.0](#v6160) - [Minor Changes](#minor-changes-24) - - [Patch Changes](#patch-changes-55) + - [Patch Changes](#patch-changes-56) - [v6.15.0](#v6150) - [Minor Changes](#minor-changes-25) - - [Patch Changes](#patch-changes-56) - - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-57) - - [v6.14.1](#v6141) + - [v6.14.2](#v6142) - [Patch Changes](#patch-changes-58) + - [v6.14.1](#v6141) + - [Patch Changes](#patch-changes-59) - [v6.14.0](#v6140) - [What's Changed](#whats-changed-16) - [JSON/Text Submissions](#jsontext-submissions) - [Minor Changes](#minor-changes-26) - - [Patch Changes](#patch-changes-59) + - [Patch Changes](#patch-changes-60) - [v6.13.0](#v6130) - [What's Changed](#whats-changed-17) - [`future.v7_startTransition`](#futurev7_starttransition) - [Minor Changes](#minor-changes-27) - - [Patch Changes](#patch-changes-60) - - [v6.12.1](#v6121) - [Patch Changes](#patch-changes-61) + - [v6.12.1](#v6121) + - [Patch Changes](#patch-changes-62) - [v6.12.0](#v6120) - [What's Changed](#whats-changed-18) - [`React.startTransition` support](#reactstarttransition-support) - [Minor Changes](#minor-changes-28) - - [Patch Changes](#patch-changes-62) - - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-63) - - [v6.11.1](#v6111) + - [v6.11.2](#v6112) - [Patch Changes](#patch-changes-64) + - [v6.11.1](#v6111) + - [Patch Changes](#patch-changes-65) - [v6.11.0](#v6110) - [Minor Changes](#minor-changes-29) - - [Patch Changes](#patch-changes-65) + - [Patch Changes](#patch-changes-66) - [v6.10.0](#v6100) - [What's Changed](#whats-changed-19) - [Minor Changes](#minor-changes-30) - [`future.v7_normalizeFormMethod`](#futurev7_normalizeformmethod) - - [Patch Changes](#patch-changes-66) + - [Patch Changes](#patch-changes-67) - [v6.9.0](#v690) - [What's Changed](#whats-changed-20) - [`Component`/`ErrorBoundary` route properties](#componenterrorboundary-route-properties) - [Introducing Lazy Route Modules](#introducing-lazy-route-modules) - [Minor Changes](#minor-changes-31) - - [Patch Changes](#patch-changes-67) - - [v6.8.2](#v682) - [Patch Changes](#patch-changes-68) - - [v6.8.1](#v681) + - [v6.8.2](#v682) - [Patch Changes](#patch-changes-69) + - [v6.8.1](#v681) + - [Patch Changes](#patch-changes-70) - [v6.8.0](#v680) - [Minor Changes](#minor-changes-32) - - [Patch Changes](#patch-changes-70) + - [Patch Changes](#patch-changes-71) - [v6.7.0](#v670) - [Minor Changes](#minor-changes-33) - - [Patch Changes](#patch-changes-71) - - [v6.6.2](#v662) - [Patch Changes](#patch-changes-72) - - [v6.6.1](#v661) + - [v6.6.2](#v662) - [Patch Changes](#patch-changes-73) + - [v6.6.1](#v661) + - [Patch Changes](#patch-changes-74) - [v6.6.0](#v660) - [What's Changed](#whats-changed-21) - [Minor Changes](#minor-changes-34) - - [Patch Changes](#patch-changes-74) + - [Patch Changes](#patch-changes-75) - [v6.5.0](#v650) - [What's Changed](#whats-changed-22) - [Minor Changes](#minor-changes-35) - - [Patch Changes](#patch-changes-75) - - [v6.4.5](#v645) - [Patch Changes](#patch-changes-76) - - [v6.4.4](#v644) + - [v6.4.5](#v645) - [Patch Changes](#patch-changes-77) - - [v6.4.3](#v643) + - [v6.4.4](#v644) - [Patch Changes](#patch-changes-78) - - [v6.4.2](#v642) + - [v6.4.3](#v643) - [Patch Changes](#patch-changes-79) - - [v6.4.1](#v641) + - [v6.4.2](#v642) - [Patch Changes](#patch-changes-80) + - [v6.4.1](#v641) + - [Patch Changes](#patch-changes-81) - [v6.4.0](#v640) - [What's Changed](#whats-changed-23) - [Remix Data APIs](#remix-data-apis) - - [Patch Changes](#patch-changes-81) + - [Patch Changes](#patch-changes-82) - [v6.3.0](#v630) - [Minor Changes](#minor-changes-36) - [v6.2.2](#v622) - - [Patch Changes](#patch-changes-82) - - [v6.2.1](#v621) - [Patch Changes](#patch-changes-83) + - [v6.2.1](#v621) + - [Patch Changes](#patch-changes-84) - [v6.2.0](#v620) - [Minor Changes](#minor-changes-37) - - [Patch Changes](#patch-changes-84) - - [v6.1.1](#v611) - [Patch Changes](#patch-changes-85) + - [v6.1.1](#v611) + - [Patch Changes](#patch-changes-86) - [v6.1.0](#v610) - [Minor Changes](#minor-changes-38) - - [Patch Changes](#patch-changes-86) - - [v6.0.2](#v602) - [Patch Changes](#patch-changes-87) - - [v6.0.1](#v601) + - [v6.0.2](#v602) - [Patch Changes](#patch-changes-88) + - [v6.0.1](#v601) + - [Patch Changes](#patch-changes-89) - [v6.0.0](#v600) @@ -357,6 +359,16 @@ Date: YYYY-MM-DD **Full Changelog**: [`v7.X.Y...v7.X.Y`](https://github.com/remix-run/react-router/compare/react-router@7.X.Y...react-router@7.X.Y) --> +## v7.9.1 + +Date: 2025-09-12 + +### Patch Changes + +- Fix internal `Future` interface naming from `middleware` -> `v8_middleware` ([#14327](https://github.com/remix-run/react-router/pull/14327)) + +**Full Changelog**: [`v7.9.0...v7.9.1`](https://github.com/remix-run/react-router/compare/react-router@7.9.0...react-router@7.9.1) + ## v7.9.0 Date: 2025-09-12 diff --git a/GOVERNANCE.md b/GOVERNANCE.md index b0e7d38cd5..ce9bcf1e55 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -12,6 +12,7 @@ - [Stage 3 — Beta](#stage-3--beta) - [Stage 4 — Stabilization](#stage-4--stabilization) - [Stage 5 — Stable](#stage-5--stable) +- [Meeting Notes](#meeting-notes) ## Overview @@ -150,3 +151,51 @@ This table gives a high-level overview of the stages, but please see the individ - A proposal enters **Stage 5 — Stable** once it receives **Stage 4 — Stabilization** PR approvals from at least 50% of the SC members and is merged to `dev` - An SC member authoring the stabilization PR counts as an implicit approval - This will include the stable feature in `nightly` releases and the next normal SemVer release + +## Meeting Notes + +This section captures the notes from the React Router Steering Committee meetings: + + + +
+ 2025-09-08 Meeting Notes + +**Summary** + +Matt Brophy, Bryan Ross (rossipedia), Mark Dalgleish, and Pedro Cattori discussed the progress of various features, including middleware, context, the `onError` feature, and RSC framework mode, with most nearing completion or already released. Matt Brophy and Bryan Ross (rossipedia) also explored the integration of observability and OpenTelemetry with Sentry and React Router, considering OpenTelemetry as a potential standard for JavaScript monitoring. The team decided to focus on current in-progress items instead of reviewing and accepting additional proposals because there are already 10+ proposals in-progress. + +**Details** + +- Roadmap Review and Release Progress + - Matt Brophy initiated the meeting by reviewing the public roadmap, starting with [middleware](https://github.com/remix-run/react-router/issues/12695) and [context](https://github.com/remix-run/react-router/issues/14055), which are merged to dev and awaiting a pre-release for version 7.9.0 + - Bryan Ross (rossipedia) confirmed that the [`onError`](https://github.com/remix-run/react-router/issues/12958) feature, released in 7.8.2, is working as expected and providing anticipated data + - Mark Dalgleish noted that the RSC framework mode initial release will not be feature complete but is nearing completion, with the main remaining task being error handling during rendering ([RFC](https://github.com/remix-run/react-router/issues/11566)) +- Upcoming Features and API Discussions + - Pedro Cattori discussed the `useRouterState` hook, noting that Ryan's attention is elsewhere, but they are interested in revisiting it for type safety and potentially replacing the `useRouteLoaderData` hook + - Brooks Lybrand and Pedro Cattori agreed that the `use matches` API is problematic, especially concerning type safety, and suggested finding a solution that does not rely on it + - We may be able to keep the distinction that hooks for use in data mode are less type-safe than the typegen equivalents in framework mode, so it might be ok for `useRouterState().matches` to be less type-safe than `Route.ComponentProps["matches"]` + - [RFC](https://github.com/remix-run/react-router/issues/13073) +- Observability and OpenTelemetry Integration + - Matt Brophy and Bryan Ross (rossipedia) discussed the [observability](https://github.com/remix-run/react-router/discussions/13749) feature, which aims to improve Sentry's integration with React Router Apps + - Bryan Ross (rossipedia) explained that a strict event-based system would not support OpenTelemetry because OpenTelemetry requires bounding code execution within a span, unlike events which are instantaneous + - They are considering whether React Router should fully embrace OpenTelemetry as it appears to be becoming a de facto standard for JavaScript monitoring, which could potentially replace the need for a separate event system +- Meeting Wrap-up and Next Steps + - Matt Brophy announced that the pre-release for version 7.9.0 would be shipped shortly, with the full release expected by the end of the week + - Bryan Ross (rossipedia) confirmed that the duplicate loader issue fix will be included in this release + - The team decided not to overload themselves with additional tasks, focusing on the current in-progress items + +**Action Items** + +- Mark Dalgleish will work on stabilizing the split route modules and Vite environment API flags +- Matt Brophy will read through the SvelteKit blog post to understand their approach to OpenTelemetry integration +- Matt Brophy will merge the unstable [`fetcher.reset()`](https://github.com/remix-run/react-router/issues/14207) work after 7.9.0 is released () +- Matt Brophy will try to pick up the [``](https://github.com/remix-run/react-router/discussions/12375) task soon +- Matt Brophy and Pedro Cattori will sync up offline to figure out what parts of the consolidated hook can be done better with typegen and decide on the requirements ([RFC](https://github.com/remix-run/react-router/issues/13073)) +
diff --git a/docs/api/framework-conventions/react-router.config.ts.md b/docs/api/framework-conventions/react-router.config.ts.md index fcb4038240..ea5ef0f44a 100644 --- a/docs/api/framework-conventions/react-router.config.ts.md +++ b/docs/api/framework-conventions/react-router.config.ts.md @@ -66,7 +66,7 @@ A function that is called after the full React Router build is complete. ```tsx filename=react-router.config.ts export default { - buildEnd: async ({ buildManifest, serverBuildPath }) => { + buildEnd: async ({ buildManifest, reactRouterConfig, viteConfig }) => { // Custom build logic here console.log("Build completed!"); }, diff --git a/docs/api/framework-conventions/root.tsx.md b/docs/api/framework-conventions/root.tsx.md index b9822d7b9c..ab0ca32d49 100644 --- a/docs/api/framework-conventions/root.tsx.md +++ b/docs/api/framework-conventions/root.tsx.md @@ -191,7 +191,7 @@ export function Layout({ } ``` -[route-module]: ../start/framework/route-module +[route-module]: ../../start/framework/route-module [react-link]: https://react.dev/reference/react-dom/components/link [react-meta]: https://react.dev/reference/react-dom/components/meta [react-title]: https://react.dev/reference/react-dom/components/title diff --git a/docs/api/framework-conventions/routes.ts.md b/docs/api/framework-conventions/routes.ts.md index 7d4845c42d..ccac795d3d 100644 --- a/docs/api/framework-conventions/routes.ts.md +++ b/docs/api/framework-conventions/routes.ts.md @@ -42,7 +42,7 @@ You can use the following helpers to create route config entries: - [`route`][route] — Helper function for creating a route config entry - [`index`][index] — Helper function for creating a route config entry for an index route - [`layout`][layout] — Helper function for creating a route config entry for a layout route -- [`prefix`][prefix] — Helper function for adding a path prefix to a set of routes without needing to introduce a parent route file +- [`prefix`][prefix] — Helper function for adding a path prefix to a set of routes without needing to introduce a parent route - [`relative`][relative] — Creates a set of route config helpers that resolve file paths relative to the given directory. Designed to support splitting route config into multiple files within different directories ### File-based Routing diff --git a/docs/start/framework/routing.md b/docs/start/framework/routing.md index 796c571e37..157ce0c76b 100644 --- a/docs/start/framework/routing.md +++ b/docs/start/framework/routing.md @@ -204,7 +204,7 @@ Note that index routes can't have children. ## Route Prefixes -Using `prefix`, you can add a path prefix to a set of routes without needing to introduce a parent route file. +Using `prefix`, you can add a path prefix to a set of routes without needing to introduce a parent route. ```tsx filename=app/routes.ts lines=[14] import { @@ -230,6 +230,24 @@ export default [ ] satisfies RouteConfig; ``` +Note that this does not introduce a new route into the route tree. Instead, it merely modifies the paths of its children. + +For example, these two sets of routes are equivalent: + +```ts filename=app/routes.ts +// This usage of `prefix`... +prefix("parent", [ + route("child1", "./child1.tsx"), + route("child2", "./child2.tsx"), +]) + +// ...is equivalent to this: +[ + route("parent/child1", "./child1.tsx"), + route("parent/child2", "./child2.tsx"), +] +``` + ## Dynamic Segments If a path segment starts with `:` then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as `params` to other router APIs. diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 7117c23a41..0a7b1d7f72 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -23,8 +23,17 @@ function typecheck(cwd: string) { return spawnSync(nodeBin, [tscBin], { cwd }); } -const viteConfig = tsx` - import { reactRouter } from "@react-router/dev/vite"; +const viteConfig = ({ rsc }: { rsc: boolean } = { rsc: false }) => tsx` + ${ + rsc + ? tsx` + import { __INTERNAL_DO_NOT_USE_OR_YOU_WILL_GET_A_STRONGLY_WORDED_LETTER__ } from '@react-router/dev/internal'; + const { unstable_reactRouterRSC: reactRouter } = __INTERNAL_DO_NOT_USE_OR_YOU_WILL_GET_A_STRONGLY_WORDED_LETTER__; + ` + : tsx` + import { reactRouter } from "@react-router/dev/vite"; + ` + } export default { plugins: [reactRouter()], @@ -42,7 +51,7 @@ const expectType = tsx` test.describe("typegen", () => { test("basic", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -76,7 +85,7 @@ test.describe("typegen", () => { test.describe("params", () => { test("repeated", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -130,7 +139,7 @@ test.describe("typegen", () => { test("splat", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -157,7 +166,7 @@ test.describe("typegen", () => { test("with extension", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -194,7 +203,7 @@ test.describe("typegen", () => { test("normalized params", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route, layout } from "@react-router/dev/routes"; @@ -295,7 +304,7 @@ test.describe("typegen", () => { test("clientLoader.hydrate = true", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes/_index.tsx": tsx` import type { Expect, Equal } from "../expect-type" @@ -328,7 +337,7 @@ test.describe("typegen", () => { test("clientLoader data should not be serialized", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes/_index.tsx": tsx` import { useRouteLoaderData } from "react-router" @@ -356,9 +365,291 @@ test.describe("typegen", () => { expect(proc.status).toBe(0); }); + test.describe("server-first route component detection", async () => { + test.describe("ServerComponent export", async () => { + test("when RSC Framework Mode plugin is present", async () => { + const cwd = await await createProject({ + "vite.config.ts": viteConfig({ rsc: true }), + "app/expect-type.ts": expectType, + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("server-component/:id", "routes/server-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/server-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/server-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + export function ServerComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ServerComponent

+

Loader data: {loaderData.server}

+

Action data: {actionData?.server}

+ + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+

Loader data: {loaderData?.server}

+

Action data: {actionData?.server}

+ + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+

Loader data: {loaderData?.server}

+

Action data: {actionData?.server}

+ + ) + } + `, + }); + const proc = typecheck(cwd); + expect(proc.stdout.toString()).toBe(""); + expect(proc.stderr.toString()).toBe(""); + expect(proc.status).toBe(0); + }); + + test("when RSC Framework Mode plugin is not present", async () => { + const cwd = await await createProject({ + "vite.config.ts": viteConfig({ rsc: false }), + "app/expect-type.ts": expectType, + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("server-component/:id", "routes/server-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/server-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/server-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + // This export is not used in standard Framework Mode. This is just + // to test that the typegen is unaffected by this export outside of + // RSC Framework Mode. + export function ServerComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ServerComponent (unused)

+

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

+ {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + `, + }); + const proc = typecheck(cwd); + expect(proc.stdout.toString()).toBe(""); + expect(proc.stderr.toString()).toBe(""); + expect(proc.status).toBe(0); + }); + }); + + test.describe("default export", async () => { + async function createClientFirstRouteProject({ rsc }: { rsc: boolean }) { + return await await createProject({ + "vite.config.ts": viteConfig({ rsc }), + "app/expect-type.ts": expectType, + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("client-component/:id", "routes/client-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/client-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/client-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + export default function ClientComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

default (Component)

+

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

+ {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + `, + }); + } + + test("when RSC Framework Mode plugin is present", async () => { + const cwd = await createClientFirstRouteProject({ rsc: true }); + const proc = typecheck(cwd); + expect(proc.stdout.toString()).toBe(""); + expect(proc.stderr.toString()).toBe(""); + expect(proc.status).toBe(0); + }); + + test("when RSC Framework Mode plugin is not present", async () => { + const cwd = await createClientFirstRouteProject({ rsc: false }); + const proc = typecheck(cwd); + expect(proc.stdout.toString()).toBe(""); + expect(proc.stderr.toString()).toBe(""); + expect(proc.status).toBe(0); + }); + }); + }); + test("custom app dir", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "react-router.config.ts": tsx` export default { appDirectory: "src/myapp", @@ -391,7 +682,7 @@ test.describe("typegen", () => { test("matches", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -488,7 +779,7 @@ test.describe("typegen", () => { test("route files with absolute paths", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import path from "node:path"; @@ -522,7 +813,7 @@ test.describe("typegen", () => { test("href", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import path from "node:path"; @@ -592,7 +883,7 @@ test.describe("typegen", () => { test.describe("virtual:react-router/server-build", async () => { test("static import matches 'createRequestHandler' argument type", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/routes.ts": tsx` import { type RouteConfig } from "@react-router/dev/routes"; export default [] satisfies RouteConfig; @@ -612,7 +903,7 @@ test.describe("typegen", () => { test("works with tsconfig 'moduleDetection' set to 'force'", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/routes.ts": tsx` import { type RouteConfig } from "@react-router/dev/routes"; export default [] satisfies RouteConfig; @@ -642,7 +933,7 @@ test.describe("typegen", () => { test("dynamic import matches 'createRequestHandler' function argument type", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/routes.ts": tsx` import { type RouteConfig } from "@react-router/dev/routes"; export default [] satisfies RouteConfig; @@ -664,7 +955,7 @@ test.describe("typegen", () => { test("reuse route file at multiple paths", async () => { const cwd = await createProject({ - "vite.config.ts": viteConfig, + "vite.config.ts": viteConfig(), "app/expect-type.ts": expectType, "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; diff --git a/integration/vite-dotenv-test.ts b/integration/vite-dotenv-test.ts index 4c70dcbee9..c9faf7ad54 100644 --- a/integration/vite-dotenv-test.ts +++ b/integration/vite-dotenv-test.ts @@ -2,18 +2,32 @@ import { test, expect } from "@playwright/test"; import getPort from "get-port"; import { + type TemplateName, createProject, customDev, EXPRESS_SERVER, viteConfig, } from "./helpers/vite.js"; -let getFiles = async ({ envDir, port }: { envDir?: string; port: number }) => { +const templateNames = [ + "vite-5-template", + "rsc-vite-framework", +] as const satisfies TemplateName[]; + +let getFiles = async ({ + templateName, + envDir, + port, +}: { + templateName: TemplateName; + envDir?: string; + port: number; +}) => { let envPath = `${envDir ? `${envDir}/` : ""}.env`; return { - "vite.config.js": await viteConfig.basic({ port, envDir }), - "server.mjs": EXPRESS_SERVER({ port }), + "vite.config.js": await viteConfig.basic({ templateName, port, envDir }), + "server.mjs": EXPRESS_SERVER({ port, templateName }), [envPath]: ` ENV_VAR_FROM_DOTENV_FILE=Content from ${envPath} file `, @@ -49,72 +63,90 @@ let getFiles = async ({ envDir, port }: { envDir?: string; port: number }) => { }; test.describe("Vite .env", () => { - test.describe("defaults", async () => { - let port: number; - let cwd: string; - let stop: () => void; - - test.beforeAll(async () => { - port = await getPort(); - cwd = await createProject(await getFiles({ port })); - stop = await customDev({ cwd, port }); - }); - test.afterAll(() => stop()); - - test("express", async ({ page }) => { - let pageErrors: unknown[] = []; - page.on("pageerror", (error) => pageErrors.push(error)); - - await page.goto(`http://localhost:${port}/dotenv`, { - waitUntil: "networkidle", + for (const templateName of templateNames) { + test.describe(`template: ${templateName}`, () => { + test.describe("defaults", async () => { + let port: number; + let cwd: string; + let stop: () => void; + + test.beforeAll(async () => { + port = await getPort(); + cwd = await createProject( + await getFiles({ port, templateName }), + templateName, + ); + stop = await customDev({ cwd, port }); + }); + test.afterAll(() => stop()); + + test("express", async ({ page }) => { + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await page.goto(`http://localhost:${port}/dotenv`, { + waitUntil: "networkidle", + }); + expect(pageErrors).toEqual([]); + + let loaderContent = page.locator( + "[data-dotenv-route-loader-content]", + ); + await expect(loaderContent).toHaveText("Content from .env file"); + + let clientContent = page.locator( + "[data-dotenv-route-client-content]", + ); + await expect(clientContent).toHaveText( + "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing", + ); + + expect(pageErrors).toEqual([]); + }); }); - expect(pageErrors).toEqual([]); - - let loaderContent = page.locator("[data-dotenv-route-loader-content]"); - await expect(loaderContent).toHaveText("Content from .env file"); - - let clientContent = page.locator("[data-dotenv-route-client-content]"); - await expect(clientContent).toHaveText( - "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing", - ); - - expect(pageErrors).toEqual([]); - }); - }); - - test.describe("custom env dir", async () => { - let port: number; - let cwd: string; - let stop: () => void; - - test.beforeAll(async () => { - const envDir = "custom-env-dir"; - port = await getPort(); - cwd = await createProject(await getFiles({ envDir, port })); - stop = await customDev({ cwd, port }); - }); - test.afterAll(() => stop()); - - test("express", async ({ page }) => { - let pageErrors: unknown[] = []; - page.on("pageerror", (error) => pageErrors.push(error)); - await page.goto(`http://localhost:${port}/dotenv`, { - waitUntil: "networkidle", + test.describe("custom env dir", async () => { + let port: number; + let cwd: string; + let stop: () => void; + + test.beforeAll(async () => { + const envDir = "custom-env-dir"; + port = await getPort(); + cwd = await createProject( + await getFiles({ envDir, port, templateName }), + templateName, + ); + stop = await customDev({ cwd, port }); + }); + test.afterAll(() => stop()); + + test("express", async ({ page }) => { + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await page.goto(`http://localhost:${port}/dotenv`, { + waitUntil: "networkidle", + }); + expect(pageErrors).toEqual([]); + + let loaderContent = page.locator( + "[data-dotenv-route-loader-content]", + ); + await expect(loaderContent).toHaveText( + "Content from custom-env-dir/.env file", + ); + + let clientContent = page.locator( + "[data-dotenv-route-client-content]", + ); + await expect(clientContent).toHaveText( + "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing", + ); + + expect(pageErrors).toEqual([]); + }); }); - expect(pageErrors).toEqual([]); - - let loaderContent = page.locator("[data-dotenv-route-loader-content]"); - await expect(loaderContent).toHaveText( - "Content from custom-env-dir/.env file", - ); - - let clientContent = page.locator("[data-dotenv-route-client-content]"); - await expect(clientContent).toHaveText( - "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing", - ); - - expect(pageErrors).toEqual([]); }); - }); + } }); diff --git a/packages/create-react-router/CHANGELOG.md b/packages/create-react-router/CHANGELOG.md index 7eff0ca5d5..3389ec3b20 100644 --- a/packages/create-react-router/CHANGELOG.md +++ b/packages/create-react-router/CHANGELOG.md @@ -1,5 +1,9 @@ # `create-react-router` +## 7.9.1 + +_No changes_ + ## 7.9.0 _No changes_ diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json index 8c9a1349ab..757459ba8a 100644 --- a/packages/create-react-router/package.json +++ b/packages/create-react-router/package.json @@ -1,6 +1,6 @@ { "name": "create-react-router", - "version": "7.9.0", + "version": "7.9.1", "description": "Create a new React Router app", "homepage": "https://reactrouter.com/", "bugs": { diff --git a/packages/react-router-architect/CHANGELOG.md b/packages/react-router-architect/CHANGELOG.md index f3697f945b..c24b6ee0cb 100644 --- a/packages/react-router-architect/CHANGELOG.md +++ b/packages/react-router-architect/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/architect` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + - `@react-router/node@7.9.1` + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json index 33656d777d..2fff134271 100644 --- a/packages/react-router-architect/package.json +++ b/packages/react-router-architect/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/architect", - "version": "7.9.0", + "version": "7.9.1", "description": "Architect server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-cloudflare/CHANGELOG.md b/packages/react-router-cloudflare/CHANGELOG.md index 548ac9d80c..1df5d8f4e8 100644 --- a/packages/react-router-cloudflare/CHANGELOG.md +++ b/packages/react-router-cloudflare/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/cloudflare` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json index 287872d694..e7aa699130 100644 --- a/packages/react-router-cloudflare/package.json +++ b/packages/react-router-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/cloudflare", - "version": "7.9.0", + "version": "7.9.1", "description": "Cloudflare platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md index 47d3aa31a9..96589802c0 100644 --- a/packages/react-router-dev/CHANGELOG.md +++ b/packages/react-router-dev/CHANGELOG.md @@ -1,5 +1,15 @@ # `@react-router/dev` +## 7.9.1 + +### Patch Changes + +- Fix internal `Future` interface naming from `middleware` -> `v8_middleware` ([#14327](https://github.com/remix-run/react-router/pull/14327)) +- Updated dependencies: + - `react-router@7.9.1` + - `@react-router/node@7.9.1` + - `@react-router/serve@7.9.1` + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router-dev/cli/commands.ts b/packages/react-router-dev/cli/commands.ts index 04d338754b..90b900ddcf 100644 --- a/packages/react-router-dev/cli/commands.ts +++ b/packages/react-router-dev/cli/commands.ts @@ -247,6 +247,14 @@ export async function typegen( ) { root = resolveRootDirectory(root, flags); + const rsc = await hasReactRouterRscPlugin({ + root, + viteBuildOptions: { + config: flags.config, + mode: flags.mode, + }, + }); + if (flags.watch) { await preloadVite(); const vite = getVite(); @@ -254,6 +262,7 @@ export async function typegen( await Typegen.watch(root, { mode: flags.mode ?? "development", + rsc, logger, }); await new Promise(() => {}); // keep alive @@ -262,5 +271,6 @@ export async function typegen( await Typegen.run(root, { mode: flags.mode ?? "production", + rsc, }); } diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts index 68ef25cb12..c0d92b2714 100644 --- a/packages/react-router-dev/config/config.ts +++ b/packages/react-router-dev/config/config.ts @@ -345,6 +345,8 @@ type Result = error: string; }; +type ConfigResult = Result; + function ok(value: T): Result { return { ok: true, value }; } @@ -365,7 +367,7 @@ async function resolveConfig({ reactRouterConfigFile?: string; skipRoutes?: boolean; validateConfig?: ValidateConfigFunction; -}): Promise> { +}): Promise { let reactRouterUserConfig: ReactRouterConfig = {}; if (reactRouterConfigFile) { @@ -506,7 +508,7 @@ async function resolveConfig({ let appDirectory = Path.resolve(root, userAppDirectory || "app"); let buildDirectory = Path.resolve(root, userBuildDirectory); - let rootRouteFile = findEntry(appDirectory, "root"); + let rootRouteFile = findEntry(appDirectory, "root", { absolute: true }); if (!rootRouteFile) { let rootRouteDisplayPath = Path.relative( root, @@ -556,7 +558,7 @@ async function resolveConfig({ { id: "root", path: "", - file: rootRouteFile, + file: Path.relative(appDirectory, rootRouteFile), children: result.routeConfig, }, ]; @@ -622,7 +624,7 @@ async function resolveConfig({ type ChokidarEventName = ChokidarEmitArgs[0]; type ChangeHandler = (args: { - result: Result; + result: ConfigResult; configCodeChanged: boolean; routeConfigCodeChanged: boolean; configChanged: boolean; @@ -632,7 +634,7 @@ type ChangeHandler = (args: { }) => void; export type ConfigLoader = { - getConfig: () => Promise>; + getConfig: () => Promise; onChange: (handler: ChangeHandler) => () => void; close: () => Promise; }; diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json index 361fe17038..35540a8efa 100644 --- a/packages/react-router-dev/package.json +++ b/packages/react-router-dev/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/dev", - "version": "7.9.0", + "version": "7.9.1", "description": "Dev tools and CLI for React Router", "homepage": "https://reactrouter.com/", "bugs": { diff --git a/packages/react-router-dev/typegen/context.ts b/packages/react-router-dev/typegen/context.ts index 31909eb92f..efad1e28dc 100644 --- a/packages/react-router-dev/typegen/context.ts +++ b/packages/react-router-dev/typegen/context.ts @@ -8,16 +8,19 @@ export type Context = { rootDirectory: string; configLoader: ConfigLoader; config: ResolvedReactRouterConfig; + rsc: boolean; }; export async function createContext({ rootDirectory, watch, mode, + rsc, }: { rootDirectory: string; watch: boolean; mode: string; + rsc: boolean; }): Promise { const configLoader = await createConfigLoader({ rootDirectory, mode, watch }); const configResult = await configLoader.getConfig(); @@ -32,5 +35,6 @@ export async function createContext({ configLoader, rootDirectory, config, + rsc, }; } diff --git a/packages/react-router-dev/typegen/generate.ts b/packages/react-router-dev/typegen/generate.ts index 6bb3742db3..74df004ee6 100644 --- a/packages/react-router-dev/typegen/generate.ts +++ b/packages/react-router-dev/typegen/generate.ts @@ -23,7 +23,7 @@ export function generateFuture(ctx: Context): VirtualFile { declare module "react-router" { interface Future { - middleware: ${ctx.config.future.v8_middleware} + v8_middleware: ${ctx.config.future.v8_middleware} } } `; @@ -274,7 +274,7 @@ function getRouteAnnotations({ Babel.generate(matchesType).code + "\n\n" + ts` - type Annotations = GetAnnotations; + type Annotations = GetAnnotations; export namespace Route { // links diff --git a/packages/react-router-dev/typegen/index.ts b/packages/react-router-dev/typegen/index.ts index d8cd731454..32ff56292b 100644 --- a/packages/react-router-dev/typegen/index.ts +++ b/packages/react-router-dev/typegen/index.ts @@ -29,8 +29,11 @@ async function write(...files: Array) { ); } -export async function run(rootDirectory: string, { mode }: { mode: string }) { - const ctx = await createContext({ rootDirectory, mode, watch: false }); +export async function run( + rootDirectory: string, + { mode, rsc }: { mode: string; rsc: boolean }, +) { + const ctx = await createContext({ rootDirectory, mode, rsc, watch: false }); await fs.rm(typesDirectory(ctx), { recursive: true, force: true }); await write( generateFuture(ctx), @@ -45,9 +48,9 @@ export type Watcher = { export async function watch( rootDirectory: string, - { mode, logger }: { mode: string; logger?: vite.Logger }, + { mode, logger, rsc }: { mode: string; logger?: vite.Logger; rsc: boolean }, ): Promise { - const ctx = await createContext({ rootDirectory, mode, watch: true }); + const ctx = await createContext({ rootDirectory, mode, rsc, watch: true }); await fs.rm(typesDirectory(ctx), { recursive: true, force: true }); await write( generateFuture(ctx), diff --git a/packages/react-router-dev/vite/load-dotenv.ts b/packages/react-router-dev/vite/load-dotenv.ts new file mode 100644 index 0000000000..ddde2c6bc0 --- /dev/null +++ b/packages/react-router-dev/vite/load-dotenv.ts @@ -0,0 +1,24 @@ +import type * as Vite from "vite"; + +export async function loadDotenv({ + rootDirectory, + viteUserConfig, + mode, +}: { + rootDirectory: string; + viteUserConfig: Vite.UserConfig; + mode: string; +}) { + const vite = await import("vite"); + Object.assign( + process.env, + vite.loadEnv( + mode, + viteUserConfig.envDir ?? rootDirectory, + // We override the default prefix of "VITE_" with a blank string since + // we're targeting the server, so we want to load all environment + // variables, not just those explicitly marked for the client + "", + ), + ); +} diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index bda01f6c4c..4f86b5b3f3 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -81,6 +81,7 @@ import { } from "../config/config"; import { getOptimizeDepsEntries } from "./optimize-deps-entries"; import { decorateComponentExportsWithProps } from "./with-props"; +import { loadDotenv } from "./load-dotenv"; import { validatePluginOrder } from "./plugins/validate-plugin-order"; import { warnOnClientSourceMaps } from "./plugins/warn-on-client-source-maps"; @@ -1197,6 +1198,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { if (viteCommand === "serve") { typegenWatcherPromise = Typegen.watch(rootDirectory, { mode, + rsc: false, // ignore `info` logs from typegen since they are redundant when Vite plugin logs are active logger: vite.createLogger("warn", { prefix: "[react-router]" }), }); @@ -1210,17 +1212,11 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { await updatePluginContext(); - Object.assign( - process.env, - vite.loadEnv( - viteConfigEnv.mode, - viteUserConfig.envDir ?? ctx.rootDirectory, - // We override the default prefix of "VITE_" with a blank string since - // we're targeting the server, so we want to load all environment - // variables, not just those explicitly marked for the client - "", - ), - ); + await loadDotenv({ + rootDirectory, + viteUserConfig, + mode, + }); let environments = await getEnvironmentsOptions(ctx, viteCommand, { viteUserConfig, diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 4cc139f281..79777bf1a9 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -1,5 +1,6 @@ import type * as Vite from "vite"; import { init as initEsModuleLexer } from "es-module-lexer"; +import * as Path from "pathe"; import * as babel from "@babel/core"; import colors from "picocolors"; @@ -23,12 +24,12 @@ import { isVirtualClientRouteModuleId, CLIENT_NON_COMPONENT_EXPORTS, } from "./virtual-route-modules"; +import { loadDotenv } from "../load-dotenv"; import { validatePluginOrder } from "../plugins/validate-plugin-order"; import { warnOnClientSourceMaps } from "../plugins/warn-on-client-source-maps"; export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { let configLoader: ConfigLoader; - let config: ResolvedReactRouterConfig; let typegenWatcherPromise: Promise | undefined; let viteCommand: Vite.ConfigEnv["command"]; let routeIdByFile: Map | undefined; @@ -36,6 +37,16 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { const defaultEntries = getDefaultEntries(); + let config: ResolvedReactRouterConfig; + let rootRouteFile: string; + function updateConfig(newConfig: ResolvedReactRouterConfig) { + config = newConfig; + rootRouteFile = Path.resolve( + newConfig.appDirectory, + newConfig.routes.root.file, + ); + } + return [ { name: "react-router/rsc", @@ -75,7 +86,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { const configResult = await configLoader.getConfig(); if (!configResult.ok) throw new Error(configResult.error); - config = configResult.value; + updateConfig(configResult.value); if ( viteUserConfig.base && @@ -91,6 +102,12 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { ); } + await loadDotenv({ + rootDirectory, + viteUserConfig, + mode, + }); + const vite = await import("vite"); logger = vite.createLogger(viteUserConfig.logLevel, { prefix: "[react-router]", @@ -246,7 +263,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { }); // Update shared plugin config reference - config = result.value; + updateConfig(result.value); if (configChanged || routeConfigChanged) { invalidateVirtualModules(viteDevServer); @@ -267,6 +284,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { getRootDirectory(viteUserConfig), { mode, + rsc: true, // ignore `info` logs from typegen since they are // redundant when Vite plugin logs are active logger: vite.createLogger("warn", { @@ -314,6 +332,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { id, viteCommand, routeIdByFile, + rootRouteFile, viteEnvironment: this.environment, }); }, diff --git a/packages/react-router-dev/vite/rsc/virtual-route-config.ts b/packages/react-router-dev/vite/rsc/virtual-route-config.ts index a9a4352b4a..1edddcb042 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-config.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-config.ts @@ -28,7 +28,7 @@ export function createVirtualRouteConfig({ const routeId = route.id || createRouteId(route.file, appDirectory); routeIdByFile.set(routeFile, routeId); code += `lazy: () => import(${JSON.stringify( - `${routeFile}?route-module${routeId === "root" ? "&root-route=true" : ""}`, + `${routeFile}?route-module`, )}),`; code += `id: ${JSON.stringify(routeId)},`; diff --git a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts index 0dc93141ff..5c5cdea04e 100644 --- a/packages/react-router-dev/vite/rsc/virtual-route-modules.ts +++ b/packages/react-router-dev/vite/rsc/virtual-route-modules.ts @@ -97,40 +97,54 @@ export function transformVirtualRouteModules({ code, viteCommand, routeIdByFile, + rootRouteFile, viteEnvironment, }: { id: string; code: string; viteCommand: ViteCommand; routeIdByFile: Map; + rootRouteFile: string; viteEnvironment: Vite.Environment; }) { if (isVirtualRouteModuleId(id) || routeIdByFile.has(id)) { return createVirtualRouteModuleCode({ id, code, + rootRouteFile, viteCommand, viteEnvironment, }); } if (isVirtualServerRouteModuleId(id)) { - return createVirtualServerRouteModuleCode({ id, code, viteEnvironment }); + return createVirtualServerRouteModuleCode({ + id, + code, + viteEnvironment, + }); } if (isVirtualClientRouteModuleId(id)) { - return createVirtualClientRouteModuleCode({ id, code, viteCommand }); + return createVirtualClientRouteModuleCode({ + id, + code, + rootRouteFile, + viteCommand, + }); } } async function createVirtualRouteModuleCode({ id, code: routeSource, + rootRouteFile, viteCommand, viteEnvironment, }: { id: string; code: string; + rootRouteFile: string; viteCommand: ViteCommand; viteEnvironment: Vite.Environment; }) { @@ -183,7 +197,10 @@ async function createVirtualRouteModuleCode({ } } - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteFile({ id, rootRouteFile }) && + !staticExports.includes("ErrorBoundary") + ) { code += `export { ErrorBoundary } from "${clientModuleId}";\n`; } @@ -236,10 +253,12 @@ function createVirtualServerRouteModuleCode({ function createVirtualClientRouteModuleCode({ id, code: routeSource, + rootRouteFile, viteCommand, }: { id: string; code: string; + rootRouteFile: string; viteCommand: ViteCommand; }) { const { staticExports, isServerFirstRoute, hasClientExports } = @@ -256,7 +275,10 @@ function createVirtualClientRouteModuleCode({ const generatorResult = babel.generate(clientRouteModuleAst); generatorResult.code = '"use client";' + generatorResult.code; - if (isRootRouteId(id) && !staticExports.includes("ErrorBoundary")) { + if ( + isRootRouteFile({ id, rootRouteFile }) && + !staticExports.includes("ErrorBoundary") + ) { const hasRootLayout = staticExports.includes("Layout"); generatorResult.code += `\nimport { createElement as __rr_createElement } from "react";\n`; generatorResult.code += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`; @@ -288,15 +310,11 @@ export function parseRouteExports(code: string) { } function getVirtualClientModuleId(id: string): string { - return `${id.split("?")[0]}?client-route-module${isRootRouteId(id) ? "&root-route=true" : ""}`; + return `${id.split("?")[0]}?client-route-module`; } function getVirtualServerModuleId(id: string): string { - return `${id.split("?")[0]}?server-route-module${isRootRouteId(id) ? "&root-route=true" : ""}`; -} - -function isRootRouteId(id: string): boolean { - return /(\?|&)root-route=true(&|$)/.test(id); + return `${id.split("?")[0]}?server-route-module`; } function isVirtualRouteModuleId(id: string): boolean { @@ -310,3 +328,14 @@ export function isVirtualClientRouteModuleId(id: string): boolean { function isVirtualServerRouteModuleId(id: string): boolean { return /(\?|&)server-route-module(&|$)/.test(id); } + +function isRootRouteFile({ + id, + rootRouteFile, +}: { + id: string; + rootRouteFile: string; +}): boolean { + const filePath = id.split("?")[0]; + return filePath === rootRouteFile; +} diff --git a/packages/react-router-dom/CHANGELOG.md b/packages/react-router-dom/CHANGELOG.md index a354aeaa86..e0b98360a6 100644 --- a/packages/react-router-dom/CHANGELOG.md +++ b/packages/react-router-dom/CHANGELOG.md @@ -1,5 +1,12 @@ # react-router-dom +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + ## 7.9.0 ### Patch Changes diff --git a/packages/react-router-dom/package.json b/packages/react-router-dom/package.json index 0f6a117a57..61cb4ea2ee 100644 --- a/packages/react-router-dom/package.json +++ b/packages/react-router-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-router-dom", - "version": "7.9.0", + "version": "7.9.1", "description": "Declarative routing for React web applications", "keywords": [ "react", diff --git a/packages/react-router-express/CHANGELOG.md b/packages/react-router-express/CHANGELOG.md index b7501da330..86d0634bf4 100644 --- a/packages/react-router-express/CHANGELOG.md +++ b/packages/react-router-express/CHANGELOG.md @@ -1,5 +1,13 @@ # `@react-router/express` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + - `@react-router/node@7.9.1` + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json index a58c403c92..89d58cbbd8 100644 --- a/packages/react-router-express/package.json +++ b/packages/react-router-express/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/express", - "version": "7.9.0", + "version": "7.9.1", "description": "Express server request handler for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-fs-routes/CHANGELOG.md b/packages/react-router-fs-routes/CHANGELOG.md index 619521de63..a67fdbfc2e 100644 --- a/packages/react-router-fs-routes/CHANGELOG.md +++ b/packages/react-router-fs-routes/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/fs-routes` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `@react-router/dev@7.9.1` + ## 7.9.0 ### Patch Changes diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json index b8ca3f3de1..47a846e5d5 100644 --- a/packages/react-router-fs-routes/package.json +++ b/packages/react-router-fs-routes/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/fs-routes", - "version": "7.9.0", + "version": "7.9.1", "description": "File system routing conventions for React Router, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-node/CHANGELOG.md b/packages/react-router-node/CHANGELOG.md index e8f6d1b735..4ffca342fc 100644 --- a/packages/react-router-node/CHANGELOG.md +++ b/packages/react-router-node/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/node` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json index 9d93b27ac0..2ea5049029 100644 --- a/packages/react-router-node/package.json +++ b/packages/react-router-node/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/node", - "version": "7.9.0", + "version": "7.9.1", "description": "Node.js platform abstractions for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md index 94585601cf..6af5a0305a 100644 --- a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md +++ b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # `@react-router/remix-config-routes-adapter` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `@react-router/dev@7.9.1` + ## 7.9.0 ### Patch Changes diff --git a/packages/react-router-remix-routes-option-adapter/package.json b/packages/react-router-remix-routes-option-adapter/package.json index 1f1359d1dc..c819e0503c 100644 --- a/packages/react-router-remix-routes-option-adapter/package.json +++ b/packages/react-router-remix-routes-option-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/remix-routes-option-adapter", - "version": "7.9.0", + "version": "7.9.1", "description": "Adapter for Remix's \"routes\" config option, for use within routes.ts", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router-serve/CHANGELOG.md b/packages/react-router-serve/CHANGELOG.md index f84c990c1f..770fb25277 100644 --- a/packages/react-router-serve/CHANGELOG.md +++ b/packages/react-router-serve/CHANGELOG.md @@ -1,5 +1,14 @@ # `@react-router/serve` +## 7.9.1 + +### Patch Changes + +- Updated dependencies: + - `react-router@7.9.1` + - `@react-router/node@7.9.1` + - `@react-router/express@7.9.1` + ## 7.9.0 ### Patch Changes diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json index 18bd23a0e6..b087d4e44d 100644 --- a/packages/react-router-serve/package.json +++ b/packages/react-router-serve/package.json @@ -1,6 +1,6 @@ { "name": "@react-router/serve", - "version": "7.9.0", + "version": "7.9.1", "description": "Production application server for React Router", "bugs": { "url": "https://github.com/remix-run/react-router/issues" diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index cd1d12b459..657d2583b7 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -1,5 +1,11 @@ # `react-router` +## 7.9.1 + +### Patch Changes + +- Fix internal `Future` interface naming from `middleware` -> `v8_middleware` ([#14327](https://github.com/remix-run/react-router/pull/14327)) + ## 7.9.0 ### Minor Changes diff --git a/packages/react-router/lib/types/future.ts b/packages/react-router/lib/types/future.ts index 57e9e879fe..683a2185d7 100644 --- a/packages/react-router/lib/types/future.ts +++ b/packages/react-router/lib/types/future.ts @@ -2,8 +2,13 @@ * An augmentable interface users can modify in their app-code to opt into * future-flag-specific types */ -export interface Future {} +export interface Future { + // We list the potential fields here in comments strictly for clarity. + // They will be generated by the react-router/dev/typegen/generate.ts module + // + // v8_middleware: boolean +} // prettier-ignore export type MiddlewareEnabled = - Future extends { middleware: infer T extends boolean; } ? T : false + Future extends { v8_middleware: infer T extends boolean; } ? T : false diff --git a/packages/react-router/lib/types/route-module-annotations.ts b/packages/react-router/lib/types/route-module-annotations.ts index ceef11ede2..30ba7c983e 100644 --- a/packages/react-router/lib/types/route-module-annotations.ts +++ b/packages/react-router/lib/types/route-module-annotations.ts @@ -120,11 +120,33 @@ type CreateClientActionArgs = ClientDataFunctionArgs< serverAction: () => Promise>; }; -type CreateHydrateFallbackProps = { +type IsServerFirstRoute< + T extends RouteInfo, + RSCEnabled extends boolean, +> = RSCEnabled extends true + ? T["module"] extends { ServerComponent: Func } + ? true + : false + : false; + +type CreateHydrateFallbackProps< + T extends RouteInfo, + RSCEnabled extends boolean, +> = { params: T["params"]; - loaderData?: T["loaderData"]; - actionData?: T["actionData"]; -}; +} & (IsServerFirstRoute extends true + ? { + /** The data returned from the `loader` */ + loaderData?: ServerDataFrom; + /** The data returned from the `action` following an action submission. */ + actionData?: ServerDataFrom; + } + : { + /** The data returned from the `loader` or `clientLoader` */ + loaderData?: T["loaderData"]; + /** The data returned from the `action` or `clientAction` following an action submission. */ + actionData?: T["actionData"]; + }); type Match = Pretty<{ id: T["id"]; @@ -142,7 +164,7 @@ type Matches> = ? [Match, ...Matches] : Array | undefined>; -type CreateComponentProps = { +type CreateComponentProps = { /** * {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route. * @example @@ -158,15 +180,26 @@ type CreateComponentProps = { * } **/ params: T["params"]; - /** The data returned from the `loader` or `clientLoader` */ - loaderData: T["loaderData"]; - /** The data returned from the `action` or `clientAction` following an action submission. */ - actionData?: T["actionData"]; /** An array of the current {@link https://api.reactrouter.com/v7/interfaces/react_router.UIMatch.html route matches}, including parent route matches. */ matches: Matches; -}; - -type CreateErrorBoundaryProps = { +} & (IsServerFirstRoute extends true + ? { + /** The data returned from the `loader` */ + loaderData: ServerDataFrom; + /** The data returned from the `action` following an action submission. */ + actionData?: ServerDataFrom; + } + : { + /** The data returned from the `loader` or `clientLoader` */ + loaderData: T["loaderData"]; + /** The data returned from the `action` or `clientAction` following an action submission. */ + actionData?: T["actionData"]; + }); + +type CreateErrorBoundaryProps< + T extends RouteInfo, + RSCEnabled extends boolean, +> = { /** * {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route. * @example @@ -183,11 +216,24 @@ type CreateErrorBoundaryProps = { **/ params: T["params"]; error: unknown; - loaderData?: T["loaderData"]; - actionData?: T["actionData"]; -}; - -export type GetAnnotations = { +} & (IsServerFirstRoute extends true + ? { + /** The data returned from the `loader` */ + loaderData?: ServerDataFrom; + /** The data returned from the `action` following an action submission. */ + actionData?: ServerDataFrom; + } + : { + /** The data returned from the `loader` or `clientLoader` */ + loaderData?: T["loaderData"]; + /** The data returned from the `action` or `clientAction` following an action submission. */ + actionData?: T["actionData"]; + }); + +export type GetAnnotations< + Info extends RouteInfo, + RSCEnabled extends boolean, +> = { // links LinkDescriptors: LinkDescriptor[]; LinksFunction: () => LinkDescriptor[]; @@ -220,13 +266,13 @@ export type GetAnnotations = { ClientActionArgs: CreateClientActionArgs; // HydrateFallback - HydrateFallbackProps: CreateHydrateFallbackProps; + HydrateFallbackProps: CreateHydrateFallbackProps; // default (Component) - ComponentProps: CreateComponentProps; + ComponentProps: CreateComponentProps; // ErrorBoundary - ErrorBoundaryProps: CreateErrorBoundaryProps; + ErrorBoundaryProps: CreateErrorBoundaryProps; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/react-router/package.json b/packages/react-router/package.json index f58103527a..0954e6129b 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "react-router", - "version": "7.9.0", + "version": "7.9.1", "description": "Declarative routing for React", "keywords": [ "react", diff --git a/scripts/delete-pre-tags.sh b/scripts/delete-pre-tags.sh new file mode 100755 index 0000000000..2959d37279 --- /dev/null +++ b/scripts/delete-pre-tags.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +echo "Pruning tags before looking for tags to delete..." +git fetch --prune --prune-tags + +PATTERN="@7\.\d\+\.\d\+-pre" + +TAGS=$(git tag | grep -e "${PATTERN}") + +if [[ $TAGS == "" ]]; then + echo "No tags to delete, exiting" + exit 0 +fi + +# Delay setting this because if it's set when no tags exist the program exits +# on the TAGS assignment above +set -e + +NUM_TAGS=$(git tag | grep -e "${PATTERN}" | wc -l | sed 's/ //g') +TAGS_LINE=$(git tag | grep -e "${PATTERN}" | tr '\n' ' ') + +echo "" +echo "Found ${NUM_TAGS} tags to delete. To delete, run the following commands:" +echo "" +echo "git push origin --delete ${TAGS_LINE}" +echo "git fetch --prune --prune-tags" + +set +e diff --git a/scripts/finish-stable-release.sh b/scripts/finish-stable-release.sh index 1949d7d041..a34ff751e5 100755 --- a/scripts/finish-stable-release.sh +++ b/scripts/finish-stable-release.sh @@ -40,40 +40,10 @@ git push git branch -d release-next if [[ -n $(git show-ref refs/heads/changeset-release/release-next) ]]; then - git branch -d changeset-release/release-next + git branch -D changeset-release/release-next fi -# If this is set when no tags exist the program exits on the TAGS assignment -set +e - -echo "Pruning tags before looking for tags to delete:" -git fetch --prune --prune-tags - -PATTERN="@7\.\d\+\.\d\+-pre" - -# Don't keep around prerelease tags for all packages - only the `react-router` package -TAGS=$(git tag | grep -e "${PATTERN}" | grep -ve "^react-router@") - -if [[ $TAGS == "" ]]; then - echo "No tags to delete, exiting" - exit 0 -fi - -set -e - -NUM_TAGS=$(git tag | grep -e "${PATTERN}" | grep -ve "^react-router@" | wc -l | sed 's/ //g') -TAGS_LINE=$(git tag | grep -e "${PATTERN}" | grep -ve "^react-router@" | tr '\n' ' ') - -echo "Found ${NUM_TAGS} tags to delete: ${TAGS_LINE}" - -echo "To delete, run the following commands:" -echo "" -echo "git push origin --delete ${TAGS_LINE}" -echo "git fetch --prune --prune-tags" - -set +e -set +x - +./scripts/delete-pre-tags.sh set +e set +x \ No newline at end of file