diff --git a/.gitignore b/.gitignore index b23d2943a3..b7ed059661 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ node_modules/ /packages/*/esm/ /packages/*/umd/ +# compat module copies +/packages/react-router-dom-v5-compat/react-router-dom + .eslintcache diff --git a/contributors.yml b/contributors.yml index 50c3af1672..7a3b3c1d13 100644 --- a/contributors.yml +++ b/contributors.yml @@ -34,6 +34,7 @@ - paulsmithkc - petersendidit - RobHannay +- ryanflorence - sanketshah19 - sergiodxa - shamsup diff --git a/packages/react-router-dom-v5-compat/README.md b/packages/react-router-dom-v5-compat/README.md new file mode 100644 index 0000000000..0f9b3f7bbd --- /dev/null +++ b/packages/react-router-dom-v5-compat/README.md @@ -0,0 +1,394 @@ +# React Router DOM Compat v5 + +This package enables React Router web apps to incrementally migrate to the latest API in v6 by running it in parallel with v5. It is a copy of v6 with an extra couple of components to keep the two in sync. + +## Incremental Migration + +Instead of upgrading and updating all of your code at once (which is incredibly difficult and prone to bugs), this strategy enables you to upgrade one component, one hook, and one route at a time by running both v5 and v6 in parallel. Any code you haven't touched is still running the very same code it was before. Once all components are exclusively using the v6 APIs, your app no longer needs the compatibility package and is running on v6. + +It looks something like this: + +- Setup the `CompatRouter` +- Change a `` inside of a `` to a `` +- Update all APIs inside this route element tree to v6 APIs one at a time +- Repeat for all routes in the `` +- Convert the `` to a `` +- Repeat for all ancestor ``s +- Update `` +- You're done! + +## Setting up + +These are the most common cases, be sure to read the v6 docs to figure out how to do anything not shown here. + +Please [open a discussion on GitHub][discussion] if you're stuck, we'll be happy to help. We would also love for this GitHub Q&A to fill up with migration tips for the entire community, so feel free to add your tips even when you're not stuck! + +### 1) Upgrade your app to React 16.8+ + +React Router v6 has been rewritten with React Hooks, significantly improving bundle sizes and composition. You will need to upgrade to 16.8+ in order to migrate to React Router v6. + +You can read the [Hooks Adoption Strategy](https://reactjs.org/docs/hooks-faq.html#adoption-strategy) from the React docs for help there. + +### 2) Install Compatibility Package + +👉 Install the package + +```sh +npm install react-router-dom-v5-compat +``` + +This package includes the v6 API so that you can run it in parallel with v5. + +### 3) Render the Compatibility Router + +The compatibility package includes a special `CompatRouter` that synchronizes the v5 and v6 APIs state so that both APIs are available. + +👉 Render the `` directly below your v5 ``. + +```diff + import { BrowserRouter } from "react-router-dom"; ++import { CompatRouter } from "react-router-dom-v5-compat"; + + export function App() { + return ( + ++ + + + {/* ... */} + ++ + + ); + } +``` + +For the curious, this component accesses the `history` from v5, sets up a listener, and then renders a "controlled" v6 ``. This way both v5 and v6 APIs are talking to the same `history` instance. + +### 4) Commit and Ship! + +The whole point of this package is to allow you to incrementally migrate your code instead of a giant, risky upgrade that often halts any other feature work. + +👉 Commit the changes and ship! + +```sh +git add . +git commit -m 'setup router compatibility package' +# of course this may be different for you +git push origin main +``` + +It's not much yet, but now you're ready. + +## Migration Strategy + +The migration is easiest if you start from the bottom of your component tree and climb up each branch to the top. + +You can start at the top, too, but then you can't migrate an entire branch of your UI to v6 completely which makes it tempting to keep using v5 APIs when working in any part of your app: "two steps forward, one step back". By migrating an entire Route's element tree to v6, new feature work there is less likely to pull in the v5 APIs. + +### 1) Render CompatRoute elements inside of Switch + +👉 Change `` to `` + +```diff + import { Route } from "react-router-dom"; ++ import { CompatRoute } from "react-router-dom-v5-compat"; + + export function SomComp() { + return ( + +- ++ + + ) + } +``` + +`` renders a v5 `` wrapped inside of a v6 context. This is the special sauce that makes both APIs available to the component tree inside of this route. + +⚠️️ You can only use `CompatRoute` inside of a `Switch`, it will not work for Routes that are rendered outside of ``. Depending on the use case, there will be a hook in v6 to meet it. + +⚠️ You can't use regular expressions or optional params in v6 route paths. Instead, repeat the route with the extra params/regex patterns you're trying to match. + +```diff +- ++ ++ +``` + +### 2) Change component code use v6 instead of v5 APIs + +This route now has both v5 and v6 routing contexts, so we can start migrating component code to v6. + +If the component is a class component, you'll need to convert it to a function component first so that you can use hooks. + +👉 Read from v6 `useParams()` instead of v5 `props.match` + +```diff ++ import { useParams } from "react-router-dom-v5-compat"; + + function Project(props) { +- const { params } = props.match; ++ const params = useParams(); + // ... + } +``` + +👉 Commit and ship! + +```sh +git add . +git commit -m "chore: RR v5 props.match -> v6 useParams" +git push origin main +``` + +This component is now using both APIs at the same time. Every small change can be committed and shipped. No need for a long running branch that makes you want to quit your job, build a cabin in the woods, and live off of squirrels and papago lilies. + +👉 Read from v6 `useLocation()` instead of v5 `props.location` + +```diff ++ import { useLocation } from "react-router-dom-v5-compat"; + + function Project(props) { +- const location = props.location; ++ const location = useLocation(); + // ... + } +``` + +👉 Use `navigate` instead of `history` + +```diff ++ import { useNavigate } from "react-router-dom-v5-compat"; + + function Project(props) { +- const history = props.history; ++ const navigate = useNavigate(); + + return ( +
+ + { +- history.push("/elsewhere"); ++ navigate("/elsewhere"); + +- history.replace("/elsewhere"); ++ navigate("/elsewhere", { replace: true }); + +- history.go(-1); ++ navigate(-1); + }} /> + +
+ ) + } +``` + +There are more APIs you may be accessing, but these are the most common. Again, [open a discussion on GitHub][discussion] if you're stuck and we'll do our best to help out. + +### 3) (Maybe) Update Links and NavLinks + +Some links may be building on `match.url` to link to deeper URLs without needing to know the portion of the URL before them. You no longer need to build the path manually, React Router v6 supports relative links. + +👉 Update links to use relative `to` values + +```diff +- import { Link } from "react-router-dom"; ++ import { Link } from "react-router-dom-v5-compat"; + + function Project(props) { + return ( +
+- ++ +
+ ) + } +``` + +The way to define active className and style props has been simplified to a callback to avoid specificity issues with CSS: + +👉 Update nav links + +```diff +- import { NavLink } from "react-router-dom"; ++ import { NavLink } from "react-router-dom-v5-compat"; + + function Project(props) { + return ( +
+- ++ + +- ++ isActive ? "blue" : "red" } /> + +- ++ ({ color: isActive ? "blue" : "red" }) /> +
+ ) + } +``` + +### 4) Convert `Switch` to `Routes` + +Once every descendant component in a `` has been migrated to v6, you can convert the `` to `` and change the `` elements to v6 `` elements. + +👉 Convert `` to `` and `` to v6 `` + +```diff + import { Routes, Route } from "react-router-dom-v5-compat"; +- import { Switch, Route } from "react-router-dom" + +- +- +- +- ++ ++ } /> ++ } /> ++ +``` + +BAM 💥 This entire branch of your UI is migrated to v6! + +### 5) Rinse and Repeat up the tree + +Once your deepest `Switch` components are converted, go up to their parent `` and repeat the process. Keep doing this all the way up the tree until all components are migrated to v6 APIs. + +If you have `` rendered deeper in the tree of a ``, you'll need to use a **splat path** so that those deeper URLs continue to match. + +👉️ Add splat paths to any `` with a **descendant** `` + +```diff + function Ancestor() { + return ( +- +- +- ++ ++ {/* Note the trailing "*" in the path! */} ++ } /> ++ + ); + } + + // This one was already migrated earlier but it won't match anymore + // if you don't add the "*" when you change to + function Descendant() { + return ( +
+

