Skip to content

Instantly share code, notes, and snippets.

@kiliman
Created October 13, 2022 15:06
Show Gist options
  • Save kiliman/1a8eb57a6558c96d292bb913add5a178 to your computer and use it in GitHub Desktop.
Save kiliman/1a8eb57a6558c96d292bb913add5a178 to your computer and use it in GitHub Desktop.
Patch to backport React Router "non-alphanumeric route prefix" fix to Remix

Patch bug in React Router 6.3

This is a backport of React Router PR #9300 to React Router 6.3 which is the version pinned in Remix.

This fix allows non-alphanumeric characters to start a route path. This means /@user or /.well-known/ or even /😍 will work.

Currently this only patches the development build as the production files are minified. I will update the patch once I setup to build React Router locally.

diff --git a/node_modules/react-router/index.js b/node_modules/react-router/index.js
index 7f42297..763a633 100644
--- a/node_modules/react-router/index.js
+++ b/node_modules/react-router/index.js
@@ -8,9 +8,10 @@
*
* @license MIT
*/
-import { parsePath, createMemoryHistory, Action } from 'history';
+import { Action, createMemoryHistory, parsePath } from 'history';
+import { Children, createContext, createElement, Fragment, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
export { Action as NavigationType, createPath, parsePath } from 'history';
-import { createContext, useContext, useMemo, useRef, useEffect, useCallback, createElement, useState, useLayoutEffect, Children, isValidElement, Fragment } from 'react';
+export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
const NavigationContext = /*#__PURE__*/createContext(null);
@@ -297,17 +298,24 @@ function compilePath(path, caseSensitive, end) {
if (path.endsWith("*")) {
paramNames.push("*");
- regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
- : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
+ regexpSource +=
+ path === "*" || path === "/*"
+ ? "(.*)$" // Already matched the initial /, just match the rest
+ : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
+ } else if (end) {
+ // When matching to the end, ignore trailing slashes
+ regexpSource += "\\/*$";
+ } else if (path !== "" && path !== "/") {
+ // If our path is non-empty and contains anything beyond an initial slash,
+ // then we have _some_ form of path in our regex so we should expect to
+ // match only if we find the end of this path segment. Look for an optional
+ // non-captured trailing slash (to match a portion of the URL) or the end
+ // of the path (if we've matched to the end). We used to do this with a
+ // word boundary but that gives false positives on routes like
+ // /user-preferences since `-` counts as a word boundary.
+ regexpSource += "(?:(?=\\/|$))";
} else {
- regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
- : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
- // parent routes to matching only their own words and nothing more, e.g. parent
- // route "/home" should not match "/home2".
- // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
- // but do not consume the character in the matched path so they can match against
- // nested paths.
- "(?:(?=[.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
+ // Nothing to match for "" or "/"
}
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
@@ -937,5 +945,4 @@ function renderMatches(matches) {
return _renderMatches(matches);
}
-export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
//# sourceMappingURL=index.js.map
diff --git a/node_modules/react-router/react-router.development.js b/node_modules/react-router/react-router.development.js
index aded811..1e6fa71 100644
--- a/node_modules/react-router/react-router.development.js
+++ b/node_modules/react-router/react-router.development.js
@@ -8,9 +8,10 @@
*
* @license MIT
*/
-import { parsePath, createMemoryHistory, Action } from 'history';
+import { Action, createMemoryHistory, parsePath } from 'history';
+import { Children, createContext, createElement, Fragment, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
export { Action as NavigationType, createPath, parsePath } from 'history';
-import { createContext, useContext, useMemo, useRef, useEffect, useCallback, createElement, useState, useLayoutEffect, Children, isValidElement, Fragment } from 'react';
+export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
const NavigationContext = /*#__PURE__*/createContext(null);
@@ -891,5 +892,4 @@ function renderMatches(matches) {
return _renderMatches(matches);
}
-export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
//# sourceMappingURL=react-router.development.js.map
diff --git a/node_modules/react-router/umd/react-router.development.js b/node_modules/react-router/umd/react-router.development.js
index e3b65ee..8a54d36 100644
--- a/node_modules/react-router/umd/react-router.development.js
+++ b/node_modules/react-router/umd/react-router.development.js
@@ -299,17 +299,24 @@
if (path.endsWith("*")) {
paramNames.push("*");
- regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
- : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
+ regexpSource +=
+ path === "*" || path === "/*"
+ ? "(.*)$" // Already matched the initial /, just match the rest
+ : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
+ } else if (end) {
+ // When matching to the end, ignore trailing slashes
+ regexpSource += "\\/*$";
+ } else if (path !== "" && path !== "/") {
+ // If our path is non-empty and contains anything beyond an initial slash,
+ // then we have _some_ form of path in our regex so we should expect to
+ // match only if we find the end of this path segment. Look for an optional
+ // non-captured trailing slash (to match a portion of the URL) or the end
+ // of the path (if we've matched to the end). We used to do this with a
+ // word boundary but that gives false positives on routes like
+ // /user-preferences since `-` counts as a word boundary.
+ regexpSource += "(?:(?=\\/|$))";
} else {
- regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
- : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
- // parent routes to matching only their own words and nothing more, e.g. parent
- // route "/home" should not match "/home2".
- // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
- // but do not consume the character in the matched path so they can match against
- // nested paths.
- "(?:(?=[.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
+ // Nothing to match for "" or "/"
}
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment