Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dom subpath export #11851

Merged
merged 7 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/weak-goats-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"react-router": major
---

Add `react-router/dom` subpath export to properly enable `react-dom` as an optional `peerDependency`

- This ensures that we don't blindly `import ReactDOM from "react-dom"` in `<RouterProvider>` in order to access `ReactDOM.flushSync()`, since that would break `createMemoryRouter` use cases in non-DOM environments
- DOM environments should import from `react-router/dom` to get the proper component that makes `ReactDOM.flushSync()` available:
- If you are using the Vite plugin, use this in your `entry.client.tsx`:
- `import { HydratedRouter } from 'react-router/dom'`
- If you are not using the Vite plugin and are manually calling `createBrowserRouter`/`createHashRouter`:
- `import { RouterProvider } from "react-router/dom"`
2 changes: 1 addition & 1 deletion docs/upgrading/vite-component-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ You would rename it to `entry.client.tsx` and have it look like this:
import "./index.css";
import React from "react";
import ReactDOM from "react-dom/client";
import { HydratedRouter } from "react-router";
import { HydratedRouter } from "react-router/dom";

ReactDOM.hydrateRoot(
document,
Expand Down
2 changes: 1 addition & 1 deletion integration/fog-of-war-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ test.describe("Fog of War", () => {
files: {
...getFiles(),
"app/entry.client.tsx": js`
import { HydratedRouter } from "react-router";
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
startTransition(() => {
Expand Down
14 changes: 7 additions & 7 deletions integration/vite-basename-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ test.describe("Vite base / React Router basename / express dev", async () => {
});

test("works when base and basename are different", async ({ page }) => {
await setup({ base: "/mybase/", basename: "/mybase/app/" });
await workflowDev({ page, cwd, port, basename: "/mybase/app/" });
await setup({ base: "/mybase/", basename: "/mybase/dashboard/" });
await workflowDev({ page, cwd, port, basename: "/mybase/dashboard/" });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to avoid ambiguity between the app URL path and the app folder in requests in vite dev mode

});

test("works when basename does not start with base", async ({ page }) => {
Expand Down Expand Up @@ -361,9 +361,9 @@ async function workflowDev({

let isAssetRequest = (url: string) =>
/\.[jt]sx?/.test(url) ||
/@id\/__x00__virtual:/.test(url) ||
/@vite\/client/.test(url) ||
/node_modules\/vite\/dist\/client\/env/.test(url);
/\/@id\/__x00__virtual:/.test(url) ||
/\/@vite\/client/.test(url) ||
/\/@fs\//.test(url);

// verify client asset requests are all under base
expect(
Expand Down Expand Up @@ -413,8 +413,8 @@ test.describe("Vite base / React Router basename / vite build", () => {
});

test("works when base and basename are different", async ({ page }) => {
await setup({ base: "/mybase/", basename: "/mybase/app/" });
await workflowBuild({ page, port, basename: "/mybase/app/" });
await setup({ base: "/mybase/", basename: "/mybase/dashboard/" });
await workflowBuild({ page, port, basename: "/mybase/dashboard/" });
});

test("works when basename does not start with base", async ({ page }) => {
Expand Down
2 changes: 1 addition & 1 deletion integration/vite-css-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const files = {
"app/entry.client.tsx": js`
import "./entry.client.css";

import { HydratedRouter } from "react-router";
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

Expand Down
2 changes: 1 addition & 1 deletion integration/vite-spa-mode-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ test.describe("SPA Mode", () => {
</html>
`,
"app/entry.client.tsx": js`
import { HydratedRouter } from "react-router";
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

Expand Down
2 changes: 1 addition & 1 deletion packages/react-router-dev/config/defaults/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
hydrateRoot(
Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/__tests__/data-router-no-dom-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import * as React from "react";
import renderer from "react-test-renderer";
import { RouterProvider, useFetcher } from "../lib/dom/lib";
import { useFetcher } from "../lib/dom/lib";
import { RouterProvider } from "../lib/dom-export/dom-router-provider";
import { createMemoryRouter } from "../lib/components";
import { useLoaderData, useNavigate } from "../lib/hooks";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as React from "react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { JSDOM } from "jsdom";

import {
createBrowserRouter,
useNavigate,
useSubmit,
useFetcher,
} from "../../index";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { JSDOM } from "jsdom";

// TODO: figure this out
import { RouterProvider } from "../../lib/dom/lib";
import { RouterProvider } from "../../lib/dom-export/dom-router-provider";

describe("flushSync", () => {
it("wraps useNavigate updates in flushSync when specified", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Outlet,
RouterProvider,
} from "../../../index";
import { HydratedRouter } from "../../../lib/dom/ssr/browser";
import { HydratedRouter } from "../../../lib/dom-export/hydrated-router";
import { FrameworkContext } from "../../../lib/dom/ssr/components";
import invariant from "../../../lib/dom/ssr/invariant";
import { ServerRouter } from "../../../lib/dom/ssr/server";
Expand Down
3 changes: 3 additions & 0 deletions packages/react-router/dom-export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { RouterProviderProps } from "./lib/dom-export/dom-router-provider";
export { RouterProvider } from "./lib/dom-export/dom-router-provider";
export { HydratedRouter } from "./lib/dom-export/hydrated-router";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming of this file may be temporary - went with dom-export to avoid ambiguity with the existing dom/ folder containing the old react-router-dom implementation - but open to suggestions

63 changes: 50 additions & 13 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export type {
PathRouteProps,
RouteProps,
RouterProps,
RouterProviderProps,
RoutesProps,
PatchRoutesOnMissFunction as unstable_PatchRoutesOnMissFunction,
} from "./lib/components";
Expand All @@ -99,6 +100,7 @@ export {
Outlet,
Route,
Router,
RouterProvider,
Routes,
createMemoryRouter,
createRoutesFromChildren,
Expand Down Expand Up @@ -144,7 +146,6 @@ export type {
SubmitFunction,
FetcherSubmitFunction,
FetcherWithComponents,
RouterProviderProps,
} from "./lib/dom/lib";
export {
createBrowserRouter,
Expand All @@ -155,7 +156,6 @@ export {
HistoryRouter as unstable_HistoryRouter,
NavLink,
Form,
RouterProvider,
ScrollRestoration,
useLinkClickHandler,
useSearchParams,
Expand Down Expand Up @@ -185,7 +185,6 @@ export {
StaticRouter,
StaticRouterProvider,
} from "./lib/dom/server";
export { HydratedRouter } from "./lib/dom/ssr/browser";
export {
Meta,
Links,
Expand Down Expand Up @@ -338,32 +337,70 @@ export type {
///////////////////////////////////////////////////////////////////////////////

/** @internal */
export { ErrorResponseImpl as UNSAFE_ErrorResponseImpl } from "./lib/router/utils";
export {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to export a bunch of stuff as UNSAFE here to consume from the subpath export file to avoid duplicating any implementations

createBrowserHistory as UNSAFE_createBrowserHistory,
invariant as UNSAFE_invariant,
} from "./lib/router/history";

/** @internal */
export {
decodeViaTurboStream as UNSAFE_decodeViaTurboStream,
SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol,
} from "./lib/dom/ssr/single-fetch";
export { createRouter as UNSAFE_createRouter } from "./lib/router/router";

/** @internal */
export { ErrorResponseImpl as UNSAFE_ErrorResponseImpl } from "./lib/router/utils";

/** @internal */
export {
DataRouterContext as UNSAFE_DataRouterContext,
DataRouterStateContext as UNSAFE_DataRouterStateContext,
FetchersContext as UNSAFE_FetchersContext,
LocationContext as UNSAFE_LocationContext,
NavigationContext as UNSAFE_NavigationContext,
RouteContext as UNSAFE_RouteContext,
ViewTransitionContext as UNSAFE_ViewTransitionContext,
} from "./lib/context";

/** @internal */
export {
ViewTransitionContext as UNSAFE_ViewTransitionContext,
FetchersContext as UNSAFE_FetchersContext,
useScrollRestoration as UNSAFE_useScrollRestoration,
} from "./lib/dom/lib";
export { mapRouteProperties as UNSAFE_mapRouteProperties } from "./lib/components";

/** @internal */
export { FrameworkContext as UNSAFE_FrameworkContext } from "./lib/dom/ssr/components";

/** @internal */
export type { AssetsManifest as UNSAFE_AssetsManifest } from "./lib/dom/ssr/entry";

/** @internal */
export { deserializeErrors as UNSAFE_deserializeErrors } from "./lib/dom/ssr/errors";

/** @internal */
export { RemixErrorBoundary as UNSAFE_RemixErrorBoundary } from "./lib/dom/ssr/errorBoundaries";

/** @internal */
export {
initFogOfWar as UNSAFE_initFogOfWar,
useFogOFWarDiscovery as UNSAFE_useFogOFWarDiscovery,
} from "./lib/dom/ssr/fog-of-war";

/** @internal */
export type { RouteModules as UNSAFE_RouteModules } from "./lib/dom/ssr/routeModules";

/** @internal */
export {
createClientRoutes as UNSAFE_createClientRoutes,
createClientRoutesWithHMRRevalidationOptOut as UNSAFE_createClientRoutesWithHMRRevalidationOptOut,
shouldHydrateRouteLoader as UNSAFE_shouldHydrateRouteLoader,
} from "./lib/dom/ssr/routes";

/** @internal */
export { getSingleFetchDataStrategy as UNSAFE_getSingleFetchDataStrategy } from "./lib/dom/ssr/single-fetch";

/** @internal */
export {
decodeViaTurboStream as UNSAFE_decodeViaTurboStream,
SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol,
} from "./lib/dom/ssr/single-fetch";

/** @internal */
export { ServerMode as UNSAFE_ServerMode } from "./lib/server-runtime/mode";

/** @internal */
export { useScrollRestoration as UNSAFE_useScrollRestoration } from "./lib/dom/lib";
Loading