history
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if(this.fallback){
mode = 'hash'
}
if(!inBrowser){
mode = 'abstract'
}
this.mode = mode
switch(mode){
case 'history':
this.history = new HTML5History(this,options.base)
break
case 'hash':
this.history = new HashHistory(this,options.base,this.fallback)
break;
case 'abstract':
this.histroy = new AbstractHistory(this,options.base)
break
default:
if(process.env.NODE_ENV !== 'production'){
assert(false,`invalid mode: ${mode}`)
}
}
historyBase
class History {
router,base,current,pending,cb,ready,readyCbs,readyErrorCbs,errorCbs,listeners,cleanupListeners,go,push,replace,ensureURL,getCurrentLocation,setupListeners;
constructor(router,base){
this.router = router
this.base = normalizeBase(base)
this.current = START
this.pending = null
this.ready = false
this.readyCbs = []
this.readyErrorCbs = []
this.errorCbs = []
this.listeners = []
}
listen(cb){
this.cb = cb
}
onReady(cb,errorCb){
if(this.ready){
cb()
}else{
this.readyCbs.push(cb)
if(errorCb){
this.readyErrorCbs.push(errorCb)
}
}
}
onError(errorCb){
this.errorCbs.push(errorCb)
}
transitionTo(location,onComplete,onAbort){
let route
try{
route = this.router.match(location,this.current)
}catch(e){
this.errorCbs.forEach((cb) =>{
cb(e)
})
throw e
}
const prev = this.current
this.confirmTransition(route,() => {
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
this.router.afterHooks.forEach(hook => hook && hook(route,prev))
},err =>{
if(onAbort){
onAbort(err)
}
if(err && !this.ready){
if(!isNavigationFailure(err,NavigationFailureType.redirected) || prev!==START){
this.ready = true
this.readyErrorCbs.forEach(cb => cb(err))
}
}
})
}
confirmTransition(route,onComplete,onAbort){
const current = this.current
this.pending = route
const abort = err => {
if(!isNavigationFailure(err) && isError(err)){
if(this.errorCbs.length){
this.errorCbs.forEach(cb => cb(err))
}else{
...
}
}
onAbort && onAbort(err)
}
const lastRouteIndex = route.matched.length - 1
const lastCurrentIndex = current.matched.length - 1
if(isSameRoute(route,current) && lastRouteIndex === lastCurrentIndex && route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]){
this.ensureURL()
return abort(createNavigationDuplicatedError(current,route))
}
const { updated, deactivated, activated } = resolveQueue(this.current.matched,route.matched)
const queue = [].concat(extractLeaveGuards(deactivated),this.router.beforeHooks,extractUpdatedHooks(updated),activated.map(m => m.beforeEnter),resolveAsyncComponents(activated))
const iterator = (hook,next) => {
if(this.route !== pending){
return abort(createNavigationCancelledError(current,route))
}
try{
hook(route,current,(to) => {
if(to === false){
this.ensureURL(true)
abort(createNavigationAbortedError(current,to))
}else if(isError(to)){
this.ensureURL(true)
abort(to)
}else if(typeof to === 'string' || (typeof to === 'object' && typeof to.path === 'string' || typeof to.name === 'string')){
abort(createNavigationRedirectedError(current,route))
if(typeof to === 'object' && to.replace){
this.replace(to)
}else{
this.push(to)
}
}else{
next(to)
}
})
}catch(e){
abort(e)
}
}
runQueue(queue,iterator,() => {
const enterGuards = extractEnterGuards(activated)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue,iterator,() => {
if(this.pending !== route){
return abort(createNavigationCancelError(current,route))
}
this.pending = null
onComplete(route)
if(this.router.app){
this.router.app.$nextTick(() => {
handleRouterEntered(route)
})
}
})
})
}
updateRoute(route){
this.current = route
this.cb && this.cb(route)
}
setupListeners(){}
teardown(){
this.listeners.forEach(cleanuplistener => {
cleanuplistener()
})
this.listeners = []
this.current = START
this.pending = null
}
}
normalizeBase
function normalizeBase(base){
if(!base){
if(inBrowser){
const baseEl = document.querySelector('.base')
base = ( baseEl && baseEl.getAttribute('href')) || '/'
base = base.replace(/^https?:\/\/[^\/]+/,'')
}else{
base = '/'
}
}
if(base.charAt(0) !== '/'){
base = '/' + base
}
return base.replace(/\/$/,'')
}
resolveQueue
function resolveQueue(current,next){
let i
const max = Math.max(current.length,next.length)
for(i = 0; i < max; i ++){
if(current[i] !== next[i]){
break
}
}
return {
updated:next.slice(0,i),
activated:next.slice(i),
deactivated:current.slice(i)
}
}
extractLeaveGuards
function extractLeaveGuards(deactivated){
return extractGuards(deactivated,'beforeRouteLeave',bindGuard,true)
}
extractGuards
function extractGuards(records,name,bind,reverse){
const guards = flatMapComponents(record,(def,instance,match,key) => {
const guard = extractGuard(def,name)
if(guard){
return Array.isArray(guard) ? guard.map(guard => bind(guard,instance,match,key)) : bind(guard,instance,match,key)
}
return flatten(reverse ? guards.reverse() : guards)
})
}
extractGuard
function extractGuard(def,key){
if(typeof def !== 'function'){
def = _Vue.extend(def)
}
return def.options[key]
}
bindGuard
function bindGuard(guard,instance){
if(instance){
return function boundRouteGurad(){
return guard.apply(instance,arguments)
}
}
}
extractEnterGuards
function extractEnterGuards(activated){
return extractGuards(activated,'beforeRouteEnter',(guard,_,match,key) => bindEnterGuard(guard,match,key))
}
bindEnterGuard
function bindEnterGuard(guard,match,key){
return function routeEnterGuard(to,from,next){
return guard(to,from,cb => {
if(typeof cb === 'function'){
if(!match.enterCbs[key]){
match.enterCbs[key] = []
}
match.enterCbs[key].push(cb)
}
next(cb)
})
}
}
runQueue
function runQueue(queue,fn,cb){
const step = index => {
if(index >= queue.length){
cb()
}else{
if(queue[index]){
fn(queue[index],() =>{
step(index + 1)
})
}else{
step(index + 1)
}
}
}
step(0)
}