diff --git a/packages/router/__tests__/initialNavigation.spec.ts b/packages/router/__tests__/initialNavigation.spec.ts index 4e2e63787..280c9d4f4 100644 --- a/packages/router/__tests__/initialNavigation.spec.ts +++ b/packages/router/__tests__/initialNavigation.spec.ts @@ -1,3 +1,4 @@ +import { createApp } from 'vue' import { JSDOM } from 'jsdom' import { createRouter, createWebHistory } from '../src' import { createDom, components, nextNavigation } from './utils' @@ -74,4 +75,17 @@ describe('Initial Navigation', () => { await nextNavigation(router) expect(router.currentRoute.value).toMatchObject({ path: '/' }) }) + + it('Should respect the replaced state right after the initial navigation', async () => { + const { router, history } = newRouter('/') + window.history.replaceState({}, '', '/bar') + const app = createApp({ + template: ` + + `, + }) + app.use(router) + await router.isReady() + expect(history.location).toBe('/bar') + }) }) diff --git a/packages/router/src/history/common.ts b/packages/router/src/history/common.ts index 0abf48150..0c5bf4793 100644 --- a/packages/router/src/history/common.ts +++ b/packages/router/src/history/common.ts @@ -1,5 +1,5 @@ import { isBrowser } from '../utils' -import { removeTrailingSlash } from '../location' +import { removeTrailingSlash, stripBase } from '../location' export type HistoryLocation = string /** @@ -183,3 +183,28 @@ const BEFORE_HASH_RE = /^[^#]+#/ export function createHref(base: string, location: HistoryLocation): string { return base.replace(BEFORE_HASH_RE, '#') + location } + +/** + * Creates a normalized history location from a window.location object + * @param base - The base path + * @param location - The window.location object + */ +export function createCurrentLocation( + base: string, + location: Location +): HistoryLocation { + const { pathname, search, hash } = location + // allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end + const hashPos = base.indexOf('#') + if (hashPos > -1) { + let slicePos = hash.includes(base.slice(hashPos)) + ? base.slice(hashPos).length + : 1 + let pathFromHash = hash.slice(slicePos) + // prepend the starting slash to hash so the url starts with /# + if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash + return stripBase(pathFromHash, '') + } + const path = stripBase(pathname, base) + return path + search + hash +} diff --git a/packages/router/src/history/html5.ts b/packages/router/src/history/html5.ts index 79c5cedf0..c89cb54f2 100644 --- a/packages/router/src/history/html5.ts +++ b/packages/router/src/history/html5.ts @@ -7,6 +7,7 @@ import { ValueContainer, normalizeBase, createHref, + createCurrentLocation, HistoryLocation, } from './common' import { @@ -14,7 +15,6 @@ import { _ScrollPositionNormalized, } from '../scrollBehavior' import { warn } from '../warning' -import { stripBase } from '../location' import { assign } from '../utils' type PopStateListener = (this: Window, ev: PopStateEvent) => any @@ -30,31 +30,6 @@ interface StateEntry extends HistoryState { scroll: _ScrollPositionNormalized | null | false } -/** - * Creates a normalized history location from a window.location object - * @param base - The base path - * @param location - The window.location object - */ -function createCurrentLocation( - base: string, - location: Location -): HistoryLocation { - const { pathname, search, hash } = location - // allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end - const hashPos = base.indexOf('#') - if (hashPos > -1) { - let slicePos = hash.includes(base.slice(hashPos)) - ? base.slice(hashPos).length - : 1 - let pathFromHash = hash.slice(slicePos) - // prepend the starting slash to hash so the url starts with /# - if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash - return stripBase(pathFromHash, '') - } - const path = stripBase(pathname, base) - return path + search + hash -} - function useHistoryListeners( base: string, historyState: ValueContainer, diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 533a1b1fa..28f14f5f7 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -72,6 +72,7 @@ import { EXPERIMENTAL_Router_Base, _OnReadyCallback, } from './experimental/router' +import { createCurrentLocation } from './history/common' /** * Options to initialize a {@link Router} instance. @@ -730,15 +731,21 @@ export function createRouter(options: RouterOptions): Router { // only consider as push if it's not the first navigation const isFirstNavigation = from === START_LOCATION_NORMALIZED const state: Partial | null = !isBrowser ? {} : history.state - // change URL only if the user did a push/replace and if it's not the initial navigation because // it's just reflecting the url if (isPush) { // on the initial navigation, we want to reuse the scroll position from // history state if it exists - if (replace || isFirstNavigation) + if (replace || isFirstNavigation) { + // In the first navigation, we regard the current browser location as the target, + // As the state of routerHistory maybe stale when initializing the router. + const toLocationPath = + isFirstNavigation && isBrowser + ? createCurrentLocation(routerHistory.base, location) + : toLocation.fullPath + routerHistory.replace( - toLocation.fullPath, + toLocationPath, assign( { scroll: isFirstNavigation && state && state.scroll, @@ -746,7 +753,7 @@ export function createRouter(options: RouterOptions): Router { data ) ) - else routerHistory.push(toLocation.fullPath, data) + } else routerHistory.push(toLocation.fullPath, data) } // accept current navigation