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/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 6ee7e7dfe3..33ae8265da 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 b5a24dbb87..54ede6d3d4 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 7cff4e8ae4..2e97df4d92 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 824428ac43..f860deed01 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 520bd26513..8dc9d7c5cf 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 6726f9741a..4542bd5fb8 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 ecfd299bd0..0cb8db8499 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