vueRouter源码分析
# vueRouter源码分析
- hash 和 history 实现路由的原理
- vue-router 中的 router-view 是如何匹配到页面组件
# Vue.mixin
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
# hash 和 history
# hash
- 类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容
- 这种
#
后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面 - 另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化
- 然后我们便可以监听 hashchange 来实现更新页面部分内容的操作
# history
- HTML5 新增的API: pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求
- 但因为没有
#
号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求 - 为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。
// 当活动历史记录条目更改时,将触发popstate事件
// 调用 history.pushState() 或 history.replaceState() 不会触发 popstate 事件。
// 只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)
window.addEventListener('popstate', () => {
// do something
})
1
2
3
4
5
6
2
3
4
5
6
# vueRouter 目录结构



# vueRouter 源码
# install.js
- 通过 _Vue 变量保存传入的 根实例,在 History 类中将会用到
- 混入 beforeCreate,给每一个组件 添加上 __routerRoot(Vue根组件对象),_router(VueRouter实例对象)
- 通过 $options.router 来判断是否值根组件,如果是根组件,就注册 history 路径变化的回调函数(用来监听URL地址变化,切换当前
$route
)
new Vue({
router, // 根组件一般这样传入 VueRouter 实例对象
render: h => h(App),
}).$mount('#app')
1
2
3
4
2
3
4
- 如果在子组件中也传入了 router 对象,将会改变 子组件下的所有组件的 router
export default {
name: 'home',
router: null, // 修改了 子组件的 router;使用到改组件的页面将会报错
data() {
return {
}
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 在 Vue 的原型上添加
$router
和$route
属性,让 子组件 通过this.$router
(this.$route
) 来访问根组件(_routerRoot)的_router || 根组件(_routerRoot)的_route
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue // 存储 Vue 实例,将会在别的地方用到
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
// RouterView 生成的 vnode 会有这个 registerRouteInstance 方法
// 其实是用来判断是否是 RouterView 节点
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
// 通过 router 判断是否是 根组件
// router 实例一般都是在 根组件 作为 options 参数添加
// 如果 在子组件中也 添加 { router: null },使用到改页面的组件将会报错
// new Vue({
// router,
// render: h => h(App),
// }).$mount('#app')
if (isDef(this.$options.router)) { // 根组件
this._routerRoot = this // 变量保存 根组件
this._router = this.$options.router // 变量保存 router
this._router.init(this) // 给 router 添加监听 路由变化的方法
Vue.util.defineReactive(this, '_route', this._router.history.current) // _route 定义响应式的 Object.defineproperty()
} else { // 子组件
// 在子组件中获取 父组件中的 根组件
// 开始创建子组件的时候,其父组件已经 执行过该代码
// 并在 子组件中添加 _routerRoot 变量 保存根组件
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
Object.defineProperty(Vue.prototype, '$router', { // 防止被修改
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', { // 防止被修改
get () { return this._routerRoot._route }
})
Vue.component('RouterView', View) // 注册 RouterView 组件
Vue.component('RouterLink', Link) // 注册 RouterLink 组件
const strats = Vue.config.optionMergeStrategies // Vue options 参数合并策略
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# index.js
- VueRouter 的构造函数
- 生成 路由匹配器:pathList(所有路径组成的数组),pathMap(path 到 routeRecord 映射),nameMap(name 到 routeRecord 映射)
// pathList:route 里的 path 组成的数组 [ '/home', '/home/chart', '/home/chart/detail' ]
// pathMap:定义的 path this.$router.push({ path: '/home' })
// 通过 path 匹配 record 记录
// nameMap: 定义的 name this.$router.push({ name: 'home' })
// 通过 name 匹配 record 记录
/** 源码 record 字段*/
const record: RouteRecord = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
components: route.components || { default: route.component }, // 命名视图组件
alias: route.alias
? typeof route.alias === 'string'
? [route.alias]
: route.alias
: [],
instances: {},
enteredCbs: {},
name, // 命名路由
parent,
matchAs,
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {},
props:
route.props == null
? {}
: route.components
? route.props
: { default: route.props }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 根据传入的 mode, 确认
history
、hash
、abstract
模式,并通过其类创建相对应对象 - init 方法 注册 history 路径变化的回调
- 提供 push、replace、go、back、forward等方法(最终都是调用 上一步生成的 history 对象来处理)
- 将用户传入的 钩子函数 添加到 对应数组中暂存
export default class VueRouter {
static install: () => void
static version: string
static isNavigationFailure: Function
static NavigationFailureType: any
static START_LOCATION: Route
app: any // 当前组件实例(一般来说是 根组件)
apps: Array<any>
ready: boolean // 未用到
readyCbs: Array<Function> // 未用到
options: RouterOptions // router 构建选项
mode: string // 路由模式
history: HashHistory | HTML5History | AbstractHistory // 监听路由变化以及提供路由操作的对象(根据模式区分)
matcher: Matcher // 根据 routes 创建映射匹配(包含根据name|path匹配路由)
fallback: boolean
beforeHooks: Array<?NavigationGuard> // 全局路由前置钩子
resolveHooks: Array<?NavigationGuard> // 需要展示的组件全部 生成完成并且已经执行了 vuerouter 新增的两个钩子函数(即页面切换完成)
afterHooks: Array<?AfterNavigationHook> // 全局路由后置钩子
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this) // 根据 routes 创建映射匹配
let mode = options.mode || 'hash'
this.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
// 生成 history 对象
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract': // 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
// 匹配路由
// 生成 route 结构
// const route: Route = {
// name: location.name || (record && record.name),
// meta: (record && record.meta) || {},
// path: location.path || '/',
// hash: location.hash || '',
// query,
// params: location.params || {},
// fullPath: getFullPath(location, stringifyQuery),
// matched: record ? formatMatch(record) : []
// }
return this.matcher.match(raw, current, redirectedFrom)
}
get currentRoute (): ?Route {
return this.history && this.history.current
}
init (app: any /* Vue component instance */) {
this.apps.push(app)
// set up app destroyed handler
// https://github.com/vuejs/vue-router/issues/2639
// 监听组件 的 销毁(destroyed) 生命周期 并执行回调
app.$once('hook:destroyed', () => {
// clean out app from this.apps array once destroyed
const index = this.apps.indexOf(app)
if (index > -1) this.apps.splice(index, 1)
// ensure we still have a main app or null if no apps
// we do not release the router so it can be reused
if (this.app === app) this.app = this.apps[0] || null
if (!this.app) this.history.teardown() // 移除监听的 popstate 事件
})
// main app previously initialized
// return as we don't need to set up new history listener
if (this.app) {
return
}
this.app = app
const history = this.history
// 注册 history 路径变化的回调函数
if (history instanceof HTML5History || history instanceof HashHistory) {
// scrollBehavior 这个功能只在支持 history.pushState 的浏览器中可用
const handleInitialScroll = routeOrError => {
const from = history.current
const expectScroll = this.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll && 'fullPath' in routeOrError) {
handleScroll(this, routeOrError, from, false)
}
}
const setupListeners = routeOrError => {
history.setupListeners()
handleInitialScroll(routeOrError)
}
history.transitionTo(
history.getCurrentLocation(),
setupListeners,
setupListeners
)
}
history.listen(route => {
// 当前 current route 变化,将会执行这个函数
this.apps.forEach(app => {
app._route = route
})
})
}
beforeEach (fn: Function): Function {
// 将 fn 添加到 beforeHooks 数组中,返回的是一个函数(用来删除添加的 fn)
return registerHook(this.beforeHooks, fn)
}
beforeResolve (fn: Function): Function {
return registerHook(this.resolveHooks, fn)
}
afterEach (fn: Function): Function {
return registerHook(this.afterHooks, fn)
}
onReady (cb: Function, errorCb?: Function) {
this.history.onReady(cb, errorCb)
}
onError (errorCb: Function) {
this.history.onError(errorCb)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.push(location, resolve, reject)
})
} else {
this.history.push(location, onComplete, onAbort)
}
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.replace(location, resolve, reject)
})
} else {
this.history.replace(location, onComplete, onAbort)
}
}
go (n: number) {
this.history.go(n)
}
back () {
this.go(-1)
}
forward () {
this.go(1)
}
// 返回目标位置或是当前路由匹配的组件数组 (是数组的定义/构造类,不是实例)。通常在服务端渲染的数据预加载时使用。
getMatchedComponents (to?: RawLocation | Route): Array<any> {
const route: any = to
? to.matched
? to
: this.resolve(to).route
: this.currentRoute
if (!route) {
return []
}
return [].concat.apply(
[],
route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
})
})
)
}
// 用来 解析目标位置
// 可以用来 根据 name 或 path 地址映射,生成跳转的 地址
// 比如:let url = this.$router.resolve({ name: 'Home' })
// window.open(url)
resolve (
to: RawLocation,
current?: Route,
append?: boolean
): {
location: Location,
route: Route,
href: string,
// for backwards compat
normalizedTo: Location,
resolved: Route
} {
current = current || this.history.current
const location = normalizeLocation(to, current, append, this)
const route = this.match(location, current)
const fullPath = route.redirectedFrom || route.fullPath
const base = this.history.base
const href = createHref(base, fullPath, this.mode)
return {
location,
route,
href,
// for backwards compat
normalizedTo: location,
resolved: route
}
}
getRoutes () {
return this.matcher.getRoutes()
}
// 一个参数: 添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。
// 两个参数:添加一条新的路由规则记录作为现有路由的子路由。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。
addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
this.matcher.addRoute(parentOrRoute, route)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
// 已废弃
addRoutes (routes: Array<RouteConfig>) {
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
}
function registerHook (list: Array<any>, fn: Function): Function {
list.push(fn)
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
function createHref (base: string, fullPath: string, mode) {
var path = mode === 'hash' ? '#' + fullPath : fullPath
return base ? cleanPath(base + '/' + path) : path
}
VueRouter.install = install
VueRouter.version = '__VERSION__'
VueRouter.isNavigationFailure = isNavigationFailure
VueRouter.NavigationFailureType = NavigationFailureType
VueRouter.START_LOCATION = START
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# create-matcher.js
- 创建路由匹配,通过 调用 create-route-map.js 提供的方法生成 路径映射
- 提供 match 方法,来通过 当前 location(
{ path: '/home' }
) 地址匹配 route
const route: Route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/',
hash: location.hash || '',
query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery),
matched: record ? formatMatch(record) : []
}
// matched 说明
const routes = [
{
path: '/data', // 路径:/data ———— matched: [ Home ]
component: Home,
children: [
{
path: 'list', // 路径:/data/list ———— matched: [ Home, List ]
component: List,
children: [
{
path: 'detail', // 路径:/data/list/detail ———— matched: [ Home, List, Detail ]
component: Detail,
}
]
},
{
path: 'charts', // 路径:/data/charts ———— matched: [ Home, Charts ]
component: Charts,
}
]
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- addRoute 可以添加新 route 记录(以及覆盖原有 route 记录)
- getRoutes 获取 所有 path -> routeRecord 记录数组
export function createMatcher (
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
// pathList:route 里得 path 组成的数组 [ '/home', '/home/chart', '/home/chart/detail' ]
// pathMap:定义的 path this.$router.push({ path: '/home' })
// nameMap: 定义的 name this.$router.push({ name: 'home' })
const { pathList, pathMap, nameMap } = createRouteMap(routes) // 路由映射
function addRoutes (routes) { // 已废弃:使用 router.addRoute() 代替
createRouteMap(routes, pathList, pathMap, nameMap)
}
function addRoute (parentOrRoute, route) {
// 一个参数: 添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。
// 两个参数:添加一条新的路由规则记录作为现有路由的子路由。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。
const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
// $flow-disable-line
createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
// add aliases of parent
if (parent) {
createRouteMap(
// $flow-disable-line route is defined if parent is
parent.alias.map(alias => ({ path: alias, children: [route] })),
pathList,
pathMap,
nameMap,
parent
)
}
}
function getRoutes () {
return pathList.map(path => pathMap[path])
}
function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {
// 传入的 可能是 { name } 形式
// 生成 { path, query, hash }
const location = normalizeLocation(raw, currentRoute, false, router)
const { name } = location
if (name) {
const record = nameMap[name]
if (!record) return _createRoute(null, location)
const paramNames = record.regex.keys
.filter(key => !key.optional)
.map(key => key.name)
if (typeof location.params !== 'object') {
location.params = {}
}
if (currentRoute && typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
location.params[key] = currentRoute.params[key]
}
}
}
location.path = fillParams(record.path, location.params, `named route "${name}"`)
return _createRoute(record, location, redirectedFrom)
} else if (location.path) {
location.params = {}
for (let i = 0; i < pathList.length; i++) {
const path = pathList[i]
const record = pathMap[path]
if (matchRoute(record.regex, location.path, location.params)) {
return _createRoute(record, location, redirectedFrom)
}
}
}
// no match
return _createRoute(null, location)
}
function redirect (
record: RouteRecord,
location: Location
): Route {
const originalRedirect = record.redirect
let redirect = typeof originalRedirect === 'function'
? originalRedirect(createRoute(record, location, null, router))
: originalRedirect
if (typeof redirect === 'string') {
redirect = { path: redirect }
}
if (!redirect || typeof redirect !== 'object') {
return _createRoute(null, location)
}
const re: Object = redirect
const { name, path } = re
let { query, hash, params } = location
query = re.hasOwnProperty('query') ? re.query : query
hash = re.hasOwnProperty('hash') ? re.hash : hash
params = re.hasOwnProperty('params') ? re.params : params
if (name) {
// resolved named direct
const targetRecord = nameMap[name]
return match({
_normalized: true,
name,
query,
hash,
params
}, undefined, location)
} else if (path) {
// 1. resolve relative redirect
// 解析相对重定向
const rawPath = resolveRecordPath(path, record)
// 2. resolve params
// 解析参数
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
// 3. rematch with existing query and hash
// 与现有查询和哈希重新匹配
return match({
_normalized: true,
path: resolvedPath,
query,
hash
}, undefined, location)
} else {
return _createRoute(null, location)
}
}
function alias (
record: RouteRecord,
location: Location,
matchAs: string
): Route {
const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
const aliasedMatch = match({
_normalized: true,
path: aliasedPath
})
if (aliasedMatch) {
const matched = aliasedMatch.matched
const aliasedRecord = matched[matched.length - 1] // 取匹配到的 最后一个
location.params = aliasedMatch.params
return _createRoute(aliasedRecord, location)
}
return _createRoute(null, location)
}
function _createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {
if (record && record.redirect) {
return redirect(record, redirectedFrom || location)
}
if (record && record.matchAs) {
return alias(record, location, record.matchAs)
}
return createRoute(record, location, redirectedFrom, router)
}
return {
match,
addRoute,
getRoutes,
addRoutes
}
}
function matchRoute (
regex: RouteRegExp,
path: string,
params: Object
): boolean {
const m = path.match(regex)
if (!m) {
return false
} else if (!params) {
return true
}
for (let i = 1, len = m.length; i < len; ++i) {
const key = regex.keys[i - 1]
if (key) {
// Fix #1994: using * with props: true generates a param named 0
params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
}
}
return true
}
function resolveRecordPath (path: string, record: RouteRecord): string {
return resolvePath(path, record.parent ? record.parent.path : '/', true)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# create-route-map.js
- 提供 createRouteMap 用来生成 pathList, pathMap, nameMap(根据传入的 routes 配置递归生成 对应映射 RouteRecord )
- 使用
path-to-regexp
插件,将 path 路径转换成 相对应 匹配的正则表达式
上次更新: 2021/08/13, 10:20:29