one 1.12.8 → 1.13.0
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/cjs/cli/build.cjs +1 -0
- package/dist/cjs/cli/build.native.js +1 -0
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/createHandleRequest.cjs +18 -1
- package/dist/cjs/createHandleRequest.native.js +26 -3
- package/dist/cjs/createHandleRequest.native.js.map +1 -1
- package/dist/cjs/fork/SSRNavigationContainer.cjs +39 -9
- package/dist/cjs/fork/SSRNavigationContainer.native.js +47 -9
- package/dist/cjs/fork/SSRNavigationContainer.native.js.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.native.js +2 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/layouts/NativeTabs.cjs +49 -0
- package/dist/cjs/layouts/NativeTabs.native.js +53 -0
- package/dist/cjs/layouts/NativeTabs.native.js.map +1 -0
- package/dist/cjs/layouts/Tabs.cjs +4 -1
- package/dist/cjs/layouts/Tabs.native.js +4 -1
- package/dist/cjs/layouts/Tabs.native.js.map +1 -1
- package/dist/cjs/native-tabs.cjs +27 -0
- package/dist/cjs/native-tabs.native.js +30 -0
- package/dist/cjs/native-tabs.native.js.map +1 -0
- package/dist/cjs/render.cjs +3 -1
- package/dist/cjs/router/router.cjs +11 -3
- package/dist/cjs/router/router.native.js +15 -3
- package/dist/cjs/router/router.native.js.map +1 -1
- package/dist/cjs/router/useViteRoutes.cjs +5 -1
- package/dist/cjs/router/useViteRoutes.native.js +21 -1
- package/dist/cjs/router/useViteRoutes.native.js.map +1 -1
- package/dist/cjs/serve.cjs +9 -3
- package/dist/cjs/serve.native.js +13 -4
- package/dist/cjs/serve.native.js.map +1 -1
- package/dist/cjs/server/oneServe.cjs +1 -0
- package/dist/cjs/server/oneServe.native.js +1 -0
- package/dist/cjs/server/oneServe.native.js.map +1 -1
- package/dist/cjs/server/workerHandler.cjs +1 -0
- package/dist/cjs/server/workerHandler.native.js +1 -0
- package/dist/cjs/server/workerHandler.native.js.map +1 -1
- package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.cjs +1 -1
- package/dist/cjs/typed-routes/getTypedRoutesDeclarationFile.native.js +1 -1
- package/dist/cjs/utils/getPathnameFromFilePath.cjs +25 -20
- package/dist/cjs/utils/getPathnameFromFilePath.native.js +28 -23
- package/dist/cjs/utils/getPathnameFromFilePath.native.js.map +1 -1
- package/dist/cjs/utils/getPathnameFromFilePath.test.cjs +13 -2
- package/dist/cjs/utils/getPathnameFromFilePath.test.native.js +13 -2
- package/dist/cjs/utils/getPathnameFromFilePath.test.native.js.map +1 -1
- package/dist/cjs/vercel/build/generate/createSsrServerlessFunction.cjs +8 -0
- package/dist/cjs/vercel/build/generate/createSsrServerlessFunction.native.js +8 -0
- package/dist/cjs/vercel/build/generate/createSsrServerlessFunction.native.js.map +1 -1
- package/dist/cjs/views/Navigator.cjs +17 -3
- package/dist/cjs/views/Navigator.native.js +45 -2
- package/dist/cjs/views/Navigator.native.js.map +1 -1
- package/dist/cjs/vite/DevHead.cjs +1 -1
- package/dist/cjs/vite/DevHead.native.js.map +1 -1
- package/dist/cjs/vite/one.cjs +3 -2
- package/dist/cjs/vite/one.native.js +8 -7
- package/dist/cjs/vite/one.native.js.map +1 -1
- package/dist/cjs/vite/resolveResponse.cjs +5 -4
- package/dist/cjs/vite/resolveResponse.native.js +5 -4
- package/dist/cjs/vite/resolveResponse.native.js.map +1 -1
- package/dist/esm/cli/build.mjs +1 -0
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +1 -0
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/createHandleRequest.mjs +18 -2
- package/dist/esm/createHandleRequest.mjs.map +1 -1
- package/dist/esm/createHandleRequest.native.js +24 -2
- package/dist/esm/createHandleRequest.native.js.map +1 -1
- package/dist/esm/fork/SSRNavigationContainer.mjs +40 -10
- package/dist/esm/fork/SSRNavigationContainer.mjs.map +1 -1
- package/dist/esm/fork/SSRNavigationContainer.native.js +48 -10
- package/dist/esm/fork/SSRNavigationContainer.native.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/layouts/NativeTabs.mjs +25 -0
- package/dist/esm/layouts/NativeTabs.mjs.map +1 -0
- package/dist/esm/layouts/NativeTabs.native.js +26 -0
- package/dist/esm/layouts/NativeTabs.native.js.map +1 -0
- package/dist/esm/layouts/Tabs.mjs +4 -1
- package/dist/esm/layouts/Tabs.mjs.map +1 -1
- package/dist/esm/layouts/Tabs.native.js +4 -1
- package/dist/esm/layouts/Tabs.native.js.map +1 -1
- package/dist/esm/native-tabs.mjs +3 -0
- package/dist/esm/native-tabs.mjs.map +1 -0
- package/dist/esm/native-tabs.native.js +3 -0
- package/dist/esm/native-tabs.native.js.map +1 -0
- package/dist/esm/render.mjs +3 -1
- package/dist/esm/render.mjs.map +1 -1
- package/dist/esm/router/router.mjs +11 -4
- package/dist/esm/router/router.mjs.map +1 -1
- package/dist/esm/router/router.native.js +15 -4
- package/dist/esm/router/router.native.js.map +1 -1
- package/dist/esm/router/useViteRoutes.mjs +5 -1
- package/dist/esm/router/useViteRoutes.mjs.map +1 -1
- package/dist/esm/router/useViteRoutes.native.js +21 -1
- package/dist/esm/router/useViteRoutes.native.js.map +1 -1
- package/dist/esm/serve.mjs +9 -3
- package/dist/esm/serve.mjs.map +1 -1
- package/dist/esm/serve.native.js +13 -4
- package/dist/esm/serve.native.js.map +1 -1
- package/dist/esm/server/oneServe.mjs +2 -1
- package/dist/esm/server/oneServe.mjs.map +1 -1
- package/dist/esm/server/oneServe.native.js +2 -1
- package/dist/esm/server/oneServe.native.js.map +1 -1
- package/dist/esm/server/workerHandler.mjs +2 -1
- package/dist/esm/server/workerHandler.mjs.map +1 -1
- package/dist/esm/server/workerHandler.native.js +2 -1
- package/dist/esm/server/workerHandler.native.js.map +1 -1
- package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.mjs +1 -1
- package/dist/esm/typed-routes/getTypedRoutesDeclarationFile.native.js +1 -1
- package/dist/esm/utils/getPathnameFromFilePath.mjs +25 -20
- package/dist/esm/utils/getPathnameFromFilePath.mjs.map +1 -1
- package/dist/esm/utils/getPathnameFromFilePath.native.js +28 -23
- package/dist/esm/utils/getPathnameFromFilePath.native.js.map +1 -1
- package/dist/esm/utils/getPathnameFromFilePath.test.mjs +13 -2
- package/dist/esm/utils/getPathnameFromFilePath.test.mjs.map +1 -1
- package/dist/esm/utils/getPathnameFromFilePath.test.native.js +13 -2
- package/dist/esm/utils/getPathnameFromFilePath.test.native.js.map +1 -1
- package/dist/esm/vercel/build/generate/createSsrServerlessFunction.mjs +8 -0
- package/dist/esm/vercel/build/generate/createSsrServerlessFunction.mjs.map +1 -1
- package/dist/esm/vercel/build/generate/createSsrServerlessFunction.native.js +8 -0
- package/dist/esm/vercel/build/generate/createSsrServerlessFunction.native.js.map +1 -1
- package/dist/esm/views/Navigator.mjs +18 -4
- package/dist/esm/views/Navigator.mjs.map +1 -1
- package/dist/esm/views/Navigator.native.js +46 -3
- package/dist/esm/views/Navigator.native.js.map +1 -1
- package/dist/esm/vite/DevHead.mjs +1 -1
- package/dist/esm/vite/DevHead.mjs.map +1 -1
- package/dist/esm/vite/DevHead.native.js.map +1 -1
- package/dist/esm/vite/one.mjs +3 -2
- package/dist/esm/vite/one.mjs.map +1 -1
- package/dist/esm/vite/one.native.js +8 -7
- package/dist/esm/vite/one.native.js.map +1 -1
- package/dist/esm/vite/resolveResponse.mjs +5 -4
- package/dist/esm/vite/resolveResponse.mjs.map +1 -1
- package/dist/esm/vite/resolveResponse.native.js +5 -4
- package/dist/esm/vite/resolveResponse.native.js.map +1 -1
- package/package.json +12 -21
- package/src/cli/build.ts +3 -0
- package/src/createHandleRequest.ts +36 -1
- package/src/fork/SSRNavigationContainer.tsx +41 -10
- package/src/index.ts +1 -0
- package/src/layouts/NativeTabs.tsx +46 -0
- package/src/layouts/Tabs.tsx +48 -44
- package/src/native-tabs.ts +1 -0
- package/src/render.tsx +6 -1
- package/src/router/router.ts +21 -0
- package/src/router/useViteRoutes.tsx +7 -1
- package/src/serve.ts +14 -2
- package/src/server/oneServe.ts +2 -0
- package/src/server/workerHandler.ts +2 -0
- package/src/typed-routes/getTypedRoutesDeclarationFile.ts +1 -1
- package/src/types.ts +1 -0
- package/src/utils/getPathnameFromFilePath.test.ts +24 -4
- package/src/utils/getPathnameFromFilePath.ts +13 -7
- package/src/vercel/build/generate/createSsrServerlessFunction.ts +8 -0
- package/src/views/Navigator.tsx +40 -6
- package/src/vite/DevHead.tsx +5 -0
- package/src/vite/one.ts +7 -1
- package/src/vite/resolveResponse.ts +8 -5
- package/types/cli/build.d.ts.map +1 -1
- package/types/createHandleRequest.d.ts +1 -0
- package/types/createHandleRequest.d.ts.map +1 -1
- package/types/fork/SSRNavigationContainer.d.ts +2 -2
- package/types/fork/SSRNavigationContainer.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/layouts/NativeTabs.d.ts +8 -0
- package/types/layouts/NativeTabs.d.ts.map +1 -0
- package/types/layouts/Tabs.d.ts +2 -0
- package/types/layouts/Tabs.d.ts.map +1 -1
- package/types/native-tabs.d.ts +2 -0
- package/types/native-tabs.d.ts.map +1 -0
- package/types/render.d.ts.map +1 -1
- package/types/router/router.d.ts +1 -0
- package/types/router/router.d.ts.map +1 -1
- package/types/router/useViteRoutes.d.ts.map +1 -1
- package/types/server/oneServe.d.ts.map +1 -1
- package/types/server/workerHandler.d.ts.map +1 -1
- package/types/types.d.ts +1 -0
- package/types/types.d.ts.map +1 -1
- package/types/utils/getPathnameFromFilePath.d.ts.map +1 -1
- package/types/vercel/build/generate/createSsrServerlessFunction.d.ts.map +1 -1
- package/types/views/Navigator.d.ts.map +1 -1
- package/types/vite/DevHead.d.ts.map +1 -1
- package/types/vite/one.d.ts.map +1 -1
|
@@ -28,6 +28,23 @@ type RequestHandlerResponse = null | string | Response
|
|
|
28
28
|
|
|
29
29
|
const debugRouter = process.env.ONE_DEBUG_ROUTER
|
|
30
30
|
|
|
31
|
+
// ensure handler results are always a proper Response so middleware
|
|
32
|
+
// can safely use response.body / response.headers / new Response(response.body, ...)
|
|
33
|
+
function ensureResponse(value: any): Response {
|
|
34
|
+
// use isResponse (duck-type check) instead of instanceof — the Response
|
|
35
|
+
// constructor may differ across module realms (e.g. API handler vs middleware)
|
|
36
|
+
if (isResponse(value)) return value
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return new Response(value, {
|
|
39
|
+
headers: { 'Content-Type': 'text/html' },
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
if (value && typeof value === 'object') {
|
|
43
|
+
return Response.json(value)
|
|
44
|
+
}
|
|
45
|
+
return new Response(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
export async function runMiddlewares(
|
|
32
49
|
handlers: RequestHandlers,
|
|
33
50
|
request: Request,
|
|
@@ -57,7 +74,7 @@ export async function runMiddlewares(
|
|
|
57
74
|
if (debugRouter) {
|
|
58
75
|
console.info(`[one] ✓ middleware chain complete`)
|
|
59
76
|
}
|
|
60
|
-
return await getResponse()
|
|
77
|
+
return ensureResponse(await getResponse())
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
if (debugRouter) {
|
|
@@ -123,6 +140,7 @@ export async function resolveAPIRoute(
|
|
|
123
140
|
loaderProps: {
|
|
124
141
|
path: pathname,
|
|
125
142
|
search: url.search,
|
|
143
|
+
subdomain: getSubdomain(url),
|
|
126
144
|
params,
|
|
127
145
|
},
|
|
128
146
|
}),
|
|
@@ -176,6 +194,7 @@ export async function resolveLoaderRoute(
|
|
|
176
194
|
loaderProps: {
|
|
177
195
|
path: url.pathname,
|
|
178
196
|
search: url.search,
|
|
197
|
+
subdomain: getSubdomain(url),
|
|
179
198
|
request: route.type === 'ssr' ? request : undefined,
|
|
180
199
|
params: getLoaderParams(url, route),
|
|
181
200
|
},
|
|
@@ -272,6 +291,7 @@ export async function resolvePageRoute(
|
|
|
272
291
|
const loaderProps = {
|
|
273
292
|
path: pathname,
|
|
274
293
|
search: search,
|
|
294
|
+
subdomain: getSubdomain(url),
|
|
275
295
|
request: route.type === 'ssr' ? request : undefined,
|
|
276
296
|
params: getLoaderParams(url, route),
|
|
277
297
|
}
|
|
@@ -305,6 +325,21 @@ export function getURLfromRequestURL(request: Request) {
|
|
|
305
325
|
return url
|
|
306
326
|
}
|
|
307
327
|
|
|
328
|
+
export function getSubdomain(url: URL): string | undefined {
|
|
329
|
+
const host = url.hostname
|
|
330
|
+
// skip for IP addresses and localhost
|
|
331
|
+
if (!host || host === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
332
|
+
return undefined
|
|
333
|
+
}
|
|
334
|
+
const parts = host.split('.')
|
|
335
|
+
// need at least 3 parts for a subdomain (sub.example.com)
|
|
336
|
+
if (parts.length < 3) {
|
|
337
|
+
return undefined
|
|
338
|
+
}
|
|
339
|
+
// return everything before the last two parts (domain.tld)
|
|
340
|
+
return parts.slice(0, -2).join('.')
|
|
341
|
+
}
|
|
342
|
+
|
|
308
343
|
function compileRouteRegex(route: RouteInfo): RouteInfoCompiled {
|
|
309
344
|
return {
|
|
310
345
|
...route,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SSR-optimized replacement for BaseNavigationContainer.
|
|
3
|
-
* Provides only the
|
|
4
|
-
* with static/no-op values. Eliminates 32+ hooks and reduces 8 providers to
|
|
3
|
+
* Provides only the 5 contexts that child navigators need during SSR render,
|
|
4
|
+
* with static/no-op values. Eliminates 32+ hooks and reduces 8 providers to 5.
|
|
5
5
|
*
|
|
6
6
|
* Requires @react-navigation/core package.json exports to include internal context paths.
|
|
7
7
|
* See postinstall patch in the repo.
|
|
@@ -13,7 +13,7 @@ import { NavigationBuilderContext } from '@react-navigation/core/lib/module/Navi
|
|
|
13
13
|
import { NavigationStateContext } from '@react-navigation/core/lib/module/NavigationStateContext'
|
|
14
14
|
// @ts-ignore internal module
|
|
15
15
|
import { SingleNavigatorContext } from '@react-navigation/core/lib/module/EnsureSingleNavigator'
|
|
16
|
-
import { ThemeProvider } from '@react-navigation/core'
|
|
16
|
+
import { NavigationContainerRefContext, ThemeProvider } from '@react-navigation/core'
|
|
17
17
|
import { LinkingContext } from '@react-navigation/native'
|
|
18
18
|
import * as React from 'react'
|
|
19
19
|
|
|
@@ -37,6 +37,35 @@ const SSR_SINGLE_NAV_CTX = {
|
|
|
37
37
|
unregister: noop,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// no-op navigation ref so useNavigation() doesn't throw during SSR
|
|
41
|
+
const SSR_NAV_REF = {
|
|
42
|
+
dispatch: noop,
|
|
43
|
+
navigate: noop,
|
|
44
|
+
reset: noop,
|
|
45
|
+
goBack: noop,
|
|
46
|
+
isFocused: () => false,
|
|
47
|
+
canGoBack: () => false,
|
|
48
|
+
getParent: () => undefined,
|
|
49
|
+
getState: () => undefined,
|
|
50
|
+
getRootState: () => undefined,
|
|
51
|
+
getCurrentRoute: () => undefined,
|
|
52
|
+
getCurrentOptions: () => undefined,
|
|
53
|
+
isReady: () => false,
|
|
54
|
+
addListener: () => noop,
|
|
55
|
+
removeListener: noop,
|
|
56
|
+
resetRoot: noop,
|
|
57
|
+
setOptions: noop,
|
|
58
|
+
// CommonActions methods
|
|
59
|
+
setParams: noop,
|
|
60
|
+
popTo: noop,
|
|
61
|
+
pop: noop,
|
|
62
|
+
popToTop: noop,
|
|
63
|
+
push: noop,
|
|
64
|
+
replace: noop,
|
|
65
|
+
jumpTo: noop,
|
|
66
|
+
preload: noop,
|
|
67
|
+
} as any
|
|
68
|
+
|
|
40
69
|
const getPartialState = (state: any): any => {
|
|
41
70
|
if (!state) return undefined
|
|
42
71
|
const { key, routeNames, ...partial } = state
|
|
@@ -105,13 +134,15 @@ export function SSRNavigationContainer({
|
|
|
105
134
|
const linkingCtx = linking ? { options: linking } : SSR_LINKING_CTX
|
|
106
135
|
return (
|
|
107
136
|
<LinkingContext.Provider value={linkingCtx}>
|
|
108
|
-
<
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
<NavigationContainerRefContext.Provider value={SSR_NAV_REF}>
|
|
138
|
+
<NavigationBuilderContext.Provider value={SSR_BUILDER_CTX}>
|
|
139
|
+
<NavigationStateContext.Provider value={getStateContext(initialState)}>
|
|
140
|
+
<SingleNavigatorContext.Provider value={SSR_SINGLE_NAV_CTX}>
|
|
141
|
+
<ThemeProvider value={theme}>{children}</ThemeProvider>
|
|
142
|
+
</SingleNavigatorContext.Provider>
|
|
143
|
+
</NavigationStateContext.Provider>
|
|
144
|
+
</NavigationBuilderContext.Provider>
|
|
145
|
+
</NavigationContainerRefContext.Provider>
|
|
115
146
|
</LinkingContext.Provider>
|
|
116
147
|
)
|
|
117
148
|
}
|
package/src/index.ts
CHANGED
|
@@ -74,6 +74,7 @@ export { href } from './href'
|
|
|
74
74
|
// components
|
|
75
75
|
export { Stack } from './layouts/Stack'
|
|
76
76
|
export { Tabs } from './layouts/Tabs'
|
|
77
|
+
export { NativeTabs } from './layouts/NativeTabs'
|
|
77
78
|
export { Protected, type ProtectedProps } from './views/Protected'
|
|
78
79
|
// Stack header compositional API types
|
|
79
80
|
export type {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ParamListBase, TabNavigationState } from '@react-navigation/native'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
|
|
4
|
+
import { Protected } from '../views/Protected'
|
|
5
|
+
import { withLayoutContext } from './withLayoutContext'
|
|
6
|
+
|
|
7
|
+
let NativeBottomTabNavigator: React.ComponentType<any> | null = null
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const mod = require('@bottom-tabs/react-navigation')
|
|
11
|
+
NativeBottomTabNavigator = mod.createNativeBottomTabNavigator().Navigator
|
|
12
|
+
} catch {
|
|
13
|
+
// @bottom-tabs/react-navigation not installed
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type NativeTabsType = ReturnType<typeof withLayoutContext> & {
|
|
17
|
+
Protected: typeof Protected
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createNativeTabs(): NativeTabsType {
|
|
21
|
+
if (!NativeBottomTabNavigator) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'NativeTabs requires @bottom-tabs/react-navigation and react-native-bottom-tabs.\n' +
|
|
24
|
+
'Install: npx expo install @bottom-tabs/react-navigation react-native-bottom-tabs'
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Object.assign(withLayoutContext(NativeBottomTabNavigator), {
|
|
29
|
+
Protected,
|
|
30
|
+
}) as NativeTabsType
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let _nativeTabs: NativeTabsType | null = null
|
|
34
|
+
|
|
35
|
+
export const NativeTabs = new Proxy({} as NativeTabsType, {
|
|
36
|
+
get(_, prop) {
|
|
37
|
+
if (!_nativeTabs) _nativeTabs = createNativeTabs()
|
|
38
|
+
return (_nativeTabs as any)[prop]
|
|
39
|
+
},
|
|
40
|
+
apply(_, thisArg, args) {
|
|
41
|
+
if (!_nativeTabs) _nativeTabs = createNativeTabs()
|
|
42
|
+
return (_nativeTabs as any).apply(thisArg, args)
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export default NativeTabs
|
package/src/layouts/Tabs.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { Platform, Pressable } from 'react-native'
|
|
|
10
10
|
|
|
11
11
|
import type { OneRouter } from '../interfaces/router'
|
|
12
12
|
import { Link } from '../link/Link'
|
|
13
|
+
import { Protected } from '../views/Protected'
|
|
13
14
|
import { withLayoutContext } from './withLayoutContext'
|
|
14
15
|
|
|
15
16
|
const TabBar = ({ state, ...restProps }: BottomTabBarProps) => {
|
|
@@ -41,53 +42,56 @@ type BottomTabNavigationOptionsWithHref = BottomTabNavigationOptions & {
|
|
|
41
42
|
href?: OneRouter.Href | null
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export const Tabs =
|
|
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
|
-
|
|
45
|
+
export const Tabs = Object.assign(
|
|
46
|
+
withLayoutContext<
|
|
47
|
+
BottomTabNavigationOptionsWithHref,
|
|
48
|
+
typeof BottomTabNavigator,
|
|
49
|
+
TabNavigationState<ParamListBase>,
|
|
50
|
+
BottomTabNavigationEventMap
|
|
51
|
+
>(
|
|
52
|
+
BottomTabNavigator,
|
|
53
|
+
(screens) => {
|
|
54
|
+
// Support the `href` shortcut prop.
|
|
55
|
+
return screens.map((screen) => {
|
|
56
|
+
if (typeof screen.options !== 'function' && screen.options?.href !== undefined) {
|
|
57
|
+
const { href, ...options } = screen.options
|
|
58
|
+
if (options.tabBarButton) {
|
|
59
|
+
throw new Error('Cannot use `href` and `tabBarButton` together.')
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
...screen,
|
|
63
|
+
options: {
|
|
64
|
+
...options,
|
|
65
|
+
tabBarButton: (props) => {
|
|
66
|
+
if (href == null) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
const children =
|
|
70
|
+
Platform.OS === 'web' ? (
|
|
71
|
+
props.children
|
|
72
|
+
) : (
|
|
73
|
+
<Pressable>{props.children}</Pressable>
|
|
74
|
+
)
|
|
75
|
+
return (
|
|
76
|
+
<Link
|
|
77
|
+
{...(props as any)}
|
|
78
|
+
style={[{ display: 'flex' }, props.style]}
|
|
79
|
+
href={href}
|
|
80
|
+
asChild={Platform.OS !== 'web'}
|
|
81
|
+
// biome-ignore lint/correctness/noChildrenProp: children prop needed for asChild pattern
|
|
82
|
+
children={children}
|
|
83
|
+
/>
|
|
72
84
|
)
|
|
73
|
-
|
|
74
|
-
<Link
|
|
75
|
-
{...(props as any)}
|
|
76
|
-
style={[{ display: 'flex' }, props.style]}
|
|
77
|
-
href={href}
|
|
78
|
-
asChild={Platform.OS !== 'web'}
|
|
79
|
-
// biome-ignore lint/correctness/noChildrenProp: children prop needed for asChild pattern
|
|
80
|
-
children={children}
|
|
81
|
-
/>
|
|
82
|
-
)
|
|
85
|
+
},
|
|
83
86
|
},
|
|
84
|
-
}
|
|
87
|
+
}
|
|
85
88
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
return screen
|
|
90
|
+
})
|
|
91
|
+
},
|
|
92
|
+
{ props: { tabBar: TabBar } }
|
|
93
|
+
),
|
|
94
|
+
{ Protected }
|
|
91
95
|
)
|
|
92
96
|
|
|
93
97
|
export default Tabs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NativeTabs, NativeTabs as default } from './layouts/NativeTabs'
|
package/src/render.tsx
CHANGED
|
@@ -11,7 +11,12 @@ export function render(element: React.ReactNode) {
|
|
|
11
11
|
|
|
12
12
|
if (globalThis['__vxrnRoot']) {
|
|
13
13
|
globalThis['__vxrnVersion']++
|
|
14
|
-
|
|
14
|
+
// wrap in startTransition so Suspense-based route resolution keeps the
|
|
15
|
+
// old tree visible while new route modules resolve, instead of flashing
|
|
16
|
+
// the fallback (or losing the tree entirely in error boundaries)
|
|
17
|
+
startTransition(() => {
|
|
18
|
+
globalThis['__vxrnRoot'].render(element)
|
|
19
|
+
})
|
|
15
20
|
} else {
|
|
16
21
|
startTransition(() => {
|
|
17
22
|
const rootElement = process.env.ONE_USE_FASTER_DOCUMENT
|
package/src/router/router.ts
CHANGED
|
@@ -131,6 +131,10 @@ export function isRouteProtected(href: string): boolean {
|
|
|
131
131
|
export let hasAttemptedToHideSplash = false
|
|
132
132
|
export let initialState: OneRouter.ResultState | undefined
|
|
133
133
|
export let rootState: OneRouter.ResultState | undefined
|
|
134
|
+
// the original pathname from the initial page load, used by late-mounting
|
|
135
|
+
// navigators to determine the correct initial route even after React Navigation's
|
|
136
|
+
// linking has pushed a different URL during unmount/remount cycles
|
|
137
|
+
export let initialPathname: string | undefined
|
|
134
138
|
|
|
135
139
|
let nextState: OneRouter.ResultState | undefined
|
|
136
140
|
export let routeInfo: UrlObject | undefined
|
|
@@ -282,6 +286,7 @@ export function initialize(
|
|
|
282
286
|
|
|
283
287
|
function cleanUpState() {
|
|
284
288
|
initialState = undefined
|
|
289
|
+
initialPathname = undefined
|
|
285
290
|
rootState = undefined
|
|
286
291
|
nextState = undefined
|
|
287
292
|
routeInfo = undefined
|
|
@@ -293,6 +298,11 @@ function cleanUpState() {
|
|
|
293
298
|
function setupLinkingAndRouteInfo(initialLocation?: URL) {
|
|
294
299
|
initialState = setupLinking(routeNode, initialLocation)
|
|
295
300
|
|
|
301
|
+
// capture the original pathname before React Navigation's linking can modify it
|
|
302
|
+
initialPathname =
|
|
303
|
+
initialLocation?.pathname ??
|
|
304
|
+
(typeof window !== 'undefined' ? window.location.pathname : undefined)
|
|
305
|
+
|
|
296
306
|
if (initialState) {
|
|
297
307
|
rootState = initialState
|
|
298
308
|
routeInfo = getRouteInfo(initialState)
|
|
@@ -567,6 +577,17 @@ function syncStoreRootState() {
|
|
|
567
577
|
if (navigationRef.isReady()) {
|
|
568
578
|
const currentState = navigationRef.getRootState() as unknown as OneRouter.ResultState
|
|
569
579
|
if (rootState !== currentState) {
|
|
580
|
+
// when a parent layout conditionally renders (e.g. auth gate), getRootState()
|
|
581
|
+
// can return incomplete/wrong state before all navigators mount. don't
|
|
582
|
+
// overwrite routeInfo with wrong pathname while initial state is still valid.
|
|
583
|
+
if (initialState && routeInfo?.pathname) {
|
|
584
|
+
const nextRouteInfo = getRouteInfo(currentState)
|
|
585
|
+
if (nextRouteInfo.pathname !== routeInfo.pathname) {
|
|
586
|
+
// pathname would change — skip to preserve initial URL truth
|
|
587
|
+
rootState = currentState
|
|
588
|
+
return
|
|
589
|
+
}
|
|
590
|
+
}
|
|
570
591
|
updateState(currentState)
|
|
571
592
|
}
|
|
572
593
|
}
|
|
@@ -15,9 +15,15 @@ export function useViteRoutes(
|
|
|
15
15
|
version?: number
|
|
16
16
|
) {
|
|
17
17
|
if (version && version > lastVersion) {
|
|
18
|
-
// reload
|
|
18
|
+
// reload — clear stale route caches so fresh modules are used
|
|
19
19
|
context = null
|
|
20
20
|
lastVersion = version
|
|
21
|
+
// clear preloaded modules from previous render cycle — they point to
|
|
22
|
+
// old route module instances and would short-circuit resolve() before
|
|
23
|
+
// it reaches the new routesSync functions
|
|
24
|
+
for (const key of Object.keys(preloadedModules)) {
|
|
25
|
+
delete preloadedModules[key]
|
|
26
|
+
}
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
if (!context) {
|
package/src/serve.ts
CHANGED
|
@@ -181,7 +181,12 @@ async function startWorker(args: Parameters<typeof serve>[0]) {
|
|
|
181
181
|
|
|
182
182
|
const { labelProcess } = await import('./cli/label-process')
|
|
183
183
|
const { removeUndefined } = await import('./utils/removeUndefined')
|
|
184
|
-
const {
|
|
184
|
+
const {
|
|
185
|
+
loadEnv,
|
|
186
|
+
serve: vxrnServe,
|
|
187
|
+
serveStaticAssets,
|
|
188
|
+
compileCacheRules,
|
|
189
|
+
} = await import('vxrn/serve')
|
|
185
190
|
const { oneServe } = await import('./server/oneServe')
|
|
186
191
|
|
|
187
192
|
labelProcess('serve')
|
|
@@ -190,6 +195,11 @@ async function startWorker(args: Parameters<typeof serve>[0]) {
|
|
|
190
195
|
await loadEnv('production')
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
// compile cache rules once at startup so every request is a single regex test
|
|
199
|
+
const cacheRules = oneOptions.server?.cacheControl
|
|
200
|
+
? compileCacheRules(oneOptions.server.cacheControl)
|
|
201
|
+
: undefined
|
|
202
|
+
|
|
193
203
|
return await vxrnServe({
|
|
194
204
|
outDir: buildInfo.outDir || outDir,
|
|
195
205
|
app: args?.app,
|
|
@@ -201,7 +211,9 @@ async function startWorker(args: Parameters<typeof serve>[0]) {
|
|
|
201
211
|
}),
|
|
202
212
|
|
|
203
213
|
async beforeRegisterRoutes(options, app) {
|
|
204
|
-
await oneServe(oneOptions, buildInfo, app, {
|
|
214
|
+
await oneServe(oneOptions, buildInfo, app, {
|
|
215
|
+
serveStaticAssets: (ctx) => serveStaticAssets({ ...ctx, cacheRules }),
|
|
216
|
+
})
|
|
205
217
|
},
|
|
206
218
|
|
|
207
219
|
async afterRegisterRoutes(options, app) {},
|
package/src/server/oneServe.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '../constants'
|
|
10
10
|
import {
|
|
11
11
|
compileManifest,
|
|
12
|
+
getSubdomain,
|
|
12
13
|
getURLfromRequestURL,
|
|
13
14
|
type RequestHandlers,
|
|
14
15
|
runMiddlewares,
|
|
@@ -748,6 +749,7 @@ url: ${url}`)
|
|
|
748
749
|
const loaderProps = {
|
|
749
750
|
path: pathname,
|
|
750
751
|
search,
|
|
752
|
+
subdomain: getSubdomain(getURLfromRequestURL(request)),
|
|
751
753
|
request,
|
|
752
754
|
params,
|
|
753
755
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../constants'
|
|
6
6
|
import {
|
|
7
7
|
compileManifest,
|
|
8
|
+
getSubdomain,
|
|
8
9
|
getURLfromRequestURL,
|
|
9
10
|
type RequestHandlers,
|
|
10
11
|
resolveAPIRoute,
|
|
@@ -704,6 +705,7 @@ export function createWorkerHandler(options: WorkerHandlerOptions) {
|
|
|
704
705
|
const loaderProps = {
|
|
705
706
|
path: pathname,
|
|
706
707
|
search: url.search,
|
|
708
|
+
subdomain: getSubdomain(url),
|
|
707
709
|
request,
|
|
708
710
|
params,
|
|
709
711
|
}
|
|
@@ -52,7 +52,7 @@ ${
|
|
|
52
52
|
*/
|
|
53
53
|
type RouteInfo<Params = Record<string, never>> = {
|
|
54
54
|
Params: Params
|
|
55
|
-
LoaderProps: { path: string; params: Params; request?: Request }
|
|
55
|
+
LoaderProps: { path: string; search?: string; subdomain?: string; params: Params; request?: Request }
|
|
56
56
|
}`
|
|
57
57
|
: ''
|
|
58
58
|
}
|
package/src/types.ts
CHANGED
|
@@ -92,15 +92,35 @@ describe('getPathnameFromFilePath', () => {
|
|
|
92
92
|
)
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
it('substitutes
|
|
96
|
-
// getPathnameFromFilePath only substitutes params in the filename segment,
|
|
97
|
-
// dirname params are converted to :param placeholders via regex
|
|
95
|
+
it('substitutes params in both dirname and filename', () => {
|
|
98
96
|
expect(
|
|
99
97
|
getPathnameFromFilePath('/servers/[serverId]/[channelId]+spa.tsx', {
|
|
100
98
|
serverId: 'abc',
|
|
101
99
|
channelId: '123',
|
|
102
100
|
})
|
|
103
|
-
).toBe('/servers
|
|
101
|
+
).toBe('/servers/abc/123')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('substitutes dirname params for SSG dynamic routes', () => {
|
|
105
|
+
expect(getPathnameFromFilePath('/[lang]/index+ssg.tsx', { lang: 'en' }, true)).toBe(
|
|
106
|
+
'/en/'
|
|
107
|
+
)
|
|
108
|
+
expect(getPathnameFromFilePath('/[lang]/index+ssg.tsx', { lang: 'ko' }, true)).toBe(
|
|
109
|
+
'/ko/'
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('substitutes nested dirname params', () => {
|
|
114
|
+
expect(
|
|
115
|
+
getPathnameFromFilePath(
|
|
116
|
+
'/[lang]/[region]/index+ssg.tsx',
|
|
117
|
+
{
|
|
118
|
+
lang: 'en',
|
|
119
|
+
region: 'us',
|
|
120
|
+
},
|
|
121
|
+
true
|
|
122
|
+
)
|
|
123
|
+
).toBe('/en/us/')
|
|
104
124
|
})
|
|
105
125
|
})
|
|
106
126
|
|
|
@@ -7,13 +7,6 @@ export function getPathnameFromFilePath(
|
|
|
7
7
|
options: { preserveExtensions?: boolean; includeIndex?: boolean } = {}
|
|
8
8
|
) {
|
|
9
9
|
const path = inputPath.replace(/\+(spa|ssg|ssr|api)\.tsx?$/, '')
|
|
10
|
-
// remove groups, folder render mode suffixes, and convert [param] to :param in dirname
|
|
11
|
-
const dirname = Path.dirname(path)
|
|
12
|
-
.split('/')
|
|
13
|
-
.map((segment) => segment.replace(/\+(api|ssg|ssr|spa)$/, ''))
|
|
14
|
-
.join('/')
|
|
15
|
-
.replace(/\([^/]+\)/gi, '')
|
|
16
|
-
.replace(/\[([^\]]+)\]/g, ':$1')
|
|
17
10
|
const file = Path.basename(path)
|
|
18
11
|
const fileName = options.preserveExtensions ? file : file.replace(/\.[a-z]+$/, '')
|
|
19
12
|
|
|
@@ -30,6 +23,19 @@ ${JSON.stringify(params, null, 2)}`
|
|
|
30
23
|
)
|
|
31
24
|
}
|
|
32
25
|
|
|
26
|
+
// remove groups, folder render mode suffixes, and substitute [param] in dirname
|
|
27
|
+
const dirname = Path.dirname(path)
|
|
28
|
+
.split('/')
|
|
29
|
+
.map((segment) => segment.replace(/\+(api|ssg|ssr|spa)$/, ''))
|
|
30
|
+
.join('/')
|
|
31
|
+
.replace(/\([^/]+\)/gi, '')
|
|
32
|
+
.replace(/\[([^\]]+)\]/g, (_, paramName) => {
|
|
33
|
+
const value = params[paramName]
|
|
34
|
+
if (value != null) return String(value)
|
|
35
|
+
if (strict) throw paramsError(paramName)
|
|
36
|
+
return ':' + paramName
|
|
37
|
+
})
|
|
38
|
+
|
|
33
39
|
const nameWithParams = (() => {
|
|
34
40
|
if (fileName === 'index' && !options.includeIndex) {
|
|
35
41
|
return '/'
|
|
@@ -101,9 +101,17 @@ export async function createSsrServerlessFunction(
|
|
|
101
101
|
originalPath = originalPath.replace(\`:$\{key}\`, String(value));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
const host = url.hostname;
|
|
105
|
+
let subdomain;
|
|
106
|
+
if (host && host !== 'localhost' && !/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host)) {
|
|
107
|
+
const parts = host.split('.');
|
|
108
|
+
if (parts.length >= 3) subdomain = parts.slice(0, -2).join('.');
|
|
109
|
+
}
|
|
110
|
+
|
|
104
111
|
const loaderProps = {
|
|
105
112
|
path: originalPath,
|
|
106
113
|
params: routeParams,
|
|
114
|
+
subdomain,
|
|
107
115
|
request,
|
|
108
116
|
}
|
|
109
117
|
|
package/src/views/Navigator.tsx
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useNotFoundState,
|
|
14
14
|
} from '../notFoundState'
|
|
15
15
|
import { useContextKey } from '../router/Route'
|
|
16
|
-
import { routeNode as globalRouteNode } from '../router/router'
|
|
16
|
+
import { routeNode as globalRouteNode, initialPathname } from '../router/router'
|
|
17
17
|
import { registerProtectedRoutes, unregisterProtectedRoutes } from '../router/router'
|
|
18
18
|
import { useSortedScreens, getQualifiedRouteComponent } from '../router/useScreens'
|
|
19
19
|
import { Screen } from './Screen'
|
|
@@ -171,6 +171,38 @@ function QualifiedNavigator({
|
|
|
171
171
|
contextKey,
|
|
172
172
|
router = StackRouter,
|
|
173
173
|
}: NavigatorProps & { contextKey: string; screens: React.ReactNode[] }) {
|
|
174
|
+
// LATE MOUNT FIX: when a parent layout conditionally renders (e.g. auth gate),
|
|
175
|
+
// this navigator may mount after initialState was consumed. compute the
|
|
176
|
+
// correct initialRouteName from the original URL so the navigator starts on
|
|
177
|
+
// the right route instead of defaulting to the first one.
|
|
178
|
+
// uses initialPathname (captured at setup) instead of window.location.pathname
|
|
179
|
+
// because React Navigation's linking can push a wrong URL during the delay.
|
|
180
|
+
const resolvedInitialRouteName = React.useMemo(() => {
|
|
181
|
+
if (initialRouteName) return initialRouteName
|
|
182
|
+
|
|
183
|
+
const browserPath =
|
|
184
|
+
initialPathname ??
|
|
185
|
+
(typeof window !== 'undefined' ? window.location.pathname : undefined)
|
|
186
|
+
if (!browserPath) return undefined
|
|
187
|
+
|
|
188
|
+
// extract screen names from the screens array
|
|
189
|
+
const screenNames: string[] = []
|
|
190
|
+
for (const screen of screens) {
|
|
191
|
+
const props = (screen as any)?.props
|
|
192
|
+
if (props?.name) screenNames.push(props.name)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// find which screen matches the URL
|
|
196
|
+
for (const name of screenNames) {
|
|
197
|
+
const base = name.replace(/\/index$/, '')
|
|
198
|
+
if (browserPath.endsWith('/' + base) || browserPath.includes('/' + base + '/')) {
|
|
199
|
+
return name
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return undefined
|
|
204
|
+
}, [initialRouteName, screens])
|
|
205
|
+
|
|
174
206
|
const { state, navigation, descriptors, NavigationContent } = useNavigationBuilder(
|
|
175
207
|
router,
|
|
176
208
|
{
|
|
@@ -178,7 +210,7 @@ function QualifiedNavigator({
|
|
|
178
210
|
id: contextKey,
|
|
179
211
|
children: screens,
|
|
180
212
|
screenOptions,
|
|
181
|
-
initialRouteName,
|
|
213
|
+
initialRouteName: resolvedInitialRouteName,
|
|
182
214
|
}
|
|
183
215
|
)
|
|
184
216
|
|
|
@@ -252,11 +284,13 @@ export function useSlot() {
|
|
|
252
284
|
|
|
253
285
|
const renderedElement = descriptorsRef.current[current.key]?.render() ?? null
|
|
254
286
|
|
|
255
|
-
// Use
|
|
256
|
-
//
|
|
257
|
-
//
|
|
287
|
+
// Use key based on route name to prevent layout remounts when route keys change
|
|
288
|
+
// (same route, different key), while allowing React to swap components when
|
|
289
|
+
// the actual route changes (e.g. late-mounting navigator correcting its state).
|
|
258
290
|
if (renderedElement !== null) {
|
|
259
|
-
return React.cloneElement(renderedElement, {
|
|
291
|
+
return React.cloneElement(renderedElement, {
|
|
292
|
+
key: `${SLOT_STATIC_KEY}-${current.name}`,
|
|
293
|
+
})
|
|
260
294
|
}
|
|
261
295
|
|
|
262
296
|
return renderedElement
|