From fc85e02ea520425db198c63ee87f5f845e427795 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 12 Jun 2023 11:27:14 -0400 Subject: [PATCH] Fix webpack/terser startTransition minification bug in production mode --- .changeset/start-transition-minification.md | 6 ++++ packages/react-router-dom/index.tsx | 32 +++++++++++++++------ packages/react-router/lib/components.tsx | 28 +++++++++++++----- 3 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 .changeset/start-transition-minification.md diff --git a/.changeset/start-transition-minification.md b/.changeset/start-transition-minification.md new file mode 100644 index 0000000000..79ae098c06 --- /dev/null +++ b/.changeset/start-transition-minification.md @@ -0,0 +1,6 @@ +--- +"react-router": patch +"react-router-dom": patch +--- + +Work around webpack/terser `React.startTransition` minification bug in production mode diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index e7a26d4233..d2356a84f0 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -300,10 +300,24 @@ export interface BrowserRouterProps { window?: Window; } -// Webpack + React 17 fails to compile on the usage of `React.startTransition` or -// `React["startTransition"]` even if it's behind a feature detection of -// `"startTransition" in React`. Moving this to a constant avoids the issue :/ +// Webpack + React 17 fails to compile on any of the following: +// * import { startTransition } from "react" +// * import * as React from from "react"; +// "startTransition" in React ? React.startTransition(() => setState()) : setState() +// * import * as React from from "react"; +// "startTransition" in React ? React["startTransition"](() => setState()) : setState() +// +// Moving it to a constant such as the following solves the Webpack/React 17 issue: +// * import * as React from from "react"; +// const START_TRANSITION = "startTransition"; +// START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState() +// +// However, that introduces webpack/terser minification issues in production builds +// in React 18 where minification/obfuscation ends up removing the call of +// React.startTransition entirely from the first half of the ternary. Grabbing +// this reference once up front resolves that issue. const START_TRANSITION = "startTransition"; +const startTransitionImpl = React[START_TRANSITION]; /** * A `` for use in web browsers. Provides the cleanest URLs. @@ -325,8 +339,8 @@ export function BrowserRouter({ }); let setState = React.useCallback( (newState: { action: NavigationType; location: Location }) => { - START_TRANSITION in React - ? React[START_TRANSITION](() => setStateImpl(newState)) + startTransitionImpl + ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState); }, [setStateImpl] @@ -368,8 +382,8 @@ export function HashRouter({ basename, children, window }: HashRouterProps) { }); let setState = React.useCallback( (newState: { action: NavigationType; location: Location }) => { - START_TRANSITION in React - ? React[START_TRANSITION](() => setStateImpl(newState)) + startTransitionImpl + ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState); }, [setStateImpl] @@ -407,8 +421,8 @@ function HistoryRouter({ basename, children, history }: HistoryRouterProps) { }); let setState = React.useCallback( (newState: { action: NavigationType; location: Location }) => { - START_TRANSITION in React - ? React[START_TRANSITION](() => setStateImpl(newState)) + startTransitionImpl + ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState); }, [setStateImpl] diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index 6c62276c92..e4d4eee360 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -54,10 +54,24 @@ export interface RouterProviderProps { router: RemixRouter; } -// Webpack + React 17 fails to compile on the usage of `React.startTransition` or -// `React["startTransition"]` even if it's behind a feature detection of -// `"startTransition" in React`. Moving this to a constant avoids the issue :/ +// Webpack + React 17 fails to compile on any of the following: +// * import { startTransition } from "react" +// * import * as React from from "react"; +// "startTransition" in React ? React.startTransition(() => setState()) : setState() +// * import * as React from from "react"; +// "startTransition" in React ? React["startTransition"](() => setState()) : setState() +// +// Moving it to a constant such as the following solves the Webpack/React 17 issue: +// * import * as React from from "react"; +// const START_TRANSITION = "startTransition"; +// START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState() +// +// However, that introduces webpack/terser minification issues in production builds +// in React 18 where minification/obfuscation ends up removing the call of +// React.startTransition entirely from the first half of the ternary. Grabbing +// this reference once up front resolves that issue. const START_TRANSITION = "startTransition"; +const startTransitionImpl = React[START_TRANSITION]; /** * Given a Remix Router instance, render the appropriate UI @@ -71,8 +85,8 @@ export function RouterProvider({ let [state, setStateImpl] = React.useState(router.state); let setState = React.useCallback( (newState: RouterState) => { - START_TRANSITION in React - ? React[START_TRANSITION](() => setStateImpl(newState)) + startTransitionImpl + ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState); }, [setStateImpl] @@ -183,8 +197,8 @@ export function MemoryRouter({ }); let setState = React.useCallback( (newState: { action: NavigationType; location: Location }) => { - START_TRANSITION in React - ? React[START_TRANSITION](() => setStateImpl(newState)) + startTransitionImpl + ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState); }, [setStateImpl]