@esmx/router 3.0.0-rc.12
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/dist/history/abstract.d.ts +29 -0
- package/dist/history/abstract.mjs +107 -0
- package/dist/history/base.d.ts +79 -0
- package/dist/history/base.mjs +275 -0
- package/dist/history/html.d.ts +22 -0
- package/dist/history/html.mjs +181 -0
- package/dist/history/index.d.ts +7 -0
- package/dist/history/index.mjs +16 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/matcher/create-matcher.d.ts +5 -0
- package/dist/matcher/create-matcher.mjs +218 -0
- package/dist/matcher/create-matcher.spec.d.ts +1 -0
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +1 -0
- package/dist/matcher/index.mjs +1 -0
- package/dist/router.d.ts +111 -0
- package/dist/router.mjs +399 -0
- package/dist/task-pipe/index.d.ts +1 -0
- package/dist/task-pipe/index.mjs +1 -0
- package/dist/task-pipe/task.d.ts +30 -0
- package/dist/task-pipe/task.mjs +66 -0
- package/dist/utils/bom.d.ts +5 -0
- package/dist/utils/bom.mjs +10 -0
- package/dist/utils/encoding.d.ts +48 -0
- package/dist/utils/encoding.mjs +44 -0
- package/dist/utils/guards.d.ts +9 -0
- package/dist/utils/guards.mjs +12 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.mjs +27 -0
- package/dist/utils/path.d.ts +60 -0
- package/dist/utils/path.mjs +264 -0
- package/dist/utils/path.spec.d.ts +1 -0
- package/dist/utils/path.spec.mjs +30 -0
- package/dist/utils/scroll.d.ts +25 -0
- package/dist/utils/scroll.mjs +59 -0
- package/dist/utils/utils.d.ts +16 -0
- package/dist/utils/utils.mjs +11 -0
- package/dist/utils/warn.d.ts +2 -0
- package/dist/utils/warn.mjs +12 -0
- package/package.json +66 -0
- package/src/history/abstract.ts +149 -0
- package/src/history/base.ts +408 -0
- package/src/history/html.ts +231 -0
- package/src/history/index.ts +20 -0
- package/src/index.ts +3 -0
- package/src/matcher/create-matcher.spec.ts +3 -0
- package/src/matcher/create-matcher.ts +293 -0
- package/src/matcher/index.ts +1 -0
- package/src/router.ts +521 -0
- package/src/task-pipe/index.ts +1 -0
- package/src/task-pipe/task.ts +97 -0
- package/src/utils/bom.ts +14 -0
- package/src/utils/encoding.ts +153 -0
- package/src/utils/guards.ts +25 -0
- package/src/utils/index.ts +27 -0
- package/src/utils/path.spec.ts +44 -0
- package/src/utils/path.ts +397 -0
- package/src/utils/scroll.ts +120 -0
- package/src/utils/utils.ts +30 -0
- package/src/utils/warn.ts +13 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { warn } from './warn';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encoding Rules (␣ = Space)
|
|
5
|
+
* - Path: ␣ " < > # ? { }
|
|
6
|
+
* - Query: ␣ " < > # & =
|
|
7
|
+
* - Hash: ␣ " < > `
|
|
8
|
+
*
|
|
9
|
+
* On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
|
|
10
|
+
* defines some extra characters to be encoded. Most browsers do not encode them
|
|
11
|
+
* in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
|
|
12
|
+
* also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`)
|
|
13
|
+
* plus `-._~`. This extra safety should be applied to query by patching the
|
|
14
|
+
* string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
|
|
15
|
+
* should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
|
|
16
|
+
* into a `/` if directly typed in. The _backtick_ (`````) should also be
|
|
17
|
+
* encoded everywhere because some browsers like FF encode it when directly
|
|
18
|
+
* written while others don't. Safari and IE don't encode ``"<>{}``` in hash.
|
|
19
|
+
*/
|
|
20
|
+
// const EXTRA_RESERVED_RE = /[!'()*]/g
|
|
21
|
+
// const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)
|
|
22
|
+
|
|
23
|
+
const HASH_RE = /#/g; // %23
|
|
24
|
+
const AMPERSAND_RE = /&/g; // %26
|
|
25
|
+
const SLASH_RE = /\//g; // %2F
|
|
26
|
+
const EQUAL_RE = /=/g; // %3D
|
|
27
|
+
const IM_RE = /\?/g; // %3F
|
|
28
|
+
export const PLUS_RE = /\+/g; // %2B
|
|
29
|
+
/**
|
|
30
|
+
* NOTE: It's not clear to me if we should encode the + symbol in queries, it
|
|
31
|
+
* seems to be less flexible than not doing so and I can't find out the legacy
|
|
32
|
+
* systems requiring this for regular requests like text/html. In the standard,
|
|
33
|
+
* the encoding of the plus character is only mentioned for
|
|
34
|
+
* application/x-www-form-urlencoded
|
|
35
|
+
* (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
|
|
36
|
+
* leave the plus character as is in queries. To be more flexible, we allow the
|
|
37
|
+
* plus character on the query, but it can also be manually encoded by the user.
|
|
38
|
+
*
|
|
39
|
+
* Resources:
|
|
40
|
+
* - https://url.spec.whatwg.org/#urlencoded-parsing
|
|
41
|
+
* - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const ENC_BRACKET_OPEN_RE = /%5B/g; // [
|
|
45
|
+
const ENC_BRACKET_CLOSE_RE = /%5D/g; // ]
|
|
46
|
+
const ENC_CARET_RE = /%5E/g; // ^
|
|
47
|
+
const ENC_BACKTICK_RE = /%60/g; // `
|
|
48
|
+
const ENC_CURLY_OPEN_RE = /%7B/g; // {
|
|
49
|
+
const ENC_PIPE_RE = /%7C/g; // |
|
|
50
|
+
const ENC_CURLY_CLOSE_RE = /%7D/g; // }
|
|
51
|
+
const ENC_SPACE_RE = /%20/g; // }
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Encode characters that need to be encoded on the path, search and hash
|
|
55
|
+
* sections of the URL.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
* @param text - string to encode
|
|
59
|
+
* @returns encoded string
|
|
60
|
+
*/
|
|
61
|
+
function commonEncode(text: string | number): string {
|
|
62
|
+
return encodeURIComponent('' + text)
|
|
63
|
+
.replace(ENC_PIPE_RE, '|')
|
|
64
|
+
.replace(ENC_BRACKET_OPEN_RE, '[')
|
|
65
|
+
.replace(ENC_BRACKET_CLOSE_RE, ']');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Encode characters that need to be encoded on the hash section of the URL.
|
|
70
|
+
*
|
|
71
|
+
* @param text - string to encode
|
|
72
|
+
* @returns encoded string
|
|
73
|
+
*/
|
|
74
|
+
export function encodeHash(text: string): string {
|
|
75
|
+
return commonEncode(text)
|
|
76
|
+
.replace(ENC_CURLY_OPEN_RE, '{')
|
|
77
|
+
.replace(ENC_CURLY_CLOSE_RE, '}')
|
|
78
|
+
.replace(ENC_CARET_RE, '^');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Encode characters that need to be encoded query values on the query
|
|
83
|
+
* section of the URL.
|
|
84
|
+
*
|
|
85
|
+
* @param text - string to encode
|
|
86
|
+
* @returns encoded string
|
|
87
|
+
*/
|
|
88
|
+
export function encodeQueryValue(text: string | number): string {
|
|
89
|
+
return (
|
|
90
|
+
commonEncode(text)
|
|
91
|
+
// Encode the space as +, encode the + to differentiate it from the space
|
|
92
|
+
.replace(PLUS_RE, '%2B')
|
|
93
|
+
.replace(ENC_SPACE_RE, '+')
|
|
94
|
+
.replace(HASH_RE, '%23')
|
|
95
|
+
.replace(AMPERSAND_RE, '%26')
|
|
96
|
+
.replace(ENC_BACKTICK_RE, '`')
|
|
97
|
+
.replace(ENC_CURLY_OPEN_RE, '{')
|
|
98
|
+
.replace(ENC_CURLY_CLOSE_RE, '}')
|
|
99
|
+
.replace(ENC_CARET_RE, '^')
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Like `encodeQueryValue` but also encodes the `=` character.
|
|
105
|
+
*
|
|
106
|
+
* @param text - string to encode
|
|
107
|
+
*/
|
|
108
|
+
export function encodeQueryKey(text: string | number): string {
|
|
109
|
+
return encodeQueryValue(text).replace(EQUAL_RE, '%3D');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Encode characters that need to be encoded on the path section of the URL.
|
|
114
|
+
*
|
|
115
|
+
* @param text - string to encode
|
|
116
|
+
* @returns encoded string
|
|
117
|
+
*/
|
|
118
|
+
export function encodePath(text: string | number): string {
|
|
119
|
+
return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Encode characters that need to be encoded on the path section of the URL as a
|
|
124
|
+
* param. This function encodes everything {@link encodePath} does plus the
|
|
125
|
+
* slash (`/`) character. If `text` is `null` or `undefined`, returns an empty
|
|
126
|
+
* string instead.
|
|
127
|
+
*
|
|
128
|
+
* @param text - string to encode
|
|
129
|
+
* @returns encoded string
|
|
130
|
+
*/
|
|
131
|
+
export function encodeParam(text: string | number | null | undefined): string {
|
|
132
|
+
return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Decode text using `decodeURIComponent`. Returns the original text if it
|
|
137
|
+
* fails.
|
|
138
|
+
*
|
|
139
|
+
* @param text - string to decode
|
|
140
|
+
* @returns decoded string
|
|
141
|
+
*/
|
|
142
|
+
export function decode(text: string | number): string {
|
|
143
|
+
try {
|
|
144
|
+
return decodeURIComponent('' + text);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
warn(`Error decoding "${text}". Using original value`);
|
|
147
|
+
}
|
|
148
|
+
return '' + text;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function decodeQuery(text: string): string {
|
|
152
|
+
return decode(text.replace(PLUS_RE, ' '));
|
|
153
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RouteRecord } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 判断是否是同一个路由
|
|
5
|
+
*/
|
|
6
|
+
export function isSameRoute(from: RouteRecord, to: RouteRecord) {
|
|
7
|
+
return (
|
|
8
|
+
from.matched.length === to.matched.length &&
|
|
9
|
+
from.matched.every((record, i) => record === to.matched[i])
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 判断是否是全等的路由: 路径完全相同
|
|
15
|
+
*/
|
|
16
|
+
export function isEqualRoute(from: RouteRecord, to: RouteRecord) {
|
|
17
|
+
return (
|
|
18
|
+
// 这里不仅仅判断了前后的path是否一致
|
|
19
|
+
// 同时判断了匹配路由对象的个数
|
|
20
|
+
// 这是因为在首次初始化时 this.current 的值为 { path:'/',matched:[] }
|
|
21
|
+
// 假如我们打开页面同样为 / 路径时,此时如果单纯判断path那么就会造成无法渲染
|
|
22
|
+
from.fullPath === to.fullPath &&
|
|
23
|
+
from.matched.length === to.matched.length
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export {
|
|
2
|
+
regexDomain,
|
|
3
|
+
normalizePath,
|
|
4
|
+
parsePath,
|
|
5
|
+
stringifyPath,
|
|
6
|
+
normalizeLocation,
|
|
7
|
+
isPathWithProtocolOrDomain
|
|
8
|
+
} from './path';
|
|
9
|
+
export { isESModule, inBrowser } from './utils';
|
|
10
|
+
export { warn } from './warn';
|
|
11
|
+
export { isSameRoute, isEqualRoute } from './guards';
|
|
12
|
+
export {
|
|
13
|
+
computeScrollPosition,
|
|
14
|
+
scrollToPosition,
|
|
15
|
+
saveScrollPosition,
|
|
16
|
+
getSavedScrollPosition,
|
|
17
|
+
getKeepScrollPosition
|
|
18
|
+
} from './scroll';
|
|
19
|
+
export { openWindow } from './bom';
|
|
20
|
+
export {
|
|
21
|
+
encodeHash,
|
|
22
|
+
encodeParam,
|
|
23
|
+
encodePath,
|
|
24
|
+
encodeQueryKey,
|
|
25
|
+
encodeQueryValue,
|
|
26
|
+
decode
|
|
27
|
+
} from './encoding';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isPathWithProtocolOrDomain,
|
|
5
|
+
normalizeLocation,
|
|
6
|
+
normalizePath
|
|
7
|
+
} from './path';
|
|
8
|
+
|
|
9
|
+
// console.log(normalizeLocation({ path: '/' }));
|
|
10
|
+
|
|
11
|
+
// console.log(isPathWithProtocolOrDomain('/'));
|
|
12
|
+
// console.log(isPathWithProtocolOrDomain('www.a.b'));
|
|
13
|
+
// console.log(isPathWithProtocolOrDomain('a.b.cm'));
|
|
14
|
+
// console.log(isPathWithProtocolOrDomain('http://a.cn'));
|
|
15
|
+
// console.log(isPathWithProtocolOrDomain('a.b/path'));
|
|
16
|
+
|
|
17
|
+
describe('testing normalizeLocation', () => {
|
|
18
|
+
it('testing normal domain', ({ expect }) => {
|
|
19
|
+
expect(
|
|
20
|
+
normalizeLocation(
|
|
21
|
+
'http://localhost:5173/en/en/en/en/en',
|
|
22
|
+
'http://localhost:5173/en/'
|
|
23
|
+
).path
|
|
24
|
+
).toBe('/en/en/en/en');
|
|
25
|
+
});
|
|
26
|
+
it('testing path', ({ expect }) => {
|
|
27
|
+
expect(
|
|
28
|
+
normalizeLocation('/test1/test2?t=https://www-six.betafollowme.com')
|
|
29
|
+
.path
|
|
30
|
+
).toBe('/test1/test2');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('testing normalizePath', () => {
|
|
35
|
+
it('testing normal domain', ({ expect }) => {
|
|
36
|
+
expect(normalizePath('test2', 'test1')).toBe('/test1/test2');
|
|
37
|
+
});
|
|
38
|
+
it('testing path', ({ expect }) => {
|
|
39
|
+
expect(
|
|
40
|
+
normalizeLocation('/test1/test2?t=https://www-six.betafollowme.com')
|
|
41
|
+
.path
|
|
42
|
+
).toBe('/test1/test2');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import URLParse from 'url-parse';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
HistoryState,
|
|
5
|
+
Route,
|
|
6
|
+
RouterBase,
|
|
7
|
+
RouterLocation,
|
|
8
|
+
RouterRawLocation
|
|
9
|
+
} from '../types';
|
|
10
|
+
import {
|
|
11
|
+
decode,
|
|
12
|
+
decodeQuery,
|
|
13
|
+
encodeHash,
|
|
14
|
+
encodeQueryKey,
|
|
15
|
+
encodeQueryValue
|
|
16
|
+
} from './encoding';
|
|
17
|
+
import { isValidValue } from './utils';
|
|
18
|
+
import { warn } from './warn';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 判断路径是否以 http 或 https 开头 或者直接是域名开头
|
|
22
|
+
*/
|
|
23
|
+
export const regexDomain =
|
|
24
|
+
/^(?:https?:\/\/|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9](\/.*)?/i;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 判断路径是否以 scheme 协议开头
|
|
28
|
+
*/
|
|
29
|
+
export const regexScheme = /^(?:[a-z][a-z\d+.-]*:.+)/i;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 判断路径是否以 http(s) 协议开头
|
|
33
|
+
*/
|
|
34
|
+
export const regexHttpScheme = /^(http(s)?:\/\/)/;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 去除URL路径中重复的斜杠,但不改变协议部分的双斜杠。
|
|
38
|
+
*
|
|
39
|
+
* @param url 需要处理的URL字符串。
|
|
40
|
+
* @returns 处理后的URL,重复的斜杠被去除。
|
|
41
|
+
*/
|
|
42
|
+
function removeDuplicateSlashes(url: string): string {
|
|
43
|
+
// 正则表达式匹配除了://之外的连续两个或以上斜杠,并替换为一个斜杠
|
|
44
|
+
if (url.includes('://')) {
|
|
45
|
+
const [base, path] = url.split('://');
|
|
46
|
+
const result = path.replace(/\/{2,}/g, '/');
|
|
47
|
+
return `${base}://${result}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = url.replace(/\/{2,}/g, '/');
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 格式化路径 主要用于拼接嵌套路由的路径
|
|
56
|
+
* 返回的格式化后的路径如果有协议则以协议开头,如果没有协议则以/开头
|
|
57
|
+
*/
|
|
58
|
+
export function normalizePath(path: string, parentPath?: string) {
|
|
59
|
+
// 如果path以/开头 说明是绝对路径,不需要拼接路径
|
|
60
|
+
// 按 Hanson 要求,不提供绝对路径
|
|
61
|
+
// if (path.startsWith('/')) {
|
|
62
|
+
// return removeDuplicateSlashes(path);
|
|
63
|
+
// }
|
|
64
|
+
let normalizedPath = parentPath ? `${parentPath}/${path}` : `${path}`;
|
|
65
|
+
|
|
66
|
+
// 当解析的路径不是以http 或 https 协议开头时,给开头加上/
|
|
67
|
+
if (
|
|
68
|
+
!regexHttpScheme.test(normalizedPath) &&
|
|
69
|
+
!normalizedPath.startsWith('/')
|
|
70
|
+
) {
|
|
71
|
+
normalizedPath = `/${normalizedPath}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
// 只有存在父级路由才进行路由拼接
|
|
76
|
+
removeDuplicateSlashes(normalizedPath) // 将多个斜杠 / 替换为单个斜杠 /
|
|
77
|
+
.replace(/\/$/, '') || // 移除结尾的斜杠 /
|
|
78
|
+
'/' // 为空字符串时至少返回单个 /
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 路径解析方法
|
|
84
|
+
* @example 输入 https://www.google.com/test1/test2?a=1&b=2#123 输出 { pathname: '/test1/test2', query: { a: '1', b: '2' }, queryArray: { a: ['1'], b: ['2'] }, hash: '123' }
|
|
85
|
+
* 输入 /test1/test2?a=1&b=2#123 同样输出 { pathname: '/test1/test2', query: { a: '1', b: '2' }, queryArray: { a: ['1'], b: ['2'] }, hash: '123' }
|
|
86
|
+
*/
|
|
87
|
+
export function parsePath(path = ''): {
|
|
88
|
+
pathname: string;
|
|
89
|
+
query: Record<string, string>;
|
|
90
|
+
queryArray: Record<string, string[]>;
|
|
91
|
+
hash: string;
|
|
92
|
+
} {
|
|
93
|
+
path = normalizePath(path);
|
|
94
|
+
const { pathname, query, hash } = new URLParse(path || '/');
|
|
95
|
+
const queryObj = {};
|
|
96
|
+
const queryArray = {};
|
|
97
|
+
if (query.length > 0) {
|
|
98
|
+
query
|
|
99
|
+
.slice(1)
|
|
100
|
+
.split('&')
|
|
101
|
+
.forEach((item) => {
|
|
102
|
+
let [key = '', value = ''] = item.split('=');
|
|
103
|
+
key = decode(key);
|
|
104
|
+
value = decodeQuery(value);
|
|
105
|
+
if (key) {
|
|
106
|
+
queryObj[key] = value;
|
|
107
|
+
queryArray[key] = (queryArray[key] || []).concat(value);
|
|
108
|
+
}
|
|
109
|
+
// queryArray[key] = [
|
|
110
|
+
// ...(queryArray[key] || []),
|
|
111
|
+
// ...(value !== undefined ? [value] : [])
|
|
112
|
+
// ];
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
pathname,
|
|
117
|
+
query: queryObj,
|
|
118
|
+
queryArray,
|
|
119
|
+
hash: decode(hash)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 将path query hash合并为完整路径
|
|
125
|
+
* @example stringifyPath({ pathname: '/news', query: { a: '1' }, hash: '123' }) 输出 '/news?a=1#123'
|
|
126
|
+
*/
|
|
127
|
+
export function stringifyPath(
|
|
128
|
+
{
|
|
129
|
+
pathname = '',
|
|
130
|
+
query = {},
|
|
131
|
+
queryArray = {},
|
|
132
|
+
hash = ''
|
|
133
|
+
}: {
|
|
134
|
+
pathname: string;
|
|
135
|
+
/* 按 Hanson 要求加入 undefined 类型 */
|
|
136
|
+
query: Record<string, string | undefined>;
|
|
137
|
+
queryArray: Record<string, string[]>;
|
|
138
|
+
hash: string;
|
|
139
|
+
} = {
|
|
140
|
+
pathname: '',
|
|
141
|
+
query: {},
|
|
142
|
+
queryArray: {},
|
|
143
|
+
hash: ''
|
|
144
|
+
}
|
|
145
|
+
): string {
|
|
146
|
+
const queryString = Object.entries(
|
|
147
|
+
Object.assign({}, query, queryArray)
|
|
148
|
+
).reduce((acc, [key, value]) => {
|
|
149
|
+
let query = '';
|
|
150
|
+
const encodedKey = encodeQueryKey(key);
|
|
151
|
+
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
query = value.reduce((all, item) => {
|
|
154
|
+
if (!isValidValue(item)) return all;
|
|
155
|
+
const encodedValue = encodeQueryValue(item);
|
|
156
|
+
if (encodedValue) {
|
|
157
|
+
all = all
|
|
158
|
+
? `${all}&${encodedKey}=${encodedValue}`
|
|
159
|
+
: `${encodedKey}=${encodedValue}`;
|
|
160
|
+
}
|
|
161
|
+
return all;
|
|
162
|
+
}, '');
|
|
163
|
+
} else {
|
|
164
|
+
const encodedValue = encodeQueryValue(value);
|
|
165
|
+
if (isValidValue(value)) {
|
|
166
|
+
query = `${encodedKey}=${encodedValue}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (query) {
|
|
171
|
+
acc = acc ? `${acc}&${query}` : `?${query}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return acc;
|
|
175
|
+
}, '');
|
|
176
|
+
|
|
177
|
+
const hashContent = hash.startsWith('#') ? hash.replace(/^#/, '') : hash;
|
|
178
|
+
const hashString = hashContent ? `#${encodeHash(hashContent)}` : '';
|
|
179
|
+
return `${pathname}${queryString}${hashString}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 标准化 RouterLocation 字段
|
|
184
|
+
*/
|
|
185
|
+
export function normalizeLocation(
|
|
186
|
+
rawLocation: RouterRawLocation,
|
|
187
|
+
base: RouterBase = ''
|
|
188
|
+
): RouterLocation & {
|
|
189
|
+
path: string;
|
|
190
|
+
base: string;
|
|
191
|
+
queryArray: Record<string, string[]>;
|
|
192
|
+
} {
|
|
193
|
+
let pathname = '';
|
|
194
|
+
/* 按 Hanson 要求加入 undefined 类型 */
|
|
195
|
+
let query: Record<string, string | undefined> = {};
|
|
196
|
+
let queryArray: Record<string, string[]> = {};
|
|
197
|
+
let hash = '';
|
|
198
|
+
let params: Record<string, string> | undefined;
|
|
199
|
+
let state: HistoryState = {};
|
|
200
|
+
|
|
201
|
+
if (typeof rawLocation === 'object') {
|
|
202
|
+
const parsedOption = parsePath(rawLocation.path);
|
|
203
|
+
pathname = parsedOption.pathname;
|
|
204
|
+
|
|
205
|
+
// 只有在rawLocation初始传入了 query 或 queryArray 时才使用 rawLocation
|
|
206
|
+
if (rawLocation.query || rawLocation.queryArray) {
|
|
207
|
+
queryArray = rawLocation.queryArray || {};
|
|
208
|
+
query = rawLocation.query || {};
|
|
209
|
+
} else {
|
|
210
|
+
queryArray = parsedOption.queryArray;
|
|
211
|
+
query = parsedOption.query;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
hash = rawLocation.hash || parsedOption.hash;
|
|
215
|
+
|
|
216
|
+
params = rawLocation.params; // params 不使用默认值
|
|
217
|
+
state = rawLocation.state || {};
|
|
218
|
+
} else {
|
|
219
|
+
const parsedOption = parsePath(rawLocation);
|
|
220
|
+
pathname = parsedOption.pathname;
|
|
221
|
+
query = parsedOption.query;
|
|
222
|
+
queryArray = parsedOption.queryArray;
|
|
223
|
+
hash = parsedOption.hash;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const fullPath = stringifyPath({
|
|
227
|
+
pathname,
|
|
228
|
+
query,
|
|
229
|
+
queryArray,
|
|
230
|
+
hash
|
|
231
|
+
});
|
|
232
|
+
const baseString = normalizePath(
|
|
233
|
+
typeof base === 'function'
|
|
234
|
+
? base({
|
|
235
|
+
fullPath,
|
|
236
|
+
query,
|
|
237
|
+
queryArray,
|
|
238
|
+
hash
|
|
239
|
+
})
|
|
240
|
+
: base
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
let path = pathname;
|
|
244
|
+
// 如果 base 部分包含域名
|
|
245
|
+
if (regexDomain.test(baseString)) {
|
|
246
|
+
const { pathname } = new URLParse(baseString);
|
|
247
|
+
path = normalizePath(path.replace(new RegExp(`^(${pathname})`), ''));
|
|
248
|
+
}
|
|
249
|
+
path = normalizePath(path.replace(new RegExp(`^(${baseString})`), ''));
|
|
250
|
+
|
|
251
|
+
const { query: realQuery, queryArray: realQueryArray } =
|
|
252
|
+
parsePath(fullPath);
|
|
253
|
+
|
|
254
|
+
const res: RouterLocation & {
|
|
255
|
+
path: string;
|
|
256
|
+
base: string;
|
|
257
|
+
queryArray: Record<string, string[]>;
|
|
258
|
+
} = {
|
|
259
|
+
base: baseString,
|
|
260
|
+
path,
|
|
261
|
+
query: realQuery,
|
|
262
|
+
queryArray: realQueryArray,
|
|
263
|
+
hash,
|
|
264
|
+
state
|
|
265
|
+
};
|
|
266
|
+
if (params) res.params = params;
|
|
267
|
+
return res;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 判断路径是否以协议或域名开头
|
|
272
|
+
*/
|
|
273
|
+
export function isPathWithProtocolOrDomain(location: RouterRawLocation): {
|
|
274
|
+
/**
|
|
275
|
+
* 是否以协议或域名开头
|
|
276
|
+
*/
|
|
277
|
+
flag: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* 虚假的 route 信息,内部跳转时无法信任,只有外站跳转时使用
|
|
280
|
+
*/
|
|
281
|
+
route: Route;
|
|
282
|
+
} {
|
|
283
|
+
let url = '';
|
|
284
|
+
let state = {};
|
|
285
|
+
|
|
286
|
+
if (typeof location === 'string') {
|
|
287
|
+
url = location;
|
|
288
|
+
} else {
|
|
289
|
+
state = location.state || {};
|
|
290
|
+
const {
|
|
291
|
+
path,
|
|
292
|
+
query = {},
|
|
293
|
+
queryArray,
|
|
294
|
+
hash = '',
|
|
295
|
+
...nLocation
|
|
296
|
+
} = normalizeLocation(location);
|
|
297
|
+
url = stringifyPath({
|
|
298
|
+
...nLocation,
|
|
299
|
+
query,
|
|
300
|
+
queryArray,
|
|
301
|
+
pathname: path,
|
|
302
|
+
hash
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 如果以 scheme 协议开头 并且不是 http(s) 协议开头 则认为是外站跳转
|
|
307
|
+
if (regexScheme.test(url) && !regexHttpScheme.test(url)) {
|
|
308
|
+
try {
|
|
309
|
+
const {
|
|
310
|
+
hash,
|
|
311
|
+
host,
|
|
312
|
+
hostname,
|
|
313
|
+
href,
|
|
314
|
+
origin,
|
|
315
|
+
pathname,
|
|
316
|
+
port,
|
|
317
|
+
protocol,
|
|
318
|
+
search
|
|
319
|
+
} = new URL(url);
|
|
320
|
+
const route: Route = {
|
|
321
|
+
hash,
|
|
322
|
+
host,
|
|
323
|
+
hostname,
|
|
324
|
+
href,
|
|
325
|
+
origin,
|
|
326
|
+
pathname,
|
|
327
|
+
port,
|
|
328
|
+
protocol,
|
|
329
|
+
search,
|
|
330
|
+
params: {},
|
|
331
|
+
query: {},
|
|
332
|
+
queryArray: {},
|
|
333
|
+
state,
|
|
334
|
+
meta: {},
|
|
335
|
+
path: pathname,
|
|
336
|
+
fullPath: url,
|
|
337
|
+
base: '',
|
|
338
|
+
matched: []
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
flag: true,
|
|
342
|
+
route
|
|
343
|
+
};
|
|
344
|
+
} catch (error) {
|
|
345
|
+
warn(error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!/^https?:\/\//i.test(url)) {
|
|
350
|
+
url = `http://${url}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const {
|
|
354
|
+
hash,
|
|
355
|
+
host,
|
|
356
|
+
hostname,
|
|
357
|
+
href,
|
|
358
|
+
origin,
|
|
359
|
+
pathname,
|
|
360
|
+
port,
|
|
361
|
+
protocol,
|
|
362
|
+
query: search
|
|
363
|
+
} = new URLParse(url);
|
|
364
|
+
const { query = {}, queryArray } = normalizeLocation(url);
|
|
365
|
+
const route: Route = {
|
|
366
|
+
href,
|
|
367
|
+
origin,
|
|
368
|
+
host,
|
|
369
|
+
protocol,
|
|
370
|
+
hostname,
|
|
371
|
+
port,
|
|
372
|
+
pathname,
|
|
373
|
+
search,
|
|
374
|
+
hash,
|
|
375
|
+
query,
|
|
376
|
+
queryArray,
|
|
377
|
+
params: {},
|
|
378
|
+
state,
|
|
379
|
+
meta: {},
|
|
380
|
+
path: pathname,
|
|
381
|
+
fullPath: `${pathname}${search}${hash}`,
|
|
382
|
+
base: '',
|
|
383
|
+
matched: []
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
if (regexDomain.test(url)) {
|
|
387
|
+
return {
|
|
388
|
+
flag: true,
|
|
389
|
+
route
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
flag: false,
|
|
395
|
+
route
|
|
396
|
+
};
|
|
397
|
+
}
|