From 87618a793c02679b240c42b2c46beda6f5ce0c64 Mon Sep 17 00:00:00 2001 From: Yan Wang Date: Tue, 24 Dec 2024 18:28:33 +0800 Subject: [PATCH] upgrade path-to-regexp --- packages/cmf-router/package.json | 2 +- packages/cmf-router/src/sagaRouter.md | 39 ++++++++++++++++++++++ packages/cmf-router/src/sagaRouter.test.js | 20 +++++++++++ packages/cmf/__tests__/matchPath.test.js | 33 ++++++++++++++++++ packages/cmf/package.json | 2 +- packages/cmf/src/matchPath.js | 7 ++-- yarn.lock | 10 +++--- 7 files changed, 102 insertions(+), 11 deletions(-) diff --git a/packages/cmf-router/package.json b/packages/cmf-router/package.json index d725b674236..fdbfee877e2 100644 --- a/packages/cmf-router/package.json +++ b/packages/cmf-router/package.json @@ -29,7 +29,7 @@ "connected-react-router": "^6.9.3", "history": "^5.3.0", "lodash": "^4.17.21", - "path-to-regexp": "^3.3.0", + "path-to-regexp": "^8.2.0", "prop-types": "^15.8.1", "react-redux": "^7.2.9", "react-router": "~6.3.0", diff --git a/packages/cmf-router/src/sagaRouter.md b/packages/cmf-router/src/sagaRouter.md index 92df2566661..ef7b92f5d02 100644 --- a/packages/cmf-router/src/sagaRouter.md +++ b/packages/cmf-router/src/sagaRouter.md @@ -263,3 +263,42 @@ if the route change to `localhost/datasets/add` the `datasetsSaga` will be restarted since it still match on `/datasets/add{/:connectionId}` route and that the parameter has changed from being a value to being absent. the {/:connectionId} at the end of path means /connectionId is optional. + +### Root Path Matching + +The root path `/` has special matching behavior that's important to understand: + +1. Exact root path matching: + +```javascript +const routes = { + '/': function* rootSaga() { + yield take('SOMETHING'); + }, +}; +``` + +- Only matches exactly `/` +- Does not match child routes like `/tasks` or `/users/123` +- This is because path-to-regexp treats the root path `/` differently than other routes - it won't do partial matching even when `exact` is false +- If you want `/` to match any path that starts with `/`, you need to use a wildcard pattern like `/{*path}` + +2. Matching root and all child routes: + +```javascript +const routes = { + '/{*path}': function* rootSaga({ path }) { + yield take('SOMETHING'); + }, +}; +``` + +- Matches both root path `/` and all child routes +- For root path `/`, params will be empty `{}` +- For child routes, `params.path` will contain the remaining path: + - `/tasks` → `{ path: 'tasks' }` + - `/tasks/123` → `{ path: 'tasks/123' }` + +This pattern is particularly useful when you need a saga to run for all routes in your application while still being able to access the current route path. + +For more details about path matching and troubleshooting, see [path-to-regexp documentation](https://github.com/pillarjs/path-to-regexp#errors). diff --git a/packages/cmf-router/src/sagaRouter.test.js b/packages/cmf-router/src/sagaRouter.test.js index 33683c00de7..54480c44996 100644 --- a/packages/cmf-router/src/sagaRouter.test.js +++ b/packages/cmf-router/src/sagaRouter.test.js @@ -393,4 +393,24 @@ describe('sagaRouter route and route params', () => { expect(gen.next({ type: '@@router/LOCATION_CHANGE' }).value).toEqual(expectedCancelYield); expect(gen.next().value).toEqual(spawn(routes['/matchingroute{/:optional}'], {}, true)); }); + + it('should start root path saga when on child route', () => { + const mockHistory = { + get location() { + return { + pathname: '/tasks', + }; + }, + }; + const routes = { + '/{*path}': function* rootSaga() { + yield take('SOMETHING'); + }, + }; + const gen = sagaRouter(mockHistory, routes); + + // Root saga should be started with isExact=true for wildcard path + expect(gen.next().value).toEqual(spawn(routes['/{*path}'], { path: 'tasks' }, true)); + expect(gen.next().value).toEqual(take('@@router/LOCATION_CHANGE')); + }); }); diff --git a/packages/cmf/__tests__/matchPath.test.js b/packages/cmf/__tests__/matchPath.test.js index ccb874181f2..a573a1804f5 100644 --- a/packages/cmf/__tests__/matchPath.test.js +++ b/packages/cmf/__tests__/matchPath.test.js @@ -7,6 +7,7 @@ describe('matchPath', () => { const pathname = '/'; const match = matchPath(pathname, path); expect(match.url).toBe('/'); + expect(match.isExact).toBe(true); }); }); @@ -16,6 +17,7 @@ describe('matchPath', () => { const pathname = '/somewhere'; const match = matchPath(pathname, path); expect(match.url).toBe('/somewhere'); + expect(match.isExact).toBe(true); }); it('returns correct url at "/somewhere/else"', () => { @@ -23,6 +25,7 @@ describe('matchPath', () => { const pathname = '/somewhere/else'; const match = matchPath(pathname, path); expect(match.url).toBe('/somewhere'); + expect(match.isExact).toBe(false); }); }); @@ -34,6 +37,36 @@ describe('matchPath', () => { const pathname = '/somewhere'; const match = matchPath(pathname, options); expect(match.url).toBe('/somewhere'); + expect(match.isExact).toBe(true); + }); + }); + + describe('with path="/{*path}"', () => { + it('returns correct match at root "/"', () => { + const path = '/{*path}'; + const pathname = '/'; + const match = matchPath(pathname, path); + expect(match.url).toBe('/'); + expect(match.params).toEqual({}); + expect(match.isExact).toBe(true); + }); + + it('returns correct match and params for child route "/tasks"', () => { + const path = '/{*path}'; + const pathname = '/tasks'; + const match = matchPath(pathname, path); + expect(match.url).toBe('/tasks'); + expect(match.params).toEqual({ path: 'tasks' }); + expect(match.isExact).toBe(true); + }); + + it('returns correct match and params for nested route "/tasks/123"', () => { + const path = '/{*path}'; + const pathname = '/tasks/123'; + const match = matchPath(pathname, path); + expect(match.url).toBe('/tasks/123'); + expect(match.params).toEqual({ path: 'tasks/123' }); + expect(match.isExact).toBe(true); }); }); }); diff --git a/packages/cmf/package.json b/packages/cmf/package.json index 5d430ffddbf..df6dcc020ea 100644 --- a/packages/cmf/package.json +++ b/packages/cmf/package.json @@ -52,7 +52,7 @@ "invariant": "^2.2.4", "lodash": "^4.17.21", "nested-combine-reducers": "^1.2.2", - "path-to-regexp": "^3.3.0", + "path-to-regexp": "^8.2.0", "prop-types": "^15.8.1", "react-immutable-proptypes": "^2.2.0", "react-redux": "^7.2.9", diff --git a/packages/cmf/src/matchPath.js b/packages/cmf/src/matchPath.js index 95c0e734dc9..ccaf4f7b41c 100644 --- a/packages/cmf/src/matchPath.js +++ b/packages/cmf/src/matchPath.js @@ -2,7 +2,7 @@ * Beware! Do not modify. Forked from react-router V4 * Will be available as a dependency */ -import pathToRegexp from 'path-to-regexp'; +import { pathToRegexp } from 'path-to-regexp'; const patternCache = {}; const cacheLimit = 10000; @@ -14,9 +14,8 @@ const compilePath = (pattern, options) => { if (cache[pattern]) return cache[pattern]; - const keys = []; - const re = pathToRegexp(pattern, keys, options); - const compiledPattern = { re, keys }; + const { regexp, keys } = pathToRegexp(pattern, options); + const compiledPattern = { re: regexp, keys }; if (cacheCount < cacheLimit) { cache[pattern] = compiledPattern; diff --git a/yarn.lock b/yarn.lock index 9f4749361ff..8157e7fe574 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14953,16 +14953,16 @@ path-to-regexp@^2.2.1: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== -path-to-regexp@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" - integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== - path-to-regexp@^6.2.1, path-to-regexp@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== +path-to-regexp@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"