Skip to content

Commit

Permalink
fix(history): Resolve the href in <base> correctly (#3819)
Browse files Browse the repository at this point in the history
* Resolve the href in `<base>` correctly.

* In hash mode, the hash in base is automatically removed and the base trailing slash is distinguished.

* By default the result of `router.resolve().href` is the same as the actual switched URL.
  • Loading branch information
ichaoX committed May 24, 2023
1 parent 9f969d4 commit 992fd4b
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 14 deletions.
2 changes: 1 addition & 1 deletion examples/hash-mode/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const Query = { template: '<div>query: "{{ $route.params.q }}"</div>' }
// 3. Create the router
const router = new VueRouter({
mode: 'hash',
base: __dirname,
base: require('path').join(__dirname, '/'),
routes: [
{ path: '/', component: Home }, // all paths are defined without the hash.
{ path: '/foo', component: Foo },
Expand Down
22 changes: 15 additions & 7 deletions src/history/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,19 +272,27 @@ function normalizeBase (base: ?string): string {
if (inBrowser) {
// respect <base> tag
const baseEl = document.querySelector('base')
base = (baseEl && baseEl.getAttribute('href')) || '/'
// strip full URL origin
base = base.replace(/^https?:\/\/[^\/]+/, '')
base = (baseEl && baseEl.getAttribute('href') && typeof baseEl.href === 'string') ? baseEl.href : ''
if (base) {
const href = window.location.href
const locationOrigin = href.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1')
const baseOrigin = base.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1')
if (locationOrigin === baseOrigin) {
base = base.slice(baseOrigin.length)
} else {
// XXX: hash and history modes do not support cross-origin
base = locationOrigin
}
}
} else {
base = '/'
base = ''
}
}
// make sure there's the starting slash
if (base.charAt(0) !== '/') {
if (base && base.charAt(0) !== '/' && !base.match(/^[^\/]+:\/\//)) {
base = '/' + base
}
// remove trailing slash
return base.replace(/\/$/, '')
return base
}

function resolveQueue (
Expand Down
12 changes: 12 additions & 0 deletions src/history/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { pushState, replaceState, supportsPushState } from '../util/push-state'
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
this.base = normalizeHashBase(this.base, !base)
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
Expand Down Expand Up @@ -135,6 +136,17 @@ function getUrl (path) {
return `${base}#${path}`
}

function normalizeHashBase (base: string, replace: ?boolean): string {
if (!base) return ''
if (replace) {
const hasOrigin = !!base.match(/^[^\/]+:\/\/[^\/]+/)
base = window.location.href
// XXX: keep origin for possible cross-origin cases
if (!hasOrigin) base = base.replace(/^[^\/]+:\/\/[^\/]+/, '')
}
return base.replace(/#.*$/, '')
}

function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
Expand Down
6 changes: 3 additions & 3 deletions src/history/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class HTML5History extends History {
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
pushState(cleanPath(route.fullPath, this.base))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
Expand All @@ -67,15 +67,15 @@ export class HTML5History extends History {
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
replaceState(cleanPath(route.fullPath, this.base))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}

ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
const current = cleanPath(this.current.fullPath, this.base)
push ? pushState(current) : replaceState(current)
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ function registerHook (list: Array<any>, fn: Function): Function {

function createHref (base: string, fullPath: string, mode) {
var path = mode === 'hash' ? '#' + fullPath : fullPath
return base ? cleanPath(base + '/' + path) : path
// '/main.html#/foo' should not be converted to '/main.html/#/foo'
if (base && mode !== 'hash') base = base.replace(/\/?$/, '/')
return base ? cleanPath(path, base) : path
}

// We cannot remove this as it would be a breaking change
Expand Down
15 changes: 13 additions & 2 deletions src/util/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ export function parsePath (path: string): {
}
}

export function cleanPath (path: string): string {
return path.replace(/\/(?:\s*\/)+/g, '/')
export function cleanPath (path: string, base: ?string): string {
let prefix = ''
if (base) {
// allow base to specify an origin
const match = base.match(/^((?:[^\/]+:)?\/\/)/)
if (match) {
prefix = match[0]
path = base.slice(prefix.length) + path
} else {
path = base + path
}
}
return prefix + path.replace(/\/(?:\s*\/)+/g, '/')
}

0 comments on commit 992fd4b

Please sign in to comment.