Projects

+ + } /> + } /> + } /> + +
+ ); + } +``` + +The splat is only needed for routes with **descendant routes** in their tree. + +### 6) Remove the compatibility package! + +Once you've converted all of your code you can remove the compatibility package and install React Router DOM v6 directly. We have to do a few things all at once to finish this off. + +👉 Remove the compatibility package + +```sh +npm uninstall react-router-dom-v5-compat +``` + +👉 Uninstall `react-router` and `history` + +v6 no longer requires history or react-router to be peer dependencies (they're normal dependencies now), so you'll need to uninstall them + +``` +npm uninstall react-router history +``` + +👉 Install React Router v6 + +```sh +npm install react-router-dom@6 +``` + +👉 Remove the `CompatRouter` + +```diff + import { BrowserRouter } from "react-router-dom"; +- import { CompatRouter } from "react-router-dom-v5-compat"; + + export function App() { + return ( + +- + + } /> + {/* ... */} + +- + + ); + } +``` + +Note that `BrowserRouter` is now the v6 browser router. + +👉 Change all compat imports to "react-router-dom" + +You should be able to a find/replace across the project to change all instances of "react-router-dom-v5-compat" to "react-router-dom" + +```sh +# Change `src` to wherever your source modules live +# Also strap on a fake neckbeard cause it's shell scripting time +git grep -lz src | xargs -0 sed -i '' -e 's/react-router-dom-v5-compat/react-router-dom/g' +``` + +### 7) Optional: lift `Routes` up to single route config + +This part is optional (but you'll want it when the React Router data APIs ship). + +Once you've converted all of your app to v6, you can lift every `` to the top of the app and replace it with an ``. React Router v6 has a concept of "nested routes". + +👉 Replace descendant `` with `` + +```diff +- +- +- +- ++ +``` + +👉 Lift the `` elements to the ancestor `` + +```diff + + + ++ ++ + +``` + +If you had splat paths for descendant routes, you can remove them when the descendant routes lift up to the same route configuration: + +```diff + +- ++ + } /> + } /> + } /> + + +``` + +That's it, you're done 🙌 + +Don't forget to [open a discussion on GitHub][discussion] if you're stuck, add your own tips, and help others with their questions 🙏 + +[discussion]: https://github.com/remix-run/react-router/discussions/new?category=v5-to-v6-migration diff --git a/packages/react-router-dom-v5-compat/__tests__/link-click-test.tsx b/packages/react-router-dom-v5-compat/__tests__/link-click-test.tsx new file mode 100644 index 0000000000..8e111c7861 --- /dev/null +++ b/packages/react-router-dom-v5-compat/__tests__/link-click-test.tsx @@ -0,0 +1,259 @@ +/** + * Just copied over the link click test as a quick smoke test that it's working + * the same as v6 proper. + */ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { act } from "react-dom/test-utils"; +import { MemoryRouter, Routes, Route, Link } from "../index"; + +function click(anchor: HTMLAnchorElement, eventInit?: MouseEventInit) { + let event = new MouseEvent("click", { + view: window, + bubbles: true, + cancelable: true, + ...eventInit, + }); + anchor.dispatchEvent(event); + return event; +} + +describe("A click", () => { + let node: HTMLDivElement; + beforeEach(() => { + node = document.createElement("div"); + document.body.appendChild(node); + }); + + afterEach(() => { + document.body.removeChild(node); + node = null!; + }); + + it("navigates to the new page", () => { + function Home() { + return ( +
+

