-
-
Notifications
You must be signed in to change notification settings - Fork 68
/
routes.js
131 lines (122 loc) · 3.61 KB
/
routes.js
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
/* global $paramPattern */
import { lazy } from 'react'
const routeModules = import.meta.glob('$globPattern')
export default getRoutes()
function getRoutes () {
if (import.meta.env.SSR) {
return createRoutes(routeModules)
} else {
return hydrateRoutes(routeModules)
}
}
async function createRoutes(from, { param } = { param: $paramPattern }) {
// Otherwise we get a ReferenceError, but since
// this function is only ran once, there's no overhead
class Routes extends Array {
toJSON() {
return this.map((route) => {
return {
id: route.id,
path: route.path,
layout: route.layout,
getData: !!route.getData,
getMeta: !!route.getMeta,
onEnter: !!route.onEnter,
}
})
}
}
const importPaths = Object.keys(from)
const promises = []
if (Array.isArray(from)) {
for (const routeDef of from) {
promises.push(
getRouteModule(routeDef.path, routeDef.component).then(
(routeModule) => {
return {
id: routeDef.path,
path: routeDef.path ?? routeModule.path,
...routeModule,
}
},
),
)
}
} else {
// Ensure that static routes have precedence over the dynamic ones
for (const path of importPaths.sort((a, b) => (a > b ? -1 : 1))) {
promises.push(
getRouteModule(path, from[path]).then((routeModule) => {
return {
id: path,
layout: routeModule.layout,
path:
routeModule.path ??
path
// Remove /pages and .jsx extension
.slice(6, -4)
// Replace [id] with :id
.replace(param, (_, m) => `:${m}`)
// Replace '/index' with '/'
.replace(/\/index$/, '/')
// Remove trailing slashs
.replace(/(.+)\/+$/, (...m) => m[1]),
...routeModule,
}
}),
)
}
}
return new Routes(...(await Promise.all(promises)))
}
async function hydrateRoutes(fromInput) {
let from = fromInput
if (Array.isArray(from)) {
from = Object.fromEntries(from.map((route) => [route.path, route]))
}
return window.routes.map((route) => {
route.loader = memoImport(from[route.id])
route.component = lazy(() => route.loader())
return route
})
}
function getRouteModuleExports(routeModule) {
return {
// The Route component (default export)
component: routeModule.default,
// The Layout Route component
layout: routeModule.layout,
// Route-level hooks
getData: routeModule.getData,
getMeta: routeModule.getMeta,
onEnter: routeModule.onEnter,
// Other Route-level settings
streaming: routeModule.streaming,
clientOnly: routeModule.clientOnly,
serverOnly: routeModule.serverOnly,
...routeModule,
}
}
async function getRouteModule(path, routeModuleInput) {
let routeModule = routeModuleInput
// const isServer = typeof process !== 'undefined'
if (typeof routeModule === 'function') {
routeModule = await routeModule()
return getRouteModuleExports(routeModule)
}
return getRouteModuleExports(routeModule)
}
function memoImport(func) {
// Otherwise we get a ReferenceError, but since this function
// is only ran once for each route, there's no overhead
const kFuncExecuted = Symbol('kFuncExecuted')
const kFuncValue = Symbol('kFuncValue')
func[kFuncExecuted] = false
return async () => {
if (!func[kFuncExecuted]) {
func[kFuncValue] = await func()
func[kFuncExecuted] = true
}
return func[kFuncValue]
}
}