kdu-router 3.5.4 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +11 -11
- package/dist/composables.js +253 -0
- package/dist/composables.mjs +244 -0
- package/dist/kdu-router.common.js +2668 -2665
- package/dist/kdu-router.esm.browser.js +2677 -2671
- package/dist/kdu-router.esm.browser.min.js +5 -5
- package/dist/kdu-router.esm.js +2672 -2666
- package/dist/kdu-router.js +2675 -2672
- package/dist/kdu-router.min.js +5 -5
- package/ketur/attributes.json +38 -38
- package/ketur/tags.json +20 -20
- package/package.json +115 -90
- package/src/components/link.js +224 -224
- package/src/components/view.js +155 -155
- package/src/composables/globals.js +34 -0
- package/src/composables/guards.js +68 -0
- package/src/composables/index.js +3 -0
- package/src/composables/useLink.js +113 -0
- package/src/composables/utils.js +11 -0
- package/src/create-matcher.js +226 -226
- package/src/create-route-map.js +220 -220
- package/src/entries/cjs.js +3 -0
- package/src/entries/esm.js +12 -0
- package/src/history/abstract.js +72 -72
- package/src/history/base.js +379 -379
- package/src/history/hash.js +152 -152
- package/src/history/html5.js +99 -99
- package/src/index.js +3 -293
- package/src/install.js +52 -52
- package/src/router.js +293 -0
- package/src/util/async.js +18 -18
- package/src/util/dom.js +3 -3
- package/src/util/errors.js +86 -86
- package/src/util/location.js +69 -69
- package/src/util/misc.js +6 -6
- package/src/util/params.js +37 -37
- package/src/util/path.js +74 -74
- package/src/util/push-state.js +46 -46
- package/src/util/query.js +113 -113
- package/src/util/resolve-components.js +109 -109
- package/src/util/route.js +151 -151
- package/src/util/scroll.js +175 -175
- package/src/util/state-key.js +22 -22
- package/src/util/warn.js +14 -14
- package/types/composables.d.ts +53 -0
- package/types/index.d.ts +25 -21
- package/types/kdu.d.ts +22 -22
- package/types/router.d.ts +564 -211
package/src/history/base.js
CHANGED
|
@@ -1,379 +1,379 @@
|
|
|
1
|
-
/* @flow */
|
|
2
|
-
|
|
3
|
-
import { _Kdu } from '../install'
|
|
4
|
-
import type Router from '../index'
|
|
5
|
-
import { inBrowser } from '../util/dom'
|
|
6
|
-
import { runQueue } from '../util/async'
|
|
7
|
-
import { warn } from '../util/warn'
|
|
8
|
-
import { START, isSameRoute, handleRouteEntered } from '../util/route'
|
|
9
|
-
import {
|
|
10
|
-
flatten,
|
|
11
|
-
flatMapComponents,
|
|
12
|
-
resolveAsyncComponents
|
|
13
|
-
} from '../util/resolve-components'
|
|
14
|
-
import {
|
|
15
|
-
createNavigationDuplicatedError,
|
|
16
|
-
createNavigationCancelledError,
|
|
17
|
-
createNavigationRedirectedError,
|
|
18
|
-
createNavigationAbortedError,
|
|
19
|
-
isError,
|
|
20
|
-
isNavigationFailure,
|
|
21
|
-
NavigationFailureType
|
|
22
|
-
} from '../util/errors'
|
|
23
|
-
import { handleScroll } from '../util/scroll'
|
|
24
|
-
|
|
25
|
-
export class History {
|
|
26
|
-
router: Router
|
|
27
|
-
base: string
|
|
28
|
-
current: Route
|
|
29
|
-
pending: ?Route
|
|
30
|
-
cb: (r: Route) => void
|
|
31
|
-
ready: boolean
|
|
32
|
-
readyCbs: Array<Function>
|
|
33
|
-
readyErrorCbs: Array<Function>
|
|
34
|
-
errorCbs: Array<Function>
|
|
35
|
-
listeners: Array<Function>
|
|
36
|
-
cleanupListeners: Function
|
|
37
|
-
|
|
38
|
-
// implemented by sub-classes
|
|
39
|
-
+go: (n: number) => void
|
|
40
|
-
+push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
|
|
41
|
-
+replace: (
|
|
42
|
-
loc: RawLocation,
|
|
43
|
-
onComplete?: Function,
|
|
44
|
-
onAbort?: Function
|
|
45
|
-
) => void
|
|
46
|
-
+ensureURL: (push?: boolean) => void
|
|
47
|
-
+getCurrentLocation: () => string
|
|
48
|
-
+setupListeners: Function
|
|
49
|
-
|
|
50
|
-
constructor (router: Router, base: ?string) {
|
|
51
|
-
this.router = router
|
|
52
|
-
this.base = normalizeBase(base)
|
|
53
|
-
// start with a route object that stands for "nowhere"
|
|
54
|
-
this.current = START
|
|
55
|
-
this.pending = null
|
|
56
|
-
this.ready = false
|
|
57
|
-
this.readyCbs = []
|
|
58
|
-
this.readyErrorCbs = []
|
|
59
|
-
this.errorCbs = []
|
|
60
|
-
this.listeners = []
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
listen (cb: Function) {
|
|
64
|
-
this.cb = cb
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
onReady (cb: Function, errorCb: ?Function) {
|
|
68
|
-
if (this.ready) {
|
|
69
|
-
cb()
|
|
70
|
-
} else {
|
|
71
|
-
this.readyCbs.push(cb)
|
|
72
|
-
if (errorCb) {
|
|
73
|
-
this.readyErrorCbs.push(errorCb)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
onError (errorCb: Function) {
|
|
79
|
-
this.errorCbs.push(errorCb)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
transitionTo (
|
|
83
|
-
location: RawLocation,
|
|
84
|
-
onComplete?: Function,
|
|
85
|
-
onAbort?: Function
|
|
86
|
-
) {
|
|
87
|
-
let route
|
|
88
|
-
// catch redirect option
|
|
89
|
-
try {
|
|
90
|
-
route = this.router.match(location, this.current)
|
|
91
|
-
} catch (e) {
|
|
92
|
-
this.errorCbs.forEach(cb => {
|
|
93
|
-
cb(e)
|
|
94
|
-
})
|
|
95
|
-
// Exception should still be thrown
|
|
96
|
-
throw e
|
|
97
|
-
}
|
|
98
|
-
const prev = this.current
|
|
99
|
-
this.confirmTransition(
|
|
100
|
-
route,
|
|
101
|
-
() => {
|
|
102
|
-
this.updateRoute(route)
|
|
103
|
-
onComplete && onComplete(route)
|
|
104
|
-
this.ensureURL()
|
|
105
|
-
this.router.afterHooks.forEach(hook => {
|
|
106
|
-
hook && hook(route, prev)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// fire ready cbs once
|
|
110
|
-
if (!this.ready) {
|
|
111
|
-
this.ready = true
|
|
112
|
-
this.readyCbs.forEach(cb => {
|
|
113
|
-
cb(route)
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
err => {
|
|
118
|
-
if (onAbort) {
|
|
119
|
-
onAbort(err)
|
|
120
|
-
}
|
|
121
|
-
if (err && !this.ready) {
|
|
122
|
-
// Initial redirection should not mark the history as ready yet
|
|
123
|
-
// because it's triggered by the redirection instead
|
|
124
|
-
if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
|
|
125
|
-
this.ready = true
|
|
126
|
-
this.readyErrorCbs.forEach(cb => {
|
|
127
|
-
cb(err)
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
|
|
136
|
-
const current = this.current
|
|
137
|
-
this.pending = route
|
|
138
|
-
const abort = err => {
|
|
139
|
-
// changed after adding errors
|
|
140
|
-
// before that change, redirect and aborted navigation would produce an err == null
|
|
141
|
-
if (!isNavigationFailure(err) && isError(err)) {
|
|
142
|
-
if (this.errorCbs.length) {
|
|
143
|
-
this.errorCbs.forEach(cb => {
|
|
144
|
-
cb(err)
|
|
145
|
-
})
|
|
146
|
-
} else {
|
|
147
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
148
|
-
warn(false, 'uncaught error during route navigation:')
|
|
149
|
-
}
|
|
150
|
-
console.error(err)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
onAbort && onAbort(err)
|
|
154
|
-
}
|
|
155
|
-
const lastRouteIndex = route.matched.length - 1
|
|
156
|
-
const lastCurrentIndex = current.matched.length - 1
|
|
157
|
-
if (
|
|
158
|
-
isSameRoute(route, current) &&
|
|
159
|
-
// in the case the route map has been dynamically appended to
|
|
160
|
-
lastRouteIndex === lastCurrentIndex &&
|
|
161
|
-
route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
|
|
162
|
-
) {
|
|
163
|
-
this.ensureURL()
|
|
164
|
-
if (route.hash) {
|
|
165
|
-
handleScroll(this.router, current, route, false)
|
|
166
|
-
}
|
|
167
|
-
return abort(createNavigationDuplicatedError(current, route))
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const { updated, deactivated, activated } = resolveQueue(
|
|
171
|
-
this.current.matched,
|
|
172
|
-
route.matched
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
const queue: Array<?NavigationGuard> = [].concat(
|
|
176
|
-
// in-component leave guards
|
|
177
|
-
extractLeaveGuards(deactivated),
|
|
178
|
-
// global before hooks
|
|
179
|
-
this.router.beforeHooks,
|
|
180
|
-
// in-component update hooks
|
|
181
|
-
extractUpdateHooks(updated),
|
|
182
|
-
// in-config enter guards
|
|
183
|
-
activated.map(m => m.beforeEnter),
|
|
184
|
-
// async components
|
|
185
|
-
resolveAsyncComponents(activated)
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
const iterator = (hook: NavigationGuard, next) => {
|
|
189
|
-
if (this.pending !== route) {
|
|
190
|
-
return abort(createNavigationCancelledError(current, route))
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
hook(route, current, (to: any) => {
|
|
194
|
-
if (to === false) {
|
|
195
|
-
// next(false) -> abort navigation, ensure current URL
|
|
196
|
-
this.ensureURL(true)
|
|
197
|
-
abort(createNavigationAbortedError(current, route))
|
|
198
|
-
} else if (isError(to)) {
|
|
199
|
-
this.ensureURL(true)
|
|
200
|
-
abort(to)
|
|
201
|
-
} else if (
|
|
202
|
-
typeof to === 'string' ||
|
|
203
|
-
(typeof to === 'object' &&
|
|
204
|
-
(typeof to.path === 'string' || typeof to.name === 'string'))
|
|
205
|
-
) {
|
|
206
|
-
// next('/') or next({ path: '/' }) -> redirect
|
|
207
|
-
abort(createNavigationRedirectedError(current, route))
|
|
208
|
-
if (typeof to === 'object' && to.replace) {
|
|
209
|
-
this.replace(to)
|
|
210
|
-
} else {
|
|
211
|
-
this.push(to)
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
// confirm transition and pass on the value
|
|
215
|
-
next(to)
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
} catch (e) {
|
|
219
|
-
abort(e)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
runQueue(queue, iterator, () => {
|
|
224
|
-
// wait until async components are resolved before
|
|
225
|
-
// extracting in-component enter guards
|
|
226
|
-
const enterGuards = extractEnterGuards(activated)
|
|
227
|
-
const queue = enterGuards.concat(this.router.resolveHooks)
|
|
228
|
-
runQueue(queue, iterator, () => {
|
|
229
|
-
if (this.pending !== route) {
|
|
230
|
-
return abort(createNavigationCancelledError(current, route))
|
|
231
|
-
}
|
|
232
|
-
this.pending = null
|
|
233
|
-
onComplete(route)
|
|
234
|
-
if (this.router.app) {
|
|
235
|
-
this.router.app.$nextTick(() => {
|
|
236
|
-
handleRouteEntered(route)
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
updateRoute (route: Route) {
|
|
244
|
-
this.current = route
|
|
245
|
-
this.cb && this.cb(route)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
setupListeners () {
|
|
249
|
-
// Default implementation is empty
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
teardown () {
|
|
253
|
-
// clean up event listeners
|
|
254
|
-
this.listeners.forEach(cleanupListener => {
|
|
255
|
-
cleanupListener()
|
|
256
|
-
})
|
|
257
|
-
this.listeners = []
|
|
258
|
-
|
|
259
|
-
// reset current history route
|
|
260
|
-
this.current = START
|
|
261
|
-
this.pending = null
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function normalizeBase (base: ?string): string {
|
|
266
|
-
if (!base) {
|
|
267
|
-
if (inBrowser) {
|
|
268
|
-
// respect <base> tag
|
|
269
|
-
const baseEl = document.querySelector('base')
|
|
270
|
-
base = (baseEl && baseEl.getAttribute('href')) || '/'
|
|
271
|
-
// strip full URL origin
|
|
272
|
-
base = base.replace(/^https?:\/\/[^\/]+/, '')
|
|
273
|
-
} else {
|
|
274
|
-
base = '/'
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// make sure there's the starting slash
|
|
278
|
-
if (base.charAt(0) !== '/') {
|
|
279
|
-
base = '/' + base
|
|
280
|
-
}
|
|
281
|
-
// remove trailing slash
|
|
282
|
-
return base.replace(/\/$/, '')
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function resolveQueue (
|
|
286
|
-
current: Array<RouteRecord>,
|
|
287
|
-
next: Array<RouteRecord>
|
|
288
|
-
): {
|
|
289
|
-
updated: Array<RouteRecord>,
|
|
290
|
-
activated: Array<RouteRecord>,
|
|
291
|
-
deactivated: Array<RouteRecord>
|
|
292
|
-
} {
|
|
293
|
-
let i
|
|
294
|
-
const max = Math.max(current.length, next.length)
|
|
295
|
-
for (i = 0; i < max; i++) {
|
|
296
|
-
if (current[i] !== next[i]) {
|
|
297
|
-
break
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
updated: next.slice(0, i),
|
|
302
|
-
activated: next.slice(i),
|
|
303
|
-
deactivated: current.slice(i)
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function extractGuards (
|
|
308
|
-
records: Array<RouteRecord>,
|
|
309
|
-
name: string,
|
|
310
|
-
bind: Function,
|
|
311
|
-
reverse?: boolean
|
|
312
|
-
): Array<?Function> {
|
|
313
|
-
const guards = flatMapComponents(records, (def, instance, match, key) => {
|
|
314
|
-
const guard = extractGuard(def, name)
|
|
315
|
-
if (guard) {
|
|
316
|
-
return Array.isArray(guard)
|
|
317
|
-
? guard.map(guard => bind(guard, instance, match, key))
|
|
318
|
-
: bind(guard, instance, match, key)
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
return flatten(reverse ? guards.reverse() : guards)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function extractGuard (
|
|
325
|
-
def: Object | Function,
|
|
326
|
-
key: string
|
|
327
|
-
): NavigationGuard | Array<NavigationGuard> {
|
|
328
|
-
if (typeof def !== 'function') {
|
|
329
|
-
// extend now so that global mixins are applied.
|
|
330
|
-
def = _Kdu.extend(def)
|
|
331
|
-
}
|
|
332
|
-
return def.options[key]
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
|
|
336
|
-
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
|
|
340
|
-
return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function bindGuard (guard: NavigationGuard, instance: ?_Kdu): ?NavigationGuard {
|
|
344
|
-
if (instance) {
|
|
345
|
-
return function boundRouteGuard () {
|
|
346
|
-
return guard.apply(instance, arguments)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function extractEnterGuards (
|
|
352
|
-
activated: Array<RouteRecord>
|
|
353
|
-
): Array<?Function> {
|
|
354
|
-
return extractGuards(
|
|
355
|
-
activated,
|
|
356
|
-
'beforeRouteEnter',
|
|
357
|
-
(guard, _, match, key) => {
|
|
358
|
-
return bindEnterGuard(guard, match, key)
|
|
359
|
-
}
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function bindEnterGuard (
|
|
364
|
-
guard: NavigationGuard,
|
|
365
|
-
match: RouteRecord,
|
|
366
|
-
key: string
|
|
367
|
-
): NavigationGuard {
|
|
368
|
-
return function routeEnterGuard (to, from, next) {
|
|
369
|
-
return guard(to, from, cb => {
|
|
370
|
-
if (typeof cb === 'function') {
|
|
371
|
-
if (!match.enteredCbs[key]) {
|
|
372
|
-
match.enteredCbs[key] = []
|
|
373
|
-
}
|
|
374
|
-
match.enteredCbs[key].push(cb)
|
|
375
|
-
}
|
|
376
|
-
next(cb)
|
|
377
|
-
})
|
|
378
|
-
}
|
|
379
|
-
}
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { _Kdu } from '../install'
|
|
4
|
+
import type Router from '../index'
|
|
5
|
+
import { inBrowser } from '../util/dom'
|
|
6
|
+
import { runQueue } from '../util/async'
|
|
7
|
+
import { warn } from '../util/warn'
|
|
8
|
+
import { START, isSameRoute, handleRouteEntered } from '../util/route'
|
|
9
|
+
import {
|
|
10
|
+
flatten,
|
|
11
|
+
flatMapComponents,
|
|
12
|
+
resolveAsyncComponents
|
|
13
|
+
} from '../util/resolve-components'
|
|
14
|
+
import {
|
|
15
|
+
createNavigationDuplicatedError,
|
|
16
|
+
createNavigationCancelledError,
|
|
17
|
+
createNavigationRedirectedError,
|
|
18
|
+
createNavigationAbortedError,
|
|
19
|
+
isError,
|
|
20
|
+
isNavigationFailure,
|
|
21
|
+
NavigationFailureType
|
|
22
|
+
} from '../util/errors'
|
|
23
|
+
import { handleScroll } from '../util/scroll'
|
|
24
|
+
|
|
25
|
+
export class History {
|
|
26
|
+
router: Router
|
|
27
|
+
base: string
|
|
28
|
+
current: Route
|
|
29
|
+
pending: ?Route
|
|
30
|
+
cb: (r: Route) => void
|
|
31
|
+
ready: boolean
|
|
32
|
+
readyCbs: Array<Function>
|
|
33
|
+
readyErrorCbs: Array<Function>
|
|
34
|
+
errorCbs: Array<Function>
|
|
35
|
+
listeners: Array<Function>
|
|
36
|
+
cleanupListeners: Function
|
|
37
|
+
|
|
38
|
+
// implemented by sub-classes
|
|
39
|
+
+go: (n: number) => void
|
|
40
|
+
+push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
|
|
41
|
+
+replace: (
|
|
42
|
+
loc: RawLocation,
|
|
43
|
+
onComplete?: Function,
|
|
44
|
+
onAbort?: Function
|
|
45
|
+
) => void
|
|
46
|
+
+ensureURL: (push?: boolean) => void
|
|
47
|
+
+getCurrentLocation: () => string
|
|
48
|
+
+setupListeners: Function
|
|
49
|
+
|
|
50
|
+
constructor (router: Router, base: ?string) {
|
|
51
|
+
this.router = router
|
|
52
|
+
this.base = normalizeBase(base)
|
|
53
|
+
// start with a route object that stands for "nowhere"
|
|
54
|
+
this.current = START
|
|
55
|
+
this.pending = null
|
|
56
|
+
this.ready = false
|
|
57
|
+
this.readyCbs = []
|
|
58
|
+
this.readyErrorCbs = []
|
|
59
|
+
this.errorCbs = []
|
|
60
|
+
this.listeners = []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
listen (cb: Function) {
|
|
64
|
+
this.cb = cb
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onReady (cb: Function, errorCb: ?Function) {
|
|
68
|
+
if (this.ready) {
|
|
69
|
+
cb()
|
|
70
|
+
} else {
|
|
71
|
+
this.readyCbs.push(cb)
|
|
72
|
+
if (errorCb) {
|
|
73
|
+
this.readyErrorCbs.push(errorCb)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onError (errorCb: Function) {
|
|
79
|
+
this.errorCbs.push(errorCb)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
transitionTo (
|
|
83
|
+
location: RawLocation,
|
|
84
|
+
onComplete?: Function,
|
|
85
|
+
onAbort?: Function
|
|
86
|
+
) {
|
|
87
|
+
let route
|
|
88
|
+
// catch redirect option
|
|
89
|
+
try {
|
|
90
|
+
route = this.router.match(location, this.current)
|
|
91
|
+
} catch (e) {
|
|
92
|
+
this.errorCbs.forEach(cb => {
|
|
93
|
+
cb(e)
|
|
94
|
+
})
|
|
95
|
+
// Exception should still be thrown
|
|
96
|
+
throw e
|
|
97
|
+
}
|
|
98
|
+
const prev = this.current
|
|
99
|
+
this.confirmTransition(
|
|
100
|
+
route,
|
|
101
|
+
() => {
|
|
102
|
+
this.updateRoute(route)
|
|
103
|
+
onComplete && onComplete(route)
|
|
104
|
+
this.ensureURL()
|
|
105
|
+
this.router.afterHooks.forEach(hook => {
|
|
106
|
+
hook && hook(route, prev)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// fire ready cbs once
|
|
110
|
+
if (!this.ready) {
|
|
111
|
+
this.ready = true
|
|
112
|
+
this.readyCbs.forEach(cb => {
|
|
113
|
+
cb(route)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
err => {
|
|
118
|
+
if (onAbort) {
|
|
119
|
+
onAbort(err)
|
|
120
|
+
}
|
|
121
|
+
if (err && !this.ready) {
|
|
122
|
+
// Initial redirection should not mark the history as ready yet
|
|
123
|
+
// because it's triggered by the redirection instead
|
|
124
|
+
if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
|
|
125
|
+
this.ready = true
|
|
126
|
+
this.readyErrorCbs.forEach(cb => {
|
|
127
|
+
cb(err)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
|
|
136
|
+
const current = this.current
|
|
137
|
+
this.pending = route
|
|
138
|
+
const abort = err => {
|
|
139
|
+
// changed after adding errors
|
|
140
|
+
// before that change, redirect and aborted navigation would produce an err == null
|
|
141
|
+
if (!isNavigationFailure(err) && isError(err)) {
|
|
142
|
+
if (this.errorCbs.length) {
|
|
143
|
+
this.errorCbs.forEach(cb => {
|
|
144
|
+
cb(err)
|
|
145
|
+
})
|
|
146
|
+
} else {
|
|
147
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
148
|
+
warn(false, 'uncaught error during route navigation:')
|
|
149
|
+
}
|
|
150
|
+
console.error(err)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
onAbort && onAbort(err)
|
|
154
|
+
}
|
|
155
|
+
const lastRouteIndex = route.matched.length - 1
|
|
156
|
+
const lastCurrentIndex = current.matched.length - 1
|
|
157
|
+
if (
|
|
158
|
+
isSameRoute(route, current) &&
|
|
159
|
+
// in the case the route map has been dynamically appended to
|
|
160
|
+
lastRouteIndex === lastCurrentIndex &&
|
|
161
|
+
route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
|
|
162
|
+
) {
|
|
163
|
+
this.ensureURL()
|
|
164
|
+
if (route.hash) {
|
|
165
|
+
handleScroll(this.router, current, route, false)
|
|
166
|
+
}
|
|
167
|
+
return abort(createNavigationDuplicatedError(current, route))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { updated, deactivated, activated } = resolveQueue(
|
|
171
|
+
this.current.matched,
|
|
172
|
+
route.matched
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
const queue: Array<?NavigationGuard> = [].concat(
|
|
176
|
+
// in-component leave guards
|
|
177
|
+
extractLeaveGuards(deactivated),
|
|
178
|
+
// global before hooks
|
|
179
|
+
this.router.beforeHooks,
|
|
180
|
+
// in-component update hooks
|
|
181
|
+
extractUpdateHooks(updated),
|
|
182
|
+
// in-config enter guards
|
|
183
|
+
activated.map(m => m.beforeEnter),
|
|
184
|
+
// async components
|
|
185
|
+
resolveAsyncComponents(activated)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const iterator = (hook: NavigationGuard, next) => {
|
|
189
|
+
if (this.pending !== route) {
|
|
190
|
+
return abort(createNavigationCancelledError(current, route))
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
hook(route, current, (to: any) => {
|
|
194
|
+
if (to === false) {
|
|
195
|
+
// next(false) -> abort navigation, ensure current URL
|
|
196
|
+
this.ensureURL(true)
|
|
197
|
+
abort(createNavigationAbortedError(current, route))
|
|
198
|
+
} else if (isError(to)) {
|
|
199
|
+
this.ensureURL(true)
|
|
200
|
+
abort(to)
|
|
201
|
+
} else if (
|
|
202
|
+
typeof to === 'string' ||
|
|
203
|
+
(typeof to === 'object' &&
|
|
204
|
+
(typeof to.path === 'string' || typeof to.name === 'string'))
|
|
205
|
+
) {
|
|
206
|
+
// next('/') or next({ path: '/' }) -> redirect
|
|
207
|
+
abort(createNavigationRedirectedError(current, route))
|
|
208
|
+
if (typeof to === 'object' && to.replace) {
|
|
209
|
+
this.replace(to)
|
|
210
|
+
} else {
|
|
211
|
+
this.push(to)
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// confirm transition and pass on the value
|
|
215
|
+
next(to)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
} catch (e) {
|
|
219
|
+
abort(e)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
runQueue(queue, iterator, () => {
|
|
224
|
+
// wait until async components are resolved before
|
|
225
|
+
// extracting in-component enter guards
|
|
226
|
+
const enterGuards = extractEnterGuards(activated)
|
|
227
|
+
const queue = enterGuards.concat(this.router.resolveHooks)
|
|
228
|
+
runQueue(queue, iterator, () => {
|
|
229
|
+
if (this.pending !== route) {
|
|
230
|
+
return abort(createNavigationCancelledError(current, route))
|
|
231
|
+
}
|
|
232
|
+
this.pending = null
|
|
233
|
+
onComplete(route)
|
|
234
|
+
if (this.router.app) {
|
|
235
|
+
this.router.app.$nextTick(() => {
|
|
236
|
+
handleRouteEntered(route)
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
updateRoute (route: Route) {
|
|
244
|
+
this.current = route
|
|
245
|
+
this.cb && this.cb(route)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
setupListeners () {
|
|
249
|
+
// Default implementation is empty
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
teardown () {
|
|
253
|
+
// clean up event listeners
|
|
254
|
+
this.listeners.forEach(cleanupListener => {
|
|
255
|
+
cleanupListener()
|
|
256
|
+
})
|
|
257
|
+
this.listeners = []
|
|
258
|
+
|
|
259
|
+
// reset current history route
|
|
260
|
+
this.current = START
|
|
261
|
+
this.pending = null
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function normalizeBase (base: ?string): string {
|
|
266
|
+
if (!base) {
|
|
267
|
+
if (inBrowser) {
|
|
268
|
+
// respect <base> tag
|
|
269
|
+
const baseEl = document.querySelector('base')
|
|
270
|
+
base = (baseEl && baseEl.getAttribute('href')) || '/'
|
|
271
|
+
// strip full URL origin
|
|
272
|
+
base = base.replace(/^https?:\/\/[^\/]+/, '')
|
|
273
|
+
} else {
|
|
274
|
+
base = '/'
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// make sure there's the starting slash
|
|
278
|
+
if (base.charAt(0) !== '/') {
|
|
279
|
+
base = '/' + base
|
|
280
|
+
}
|
|
281
|
+
// remove trailing slash
|
|
282
|
+
return base.replace(/\/$/, '')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveQueue (
|
|
286
|
+
current: Array<RouteRecord>,
|
|
287
|
+
next: Array<RouteRecord>
|
|
288
|
+
): {
|
|
289
|
+
updated: Array<RouteRecord>,
|
|
290
|
+
activated: Array<RouteRecord>,
|
|
291
|
+
deactivated: Array<RouteRecord>
|
|
292
|
+
} {
|
|
293
|
+
let i
|
|
294
|
+
const max = Math.max(current.length, next.length)
|
|
295
|
+
for (i = 0; i < max; i++) {
|
|
296
|
+
if (current[i] !== next[i]) {
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
updated: next.slice(0, i),
|
|
302
|
+
activated: next.slice(i),
|
|
303
|
+
deactivated: current.slice(i)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function extractGuards (
|
|
308
|
+
records: Array<RouteRecord>,
|
|
309
|
+
name: string,
|
|
310
|
+
bind: Function,
|
|
311
|
+
reverse?: boolean
|
|
312
|
+
): Array<?Function> {
|
|
313
|
+
const guards = flatMapComponents(records, (def, instance, match, key) => {
|
|
314
|
+
const guard = extractGuard(def, name)
|
|
315
|
+
if (guard) {
|
|
316
|
+
return Array.isArray(guard)
|
|
317
|
+
? guard.map(guard => bind(guard, instance, match, key))
|
|
318
|
+
: bind(guard, instance, match, key)
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
return flatten(reverse ? guards.reverse() : guards)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function extractGuard (
|
|
325
|
+
def: Object | Function,
|
|
326
|
+
key: string
|
|
327
|
+
): NavigationGuard | Array<NavigationGuard> {
|
|
328
|
+
if (typeof def !== 'function') {
|
|
329
|
+
// extend now so that global mixins are applied.
|
|
330
|
+
def = _Kdu.extend(def)
|
|
331
|
+
}
|
|
332
|
+
return def.options[key]
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
|
|
336
|
+
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
|
|
340
|
+
return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function bindGuard (guard: NavigationGuard, instance: ?_Kdu): ?NavigationGuard {
|
|
344
|
+
if (instance) {
|
|
345
|
+
return function boundRouteGuard () {
|
|
346
|
+
return guard.apply(instance, arguments)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function extractEnterGuards (
|
|
352
|
+
activated: Array<RouteRecord>
|
|
353
|
+
): Array<?Function> {
|
|
354
|
+
return extractGuards(
|
|
355
|
+
activated,
|
|
356
|
+
'beforeRouteEnter',
|
|
357
|
+
(guard, _, match, key) => {
|
|
358
|
+
return bindEnterGuard(guard, match, key)
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function bindEnterGuard (
|
|
364
|
+
guard: NavigationGuard,
|
|
365
|
+
match: RouteRecord,
|
|
366
|
+
key: string
|
|
367
|
+
): NavigationGuard {
|
|
368
|
+
return function routeEnterGuard (to, from, next) {
|
|
369
|
+
return guard(to, from, cb => {
|
|
370
|
+
if (typeof cb === 'function') {
|
|
371
|
+
if (!match.enteredCbs[key]) {
|
|
372
|
+
match.enteredCbs[key] = []
|
|
373
|
+
}
|
|
374
|
+
match.enteredCbs[key].push(cb)
|
|
375
|
+
}
|
|
376
|
+
next(cb)
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
}
|