Home

+ About +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + let event: MouseEvent; + act(() => { + event = click(anchor); + }); + + expect(event.defaultPrevented).toBe(true); + let h1 = node.querySelector("h1"); + expect(h1).not.toBeNull(); + expect(h1?.textContent).toEqual("About"); + }); + + describe("when reloadDocument is specified", () => { + it("does not prevent default", () => { + function Home() { + return ( +
+

Home

+ + About + +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + let event: MouseEvent; + act(() => { + event = click(anchor); + }); + + expect(event.defaultPrevented).toBe(false); + }); + }); + + describe("when preventDefault is used on the click handler", () => { + it("stays on the same page", () => { + function Home() { + function handleClick(event: React.MouseEvent) { + event.preventDefault(); + } + + return ( +
+

Home

+ + About + +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + act(() => { + click(anchor); + }); + + let h1 = node.querySelector("h1"); + expect(h1).not.toBeNull(); + expect(h1?.textContent).toEqual("Home"); + }); + }); + + describe("with a right click", () => { + it("stays on the same page", () => { + function Home() { + return ( +
+

Home

+ About +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + act(() => { + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + let RightMouseButton = 2; + click(anchor, { button: RightMouseButton }); + }); + + let h1 = node.querySelector("h1"); + expect(h1).not.toBeNull(); + expect(h1?.textContent).toEqual("Home"); + }); + }); + + describe("when the link is supposed to open in a new window", () => { + it("stays on the same page", () => { + function Home() { + return ( +
+

Home

+ + About + +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + act(() => { + click(anchor); + }); + + let h1 = node.querySelector("h1"); + expect(h1).not.toBeNull(); + expect(h1?.textContent).toEqual("Home"); + }); + }); + + describe("when the modifier keys are used", () => { + it("stays on the same page", () => { + function Home() { + return ( +
+

Home

+ About +
+ ); + } + + act(() => { + ReactDOM.render( + + + } /> + About} /> + + , + node + ); + }); + + let anchor = node.querySelector("a"); + expect(anchor).not.toBeNull(); + + act(() => { + click(anchor, { ctrlKey: true }); + }); + + let h1 = node.querySelector("h1"); + expect(h1).not.toBeNull(); + expect(h1?.textContent).toEqual("Home"); + }); + }); +}); diff --git a/packages/react-router-dom-v5-compat/index.ts b/packages/react-router-dom-v5-compat/index.ts new file mode 100644 index 0000000000..673db4f153 --- /dev/null +++ b/packages/react-router-dom-v5-compat/index.ts @@ -0,0 +1,114 @@ +/** + * Welcome future Michael and Ryan 👋, or anybody else reading this. We're doing + * weird stuff here to help folks to migrate their React Router v5 apps to v6. + * + * So WTFlip is going on here? + * + * - We need to be able to run react-router-dom@v5 in parallel with + * react-router-dom@v6 + * + * - npm/node/whoever doesn't let an app depend on two versions of the same + * package + * + * - Not only do we need to run two versions of the same package, we also need + * this `CompatRouter` to *itself* depend on both versions 😬 + * + * - When this package depends on "history" and "react-router-dom", those need + * to be the `peerDependencies` that the application package.json declares. + * + * - That means this package can't depend on "react-router-dom" v6, but it needs to, + * therefore it must ~become~ react-router-dom v6 👻 + * + * - We could import from "../../react-router-dom" and get rollup to bundle it as part + * of this package instead BUT ... + * + * - TSC needs to generate the types, and it has to derive the output paths from + * the import paths. If we have a weird require *outside of this package* to + * "../../react-router-dom" it's going to generate types from the common root + * of all module paths (Which makes sense becuase what else would it do? It + * needs to write the type files next to the source files so that typescript + * can resolve the types for tooling in the same location as the modules). + * Because tsc isn't as flexible as rollup, we have no control over this + * behavior. + * + * - Therefore, we simply copy the v6 modules into this package at build time. + * TSC won't come across any weird require paths (requiring outside of this + * package with "../../") and everybody puts the files where everybody else + * expects them to be. + * + * - The v6 `StaticRouter` found in `react-router-dom/server.ts` has a funny + * require to *itself* ("react-router-dom") so we had to duplicate it in this + * package because "react-router-dom" will point to the app's v5 version. We + * can't change that require to a normal internal require ("../index") because + * we generate two bundles (one for "react-router-dom" and one for + * "react-router-dom/server"). If it were an internal require then SSR router + * apps would have two copies of "react-router-dom", two contexts, and stuff + * would break. We could stop doing two bundles in v6 "react-router-dom" and + * deprecate the deep require if we wanted to avoid the duplication here. + */ +export type { + Hash, + Location, + Path, + To, + MemoryRouterProps, + NavigateFunction, + NavigateOptions, + NavigateProps, + Navigator, + OutletProps, + Params, + PathMatch, + RouteMatch, + RouteObject, + RouteProps, + PathRouteProps, + LayoutRouteProps, + IndexRouteProps, + RouterProps, + Pathname, + Search, + RoutesProps, +} from "./react-router-dom"; +export { + BrowserRouter, + HashRouter, + Link, + MemoryRouter, + NavLink, + Navigate, + NavigationType, + Outlet, + Route, + Router, + Routes, + UNSAFE_LocationContext, + UNSAFE_NavigationContext, + UNSAFE_RouteContext, + createPath, + createRoutesFromChildren, + createSearchParams, + generatePath, + matchPath, + matchRoutes, + parsePath, + renderMatches, + resolvePath, + unstable_HistoryRouter, + useHref, + useInRouterContext, + useLinkClickHandler, + useLocation, + useMatch, + useNavigate, + useNavigationType, + useOutlet, + useOutletContext, + useParams, + useResolvedPath, + useRoutes, + useSearchParams, +} from "./react-router-dom"; + +export type { StaticRouterProps } from "./lib/components"; +export { CompatRouter, CompatRoute, StaticRouter } from "./lib/components"; diff --git a/packages/react-router-dom-v5-compat/jest-transformer.js b/packages/react-router-dom-v5-compat/jest-transformer.js new file mode 100644 index 0000000000..7658127ad4 --- /dev/null +++ b/packages/react-router-dom-v5-compat/jest-transformer.js @@ -0,0 +1,10 @@ +const babelJest = require("babel-jest"); + +module.exports = babelJest.createTransformer({ + presets: [ + ["@babel/preset-env", { loose: true }], + "@babel/preset-react", + "@babel/preset-typescript", + ], + plugins: ["babel-plugin-dev-expression"], +}); diff --git a/packages/react-router-dom-v5-compat/jest.config.js b/packages/react-router-dom-v5-compat/jest.config.js new file mode 100644 index 0000000000..42f4816c69 --- /dev/null +++ b/packages/react-router-dom-v5-compat/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + testMatch: ["**/__tests__/*-test.[jt]s?(x)"], + transform: { + "\\.[jt]sx?$": "./jest-transformer.js", + }, + globals: { + __DEV__: true, + }, +}; diff --git a/packages/react-router-dom-v5-compat/lib/components.tsx b/packages/react-router-dom-v5-compat/lib/components.tsx new file mode 100644 index 0000000000..916bb5e2dc --- /dev/null +++ b/packages/react-router-dom-v5-compat/lib/components.tsx @@ -0,0 +1,129 @@ +import * as React from "react"; +import type { Location, To } from "history"; +import { Action, createPath, parsePath } from "history"; + +// Get useHistory from react-router-dom v5 (peer dep). +// @ts-expect-error +import { useHistory, Route as RouteV5 } from "react-router-dom"; + +// We are a wrapper around react-router-dom v6, so bring it in +// and bundle it because an app can't have two versions of +// react-router-dom in its package.json. +import { Router, Routes, Route } from "../react-router-dom"; + +// v5 isn't in TypeScript, they'll also lose the @types/react-router with this +// but not worried about that for now. +export function CompatRoute(props: any) { + let { path } = props; + return ( + + } /> + + ); +} + +export function CompatRouter({ children }: { children: React.ReactNode }) { + let history = useHistory(); + let [state, setState] = React.useState(() => ({ + location: history.location, + action: history.action, + })); + + React.useLayoutEffect(() => { + history.listen((location: Location, action: Action) => + setState({ location, action }) + ); + }, [history]); + + return ( + + + + + + ); +} + +export interface StaticRouterProps { + basename?: string; + children?: React.ReactNode; + location: Partial | string; +} + +/** + * A that may not transition to any other location. This is useful + * on the server where there is no stateful UI. + */ +export function StaticRouter({ + basename, + children, + location: locationProp = "/", +}: StaticRouterProps) { + if (typeof locationProp === "string") { + locationProp = parsePath(locationProp); + } + + let action = Action.Pop; + let location: Location = { + pathname: locationProp.pathname || "/", + search: locationProp.search || "", + hash: locationProp.hash || "", + state: locationProp.state || null, + key: locationProp.key || "default", + }; + + let staticNavigator = { + createHref(to: To) { + return typeof to === "string" ? to : createPath(to); + }, + push(to: To) { + throw new Error( + `You cannot use navigator.push() on the server because it is a stateless ` + + `environment. This error was probably triggered when you did a ` + + `\`navigate(${JSON.stringify(to)})\` somewhere in your app.` + ); + }, + replace(to: To) { + throw new Error( + `You cannot use navigator.replace() on the server because it is a stateless ` + + `environment. This error was probably triggered when you did a ` + + `\`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere ` + + `in your app.` + ); + }, + go(delta: number) { + throw new Error( + `You cannot use navigator.go() on the server because it is a stateless ` + + `environment. This error was probably triggered when you did a ` + + `\`navigate(${delta})\` somewhere in your app.` + ); + }, + back() { + throw new Error( + `You cannot use navigator.back() on the server because it is a stateless ` + + `environment.` + ); + }, + forward() { + throw new Error( + `You cannot use navigator.forward() on the server because it is a stateless ` + + `environment.` + ); + }, + }; + + return ( + + ); +} diff --git a/packages/react-router-dom-v5-compat/node-main.js b/packages/react-router-dom-v5-compat/node-main.js new file mode 100644 index 0000000000..68e32e881b --- /dev/null +++ b/packages/react-router-dom-v5-compat/node-main.js @@ -0,0 +1,7 @@ +/* eslint-env node */ + +if (process.env.NODE_ENV === "production") { + module.exports = require("./umd/react-router-dom-v5-compat.production.min.js"); +} else { + module.exports = require("./umd/react-router-dom-v5-compat.development.js"); +} diff --git a/packages/react-router-dom-v5-compat/package.json b/packages/react-router-dom-v5-compat/package.json new file mode 100644 index 0000000000..402c2cdcd3 --- /dev/null +++ b/packages/react-router-dom-v5-compat/package.json @@ -0,0 +1,34 @@ +{ + "name": "react-router-dom-v5-compat", + "version": "6.2.2", + "author": "Remix Software ", + "description": "Migration path to React Router v6 from v4/5", + "repository": { + "type": "git", + "url": "https://github.com/remix-run/react-router.git", + "directory": "packages/react-router-dom-v5-compat" + }, + "license": "MIT", + "main": "./main.js", + "module": "./index.js", + "types": "./index.d.ts", + "unpkg": "./umd/react-router.production.min.js", + "dependencies": { + "history": "^5.3.0", + "react-router": "6.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "react-router-dom": "4 || 5" + }, + "sideEffects": false, + "keywords": [ + "react", + "router", + "route", + "routing", + "history", + "link" + ] +} diff --git a/packages/react-router-dom-v5-compat/tsconfig.json b/packages/react-router-dom-v5-compat/tsconfig.json new file mode 100644 index 0000000000..e2cf6ca8f5 --- /dev/null +++ b/packages/react-router-dom-v5-compat/tsconfig.json @@ -0,0 +1,20 @@ +{ + "files": ["index.ts"], + "compilerOptions": { + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + + "strict": true, + "jsx": "react", + + "declaration": true, + "emitDeclarationOnly": true, + + "skipLibCheck": true, + + "outDir": "../../build/node_modules/react-router-dom-v5-compat", + "rootDirs": ["."] + } +} diff --git a/packages/react-router-dom-v5-compat/yarn.lock b/packages/react-router-dom-v5-compat/yarn.lock new file mode 100644 index 0000000000..94440210da --- /dev/null +++ b/packages/react-router-dom-v5-compat/yarn.lock @@ -0,0 +1,37 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.7.6": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825" + integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA== + dependencies: + regenerator-runtime "^0.13.4" + +history@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" + integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== + dependencies: + "@babel/runtime" "^7.7.6" + +react-router-dom@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.2.tgz#f1a2c88365593c76b9612ae80154a13fcb72e442" + integrity sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ== + dependencies: + history "^5.2.0" + react-router "6.2.2" + +react-router@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.2.tgz#495e683a0c04461eeb3d705fe445d6cf42f0c249" + integrity sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ== + dependencies: + history "^5.2.0" + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 00d8639788..2e2928f41f 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -1,3 +1,7 @@ +/** + * NOTE: If you refactor this to split up the modules into separate files, + * you'll need to update the rollup config for react-router-dom-v5-compat. + */ import * as React from "react"; import type { BrowserHistory, HashHistory, History } from "history"; import { createBrowserHistory, createHashHistory } from "history"; diff --git a/rollup.config.js b/rollup.config.js index b8befffe1d..2d3fb360dd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -474,6 +474,149 @@ function reactRouterDom() { return [...modules, ...webModules, ...globals, ...node]; } +/** @type import("rollup").InputOptions[] */ +function reactRouterDomV5Compat() { + let SOURCE_DIR = "packages/react-router-dom-v5-compat"; + let OUTPUT_DIR = "build/node_modules/react-router-dom-v5-compat"; + let ROUTER_DOM_SOURCE = "packages/react-router-dom/index.tsx"; + let ROUTER_DOM_COPY_DEST = `${SOURCE_DIR}/react-router-dom`; + + let version = getVersion(SOURCE_DIR); + + // JS modules for bundlers + let modules = [ + { + input: `${SOURCE_DIR}/index.ts`, + output: { + file: `${OUTPUT_DIR}/index.js`, + format: "esm", + sourcemap: !PRETTY, + banner: createBanner("React Router DOM v5 Compat", version), + }, + external: [ + "history", + "react", + "react-dom", + "react-router", + "react-router-dom", + ], + plugins: [ + copy({ + targets: [{ src: ROUTER_DOM_SOURCE, dest: ROUTER_DOM_COPY_DEST }], + hook: "buildStart", + }), + extensions({ extensions: [".tsx", ".ts"] }), + babel({ + exclude: /node_modules/, + presets: [ + ["@babel/preset-env", { loose: true }], + "@babel/preset-react", + "@babel/preset-typescript", + ], + plugins: ["babel-plugin-dev-expression"], + extensions: [".ts", ".tsx"], + }), + copy({ + targets: [ + { src: `${SOURCE_DIR}/package.json`, dest: OUTPUT_DIR }, + { src: `${SOURCE_DIR}/README.md`, dest: OUTPUT_DIR }, + { src: "LICENSE.md", dest: OUTPUT_DIR }, + ], + verbose: true, + }), + ].concat(PRETTY ? prettier({ parser: "babel" }) : []), + }, + ]; + + // UMD modules for