@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,29 @@
|
|
|
1
|
+
import type { Route, RouteRecord, RouterInstance, RouterRawLocation } from '../types';
|
|
2
|
+
import { BaseRouterHistory } from './base';
|
|
3
|
+
export declare class AbstractHistory extends BaseRouterHistory {
|
|
4
|
+
index: number;
|
|
5
|
+
stack: RouteRecord[];
|
|
6
|
+
constructor(router: RouterInstance);
|
|
7
|
+
init({ replace }?: {
|
|
8
|
+
replace?: boolean;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
setupListeners(): void;
|
|
12
|
+
isSameHost(location: RouterRawLocation, route: Route): any;
|
|
13
|
+
handleOutside(location: RouterRawLocation, replace?: boolean, isTriggerWithWindow?: boolean): boolean;
|
|
14
|
+
push(location: RouterRawLocation): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* 新开浏览器窗口的方法,在服务端会调用 push 作为替代
|
|
17
|
+
*/
|
|
18
|
+
pushWindow(location: RouterRawLocation): Promise<void>;
|
|
19
|
+
replace(location: RouterRawLocation): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
|
|
22
|
+
*/
|
|
23
|
+
replaceWindow(location: RouterRawLocation): Promise<void>;
|
|
24
|
+
jump(location: RouterRawLocation, replace?: boolean): Promise<void>;
|
|
25
|
+
private _jump;
|
|
26
|
+
go(delta: number): void;
|
|
27
|
+
forward(): void;
|
|
28
|
+
back(): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isPathWithProtocolOrDomain, normalizeLocation } from "../utils/index.mjs";
|
|
2
|
+
import { BaseRouterHistory } from "./base.mjs";
|
|
3
|
+
export class AbstractHistory extends BaseRouterHistory {
|
|
4
|
+
index;
|
|
5
|
+
stack;
|
|
6
|
+
constructor(router) {
|
|
7
|
+
super(router);
|
|
8
|
+
this.index = -1;
|
|
9
|
+
this.stack = [];
|
|
10
|
+
this.init();
|
|
11
|
+
}
|
|
12
|
+
async init({ replace } = { replace: true }) {
|
|
13
|
+
const { initUrl } = this.router.options;
|
|
14
|
+
if (initUrl !== void 0) {
|
|
15
|
+
if (replace) {
|
|
16
|
+
await this.replace(initUrl);
|
|
17
|
+
} else {
|
|
18
|
+
await this.push(initUrl);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
destroy() {
|
|
23
|
+
}
|
|
24
|
+
// 设置监听函数
|
|
25
|
+
setupListeners() {
|
|
26
|
+
}
|
|
27
|
+
// 服务端判断是否是相同域名
|
|
28
|
+
isSameHost(location, route) {
|
|
29
|
+
const rawLocation = typeof location === "string" ? { path: location } : location;
|
|
30
|
+
if (rawLocation.path === void 0) {
|
|
31
|
+
rawLocation.path = this.current.fullPath;
|
|
32
|
+
}
|
|
33
|
+
const { base } = normalizeLocation(rawLocation, this.router.base);
|
|
34
|
+
return base.includes(route.hostname);
|
|
35
|
+
}
|
|
36
|
+
// 处理外站跳转逻辑
|
|
37
|
+
handleOutside(location, replace = false, isTriggerWithWindow = false) {
|
|
38
|
+
const { flag, route } = isPathWithProtocolOrDomain(location);
|
|
39
|
+
if (!flag) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const router = this.router;
|
|
43
|
+
const { validateOutside, handleOutside } = router.options;
|
|
44
|
+
const isSameHost = this.isSameHost(location, route);
|
|
45
|
+
if (isSameHost && !(validateOutside == null ? void 0 : validateOutside({ router, location, route }))) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
handleOutside == null ? void 0 : handleOutside({
|
|
49
|
+
router,
|
|
50
|
+
route,
|
|
51
|
+
replace,
|
|
52
|
+
isTriggerWithWindow,
|
|
53
|
+
isSameHost
|
|
54
|
+
});
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// 新增路由记录跳转
|
|
58
|
+
async push(location) {
|
|
59
|
+
await this.jump(location, false);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 新开浏览器窗口的方法,在服务端会调用 push 作为替代
|
|
63
|
+
*/
|
|
64
|
+
async pushWindow(location) {
|
|
65
|
+
await this._jump(location, false, true);
|
|
66
|
+
}
|
|
67
|
+
// 替换当前路由记录跳转
|
|
68
|
+
async replace(location) {
|
|
69
|
+
await this.jump(location, true);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
|
|
73
|
+
*/
|
|
74
|
+
async replaceWindow(location) {
|
|
75
|
+
await this._jump(location, true, true);
|
|
76
|
+
}
|
|
77
|
+
// 跳转方法
|
|
78
|
+
async jump(location, replace = false) {
|
|
79
|
+
await this._jump(location, replace);
|
|
80
|
+
}
|
|
81
|
+
async _jump(location, replace = false, isTriggerWithWindow = false) {
|
|
82
|
+
if (this.handleOutside(location, replace, isTriggerWithWindow)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await this.transitionTo(location, (route) => {
|
|
86
|
+
const index = replace ? this.index : this.index + 1;
|
|
87
|
+
this.stack = this.stack.slice(0, index).concat(route);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
go(delta) {
|
|
91
|
+
const targetIndex = this.index + delta;
|
|
92
|
+
if (targetIndex < 0 || targetIndex >= this.stack.length) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const route = this.stack[targetIndex];
|
|
96
|
+
this.index = targetIndex;
|
|
97
|
+
this.updateRoute(route);
|
|
98
|
+
}
|
|
99
|
+
/* 路由历史记录前进方法 */
|
|
100
|
+
forward() {
|
|
101
|
+
this.go(1);
|
|
102
|
+
}
|
|
103
|
+
/* 路由历史记录后退方法 */
|
|
104
|
+
back() {
|
|
105
|
+
this.go(-1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Tasks } from '../task-pipe';
|
|
2
|
+
import type { RouteRecord, RouterHistory, RouterInstance, RouterRawLocation } from '../types';
|
|
3
|
+
export declare abstract class BaseRouterHistory implements RouterHistory {
|
|
4
|
+
/**
|
|
5
|
+
* 路由类实例
|
|
6
|
+
*/
|
|
7
|
+
router: RouterInstance;
|
|
8
|
+
/**
|
|
9
|
+
* 路由是否冻结
|
|
10
|
+
*/
|
|
11
|
+
get isFrozen(): any;
|
|
12
|
+
/**
|
|
13
|
+
* 匹配的当前路由
|
|
14
|
+
*/
|
|
15
|
+
current: RouteRecord;
|
|
16
|
+
constructor(router: RouterInstance);
|
|
17
|
+
/**
|
|
18
|
+
* 更新当前路由
|
|
19
|
+
*/
|
|
20
|
+
updateRoute(route: RouteRecord): void;
|
|
21
|
+
/**
|
|
22
|
+
* 解析路由
|
|
23
|
+
*/
|
|
24
|
+
resolve(location: RouterRawLocation): RouteRecord;
|
|
25
|
+
/**
|
|
26
|
+
* 核心跳转方法
|
|
27
|
+
*/
|
|
28
|
+
transitionTo(location: RouterRawLocation, onComplete?: (route: RouteRecord) => void): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* TODO 逻辑解耦,抽离到task
|
|
31
|
+
* 重定向方法
|
|
32
|
+
*/
|
|
33
|
+
redirectTo(location: RouterRawLocation, from: RouteRecord, onComplete?: (route: RouteRecord) => void): Promise<void>;
|
|
34
|
+
tasks: Tasks | null;
|
|
35
|
+
abortTask(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* 执行任务
|
|
38
|
+
* 任务分为三部分: 前置守卫(beforeEach、beforeEnter、beforeUpdate、beforeLeave)、加载路由(loadRoute)、后置守卫(afterEach)
|
|
39
|
+
* 根据触发方式不同,执行顺序分别为:
|
|
40
|
+
* 进入路由时: beforeEach -> beforeEnter -> loadRoute -> afterEach
|
|
41
|
+
* 更新路由时: beforeEach -> beforeUpdate -> afterEach
|
|
42
|
+
* 离开路由进入新路由时: beforeLeave -> beforeEach -> beforeEnter -> loadRoute -> afterEach
|
|
43
|
+
* @param from
|
|
44
|
+
* @param to
|
|
45
|
+
*/
|
|
46
|
+
runTask(from: RouteRecord, to: RouteRecord, onComplete?: (route: RouteRecord) => void): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* 新开浏览器窗口的方法,在服务端会调用 push 作为替代
|
|
49
|
+
*/
|
|
50
|
+
abstract pushWindow(location: RouterRawLocation): void;
|
|
51
|
+
/**
|
|
52
|
+
* 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
|
|
53
|
+
*/
|
|
54
|
+
abstract replaceWindow(location: RouterRawLocation): void;
|
|
55
|
+
/**
|
|
56
|
+
* 跳转方法,会创建新的历史纪录
|
|
57
|
+
*/
|
|
58
|
+
abstract push(location: RouterRawLocation): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* 跳转方法,替换当前历史记录
|
|
61
|
+
*/
|
|
62
|
+
abstract replace(location: RouterRawLocation): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* 路由移动到指定历史记录方法
|
|
65
|
+
*/
|
|
66
|
+
abstract go(delta: number): void;
|
|
67
|
+
abstract forward(): void;
|
|
68
|
+
abstract back(): void;
|
|
69
|
+
/**
|
|
70
|
+
* 初始化方法
|
|
71
|
+
*/
|
|
72
|
+
abstract init(params?: {
|
|
73
|
+
replace?: boolean;
|
|
74
|
+
}): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* 卸载方法
|
|
77
|
+
*/
|
|
78
|
+
abstract destroy(): void;
|
|
79
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { createTasks } from "../task-pipe/index.mjs";
|
|
2
|
+
import {
|
|
3
|
+
isESModule,
|
|
4
|
+
isEqualRoute,
|
|
5
|
+
isSameRoute,
|
|
6
|
+
normalizeLocation,
|
|
7
|
+
stringifyPath
|
|
8
|
+
} from "../utils/index.mjs";
|
|
9
|
+
function createRouteRecord(route = {}) {
|
|
10
|
+
return {
|
|
11
|
+
base: "",
|
|
12
|
+
path: "/",
|
|
13
|
+
fullPath: "/",
|
|
14
|
+
meta: {},
|
|
15
|
+
matched: [],
|
|
16
|
+
query: {},
|
|
17
|
+
queryArray: {},
|
|
18
|
+
params: {},
|
|
19
|
+
hash: "",
|
|
20
|
+
state: {},
|
|
21
|
+
...route
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export class BaseRouterHistory {
|
|
25
|
+
/**
|
|
26
|
+
* 路由类实例
|
|
27
|
+
*/
|
|
28
|
+
router;
|
|
29
|
+
/**
|
|
30
|
+
* 路由是否冻结
|
|
31
|
+
*/
|
|
32
|
+
get isFrozen() {
|
|
33
|
+
return this.router.isFrozen;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 匹配的当前路由
|
|
37
|
+
*/
|
|
38
|
+
current = createRouteRecord();
|
|
39
|
+
constructor(router) {
|
|
40
|
+
this.router = router;
|
|
41
|
+
Object.defineProperty(this, "router", {
|
|
42
|
+
enumerable: false
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 更新当前路由
|
|
47
|
+
*/
|
|
48
|
+
updateRoute(route) {
|
|
49
|
+
this.current = route;
|
|
50
|
+
this.router.updateRoute(route);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 解析路由
|
|
54
|
+
*/
|
|
55
|
+
resolve(location) {
|
|
56
|
+
const rawLocation = typeof location === "string" ? { path: location } : location;
|
|
57
|
+
if (rawLocation.path === void 0) {
|
|
58
|
+
rawLocation.path = this.current.fullPath;
|
|
59
|
+
}
|
|
60
|
+
const { base, ...normalizedLocation } = normalizeLocation(
|
|
61
|
+
rawLocation,
|
|
62
|
+
this.router.base
|
|
63
|
+
);
|
|
64
|
+
const matcher = this.router.matcher.match(normalizedLocation, { base });
|
|
65
|
+
if (matcher) {
|
|
66
|
+
return matcher;
|
|
67
|
+
}
|
|
68
|
+
const {
|
|
69
|
+
path = "",
|
|
70
|
+
params = {},
|
|
71
|
+
query = {},
|
|
72
|
+
queryArray = {},
|
|
73
|
+
hash = "",
|
|
74
|
+
state = {}
|
|
75
|
+
} = normalizedLocation;
|
|
76
|
+
const route = createRouteRecord({
|
|
77
|
+
base,
|
|
78
|
+
fullPath: stringifyPath({
|
|
79
|
+
pathname: path,
|
|
80
|
+
query,
|
|
81
|
+
queryArray,
|
|
82
|
+
hash
|
|
83
|
+
}),
|
|
84
|
+
path,
|
|
85
|
+
params,
|
|
86
|
+
query,
|
|
87
|
+
queryArray,
|
|
88
|
+
hash,
|
|
89
|
+
state
|
|
90
|
+
});
|
|
91
|
+
return route;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 核心跳转方法
|
|
95
|
+
*/
|
|
96
|
+
async transitionTo(location, onComplete) {
|
|
97
|
+
if (this.isFrozen) return;
|
|
98
|
+
const route = this.resolve(location);
|
|
99
|
+
this.abortTask();
|
|
100
|
+
if (isEqualRoute(this.current, route)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await this.runTask(this.current, route, onComplete);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* TODO 逻辑解耦,抽离到task
|
|
107
|
+
* 重定向方法
|
|
108
|
+
*/
|
|
109
|
+
async redirectTo(location, from, onComplete) {
|
|
110
|
+
const route = this.resolve(location);
|
|
111
|
+
this.abortTask();
|
|
112
|
+
if (isEqualRoute(this.current, route)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await this.runTask(
|
|
116
|
+
this.current,
|
|
117
|
+
{
|
|
118
|
+
...route,
|
|
119
|
+
redirectedFrom: from
|
|
120
|
+
},
|
|
121
|
+
onComplete
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
/* 当前执行的任务 */
|
|
125
|
+
tasks = null;
|
|
126
|
+
/* 取消任务 */
|
|
127
|
+
async abortTask() {
|
|
128
|
+
var _a;
|
|
129
|
+
(_a = this.tasks) == null ? void 0 : _a.abort();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 执行任务
|
|
133
|
+
* 任务分为三部分: 前置守卫(beforeEach、beforeEnter、beforeUpdate、beforeLeave)、加载路由(loadRoute)、后置守卫(afterEach)
|
|
134
|
+
* 根据触发方式不同,执行顺序分别为:
|
|
135
|
+
* 进入路由时: beforeEach -> beforeEnter -> loadRoute -> afterEach
|
|
136
|
+
* 更新路由时: beforeEach -> beforeUpdate -> afterEach
|
|
137
|
+
* 离开路由进入新路由时: beforeLeave -> beforeEach -> beforeEnter -> loadRoute -> afterEach
|
|
138
|
+
* @param from
|
|
139
|
+
* @param to
|
|
140
|
+
*/
|
|
141
|
+
async runTask(from, to, onComplete) {
|
|
142
|
+
var _a;
|
|
143
|
+
const {
|
|
144
|
+
beforeEach,
|
|
145
|
+
beforeEnter,
|
|
146
|
+
beforeUpdate,
|
|
147
|
+
beforeLeave,
|
|
148
|
+
afterEach,
|
|
149
|
+
loadRoute
|
|
150
|
+
} = getNavigationHooks(this.router, from, to);
|
|
151
|
+
const guardBeforeTasks = createTasks();
|
|
152
|
+
const guardAfterTasks = createTasks();
|
|
153
|
+
const loadRouteTasks = createTasks();
|
|
154
|
+
if (isSameRoute(from, to)) {
|
|
155
|
+
guardBeforeTasks.add([...beforeEach, ...beforeUpdate]);
|
|
156
|
+
guardAfterTasks.add(afterEach);
|
|
157
|
+
} else {
|
|
158
|
+
guardBeforeTasks.add([
|
|
159
|
+
...beforeLeave,
|
|
160
|
+
...beforeEach,
|
|
161
|
+
...beforeEnter
|
|
162
|
+
]);
|
|
163
|
+
loadRouteTasks.add(loadRoute);
|
|
164
|
+
guardAfterTasks.add(afterEach);
|
|
165
|
+
}
|
|
166
|
+
this.tasks = guardBeforeTasks;
|
|
167
|
+
await guardBeforeTasks.run({
|
|
168
|
+
cb: async (res) => {
|
|
169
|
+
var _a2;
|
|
170
|
+
switch (typeof res) {
|
|
171
|
+
case "boolean":
|
|
172
|
+
if (!res) {
|
|
173
|
+
(_a2 = this.tasks) == null ? void 0 : _a2.abort();
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
case "undefined":
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
await this.redirectTo(res, from, onComplete);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
final: async () => {
|
|
184
|
+
this.tasks = loadRouteTasks;
|
|
185
|
+
await loadRouteTasks.run();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (((_a = this.tasks) == null ? void 0 : _a.status) === "finished") {
|
|
189
|
+
this.tasks = null;
|
|
190
|
+
guardAfterTasks.run();
|
|
191
|
+
onComplete && onComplete(to);
|
|
192
|
+
this.updateRoute(to);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function getNavigationHooks(router, from, to) {
|
|
197
|
+
const beforeEach = router.guards.beforeEach.map((guard) => {
|
|
198
|
+
return () => {
|
|
199
|
+
return guard(from, to);
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
const afterEach = router.guards.afterEach.map((guard) => {
|
|
203
|
+
return () => {
|
|
204
|
+
return guard(from, to);
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
const { beforeLeave } = from.matched.reduce(
|
|
208
|
+
(acc, { beforeLeave: beforeLeave2 }) => {
|
|
209
|
+
if (beforeLeave2) {
|
|
210
|
+
acc.beforeLeave.unshift(() => {
|
|
211
|
+
return beforeLeave2(from, to);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return acc;
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
beforeLeave: []
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
const { beforeEnter, beforeUpdate } = to.matched.reduce(
|
|
221
|
+
(acc, { beforeEnter: beforeEnter2, beforeUpdate: beforeUpdate2 }) => {
|
|
222
|
+
if (beforeEnter2) {
|
|
223
|
+
acc.beforeEnter.push(() => {
|
|
224
|
+
return beforeEnter2(from, to);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (beforeUpdate2) {
|
|
228
|
+
acc.beforeUpdate.push(() => {
|
|
229
|
+
return beforeUpdate2(from, to);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return acc;
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
beforeEnter: [],
|
|
236
|
+
beforeUpdate: []
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
const loadRoute = [
|
|
240
|
+
async () => {
|
|
241
|
+
return Promise.all(
|
|
242
|
+
to.matched.reduce((acc, route) => {
|
|
243
|
+
if (!route.component && route.asyncComponent) {
|
|
244
|
+
acc.push(
|
|
245
|
+
new Promise((resolve, reject) => {
|
|
246
|
+
if (!route.component && route.asyncComponent) {
|
|
247
|
+
route.asyncComponent().then((resolved) => {
|
|
248
|
+
if (!resolved) {
|
|
249
|
+
reject(
|
|
250
|
+
new Error(
|
|
251
|
+
`Couldn't resolve component at "${route.path}". Ensure you passed a function that returns a promise.`
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
route.component = isESModule(resolved) ? resolved.default : resolved;
|
|
256
|
+
resolve();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return acc;
|
|
263
|
+
}, [])
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
];
|
|
267
|
+
return {
|
|
268
|
+
beforeEach,
|
|
269
|
+
afterEach,
|
|
270
|
+
beforeEnter,
|
|
271
|
+
beforeUpdate,
|
|
272
|
+
beforeLeave,
|
|
273
|
+
loadRoute
|
|
274
|
+
};
|
|
275
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RouterInstance, type RouterRawLocation } from '../types';
|
|
2
|
+
import { BaseRouterHistory } from './base';
|
|
3
|
+
export declare class HtmlHistory extends BaseRouterHistory {
|
|
4
|
+
constructor(router: RouterInstance);
|
|
5
|
+
getCurrentLocation(): any;
|
|
6
|
+
onPopState: (e: PopStateEvent) => void;
|
|
7
|
+
init({ replace }?: {
|
|
8
|
+
replace?: boolean;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
setupListeners(): void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
pushWindow(location: RouterRawLocation): void;
|
|
13
|
+
replaceWindow(location: RouterRawLocation): void;
|
|
14
|
+
handleOutside(location: RouterRawLocation, replace?: boolean, isTriggerWithWindow?: boolean): boolean;
|
|
15
|
+
push(location: RouterRawLocation): Promise<void>;
|
|
16
|
+
replace(location: RouterRawLocation): Promise<void>;
|
|
17
|
+
jump(location: RouterRawLocation, replace?: boolean): Promise<void>;
|
|
18
|
+
go(delta: number): void;
|
|
19
|
+
forward(): void;
|
|
20
|
+
protected timer: NodeJS.Timeout | null;
|
|
21
|
+
back(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeScrollPosition,
|
|
3
|
+
getKeepScrollPosition,
|
|
4
|
+
getSavedScrollPosition,
|
|
5
|
+
isPathWithProtocolOrDomain,
|
|
6
|
+
normalizeLocation,
|
|
7
|
+
openWindow,
|
|
8
|
+
saveScrollPosition,
|
|
9
|
+
scrollToPosition
|
|
10
|
+
} from "../utils/index.mjs";
|
|
11
|
+
import { BaseRouterHistory } from "./base.mjs";
|
|
12
|
+
export class HtmlHistory extends BaseRouterHistory {
|
|
13
|
+
constructor(router) {
|
|
14
|
+
super(router);
|
|
15
|
+
if ("scrollRestoration" in window.history) {
|
|
16
|
+
window.history.scrollRestoration = "manual";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// 获取当前地址,包括 path query hash
|
|
20
|
+
getCurrentLocation() {
|
|
21
|
+
const { href } = window.location;
|
|
22
|
+
const { state } = window.history;
|
|
23
|
+
const { path, base, ...rest } = normalizeLocation(
|
|
24
|
+
href,
|
|
25
|
+
this.router.base
|
|
26
|
+
);
|
|
27
|
+
return {
|
|
28
|
+
path: path.replace(new RegExp(`^(${base})`), ""),
|
|
29
|
+
base,
|
|
30
|
+
...rest,
|
|
31
|
+
state
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
onPopState = (e) => {
|
|
35
|
+
if (this.isFrozen) return;
|
|
36
|
+
if (this.router.checkLayerState(e.state)) return;
|
|
37
|
+
const current = Object.assign({}, this.current);
|
|
38
|
+
this.transitionTo(this.getCurrentLocation(), async (route) => {
|
|
39
|
+
const { state } = window.history;
|
|
40
|
+
saveScrollPosition(current.fullPath, computeScrollPosition());
|
|
41
|
+
setTimeout(async () => {
|
|
42
|
+
const keepScrollPosition = state.keepScrollPosition;
|
|
43
|
+
if (keepScrollPosition) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const savedPosition = getSavedScrollPosition(route.fullPath);
|
|
47
|
+
const position = await this.router.scrollBehavior(
|
|
48
|
+
current,
|
|
49
|
+
route,
|
|
50
|
+
savedPosition
|
|
51
|
+
);
|
|
52
|
+
const { nextTick } = this.router.options;
|
|
53
|
+
if (position) {
|
|
54
|
+
nextTick && await nextTick();
|
|
55
|
+
scrollToPosition(position);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
async init({ replace } = { replace: true }) {
|
|
61
|
+
const { initUrl } = this.router.options;
|
|
62
|
+
let route = this.getCurrentLocation();
|
|
63
|
+
if (initUrl !== void 0) {
|
|
64
|
+
route = this.resolve(initUrl);
|
|
65
|
+
} else {
|
|
66
|
+
const state = history.state || {};
|
|
67
|
+
route.state = {
|
|
68
|
+
...state,
|
|
69
|
+
_ancientRoute: state._ancientRoute ?? true
|
|
70
|
+
// 最古历史的标记, 在调用返回事件时如果有这个标记则直接调用没有历史记录的钩子
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (replace) {
|
|
74
|
+
await this.replace(route);
|
|
75
|
+
} else {
|
|
76
|
+
await this.push(route);
|
|
77
|
+
}
|
|
78
|
+
this.setupListeners();
|
|
79
|
+
}
|
|
80
|
+
// 设置监听函数
|
|
81
|
+
setupListeners() {
|
|
82
|
+
window.addEventListener("popstate", this.onPopState);
|
|
83
|
+
}
|
|
84
|
+
destroy() {
|
|
85
|
+
window.removeEventListener("popstate", this.onPopState);
|
|
86
|
+
}
|
|
87
|
+
pushWindow(location) {
|
|
88
|
+
if (this.isFrozen) return;
|
|
89
|
+
this.handleOutside(location, false, true);
|
|
90
|
+
}
|
|
91
|
+
replaceWindow(location) {
|
|
92
|
+
if (this.isFrozen) return;
|
|
93
|
+
this.handleOutside(location, true, true);
|
|
94
|
+
}
|
|
95
|
+
// 处理外站跳转逻辑
|
|
96
|
+
handleOutside(location, replace = false, isTriggerWithWindow = false) {
|
|
97
|
+
const { flag, route } = isPathWithProtocolOrDomain(location);
|
|
98
|
+
const router = this.router;
|
|
99
|
+
const { handleOutside, validateOutside } = router.options;
|
|
100
|
+
const isSameHost = !flag || window.location.hostname === route.hostname;
|
|
101
|
+
if (!isTriggerWithWindow) {
|
|
102
|
+
if (isSameHost && !(validateOutside == null ? void 0 : validateOutside({ router, location, route }))) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (handleOutside == null ? void 0 : handleOutside({
|
|
107
|
+
router,
|
|
108
|
+
route,
|
|
109
|
+
replace,
|
|
110
|
+
isTriggerWithWindow,
|
|
111
|
+
isSameHost
|
|
112
|
+
})) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (replace) {
|
|
116
|
+
window.location.replace(route.href);
|
|
117
|
+
} else {
|
|
118
|
+
const { hostname, href } = route;
|
|
119
|
+
openWindow(href, hostname);
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
// 新增路由记录跳转
|
|
124
|
+
async push(location) {
|
|
125
|
+
await this.jump(location, false);
|
|
126
|
+
}
|
|
127
|
+
// 替换当前路由记录跳转
|
|
128
|
+
async replace(location) {
|
|
129
|
+
await this.jump(location, true);
|
|
130
|
+
}
|
|
131
|
+
// 跳转方法
|
|
132
|
+
async jump(location, replace = false) {
|
|
133
|
+
if (this.isFrozen) return;
|
|
134
|
+
if (this.handleOutside(location, replace)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const current = Object.assign({}, this.current);
|
|
138
|
+
await this.transitionTo(location, (route) => {
|
|
139
|
+
const keepScrollPosition = getKeepScrollPosition(location);
|
|
140
|
+
if (!keepScrollPosition) {
|
|
141
|
+
saveScrollPosition(current.fullPath, computeScrollPosition());
|
|
142
|
+
scrollToPosition({ left: 0, top: 0 });
|
|
143
|
+
}
|
|
144
|
+
const state = Object.assign(
|
|
145
|
+
replace ? { ...history.state, ...route.state } : { ...route.state, _ancientRoute: false },
|
|
146
|
+
{ keepScrollPosition }
|
|
147
|
+
);
|
|
148
|
+
window.history[replace ? "replaceState" : "pushState"](
|
|
149
|
+
state,
|
|
150
|
+
"",
|
|
151
|
+
route.fullPath
|
|
152
|
+
);
|
|
153
|
+
this.router.updateLayerState(route);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
go(delta) {
|
|
157
|
+
if (this.isFrozen) return;
|
|
158
|
+
window.history.go(delta);
|
|
159
|
+
}
|
|
160
|
+
forward() {
|
|
161
|
+
if (this.isFrozen) return;
|
|
162
|
+
window.history.forward();
|
|
163
|
+
}
|
|
164
|
+
timer = null;
|
|
165
|
+
back() {
|
|
166
|
+
if (this.isFrozen) return;
|
|
167
|
+
const oldState = history.state;
|
|
168
|
+
const noBackNavigation = this.router.options.noBackNavigation;
|
|
169
|
+
if (oldState._ancientRoute === true) {
|
|
170
|
+
noBackNavigation && noBackNavigation(this.router);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
window.history.back();
|
|
174
|
+
this.timer = setTimeout(() => {
|
|
175
|
+
if (history.state === oldState) {
|
|
176
|
+
noBackNavigation && noBackNavigation(this.router);
|
|
177
|
+
}
|
|
178
|
+
this.timer = null;
|
|
179
|
+
}, 80);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RouterInstance, RouterMode } from '../types';
|
|
2
|
+
import { AbstractHistory } from './abstract';
|
|
3
|
+
import { HtmlHistory } from './html';
|
|
4
|
+
export declare function createHistory({ router, mode }: {
|
|
5
|
+
router: RouterInstance;
|
|
6
|
+
mode: RouterMode;
|
|
7
|
+
}): AbstractHistory | HtmlHistory;
|