-
Notifications
You must be signed in to change notification settings - Fork 26.9k
/
router.ts
186 lines (162 loc) · 4.73 KB
/
router.ts
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
/* global window */
import React from 'react'
import Router from '../shared/lib/router/router'
import type { NextRouter } from '../shared/lib/router/router'
import { RouterContext } from '../shared/lib/router-context'
import isError from '../lib/is-error'
type SingletonRouterBase = {
router: Router | null
readyCallbacks: Array<() => any>
ready(cb: () => any): void
}
export { Router }
export type { NextRouter }
export type SingletonRouter = SingletonRouterBase & NextRouter
const singletonRouter: SingletonRouterBase = {
router: null, // holds the actual router instance
readyCallbacks: [],
ready(cb: () => void) {
if (this.router) return cb()
if (typeof window !== 'undefined') {
this.readyCallbacks.push(cb)
}
},
}
// Create public properties and methods of the router in the singletonRouter
const urlPropertyFields = [
'pathname',
'route',
'query',
'asPath',
'components',
'isFallback',
'basePath',
'locale',
'locales',
'defaultLocale',
'isReady',
'isPreview',
'isLocaleDomain',
'domainLocales',
]
const routerEvents = [
'routeChangeStart',
'beforeHistoryChange',
'routeChangeComplete',
'routeChangeError',
'hashChangeStart',
'hashChangeComplete',
] as const
export type RouterEvent = typeof routerEvents[number]
const coreMethodFields = [
'push',
'replace',
'reload',
'back',
'prefetch',
'beforePopState',
]
// Events is a static property on the router, the router doesn't have to be initialized to use it
Object.defineProperty(singletonRouter, 'events', {
get() {
return Router.events
},
})
function getRouter(): Router {
if (!singletonRouter.router) {
const message =
'No router instance found.\n' +
'You should only use "next/router" on the client side of your app.\n'
throw new Error(message)
}
return singletonRouter.router
}
urlPropertyFields.forEach((field: string) => {
// Here we need to use Object.defineProperty because we need to return
// the property assigned to the actual router
// The value might get changed as we change routes and this is the
// proper way to access it
Object.defineProperty(singletonRouter, field, {
get() {
const router = getRouter() as any
return router[field] as string
},
})
})
coreMethodFields.forEach((field: string) => {
// We don't really know the types here, so we add them later instead
;(singletonRouter as any)[field] = (...args: any[]) => {
const router = getRouter() as any
return router[field](...args)
}
})
routerEvents.forEach((event) => {
singletonRouter.ready(() => {
Router.events.on(event, (...args) => {
const eventField = `on${event.charAt(0).toUpperCase()}${event.substring(
1
)}`
const _singletonRouter = singletonRouter as any
if (_singletonRouter[eventField]) {
try {
_singletonRouter[eventField](...args)
} catch (err) {
console.error(`Error when running the Router event: ${eventField}`)
console.error(
isError(err) ? `${err.message}\n${err.stack}` : err + ''
)
}
}
})
})
})
// Export the singletonRouter and this is the public API.
export default singletonRouter as SingletonRouter
// Reexport the withRoute HOC
export { default as withRouter } from './with-router'
export function useRouter(): NextRouter {
return React.useContext(RouterContext)
}
// INTERNAL APIS
// -------------
// (do not use following exports inside the app)
/**
* Create a router and assign it as the singleton instance.
* This is used in client side when we are initializing the app.
* This should **not** be used inside the server.
* @internal
*/
export function createRouter(
...args: ConstructorParameters<typeof Router>
): Router {
singletonRouter.router = new Router(...args)
singletonRouter.readyCallbacks.forEach((cb) => cb())
singletonRouter.readyCallbacks = []
return singletonRouter.router
}
/**
* This function is used to create the `withRouter` router instance
* @internal
*/
export function makePublicRouterInstance(router: Router): NextRouter {
const scopedRouter = router as any
const instance = {} as any
for (const property of urlPropertyFields) {
if (typeof scopedRouter[property] === 'object') {
instance[property] = Object.assign(
Array.isArray(scopedRouter[property]) ? [] : {},
scopedRouter[property]
) // makes sure query is not stateful
continue
}
instance[property] = scopedRouter[property]
}
// Events is a static property on the router, the router doesn't have to be initialized to use it
instance.events = Router.events
coreMethodFields.forEach((field) => {
instance[field] = (...args: any[]) => {
return scopedRouter[field](...args)
}
})
return instance
}