rari 0.1.3
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/chunk-BLXvPPr8.js +30 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +193 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/platform-CvSUcmnc.js +102 -0
- package/dist/platform-DIsErRFA.js +3 -0
- package/dist/railway-XPhB0Ls4.js +216 -0
- package/dist/render-BtA14L2P.js +218 -0
- package/dist/runtime-client-BEWMJWMx.d.ts +283 -0
- package/dist/runtime-client-CC4YQweh.js +707 -0
- package/dist/server-BvGV8m79.js +7084 -0
- package/dist/server-MY0-nRif.d.ts +69 -0
- package/dist/server-build-Cp6_RdeA.js +3 -0
- package/dist/server-build-DaBgiV55.js +618 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +5 -0
- package/package.json +96 -0
- package/src/cli.ts +236 -0
- package/src/client.ts +61 -0
- package/src/deployment/railway.ts +244 -0
- package/src/deployment/render.ts +240 -0
- package/src/index.ts +1 -0
- package/src/platform.ts +163 -0
- package/src/router/file-routes.ts +453 -0
- package/src/router/index.ts +40 -0
- package/src/router/navigation.tsx +535 -0
- package/src/router/router.tsx +504 -0
- package/src/router/types.ts +168 -0
- package/src/router/utils.ts +473 -0
- package/src/router/vite-plugin.ts +324 -0
- package/src/runtime-client.ts +415 -0
- package/src/server.ts +79 -0
- package/src/vite/index.ts +1920 -0
- package/src/vite/server-build.ts +951 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FileRouteInfo,
|
|
3
|
+
Route,
|
|
4
|
+
RouteMatch,
|
|
5
|
+
RouteParams,
|
|
6
|
+
SearchParams,
|
|
7
|
+
} from './types'
|
|
8
|
+
|
|
9
|
+
export function filePathToRoutePath(filePath: string): string {
|
|
10
|
+
let routePath = filePath
|
|
11
|
+
.replace(/^pages\//, '')
|
|
12
|
+
.replace(/\.(tsx?|jsx?)$/, '')
|
|
13
|
+
|
|
14
|
+
if (routePath === 'index') {
|
|
15
|
+
routePath = '/'
|
|
16
|
+
}
|
|
17
|
+
else if (routePath.endsWith('/index')) {
|
|
18
|
+
routePath = routePath.replace(/\/index$/, '')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
routePath = routePath.replace(/\[([^\]]+)\]/g, (match, param) => {
|
|
22
|
+
if (param.startsWith('...')) {
|
|
23
|
+
return `:${param.slice(3)}*`
|
|
24
|
+
}
|
|
25
|
+
return `:${param}`
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (!routePath.startsWith('/')) {
|
|
29
|
+
routePath = `/${routePath}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return routePath
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function extractParamNames(routePath: string): string[] {
|
|
36
|
+
const params: string[] = []
|
|
37
|
+
const paramRegex = /:([^/]+)/g
|
|
38
|
+
|
|
39
|
+
const matches = Array.from(routePath.matchAll(paramRegex))
|
|
40
|
+
|
|
41
|
+
for (const match of matches) {
|
|
42
|
+
let paramName = match[1]
|
|
43
|
+
|
|
44
|
+
if (paramName.endsWith('*')) {
|
|
45
|
+
paramName = paramName.slice(0, -1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
params.push(paramName)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return params
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isDynamicRoute(routePath: string): boolean {
|
|
55
|
+
return routePath.includes(':')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function routePathToRegex(routePath: string): RegExp {
|
|
59
|
+
let pattern = routePath.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
60
|
+
|
|
61
|
+
pattern = pattern.replace(/:([^/]+)/g, (match, paramName) => {
|
|
62
|
+
if (paramName.endsWith('*')) {
|
|
63
|
+
return '(.*)'
|
|
64
|
+
}
|
|
65
|
+
return '([^/]+)'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
pattern = `^${pattern}$`
|
|
69
|
+
|
|
70
|
+
return new RegExp(pattern)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function matchRoute(pathname: string, route: Route): RouteMatch | null {
|
|
74
|
+
const regex = routePathToRegex(route.path)
|
|
75
|
+
const match = pathname.match(regex)
|
|
76
|
+
|
|
77
|
+
if (!match) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const params: RouteParams = {}
|
|
82
|
+
const paramNames = route.paramNames || []
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
85
|
+
const paramName = paramNames[i]
|
|
86
|
+
const paramValue = match[i + 1]
|
|
87
|
+
|
|
88
|
+
if (paramValue !== undefined) {
|
|
89
|
+
if (route.path.includes(`:${paramName}*`)) {
|
|
90
|
+
const segments = paramValue.split('/').filter(Boolean)
|
|
91
|
+
params[paramName] = segments
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
params[paramName] = decodeURIComponent(paramValue)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
route,
|
|
101
|
+
params,
|
|
102
|
+
searchParams: {},
|
|
103
|
+
pathname,
|
|
104
|
+
search: '',
|
|
105
|
+
hash: '',
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function findMatchingRoute(
|
|
110
|
+
pathname: string,
|
|
111
|
+
routes: Route[],
|
|
112
|
+
): RouteMatch | null {
|
|
113
|
+
const routeHierarchy = buildRouteHierarchy(routes)
|
|
114
|
+
|
|
115
|
+
const match = findNestedRouteMatch(pathname, routeHierarchy)
|
|
116
|
+
|
|
117
|
+
if (!match) {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
route: match.route,
|
|
123
|
+
params: match.params,
|
|
124
|
+
searchParams: {},
|
|
125
|
+
pathname,
|
|
126
|
+
search: '',
|
|
127
|
+
hash: '',
|
|
128
|
+
parentMatches: match.parentMatches,
|
|
129
|
+
layouts: match.layouts,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface NestedRouteMatch {
|
|
134
|
+
route: Route
|
|
135
|
+
params: RouteParams
|
|
136
|
+
parentMatches: RouteMatch[]
|
|
137
|
+
layouts: Route[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildRouteHierarchy(routes: Route[]): Map<string, Route> {
|
|
141
|
+
const routeMap = new Map<string, Route>()
|
|
142
|
+
|
|
143
|
+
for (const route of routes) {
|
|
144
|
+
routeMap.set(route.path, route)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return routeMap
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findNestedRouteMatch(
|
|
151
|
+
pathname: string,
|
|
152
|
+
routeHierarchy: Map<string, Route>,
|
|
153
|
+
): NestedRouteMatch | null {
|
|
154
|
+
let bestMatch: NestedRouteMatch | null = null
|
|
155
|
+
let highestPriority = -Infinity
|
|
156
|
+
|
|
157
|
+
for (const route of routeHierarchy.values()) {
|
|
158
|
+
const match = matchRouteWithHierarchy(pathname, route, routeHierarchy)
|
|
159
|
+
|
|
160
|
+
if (match) {
|
|
161
|
+
const priority = getRoutePriority(route)
|
|
162
|
+
if (priority > highestPriority) {
|
|
163
|
+
bestMatch = match
|
|
164
|
+
highestPriority = priority
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return bestMatch
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function matchRouteWithHierarchy(
|
|
173
|
+
pathname: string,
|
|
174
|
+
route: Route,
|
|
175
|
+
routeHierarchy: Map<string, Route>,
|
|
176
|
+
): NestedRouteMatch | null {
|
|
177
|
+
const routeMatch = matchRoute(pathname, route)
|
|
178
|
+
|
|
179
|
+
if (!routeMatch) {
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const parentMatches: RouteMatch[] = []
|
|
184
|
+
const layouts: Route[] = []
|
|
185
|
+
|
|
186
|
+
let currentRoute = route.parent
|
|
187
|
+
while (currentRoute) {
|
|
188
|
+
parentMatches.unshift({
|
|
189
|
+
route: currentRoute,
|
|
190
|
+
params: {},
|
|
191
|
+
searchParams: {},
|
|
192
|
+
pathname: currentRoute.path,
|
|
193
|
+
search: '',
|
|
194
|
+
hash: '',
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const layoutRoute = findLayoutForRoute(currentRoute, routeHierarchy)
|
|
198
|
+
if (layoutRoute) {
|
|
199
|
+
layouts.unshift(layoutRoute)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
currentRoute = currentRoute.parent
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const matchedRouteLayout = findLayoutForRoute(route, routeHierarchy)
|
|
206
|
+
if (matchedRouteLayout) {
|
|
207
|
+
layouts.push(matchedRouteLayout)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
route,
|
|
212
|
+
params: routeMatch.params,
|
|
213
|
+
parentMatches,
|
|
214
|
+
layouts,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function findLayoutForRoute(route: Route, routeHierarchy: Map<string, Route>): Route | null {
|
|
219
|
+
const routeDir = route.filePath.split('/').slice(0, -1).join('/')
|
|
220
|
+
const possibleLayoutPaths = [
|
|
221
|
+
`${routeDir}/layout.tsx`,
|
|
222
|
+
`${routeDir}/layout.jsx`,
|
|
223
|
+
`${routeDir}/_layout.tsx`,
|
|
224
|
+
`${routeDir}/_layout.jsx`,
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
for (const layoutPath of possibleLayoutPaths) {
|
|
228
|
+
for (const candidateRoute of routeHierarchy.values()) {
|
|
229
|
+
if (candidateRoute.filePath === layoutPath && candidateRoute.isLayout) {
|
|
230
|
+
return candidateRoute
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function parseSearchParams(search: string): SearchParams {
|
|
239
|
+
const params: SearchParams = {}
|
|
240
|
+
|
|
241
|
+
if (!search || search === '?') {
|
|
242
|
+
return params
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const searchParams = new URLSearchParams(
|
|
246
|
+
search.startsWith('?') ? search.slice(1) : search,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
for (const [key, value] of searchParams.entries()) {
|
|
250
|
+
if (params[key]) {
|
|
251
|
+
if (Array.isArray(params[key])) {
|
|
252
|
+
(params[key] as string[]).push(value)
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
params[key] = [params[key] as string, value]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
params[key] = value
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return params
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function buildSearchString(params: SearchParams): string {
|
|
267
|
+
const searchParams = new URLSearchParams()
|
|
268
|
+
|
|
269
|
+
for (const [key, value] of Object.entries(params)) {
|
|
270
|
+
if (Array.isArray(value)) {
|
|
271
|
+
value.forEach(v => searchParams.append(key, v))
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
searchParams.set(key, value)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const search = searchParams.toString()
|
|
279
|
+
return search ? `?${search}` : ''
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function parseUrl(url: string): {
|
|
283
|
+
pathname: string
|
|
284
|
+
search: string
|
|
285
|
+
hash: string
|
|
286
|
+
searchParams: SearchParams
|
|
287
|
+
} {
|
|
288
|
+
try {
|
|
289
|
+
const parsed = new URL(url, 'http://localhost')
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
pathname: parsed.pathname,
|
|
293
|
+
search: parsed.search,
|
|
294
|
+
hash: parsed.hash,
|
|
295
|
+
searchParams: parseSearchParams(parsed.search),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
const [pathname, rest] = url.split('?', 2)
|
|
300
|
+
const [search, hash] = rest ? rest.split('#', 2) : ['', '']
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
pathname: pathname || '/',
|
|
304
|
+
search: search ? `?${search}` : '',
|
|
305
|
+
hash: hash ? `#${hash}` : '',
|
|
306
|
+
searchParams: parseSearchParams(search || ''),
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function buildUrl(
|
|
312
|
+
pathname: string,
|
|
313
|
+
searchParams?: SearchParams,
|
|
314
|
+
hash?: string,
|
|
315
|
+
): string {
|
|
316
|
+
let url = pathname
|
|
317
|
+
|
|
318
|
+
if (searchParams) {
|
|
319
|
+
const search = buildSearchString(searchParams)
|
|
320
|
+
if (search) {
|
|
321
|
+
url += search
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (hash) {
|
|
326
|
+
url += hash.startsWith('#') ? hash : `#${hash}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return url
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function analyzeFilePath(filePath: string): FileRouteInfo {
|
|
333
|
+
const routePath = filePathToRoutePath(filePath)
|
|
334
|
+
const isDynamic = isDynamicRoute(routePath)
|
|
335
|
+
const paramNames = extractParamNames(routePath)
|
|
336
|
+
|
|
337
|
+
const fileName = filePath.split('/').pop() || ''
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
filePath,
|
|
341
|
+
routePath,
|
|
342
|
+
isDynamic,
|
|
343
|
+
paramNames,
|
|
344
|
+
isIndex:
|
|
345
|
+
fileName === 'index.tsx'
|
|
346
|
+
|| fileName === 'index.jsx'
|
|
347
|
+
|| fileName === 'index.ts'
|
|
348
|
+
|| fileName === 'index.js'
|
|
349
|
+
|| filePath.endsWith('/index.tsx')
|
|
350
|
+
|| filePath.endsWith('/index.jsx')
|
|
351
|
+
|| filePath === 'pages/index.tsx'
|
|
352
|
+
|| filePath === 'pages/index.jsx',
|
|
353
|
+
isLayout:
|
|
354
|
+
fileName === 'layout.tsx'
|
|
355
|
+
|| fileName === 'layout.jsx'
|
|
356
|
+
|| fileName === '_layout.tsx'
|
|
357
|
+
|| fileName === '_layout.jsx'
|
|
358
|
+
|| filePath.includes('/layout.')
|
|
359
|
+
|| filePath.includes('/_layout.'),
|
|
360
|
+
isNotFound: filePath.includes('404.') || filePath.includes('_error.'),
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function sortRoutesBySpecificity(routes: Route[]): Route[] {
|
|
365
|
+
return [...routes].sort((a, b) => {
|
|
366
|
+
if (!a.isDynamic && b.isDynamic)
|
|
367
|
+
return -1
|
|
368
|
+
if (a.isDynamic && !b.isDynamic)
|
|
369
|
+
return 1
|
|
370
|
+
|
|
371
|
+
const aSegments = a.path.split('/').length
|
|
372
|
+
const bSegments = b.path.split('/').length
|
|
373
|
+
|
|
374
|
+
if (aSegments !== bSegments) {
|
|
375
|
+
return aSegments - bSegments
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const aParamCount = a.paramNames?.length || 0
|
|
379
|
+
const bParamCount = b.paramNames?.length || 0
|
|
380
|
+
|
|
381
|
+
if (aParamCount !== bParamCount) {
|
|
382
|
+
return aParamCount - bParamCount
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const aHasCatchAll = a.path.includes('*')
|
|
386
|
+
const bHasCatchAll = b.path.includes('*')
|
|
387
|
+
|
|
388
|
+
if (aHasCatchAll && !bHasCatchAll)
|
|
389
|
+
return 1
|
|
390
|
+
if (!aHasCatchAll && bHasCatchAll)
|
|
391
|
+
return -1
|
|
392
|
+
|
|
393
|
+
return 0
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function routePathsEqual(a: string, b: string): boolean {
|
|
398
|
+
return a === b
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function isPathActive(
|
|
402
|
+
pathname: string,
|
|
403
|
+
routePath: string,
|
|
404
|
+
exact: boolean = false,
|
|
405
|
+
): boolean {
|
|
406
|
+
if (exact) {
|
|
407
|
+
return pathname === routePath
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (routePath === '/') {
|
|
411
|
+
return pathname === '/'
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return pathname === routePath || pathname.startsWith(`${routePath}/`)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function normalizePathname(pathname: string): string {
|
|
418
|
+
if (pathname === '/' || pathname === '') {
|
|
419
|
+
return '/'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return pathname.replace(/\/+$/, '')
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function joinPaths(...segments: string[]): string {
|
|
426
|
+
return (
|
|
427
|
+
segments
|
|
428
|
+
.filter(Boolean)
|
|
429
|
+
.join('/')
|
|
430
|
+
.replace(/\/+/g, '/')
|
|
431
|
+
.replace(/\/$/, '') || '/'
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function getParentPath(pathname: string): string {
|
|
436
|
+
const segments = pathname.split('/').filter(Boolean)
|
|
437
|
+
if (segments.length <= 1) {
|
|
438
|
+
return '/'
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return `/${segments.slice(0, -1).join('/')}`
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function getParentPaths(pathname: string): string[] {
|
|
445
|
+
const segments = pathname.split('/').filter(Boolean)
|
|
446
|
+
const parents: string[] = ['/']
|
|
447
|
+
|
|
448
|
+
for (let i = 1; i < segments.length; i++) {
|
|
449
|
+
parents.push(`/${segments.slice(0, i).join('/')}`)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return parents
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function getRoutePriority(route: Route): number {
|
|
456
|
+
let priority = 0
|
|
457
|
+
|
|
458
|
+
if (!route.isDynamic) {
|
|
459
|
+
priority += 1000
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const paramCount = route.paramNames?.length || 0
|
|
463
|
+
priority -= paramCount * 100
|
|
464
|
+
|
|
465
|
+
if (route.path.includes('*')) {
|
|
466
|
+
priority -= 500
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const segmentCount = route.path.split('/').length
|
|
470
|
+
priority += segmentCount * 10
|
|
471
|
+
|
|
472
|
+
return priority
|
|
473
|
+
}
|