123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- /* @flow */
- import { install } from './install'
- import { START } from './util/route'
- import { assert, warn } from './util/warn'
- import { inBrowser } from './util/dom'
- import { cleanPath } from './util/path'
- import { createMatcher } from './create-matcher'
- import { normalizeLocation } from './util/location'
- import { supportsPushState } from './util/push-state'
- import { handleScroll } from './util/scroll'
- import { HashHistory } from './history/hash'
- import { HTML5History } from './history/html5'
- import { AbstractHistory } from './history/abstract'
- import type { Matcher } from './create-matcher'
- import { isNavigationFailure, NavigationFailureType } from './util/errors'
- export default class VueRouter {
- static install: () => void
- static version: string
- static isNavigationFailure: Function
- static NavigationFailureType: any
- static START_LOCATION: Route
- app: any
- apps: Array<any>
- ready: boolean
- readyCbs: Array<Function>
- options: RouterOptions
- mode: string
- history: HashHistory | HTML5History | AbstractHistory
- matcher: Matcher
- fallback: boolean
- beforeHooks: Array<?NavigationGuard>
- resolveHooks: Array<?NavigationGuard>
- afterHooks: Array<?AfterNavigationHook>
- constructor (options: RouterOptions = {}) {
- if (process.env.NODE_ENV !== 'production') {
- warn(this instanceof VueRouter, `Router must be called with the new operator.`)
- }
- this.app = null
- this.apps = []
- this.options = options
- this.beforeHooks = []
- this.resolveHooks = []
- this.afterHooks = []
- this.matcher = createMatcher(options.routes || [], this)
- 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.history = new AbstractHistory(this, options.base)
- break
- default:
- if (process.env.NODE_ENV !== 'production') {
- assert(false, `invalid mode: ${mode}`)
- }
- }
- }
- match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
- return this.matcher.match(raw, current, redirectedFrom)
- }
- get currentRoute (): ?Route {
- return this.history && this.history.current
- }
- init (app: any /* Vue component instance */) {
- process.env.NODE_ENV !== 'production' &&
- assert(
- install.installed,
- `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
- `before creating root instance.`
- )
- this.apps.push(app)
- // set up app destroyed handler
- // https://github.com/vuejs/vue-router/issues/2639
- app.$once('hook:destroyed', () => {
- // clean out app from this.apps array once destroyed
- const index = this.apps.indexOf(app)
- if (index > -1) this.apps.splice(index, 1)
- // ensure we still have a main app or null if no apps
- // we do not release the router so it can be reused
- if (this.app === app) this.app = this.apps[0] || null
- if (!this.app) this.history.teardown()
- })
- // main app previously initialized
- // return as we don't need to set up new history listener
- if (this.app) {
- return
- }
- this.app = app
- const history = this.history
- if (history instanceof HTML5History || history instanceof HashHistory) {
- const handleInitialScroll = routeOrError => {
- const from = history.current
- const expectScroll = this.options.scrollBehavior
- const supportsScroll = supportsPushState && expectScroll
- if (supportsScroll && 'fullPath' in routeOrError) {
- handleScroll(this, routeOrError, from, false)
- }
- }
- const setupListeners = routeOrError => {
- history.setupListeners()
- handleInitialScroll(routeOrError)
- }
- history.transitionTo(
- history.getCurrentLocation(),
- setupListeners,
- setupListeners
- )
- }
- history.listen(route => {
- this.apps.forEach(app => {
- app._route = route
- })
- })
- }
- beforeEach (fn: Function): Function {
- return registerHook(this.beforeHooks, fn)
- }
- beforeResolve (fn: Function): Function {
- return registerHook(this.resolveHooks, fn)
- }
- afterEach (fn: Function): Function {
- return registerHook(this.afterHooks, fn)
- }
- onReady (cb: Function, errorCb?: Function) {
- this.history.onReady(cb, errorCb)
- }
- onError (errorCb: Function) {
- this.history.onError(errorCb)
- }
- push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
- // $flow-disable-line
- if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
- return new Promise((resolve, reject) => {
- this.history.push(location, resolve, reject)
- })
- } else {
- this.history.push(location, onComplete, onAbort)
- }
- }
- replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
- // $flow-disable-line
- if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
- return new Promise((resolve, reject) => {
- this.history.replace(location, resolve, reject)
- })
- } else {
- this.history.replace(location, onComplete, onAbort)
- }
- }
- go (n: number) {
- this.history.go(n)
- }
- back () {
- this.go(-1)
- }
- forward () {
- this.go(1)
- }
- getMatchedComponents (to?: RawLocation | Route): Array<any> {
- const route: any = to
- ? to.matched
- ? to
- : this.resolve(to).route
- : this.currentRoute
- if (!route) {
- return []
- }
- return [].concat.apply(
- [],
- route.matched.map(m => {
- return Object.keys(m.components).map(key => {
- return m.components[key]
- })
- })
- )
- }
- resolve (
- to: RawLocation,
- current?: Route,
- append?: boolean
- ): {
- location: Location,
- route: Route,
- href: string,
- // for backwards compat
- normalizedTo: Location,
- resolved: Route
- } {
- current = current || this.history.current
- const location = normalizeLocation(to, current, append, this)
- const route = this.match(location, current)
- const fullPath = route.redirectedFrom || route.fullPath
- const base = this.history.base
- const href = createHref(base, fullPath, this.mode)
- return {
- location,
- route,
- href,
- // for backwards compat
- normalizedTo: location,
- resolved: route
- }
- }
- getRoutes () {
- return this.matcher.getRoutes()
- }
- addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
- this.matcher.addRoute(parentOrRoute, route)
- if (this.history.current !== START) {
- this.history.transitionTo(this.history.getCurrentLocation())
- }
- }
- addRoutes (routes: Array<RouteConfig>) {
- if (process.env.NODE_ENV !== 'production') {
- warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')
- }
- this.matcher.addRoutes(routes)
- if (this.history.current !== START) {
- this.history.transitionTo(this.history.getCurrentLocation())
- }
- }
- }
- function registerHook (list: Array<any>, fn: Function): Function {
- list.push(fn)
- return () => {
- const i = list.indexOf(fn)
- if (i > -1) list.splice(i, 1)
- }
- }
- function createHref (base: string, fullPath: string, mode) {
- var path = mode === 'hash' ? '#' + fullPath : fullPath
- return base ? cleanPath(base + '/' + path) : path
- }
- VueRouter.install = install
- VueRouter.version = '__VERSION__'
- VueRouter.isNavigationFailure = isNavigationFailure
- VueRouter.NavigationFailureType = NavigationFailureType
- VueRouter.START_LOCATION = START
- if (inBrowser && window.Vue) {
- window.Vue.use(VueRouter)
- }
|