index.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /* @flow */
  2. import { install } from './install'
  3. import { START } from './util/route'
  4. import { assert } from './util/warn'
  5. import { inBrowser } from './util/dom'
  6. import { cleanPath } from './util/path'
  7. import { createMatcher } from './create-matcher'
  8. import { normalizeLocation } from './util/location'
  9. import { supportsPushState } from './util/push-state'
  10. import { HashHistory } from './history/hash'
  11. import { HTML5History } from './history/html5'
  12. import { AbstractHistory } from './history/abstract'
  13. import type { Matcher } from './create-matcher'
  14. export default class VueRouter {
  15. static install: () => void;
  16. static version: string;
  17. app: any;
  18. apps: Array<any>;
  19. ready: boolean;
  20. readyCbs: Array<Function>;
  21. options: RouterOptions;
  22. mode: string;
  23. history: HashHistory | HTML5History | AbstractHistory;
  24. matcher: Matcher;
  25. fallback: boolean;
  26. beforeHooks: Array<?NavigationGuard>;
  27. resolveHooks: Array<?NavigationGuard>;
  28. afterHooks: Array<?AfterNavigationHook>;
  29. constructor (options: RouterOptions = {}) {
  30. this.app = null
  31. this.apps = []
  32. this.options = options
  33. this.beforeHooks = []
  34. this.resolveHooks = []
  35. this.afterHooks = []
  36. this.matcher = createMatcher(options.routes || [], this)
  37. let mode = options.mode || 'hash'
  38. this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
  39. if (this.fallback) {
  40. mode = 'hash'
  41. }
  42. if (!inBrowser) {
  43. mode = 'abstract'
  44. }
  45. this.mode = mode
  46. switch (mode) {
  47. case 'history':
  48. this.history = new HTML5History(this, options.base)
  49. break
  50. case 'hash':
  51. this.history = new HashHistory(this, options.base, this.fallback)
  52. break
  53. case 'abstract':
  54. this.history = new AbstractHistory(this, options.base)
  55. break
  56. default:
  57. if (process.env.NODE_ENV !== 'production') {
  58. assert(false, `invalid mode: ${mode}`)
  59. }
  60. }
  61. }
  62. match (
  63. raw: RawLocation,
  64. current?: Route,
  65. redirectedFrom?: Location
  66. ): Route {
  67. return this.matcher.match(raw, current, redirectedFrom)
  68. }
  69. get currentRoute (): ?Route {
  70. return this.history && this.history.current
  71. }
  72. init (app: any /* Vue component instance */) {
  73. process.env.NODE_ENV !== 'production' && assert(
  74. install.installed,
  75. `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
  76. `before creating root instance.`
  77. )
  78. this.apps.push(app)
  79. // set up app destroyed handler
  80. // https://github.com/vuejs/vue-router/issues/2639
  81. app.$once('hook:destroyed', () => {
  82. // clean out app from this.apps array once destroyed
  83. const index = this.apps.indexOf(app)
  84. if (index > -1) this.apps.splice(index, 1)
  85. // ensure we still have a main app or null if no apps
  86. // we do not release the router so it can be reused
  87. if (this.app === app) this.app = this.apps[0] || null
  88. })
  89. // main app previously initialized
  90. // return as we don't need to set up new history listener
  91. if (this.app) {
  92. return
  93. }
  94. this.app = app
  95. const history = this.history
  96. if (history instanceof HTML5History) {
  97. history.transitionTo(history.getCurrentLocation())
  98. } else if (history instanceof HashHistory) {
  99. const setupHashListener = () => {
  100. history.setupListeners()
  101. }
  102. history.transitionTo(
  103. history.getCurrentLocation(),
  104. setupHashListener,
  105. setupHashListener
  106. )
  107. }
  108. history.listen(route => {
  109. this.apps.forEach((app) => {
  110. app._route = route
  111. })
  112. })
  113. }
  114. beforeEach (fn: Function): Function {
  115. return registerHook(this.beforeHooks, fn)
  116. }
  117. beforeResolve (fn: Function): Function {
  118. return registerHook(this.resolveHooks, fn)
  119. }
  120. afterEach (fn: Function): Function {
  121. return registerHook(this.afterHooks, fn)
  122. }
  123. onReady (cb: Function, errorCb?: Function) {
  124. this.history.onReady(cb, errorCb)
  125. }
  126. onError (errorCb: Function) {
  127. this.history.onError(errorCb)
  128. }
  129. push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  130. this.history.push(location, onComplete, onAbort)
  131. }
  132. replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  133. this.history.replace(location, onComplete, onAbort)
  134. }
  135. go (n: number) {
  136. this.history.go(n)
  137. }
  138. back () {
  139. this.go(-1)
  140. }
  141. forward () {
  142. this.go(1)
  143. }
  144. getMatchedComponents (to?: RawLocation | Route): Array<any> {
  145. const route: any = to
  146. ? to.matched
  147. ? to
  148. : this.resolve(to).route
  149. : this.currentRoute
  150. if (!route) {
  151. return []
  152. }
  153. return [].concat.apply([], route.matched.map(m => {
  154. return Object.keys(m.components).map(key => {
  155. return m.components[key]
  156. })
  157. }))
  158. }
  159. resolve (
  160. to: RawLocation,
  161. current?: Route,
  162. append?: boolean
  163. ): {
  164. location: Location,
  165. route: Route,
  166. href: string,
  167. // for backwards compat
  168. normalizedTo: Location,
  169. resolved: Route
  170. } {
  171. current = current || this.history.current
  172. const location = normalizeLocation(
  173. to,
  174. current,
  175. append,
  176. this
  177. )
  178. const route = this.match(location, current)
  179. const fullPath = route.redirectedFrom || route.fullPath
  180. const base = this.history.base
  181. const href = createHref(base, fullPath, this.mode)
  182. return {
  183. location,
  184. route,
  185. href,
  186. // for backwards compat
  187. normalizedTo: location,
  188. resolved: route
  189. }
  190. }
  191. addRoutes (routes: Array<RouteConfig>) {
  192. this.matcher.addRoutes(routes)
  193. if (this.history.current !== START) {
  194. this.history.transitionTo(this.history.getCurrentLocation())
  195. }
  196. }
  197. }
  198. function registerHook (list: Array<any>, fn: Function): Function {
  199. list.push(fn)
  200. return () => {
  201. const i = list.indexOf(fn)
  202. if (i > -1) list.splice(i, 1)
  203. }
  204. }
  205. function createHref (base: string, fullPath: string, mode) {
  206. var path = mode === 'hash' ? '#' + fullPath : fullPath
  207. return base ? cleanPath(base + '/' + path) : path
  208. }
  209. VueRouter.install = install
  210. VueRouter.version = '__VERSION__'
  211. if (inBrowser && window.Vue) {
  212. window.Vue.use(VueRouter)
  213. }