kdu-router 3.1.3 → 3.4.0-beta.0
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 +9 -56
- package/dist/kdu-router.common.js +3040 -2881
- package/dist/kdu-router.esm.browser.js +3005 -2831
- package/dist/kdu-router.esm.browser.min.js +11 -6
- package/dist/kdu-router.esm.js +3038 -2879
- package/dist/kdu-router.js +3046 -2887
- package/dist/kdu-router.min.js +11 -6
- package/ketur/attributes.json +38 -0
- package/ketur/tags.json +20 -0
- package/package.json +111 -107
- package/src/components/link.js +197 -0
- package/src/components/view.js +149 -0
- package/src/create-matcher.js +200 -200
- package/src/create-route-map.js +205 -205
- package/src/history/abstract.js +68 -0
- package/src/history/base.js +400 -0
- package/src/history/hash.js +163 -0
- package/src/history/html5.js +94 -0
- package/src/index.js +277 -261
- package/src/install.js +52 -52
- package/src/util/async.js +18 -0
- package/src/util/dom.js +3 -0
- package/src/util/errors.js +85 -0
- package/src/util/location.js +69 -0
- package/src/util/misc.js +6 -0
- package/src/util/params.js +37 -0
- package/src/util/path.js +74 -0
- package/src/util/push-state.js +46 -0
- package/src/util/query.js +96 -0
- package/src/util/resolve-components.js +109 -0
- package/src/util/route.js +132 -0
- package/src/util/scroll.js +165 -0
- package/src/util/state-key.js +22 -0
- package/src/util/warn.js +14 -0
- package/types/index.d.ts +17 -16
- package/types/kdu.d.ts +22 -22
- package/types/router.d.ts +170 -145
package/src/util/dom.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const NavigationFailureType = {
|
|
2
|
+
redirected: 2,
|
|
3
|
+
aborted: 4,
|
|
4
|
+
cancelled: 8,
|
|
5
|
+
duplicated: 16
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createNavigationRedirectedError (from, to) {
|
|
9
|
+
return createRouterError(
|
|
10
|
+
from,
|
|
11
|
+
to,
|
|
12
|
+
NavigationFailureType.redirected,
|
|
13
|
+
`Redirected when going from "${from.fullPath}" to "${stringifyRoute(
|
|
14
|
+
to
|
|
15
|
+
)}" via a navigation guard.`
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createNavigationDuplicatedError (from, to) {
|
|
20
|
+
const error = createRouterError(
|
|
21
|
+
from,
|
|
22
|
+
to,
|
|
23
|
+
NavigationFailureType.duplicated,
|
|
24
|
+
`Avoided redundant navigation to current location: "${from.fullPath}".`
|
|
25
|
+
)
|
|
26
|
+
// backwards compatible with the first introduction of Errors
|
|
27
|
+
error.name = 'NavigationDuplicated'
|
|
28
|
+
return error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createNavigationCancelledError (from, to) {
|
|
32
|
+
return createRouterError(
|
|
33
|
+
from,
|
|
34
|
+
to,
|
|
35
|
+
NavigationFailureType.cancelled,
|
|
36
|
+
`Navigation cancelled from "${from.fullPath}" to "${
|
|
37
|
+
to.fullPath
|
|
38
|
+
}" with a new navigation.`
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createNavigationAbortedError (from, to) {
|
|
43
|
+
return createRouterError(
|
|
44
|
+
from,
|
|
45
|
+
to,
|
|
46
|
+
NavigationFailureType.aborted,
|
|
47
|
+
`Navigation aborted from "${from.fullPath}" to "${
|
|
48
|
+
to.fullPath
|
|
49
|
+
}" via a navigation guard.`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createRouterError (from, to, type, message) {
|
|
54
|
+
const error = new Error(message)
|
|
55
|
+
error._isRouter = true
|
|
56
|
+
error.from = from
|
|
57
|
+
error.to = to
|
|
58
|
+
error.type = type
|
|
59
|
+
|
|
60
|
+
return error
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const propertiesToLog = ['params', 'query', 'hash']
|
|
64
|
+
|
|
65
|
+
function stringifyRoute (to) {
|
|
66
|
+
if (typeof to === 'string') return to
|
|
67
|
+
if ('path' in to) return to.path
|
|
68
|
+
const location = {}
|
|
69
|
+
propertiesToLog.forEach(key => {
|
|
70
|
+
if (key in to) location[key] = to[key]
|
|
71
|
+
})
|
|
72
|
+
return JSON.stringify(location, null, 2)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isError (err) {
|
|
76
|
+
return Object.prototype.toString.call(err).indexOf('Error') > -1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isNavigationFailure (err, errorType) {
|
|
80
|
+
return (
|
|
81
|
+
isError(err) &&
|
|
82
|
+
err._isRouter &&
|
|
83
|
+
(errorType == null || err.type === errorType)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import type KduRouter from '../index'
|
|
4
|
+
import { parsePath, resolvePath } from './path'
|
|
5
|
+
import { resolveQuery } from './query'
|
|
6
|
+
import { fillParams } from './params'
|
|
7
|
+
import { warn } from './warn'
|
|
8
|
+
import { extend } from './misc'
|
|
9
|
+
|
|
10
|
+
export function normalizeLocation (
|
|
11
|
+
raw: RawLocation,
|
|
12
|
+
current: ?Route,
|
|
13
|
+
append: ?boolean,
|
|
14
|
+
router: ?KduRouter
|
|
15
|
+
): Location {
|
|
16
|
+
let next: Location = typeof raw === 'string' ? { path: raw } : raw
|
|
17
|
+
// named target
|
|
18
|
+
if (next._normalized) {
|
|
19
|
+
return next
|
|
20
|
+
} else if (next.name) {
|
|
21
|
+
next = extend({}, raw)
|
|
22
|
+
const params = next.params
|
|
23
|
+
if (params && typeof params === 'object') {
|
|
24
|
+
next.params = extend({}, params)
|
|
25
|
+
}
|
|
26
|
+
return next
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// relative params
|
|
30
|
+
if (!next.path && next.params && current) {
|
|
31
|
+
next = extend({}, next)
|
|
32
|
+
next._normalized = true
|
|
33
|
+
const params: any = extend(extend({}, current.params), next.params)
|
|
34
|
+
if (current.name) {
|
|
35
|
+
next.name = current.name
|
|
36
|
+
next.params = params
|
|
37
|
+
} else if (current.matched.length) {
|
|
38
|
+
const rawPath = current.matched[current.matched.length - 1].path
|
|
39
|
+
next.path = fillParams(rawPath, params, `path ${current.path}`)
|
|
40
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
41
|
+
warn(false, `relative params navigation requires a current route.`)
|
|
42
|
+
}
|
|
43
|
+
return next
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parsedPath = parsePath(next.path || '')
|
|
47
|
+
const basePath = (current && current.path) || '/'
|
|
48
|
+
const path = parsedPath.path
|
|
49
|
+
? resolvePath(parsedPath.path, basePath, append || next.append)
|
|
50
|
+
: basePath
|
|
51
|
+
|
|
52
|
+
const query = resolveQuery(
|
|
53
|
+
parsedPath.query,
|
|
54
|
+
next.query,
|
|
55
|
+
router && router.options.parseQuery
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
let hash = next.hash || parsedPath.hash
|
|
59
|
+
if (hash && hash.charAt(0) !== '#') {
|
|
60
|
+
hash = `#${hash}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
_normalized: true,
|
|
65
|
+
path,
|
|
66
|
+
query,
|
|
67
|
+
hash
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/util/misc.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { warn } from './warn'
|
|
4
|
+
import Regexp from 'path-to-regexp'
|
|
5
|
+
|
|
6
|
+
// $flow-disable-line
|
|
7
|
+
const regexpCompileCache: {
|
|
8
|
+
[key: string]: Function
|
|
9
|
+
} = Object.create(null)
|
|
10
|
+
|
|
11
|
+
export function fillParams (
|
|
12
|
+
path: string,
|
|
13
|
+
params: ?Object,
|
|
14
|
+
routeMsg: string
|
|
15
|
+
): string {
|
|
16
|
+
params = params || {}
|
|
17
|
+
try {
|
|
18
|
+
const filler =
|
|
19
|
+
regexpCompileCache[path] ||
|
|
20
|
+
(regexpCompileCache[path] = Regexp.compile(path))
|
|
21
|
+
|
|
22
|
+
// Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
|
|
23
|
+
// and fix #3106 so that you can work with location descriptor object having params.pathMatch equal to empty string
|
|
24
|
+
if (typeof params.pathMatch === 'string') params[0] = params.pathMatch
|
|
25
|
+
|
|
26
|
+
return filler(params, { pretty: true })
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
29
|
+
// Fix #3072 no warn if `pathMatch` is string
|
|
30
|
+
warn(typeof params.pathMatch === 'string', `missing param for ${routeMsg}: ${e.message}`)
|
|
31
|
+
}
|
|
32
|
+
return ''
|
|
33
|
+
} finally {
|
|
34
|
+
// delete the 0 if it was added
|
|
35
|
+
delete params[0]
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/util/path.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
export function resolvePath (
|
|
4
|
+
relative: string,
|
|
5
|
+
base: string,
|
|
6
|
+
append?: boolean
|
|
7
|
+
): string {
|
|
8
|
+
const firstChar = relative.charAt(0)
|
|
9
|
+
if (firstChar === '/') {
|
|
10
|
+
return relative
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (firstChar === '?' || firstChar === '#') {
|
|
14
|
+
return base + relative
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const stack = base.split('/')
|
|
18
|
+
|
|
19
|
+
// remove trailing segment if:
|
|
20
|
+
// - not appending
|
|
21
|
+
// - appending to trailing slash (last segment is empty)
|
|
22
|
+
if (!append || !stack[stack.length - 1]) {
|
|
23
|
+
stack.pop()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// resolve relative path
|
|
27
|
+
const segments = relative.replace(/^\//, '').split('/')
|
|
28
|
+
for (let i = 0; i < segments.length; i++) {
|
|
29
|
+
const segment = segments[i]
|
|
30
|
+
if (segment === '..') {
|
|
31
|
+
stack.pop()
|
|
32
|
+
} else if (segment !== '.') {
|
|
33
|
+
stack.push(segment)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ensure leading slash
|
|
38
|
+
if (stack[0] !== '') {
|
|
39
|
+
stack.unshift('')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return stack.join('/')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function parsePath (path: string): {
|
|
46
|
+
path: string;
|
|
47
|
+
query: string;
|
|
48
|
+
hash: string;
|
|
49
|
+
} {
|
|
50
|
+
let hash = ''
|
|
51
|
+
let query = ''
|
|
52
|
+
|
|
53
|
+
const hashIndex = path.indexOf('#')
|
|
54
|
+
if (hashIndex >= 0) {
|
|
55
|
+
hash = path.slice(hashIndex)
|
|
56
|
+
path = path.slice(0, hashIndex)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const queryIndex = path.indexOf('?')
|
|
60
|
+
if (queryIndex >= 0) {
|
|
61
|
+
query = path.slice(queryIndex + 1)
|
|
62
|
+
path = path.slice(0, queryIndex)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
path,
|
|
67
|
+
query,
|
|
68
|
+
hash
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function cleanPath (path: string): string {
|
|
73
|
+
return path.replace(/\/\//g, '/')
|
|
74
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { inBrowser } from './dom'
|
|
4
|
+
import { saveScrollPosition } from './scroll'
|
|
5
|
+
import { genStateKey, setStateKey, getStateKey } from './state-key'
|
|
6
|
+
import { extend } from './misc'
|
|
7
|
+
|
|
8
|
+
export const supportsPushState =
|
|
9
|
+
inBrowser &&
|
|
10
|
+
(function () {
|
|
11
|
+
const ua = window.navigator.userAgent
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
|
|
15
|
+
ua.indexOf('Mobile Safari') !== -1 &&
|
|
16
|
+
ua.indexOf('Chrome') === -1 &&
|
|
17
|
+
ua.indexOf('Windows Phone') === -1
|
|
18
|
+
) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return window.history && typeof window.history.pushState === 'function'
|
|
23
|
+
})()
|
|
24
|
+
|
|
25
|
+
export function pushState (url?: string, replace?: boolean) {
|
|
26
|
+
saveScrollPosition()
|
|
27
|
+
// try...catch the pushState call to get around Safari
|
|
28
|
+
// DOM Exception 18 where it limits to 100 pushState calls
|
|
29
|
+
const history = window.history
|
|
30
|
+
try {
|
|
31
|
+
if (replace) {
|
|
32
|
+
// preserve existing history state as it could be overriden by the user
|
|
33
|
+
const stateCopy = extend({}, history.state)
|
|
34
|
+
stateCopy.key = getStateKey()
|
|
35
|
+
history.replaceState(stateCopy, '', url)
|
|
36
|
+
} else {
|
|
37
|
+
history.pushState({ key: setStateKey(genStateKey()) }, '', url)
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
window.location[replace ? 'replace' : 'assign'](url)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function replaceState (url?: string) {
|
|
45
|
+
pushState(url, true)
|
|
46
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { warn } from './warn'
|
|
4
|
+
|
|
5
|
+
const encodeReserveRE = /[!'()*]/g
|
|
6
|
+
const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)
|
|
7
|
+
const commaRE = /%2C/g
|
|
8
|
+
|
|
9
|
+
// fixed encodeURIComponent which is more conformant to RFC3986:
|
|
10
|
+
// - escapes [!'()*]
|
|
11
|
+
// - preserve commas
|
|
12
|
+
const encode = str => encodeURIComponent(str)
|
|
13
|
+
.replace(encodeReserveRE, encodeReserveReplacer)
|
|
14
|
+
.replace(commaRE, ',')
|
|
15
|
+
|
|
16
|
+
const decode = decodeURIComponent
|
|
17
|
+
|
|
18
|
+
export function resolveQuery (
|
|
19
|
+
query: ?string,
|
|
20
|
+
extraQuery: Dictionary<string> = {},
|
|
21
|
+
_parseQuery: ?Function
|
|
22
|
+
): Dictionary<string> {
|
|
23
|
+
const parse = _parseQuery || parseQuery
|
|
24
|
+
let parsedQuery
|
|
25
|
+
try {
|
|
26
|
+
parsedQuery = parse(query || '')
|
|
27
|
+
} catch (e) {
|
|
28
|
+
process.env.NODE_ENV !== 'production' && warn(false, e.message)
|
|
29
|
+
parsedQuery = {}
|
|
30
|
+
}
|
|
31
|
+
for (const key in extraQuery) {
|
|
32
|
+
const value = extraQuery[key]
|
|
33
|
+
parsedQuery[key] = Array.isArray(value) ? value.map(v => '' + v) : '' + value
|
|
34
|
+
}
|
|
35
|
+
return parsedQuery
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseQuery (query: string): Dictionary<string> {
|
|
39
|
+
const res = {}
|
|
40
|
+
|
|
41
|
+
query = query.trim().replace(/^(\?|#|&)/, '')
|
|
42
|
+
|
|
43
|
+
if (!query) {
|
|
44
|
+
return res
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
query.split('&').forEach(param => {
|
|
48
|
+
const parts = param.replace(/\+/g, ' ').split('=')
|
|
49
|
+
const key = decode(parts.shift())
|
|
50
|
+
const val = parts.length > 0
|
|
51
|
+
? decode(parts.join('='))
|
|
52
|
+
: null
|
|
53
|
+
|
|
54
|
+
if (res[key] === undefined) {
|
|
55
|
+
res[key] = val
|
|
56
|
+
} else if (Array.isArray(res[key])) {
|
|
57
|
+
res[key].push(val)
|
|
58
|
+
} else {
|
|
59
|
+
res[key] = [res[key], val]
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return res
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function stringifyQuery (obj: Dictionary<string>): string {
|
|
67
|
+
const res = obj ? Object.keys(obj).map(key => {
|
|
68
|
+
const val = obj[key]
|
|
69
|
+
|
|
70
|
+
if (val === undefined) {
|
|
71
|
+
return ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (val === null) {
|
|
75
|
+
return encode(key)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(val)) {
|
|
79
|
+
const result = []
|
|
80
|
+
val.forEach(val2 => {
|
|
81
|
+
if (val2 === undefined) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (val2 === null) {
|
|
85
|
+
result.push(encode(key))
|
|
86
|
+
} else {
|
|
87
|
+
result.push(encode(key) + '=' + encode(val2))
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
return result.join('&')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return encode(key) + '=' + encode(val)
|
|
94
|
+
}).filter(x => x.length > 0).join('&') : null
|
|
95
|
+
return res ? `?${res}` : ''
|
|
96
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { _Kdu } from '../install'
|
|
4
|
+
import { warn } from './warn'
|
|
5
|
+
import { isError } from '../util/errors'
|
|
6
|
+
|
|
7
|
+
export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
|
|
8
|
+
return (to, from, next) => {
|
|
9
|
+
let hasAsync = false
|
|
10
|
+
let pending = 0
|
|
11
|
+
let error = null
|
|
12
|
+
|
|
13
|
+
flatMapComponents(matched, (def, _, match, key) => {
|
|
14
|
+
// if it's a function and doesn't have cid attached,
|
|
15
|
+
// assume it's an async component resolve function.
|
|
16
|
+
// we are not using Kdu's default async resolving mechanism because
|
|
17
|
+
// we want to halt the navigation until the incoming component has been
|
|
18
|
+
// resolved.
|
|
19
|
+
if (typeof def === 'function' && def.cid === undefined) {
|
|
20
|
+
hasAsync = true
|
|
21
|
+
pending++
|
|
22
|
+
|
|
23
|
+
const resolve = once(resolvedDef => {
|
|
24
|
+
if (isESModule(resolvedDef)) {
|
|
25
|
+
resolvedDef = resolvedDef.default
|
|
26
|
+
}
|
|
27
|
+
// save resolved on async factory in case it's used elsewhere
|
|
28
|
+
def.resolved = typeof resolvedDef === 'function'
|
|
29
|
+
? resolvedDef
|
|
30
|
+
: _Kdu.extend(resolvedDef)
|
|
31
|
+
match.components[key] = resolvedDef
|
|
32
|
+
pending--
|
|
33
|
+
if (pending <= 0) {
|
|
34
|
+
next()
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const reject = once(reason => {
|
|
39
|
+
const msg = `Failed to resolve async component ${key}: ${reason}`
|
|
40
|
+
process.env.NODE_ENV !== 'production' && warn(false, msg)
|
|
41
|
+
if (!error) {
|
|
42
|
+
error = isError(reason)
|
|
43
|
+
? reason
|
|
44
|
+
: new Error(msg)
|
|
45
|
+
next(error)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let res
|
|
50
|
+
try {
|
|
51
|
+
res = def(resolve, reject)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
reject(e)
|
|
54
|
+
}
|
|
55
|
+
if (res) {
|
|
56
|
+
if (typeof res.then === 'function') {
|
|
57
|
+
res.then(resolve, reject)
|
|
58
|
+
} else {
|
|
59
|
+
// new syntax in Kdu 2.3
|
|
60
|
+
const comp = res.component
|
|
61
|
+
if (comp && typeof comp.then === 'function') {
|
|
62
|
+
comp.then(resolve, reject)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!hasAsync) next()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function flatMapComponents (
|
|
74
|
+
matched: Array<RouteRecord>,
|
|
75
|
+
fn: Function
|
|
76
|
+
): Array<?Function> {
|
|
77
|
+
return flatten(matched.map(m => {
|
|
78
|
+
return Object.keys(m.components).map(key => fn(
|
|
79
|
+
m.components[key],
|
|
80
|
+
m.instances[key],
|
|
81
|
+
m, key
|
|
82
|
+
))
|
|
83
|
+
}))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function flatten (arr: Array<any>): Array<any> {
|
|
87
|
+
return Array.prototype.concat.apply([], arr)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const hasSymbol =
|
|
91
|
+
typeof Symbol === 'function' &&
|
|
92
|
+
typeof Symbol.toStringTag === 'symbol'
|
|
93
|
+
|
|
94
|
+
function isESModule (obj) {
|
|
95
|
+
return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// in Webpack 2, require.ensure now also returns a Promise
|
|
99
|
+
// so the resolve/reject functions may get called an extra time
|
|
100
|
+
// if the user uses an arrow function shorthand that happens to
|
|
101
|
+
// return that Promise.
|
|
102
|
+
function once (fn) {
|
|
103
|
+
let called = false
|
|
104
|
+
return function (...args) {
|
|
105
|
+
if (called) return
|
|
106
|
+
called = true
|
|
107
|
+
return fn.apply(this, args)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import type KduRouter from '../index'
|
|
4
|
+
import { stringifyQuery } from './query'
|
|
5
|
+
|
|
6
|
+
const trailingSlashRE = /\/?$/
|
|
7
|
+
|
|
8
|
+
export function createRoute (
|
|
9
|
+
record: ?RouteRecord,
|
|
10
|
+
location: Location,
|
|
11
|
+
redirectedFrom?: ?Location,
|
|
12
|
+
router?: KduRouter
|
|
13
|
+
): Route {
|
|
14
|
+
const stringifyQuery = router && router.options.stringifyQuery
|
|
15
|
+
|
|
16
|
+
let query: any = location.query || {}
|
|
17
|
+
try {
|
|
18
|
+
query = clone(query)
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
|
|
21
|
+
const route: Route = {
|
|
22
|
+
name: location.name || (record && record.name),
|
|
23
|
+
meta: (record && record.meta) || {},
|
|
24
|
+
path: location.path || '/',
|
|
25
|
+
hash: location.hash || '',
|
|
26
|
+
query,
|
|
27
|
+
params: location.params || {},
|
|
28
|
+
fullPath: getFullPath(location, stringifyQuery),
|
|
29
|
+
matched: record ? formatMatch(record) : []
|
|
30
|
+
}
|
|
31
|
+
if (redirectedFrom) {
|
|
32
|
+
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
|
|
33
|
+
}
|
|
34
|
+
return Object.freeze(route)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clone (value) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map(clone)
|
|
40
|
+
} else if (value && typeof value === 'object') {
|
|
41
|
+
const res = {}
|
|
42
|
+
for (const key in value) {
|
|
43
|
+
res[key] = clone(value[key])
|
|
44
|
+
}
|
|
45
|
+
return res
|
|
46
|
+
} else {
|
|
47
|
+
return value
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// the starting route that represents the initial state
|
|
52
|
+
export const START = createRoute(null, {
|
|
53
|
+
path: '/'
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
|
|
57
|
+
const res = []
|
|
58
|
+
while (record) {
|
|
59
|
+
res.unshift(record)
|
|
60
|
+
record = record.parent
|
|
61
|
+
}
|
|
62
|
+
return res
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getFullPath (
|
|
66
|
+
{ path, query = {}, hash = '' },
|
|
67
|
+
_stringifyQuery
|
|
68
|
+
): string {
|
|
69
|
+
const stringify = _stringifyQuery || stringifyQuery
|
|
70
|
+
return (path || '/') + stringify(query) + hash
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isSameRoute (a: Route, b: ?Route): boolean {
|
|
74
|
+
if (b === START) {
|
|
75
|
+
return a === b
|
|
76
|
+
} else if (!b) {
|
|
77
|
+
return false
|
|
78
|
+
} else if (a.path && b.path) {
|
|
79
|
+
return (
|
|
80
|
+
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
|
|
81
|
+
a.hash === b.hash &&
|
|
82
|
+
isObjectEqual(a.query, b.query)
|
|
83
|
+
)
|
|
84
|
+
} else if (a.name && b.name) {
|
|
85
|
+
return (
|
|
86
|
+
a.name === b.name &&
|
|
87
|
+
a.hash === b.hash &&
|
|
88
|
+
isObjectEqual(a.query, b.query) &&
|
|
89
|
+
isObjectEqual(a.params, b.params)
|
|
90
|
+
)
|
|
91
|
+
} else {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isObjectEqual (a = {}, b = {}): boolean {
|
|
97
|
+
// handle null value #1566
|
|
98
|
+
if (!a || !b) return a === b
|
|
99
|
+
const aKeys = Object.keys(a)
|
|
100
|
+
const bKeys = Object.keys(b)
|
|
101
|
+
if (aKeys.length !== bKeys.length) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
return aKeys.every(key => {
|
|
105
|
+
const aVal = a[key]
|
|
106
|
+
const bVal = b[key]
|
|
107
|
+
// check nested equality
|
|
108
|
+
if (typeof aVal === 'object' && typeof bVal === 'object') {
|
|
109
|
+
return isObjectEqual(aVal, bVal)
|
|
110
|
+
}
|
|
111
|
+
return String(aVal) === String(bVal)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isIncludedRoute (current: Route, target: Route): boolean {
|
|
116
|
+
return (
|
|
117
|
+
current.path.replace(trailingSlashRE, '/').indexOf(
|
|
118
|
+
target.path.replace(trailingSlashRE, '/')
|
|
119
|
+
) === 0 &&
|
|
120
|
+
(!target.hash || current.hash === target.hash) &&
|
|
121
|
+
queryIncludes(current.query, target.query)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
|
|
126
|
+
for (const key in target) {
|
|
127
|
+
if (!(key in current)) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return true
|
|
132
|
+
}
|