Skip to content

Commit 992fd4b

Browse files
committed
fix(history): Resolve the href in <base> correctly (#3819)
* 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.
1 parent 9f969d4 commit 992fd4b

File tree

6 files changed

+47
-14
lines changed

6 files changed

+47
-14
lines changed

examples/hash-mode/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const Query = { template: '<div>query: "{{ $route.params.q }}"</div>' }
4040
// 3. Create the router
4141
const router = new VueRouter({
4242
mode: 'hash',
43-
base: __dirname,
43+
base: require('path').join(__dirname, '/'),
4444
routes: [
4545
{ path: '/', component: Home }, // all paths are defined without the hash.
4646
{ path: '/foo', component: Foo },

src/history/base.js

+15-7
Original file line numberDiff line numberDiff line change
@@ -272,19 +272,27 @@ function normalizeBase (base: ?string): string {
272272
if (inBrowser) {
273273
// respect <base> tag
274274
const baseEl = document.querySelector('base')
275-
base = (baseEl && baseEl.getAttribute('href')) || '/'
276-
// strip full URL origin
277-
base = base.replace(/^https?:\/\/[^\/]+/, '')
275+
base = (baseEl && baseEl.getAttribute('href') && typeof baseEl.href === 'string') ? baseEl.href : ''
276+
if (base) {
277+
const href = window.location.href
278+
const locationOrigin = href.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1')
279+
const baseOrigin = base.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1')
280+
if (locationOrigin === baseOrigin) {
281+
base = base.slice(baseOrigin.length)
282+
} else {
283+
// XXX: hash and history modes do not support cross-origin
284+
base = locationOrigin
285+
}
286+
}
278287
} else {
279-
base = '/'
288+
base = ''
280289
}
281290
}
282291
// make sure there's the starting slash
283-
if (base.charAt(0) !== '/') {
292+
if (base && base.charAt(0) !== '/' && !base.match(/^[^\/]+:\/\//)) {
284293
base = '/' + base
285294
}
286-
// remove trailing slash
287-
return base.replace(/\/$/, '')
295+
return base
288296
}
289297

290298
function resolveQueue (

src/history/hash.js

+12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { pushState, replaceState, supportsPushState } from '../util/push-state'
1010
export class HashHistory extends History {
1111
constructor (router: Router, base: ?string, fallback: boolean) {
1212
super(router, base)
13+
this.base = normalizeHashBase(this.base, !base)
1314
// check history fallback deeplinking
1415
if (fallback && checkFallback(this.base)) {
1516
return
@@ -135,6 +136,17 @@ function getUrl (path) {
135136
return `${base}#${path}`
136137
}
137138

139+
function normalizeHashBase (base: string, replace: ?boolean): string {
140+
if (!base) return ''
141+
if (replace) {
142+
const hasOrigin = !!base.match(/^[^\/]+:\/\/[^\/]+/)
143+
base = window.location.href
144+
// XXX: keep origin for possible cross-origin cases
145+
if (!hasOrigin) base = base.replace(/^[^\/]+:\/\/[^\/]+/, '')
146+
}
147+
return base.replace(/#.*$/, '')
148+
}
149+
138150
function pushHash (path) {
139151
if (supportsPushState) {
140152
pushState(getUrl(path))

src/history/html5.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class HTML5History extends History {
5858
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
5959
const { current: fromRoute } = this
6060
this.transitionTo(location, route => {
61-
pushState(cleanPath(this.base + route.fullPath))
61+
pushState(cleanPath(route.fullPath, this.base))
6262
handleScroll(this.router, route, fromRoute, false)
6363
onComplete && onComplete(route)
6464
}, onAbort)
@@ -67,15 +67,15 @@ export class HTML5History extends History {
6767
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
6868
const { current: fromRoute } = this
6969
this.transitionTo(location, route => {
70-
replaceState(cleanPath(this.base + route.fullPath))
70+
replaceState(cleanPath(route.fullPath, this.base))
7171
handleScroll(this.router, route, fromRoute, false)
7272
onComplete && onComplete(route)
7373
}, onAbort)
7474
}
7575

7676
ensureURL (push?: boolean) {
7777
if (getLocation(this.base) !== this.current.fullPath) {
78-
const current = cleanPath(this.base + this.current.fullPath)
78+
const current = cleanPath(this.current.fullPath, this.base)
7979
push ? pushState(current) : replaceState(current)
8080
}
8181
}

src/router.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,9 @@ function registerHook (list: Array<any>, fn: Function): Function {
279279

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

285287
// We cannot remove this as it would be a breaking change

src/util/path.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ export function parsePath (path: string): {
6969
}
7070
}
7171

72-
export function cleanPath (path: string): string {
73-
return path.replace(/\/(?:\s*\/)+/g, '/')
72+
export function cleanPath (path: string, base: ?string): string {
73+
let prefix = ''
74+
if (base) {
75+
// allow base to specify an origin
76+
const match = base.match(/^((?:[^\/]+:)?\/\/)/)
77+
if (match) {
78+
prefix = match[0]
79+
path = base.slice(prefix.length) + path
80+
} else {
81+
path = base + path
82+
}
83+
}
84+
return prefix + path.replace(/\/(?:\s*\/)+/g, '/')
7485
}

0 commit comments

Comments
 (0)