]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: add from argument to redirect
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 19 Aug 2025 14:55:09 +0000 (16:55 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Tue, 19 Aug 2025 14:55:09 +0000 (16:55 +0200)
packages/router/src/experimental/router.ts
packages/router/src/router.ts
packages/router/src/typed-routes/route-records.ts

index 1b12a122418a2da121f1e48764bb2797f2f8f028..4d6f9c290e66fd9035870019ef0148d443bc2642 100644 (file)
@@ -15,7 +15,7 @@ import {
   warn,
   type App,
 } from 'vue'
-import { RouterLink } from '../RouterLink'
+import { type RouterLink } from '../RouterLink'
 import {
   NavigationType,
   type HistoryState,
@@ -60,11 +60,7 @@ import {
   RouteMeta,
 } from '../types'
 import { useCallbacks } from '../utils/callbacks'
-import {
-  isSameRouteLocation,
-  parseURL,
-  START_LOCATION_NORMALIZED,
-} from '../location'
+import { isSameRouteLocation, START_LOCATION_NORMALIZED } from '../location'
 import { assign, isArray, isBrowser, noop } from '../utils'
 import {
   extractChangingRecords,
@@ -600,7 +596,9 @@ export function experimental_createRouter(
 ): EXPERIMENTAL_Router {
   const {
     resolver,
-    parseQuery = originalParseQuery,
+    // TODO: document that a custom parsing can be handled with a custom param that parses the whole query
+    // and adds a $query property to the params added at the root record, parent of all records
+    // parseQuery = originalParseQuery,
     stringifyQuery = originalStringifyQuery,
     history: routerHistory,
   } = options
@@ -627,14 +625,6 @@ export function experimental_createRouter(
   }
 
   // TODO: replace usage with resolver.resolve()
-  function locationAsObject(
-    to: RouteLocationRaw | RouteLocationNormalized,
-    currentLocation: string = currentRoute.value.path
-  ): Exclude<RouteLocationRaw, string> | RouteLocationNormalized {
-    return typeof to === 'string'
-      ? parseURL(parseQuery, to, currentLocation)
-      : to
-  }
 
   // NOTE: to support multiple overloads
   type TRecord = EXPERIMENTAL_RouteRecordNormalized
@@ -743,53 +733,16 @@ export function experimental_createRouter(
   const replace = (...args: _resolveArgs) =>
     pushWithRedirect(resolve(...args), true)
 
-  function handleRedirectRecord(to: RouteLocation): RouteLocationRaw | void {
+  function handleRedirectRecord(
+    to: RouteLocation,
+    from: RouteLocationNormalizedLoaded
+  ): RouteLocationRaw | void {
     const redirect = to.matched.at(-1)?.redirect
     if (redirect) {
-      let newTargetLocation =
-        typeof redirect === 'function' ? redirect(to) : redirect
-
-      // TODO: we should be able to just resolve(newTargetLocation)
-      // maybe we need a way to return the current location: return [redirect, current]
-      // (to, from) => [redirect, from] // relative to current location
-      // (to, from) => [redirect, to] // relative to target location
-      if (typeof newTargetLocation === 'string') {
-        newTargetLocation =
-          newTargetLocation.includes('?') || newTargetLocation.includes('#')
-            ? (newTargetLocation = locationAsObject(newTargetLocation))
-            : // force empty params
-              { path: newTargetLocation }
-        // @ts-expect-error: force empty params when a string is passed to let
-        // the router parse them again
-        newTargetLocation.params = {}
-      }
-
-      // TODO: should be removed if we use the resolve method
-      if (
-        __DEV__ &&
-        newTargetLocation.path == null &&
-        !('name' in newTargetLocation)
-      ) {
-        warn(
-          `Invalid redirect found:\n${JSON.stringify(
-            newTargetLocation,
-            null,
-            2
-          )}\n when navigating to "${
-            to.fullPath
-          }". A redirect must contain a name or path. This will break in production.`
-        )
-        throw new Error('Invalid redirect')
-      }
-
-      return assign(
-        {
-          query: to.query,
-          hash: to.hash,
-          // avoid transferring params if the redirect has a path
-          params: newTargetLocation.path != null ? {} : to.params,
-        },
-        newTargetLocation
+      return resolve(
+        // @ts-expect-error: TODO: allow redirect to return the first argument of resolve or a tuple consisting of the arguments?
+        typeof redirect === 'function' ? redirect(to, from) : redirect,
+        from
       )
     }
   }
@@ -805,7 +758,7 @@ export function experimental_createRouter(
     const data: HistoryState | undefined = (to as RouteLocationOptions).state
     const force: boolean | undefined = (to as RouteLocationOptions).force
 
-    const shouldRedirect = handleRedirectRecord(to)
+    const shouldRedirect = handleRedirectRecord(to, from)
 
     if (shouldRedirect) {
       return pushWithRedirect(
@@ -1134,7 +1087,10 @@ export function experimental_createRouter(
       // due to dynamic routing, and to hash history with manual navigation
       // (manually changing the url or calling history.hash = '#/somewhere'),
       // there could be a redirect record in history
-      const shouldRedirect = handleRedirectRecord(toLocation)
+      const shouldRedirect = handleRedirectRecord(
+        toLocation,
+        router.currentRoute.value
+      )
       if (shouldRedirect) {
         pushWithRedirect(
           assign(
index 01884ea2ab343135916d65c6ea0daa0c8f794816..9e3da8798e0fbfdf7ff0249659a9444a969e494a 100644 (file)
@@ -376,12 +376,15 @@ export function createRouter(options: RouterOptions): Router {
     return push(assign(locationAsObject(to), { replace: true }))
   }
 
-  function handleRedirectRecord(to: RouteLocation): RouteLocationRaw | void {
+  function handleRedirectRecord(
+    to: RouteLocation,
+    from: RouteLocationNormalizedLoaded
+  ): RouteLocationRaw | void {
     const lastMatched = to.matched[to.matched.length - 1]
     if (lastMatched && lastMatched.redirect) {
       const { redirect } = lastMatched
       let newTargetLocation =
-        typeof redirect === 'function' ? redirect(to) : redirect
+        typeof redirect === 'function' ? redirect(to, from) : redirect
 
       if (typeof newTargetLocation === 'string') {
         newTargetLocation =
@@ -434,7 +437,7 @@ export function createRouter(options: RouterOptions): Router {
     // to could be a string where `replace` is a function
     const replace = (to as RouteLocationOptions).replace === true
 
-    const shouldRedirect = handleRedirectRecord(targetLocation)
+    const shouldRedirect = handleRedirectRecord(targetLocation, from)
 
     if (shouldRedirect)
       return pushWithRedirect(
@@ -766,7 +769,10 @@ export function createRouter(options: RouterOptions): Router {
       // due to dynamic routing, and to hash history with manual navigation
       // (manually changing the url or calling history.hash = '#/somewhere'),
       // there could be a redirect record in history
-      const shouldRedirect = handleRedirectRecord(toLocation)
+      const shouldRedirect = handleRedirectRecord(
+        toLocation,
+        router.currentRoute.value
+      )
       if (shouldRedirect) {
         pushWithRedirect(
           assign(shouldRedirect, { replace: true, force: true }),
index d7d9d2022e94c3a2f81aa4d16a6c4b33fd025f46..533678ffa4789a9abed2a6c97901c3f6f5dc49ad 100644 (file)
@@ -1,6 +1,7 @@
 import type {
   RouteLocation,
   RouteLocationNormalized,
+  RouteLocationNormalizedLoaded,
   RouteLocationRaw,
 } from './route-location'
 import type { RouteMap, RouteMapGeneric } from './route-map'
@@ -10,7 +11,10 @@ import type { RouteMap, RouteMapGeneric } from './route-map'
  */
 export type RouteRecordRedirectOption =
   | RouteLocationRaw
-  | ((to: RouteLocation) => RouteLocationRaw)
+  | ((
+      to: RouteLocation,
+      from: RouteLocationNormalizedLoaded
+    ) => RouteLocationRaw)
 
 /**
  * Generic version of {@link RouteRecordName}.