one 2.7.7 → 2.8.1

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.
Files changed (307) hide show
  1. package/One.podspec +3 -0
  2. package/android/build.gradle +8 -1
  3. package/android/consumer-rules.pro +6 -0
  4. package/dist/cjs/cli/build.cjs +2 -2
  5. package/dist/cjs/cli/build.native.cjs +2 -2
  6. package/dist/cjs/cli/build.native.js +2 -2
  7. package/dist/cjs/cli/build.native.js.map +1 -1
  8. package/dist/cjs/cli/buildPage.cjs +1 -1
  9. package/dist/cjs/cli/buildPage.native.cjs +1 -1
  10. package/dist/cjs/cli/buildPage.native.js +1 -1
  11. package/dist/cjs/cli/buildPage.native.js.map +1 -1
  12. package/dist/cjs/constants.cjs +2 -0
  13. package/dist/cjs/constants.native.cjs +2 -0
  14. package/dist/cjs/constants.native.js +2 -0
  15. package/dist/cjs/constants.native.js.map +1 -1
  16. package/dist/cjs/fork/useLinking.cjs +1 -1
  17. package/dist/cjs/layouts/Stack.cjs +2 -2
  18. package/dist/cjs/layouts/Stack.native.cjs +3 -3
  19. package/dist/cjs/layouts/Stack.native.js +3 -3
  20. package/dist/cjs/layouts/Stack.native.js.map +1 -1
  21. package/dist/cjs/layouts/stack-navigator.cjs +5 -2
  22. package/dist/cjs/layouts/stack-navigator.native.cjs +14 -2
  23. package/dist/cjs/layouts/stack-navigator.native.js +14 -2
  24. package/dist/cjs/layouts/stack-navigator.native.js.map +1 -1
  25. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.cjs +1 -1
  26. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.cjs +1 -1
  27. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js +1 -1
  28. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -1
  29. package/dist/cjs/metro-config/getViteMetroPluginOptions.cjs +10 -4
  30. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.cjs +10 -4
  31. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js +10 -4
  32. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  33. package/dist/cjs/metro-config/withOne.cjs +2 -2
  34. package/dist/cjs/metro-config/withOne.native.cjs +2 -2
  35. package/dist/cjs/metro-config/withOne.native.js +2 -2
  36. package/dist/cjs/metro-config/withOne.native.js.map +1 -1
  37. package/dist/cjs/router/linkingConfig.cjs +3 -3
  38. package/dist/cjs/router/linkingConfig.native.cjs +3 -3
  39. package/dist/cjs/router/linkingConfig.native.js +3 -3
  40. package/dist/cjs/router/linkingConfig.native.js.map +1 -1
  41. package/dist/cjs/useLoader.cjs +0 -2
  42. package/dist/cjs/useLoader.native.cjs +1 -3
  43. package/dist/cjs/useLoader.native.js +1 -3
  44. package/dist/cjs/useLoader.native.js.map +1 -1
  45. package/dist/cjs/utils/cleanUrl.cjs +8 -3
  46. package/dist/cjs/utils/cleanUrl.native.cjs +8 -3
  47. package/dist/cjs/utils/cleanUrl.native.js +8 -3
  48. package/dist/cjs/utils/cleanUrl.native.js.map +1 -1
  49. package/dist/cjs/vite/one.cjs +5 -5
  50. package/dist/cjs/vite/one.native.cjs +5 -5
  51. package/dist/cjs/vite/one.native.js +5 -5
  52. package/dist/cjs/vite/one.native.js.map +1 -1
  53. package/dist/cjs/vite/plugins/workerdDevPlugin.cjs +3 -3
  54. package/dist/cjs/vite/plugins/workerdDevPlugin.native.cjs +3 -3
  55. package/dist/cjs/vite/plugins/workerdDevPlugin.native.js +3 -3
  56. package/dist/cjs/vite/plugins/workerdDevPlugin.native.js.map +1 -1
  57. package/dist/esm/cli/build.mjs +2 -2
  58. package/dist/esm/cli/build.mjs.map +1 -1
  59. package/dist/esm/cli/build.native.js +2 -2
  60. package/dist/esm/cli/build.native.js.map +1 -1
  61. package/dist/esm/cli/buildPage.mjs +2 -2
  62. package/dist/esm/cli/buildPage.mjs.map +1 -1
  63. package/dist/esm/cli/buildPage.native.js +2 -2
  64. package/dist/esm/cli/buildPage.native.js.map +1 -1
  65. package/dist/esm/constants.mjs +2 -1
  66. package/dist/esm/constants.mjs.map +1 -1
  67. package/dist/esm/constants.native.js +2 -1
  68. package/dist/esm/constants.native.js.map +1 -1
  69. package/dist/esm/fork/useLinking.mjs +1 -1
  70. package/dist/esm/fork/useLinking.mjs.map +1 -1
  71. package/dist/esm/layouts/Stack.mjs +2 -2
  72. package/dist/esm/layouts/Stack.mjs.map +1 -1
  73. package/dist/esm/layouts/Stack.native.js +3 -3
  74. package/dist/esm/layouts/Stack.native.js.map +1 -1
  75. package/dist/esm/layouts/stack-navigator.mjs +5 -2
  76. package/dist/esm/layouts/stack-navigator.mjs.map +1 -1
  77. package/dist/esm/layouts/stack-navigator.native.js +14 -2
  78. package/dist/esm/layouts/stack-navigator.native.js.map +1 -1
  79. package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs +1 -1
  80. package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs.map +1 -1
  81. package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js +1 -1
  82. package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -1
  83. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs +10 -4
  84. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs.map +1 -1
  85. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js +10 -4
  86. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  87. package/dist/esm/metro-config/withOne.mjs +1 -1
  88. package/dist/esm/metro-config/withOne.mjs.map +1 -1
  89. package/dist/esm/metro-config/withOne.native.js +1 -1
  90. package/dist/esm/metro-config/withOne.native.js.map +1 -1
  91. package/dist/esm/router/linkingConfig.mjs +3 -3
  92. package/dist/esm/router/linkingConfig.mjs.map +1 -1
  93. package/dist/esm/router/linkingConfig.native.js +3 -3
  94. package/dist/esm/router/linkingConfig.native.js.map +1 -1
  95. package/dist/esm/useLoader.native.js +2 -4
  96. package/dist/esm/useLoader.native.js.map +1 -1
  97. package/dist/esm/utils/cleanUrl.mjs +9 -5
  98. package/dist/esm/utils/cleanUrl.mjs.map +1 -1
  99. package/dist/esm/utils/cleanUrl.native.js +9 -5
  100. package/dist/esm/utils/cleanUrl.native.js.map +1 -1
  101. package/dist/esm/vite/one.mjs +1 -1
  102. package/dist/esm/vite/one.mjs.map +1 -1
  103. package/dist/esm/vite/one.native.js +1 -1
  104. package/dist/esm/vite/one.native.js.map +1 -1
  105. package/dist/esm/vite/plugins/workerdDevPlugin.mjs +2 -2
  106. package/dist/esm/vite/plugins/workerdDevPlugin.mjs.map +1 -1
  107. package/dist/esm/vite/plugins/workerdDevPlugin.native.js +2 -2
  108. package/dist/esm/vite/plugins/workerdDevPlugin.native.js.map +1 -1
  109. package/ios/PrivacyInfo.xcprivacy +52 -0
  110. package/metro-entry.js +1 -1
  111. package/package.json +79 -42
  112. package/src/__tests__/polyfills-mobile-metro.test.ts +2 -2
  113. package/src/cli/build.ts +2 -2
  114. package/src/cli/buildPage.ts +5 -4
  115. package/src/constants.ts +2 -0
  116. package/src/fork/useLinking.ts +4 -1
  117. package/src/layouts/Stack.tsx +5 -2
  118. package/src/layouts/stack-navigator.tsx +34 -0
  119. package/src/layouts/stack-navigator.web.ts +5 -2
  120. package/src/metro-config/buildOneMetroResolverOverrides.ts +1 -1
  121. package/src/metro-config/getViteMetroPluginOptions.integration.test.ts +5 -3
  122. package/src/metro-config/getViteMetroPluginOptions.ts +10 -4
  123. package/src/metro-config/withOne.ts +3 -1
  124. package/src/native/packedClosure.test.ts +68 -6
  125. package/src/native/shippedAndroid.test.ts +21 -0
  126. package/src/native/shippedArtifacts.test.ts +81 -0
  127. package/src/router/linkingConfig.test.ts +144 -2
  128. package/src/router/linkingConfig.ts +6 -3
  129. package/src/useLoader.ts +3 -15
  130. package/src/utils/cleanUrl.test.ts +28 -0
  131. package/src/utils/cleanUrl.ts +11 -4
  132. package/src/vite/one.ts +2 -1
  133. package/src/vite/plugins/workerdDevPlugin.ts +2 -2
  134. package/src/vite/types.ts +4 -4
  135. package/types/Frozen.d.ts +1 -1
  136. package/types/Frozen.d.ts.map +1 -1
  137. package/types/Frozen.native.d.ts +1 -1
  138. package/types/Frozen.native.d.ts.map +1 -1
  139. package/types/Root.d.ts +1 -1
  140. package/types/Root.d.ts.map +1 -1
  141. package/types/cli/buildPage.d.ts.map +1 -1
  142. package/types/constants.d.ts +1 -0
  143. package/types/constants.d.ts.map +1 -1
  144. package/types/fallbackViews/Sitemap.d.ts +1 -1
  145. package/types/fallbackViews/Sitemap.d.ts.map +1 -1
  146. package/types/fallbackViews/Sitemap.web.d.ts +2 -1
  147. package/types/fallbackViews/Sitemap.web.d.ts.map +1 -1
  148. package/types/fallbackViews/Unmatched.d.ts +2 -1
  149. package/types/fallbackViews/Unmatched.d.ts.map +1 -1
  150. package/types/fallbackViews/Unmatched.web.d.ts +2 -1
  151. package/types/fallbackViews/Unmatched.web.d.ts.map +1 -1
  152. package/types/fork/useLinking.d.ts.map +1 -1
  153. package/types/headless/Presentations.d.ts +1 -1
  154. package/types/headless/Presentations.d.ts.map +1 -1
  155. package/types/headless/keepMounted.d.ts +1 -1
  156. package/types/headless/keepMounted.d.ts.map +1 -1
  157. package/types/headless/useDrawer.d.ts +1 -1
  158. package/types/headless/useDrawer.d.ts.map +1 -1
  159. package/types/layouts/Stack.d.ts.map +1 -1
  160. package/types/layouts/stack-navigator.d.ts +6 -2
  161. package/types/layouts/stack-navigator.d.ts.map +1 -1
  162. package/types/layouts/stack-navigator.web.d.ts +3 -4
  163. package/types/layouts/stack-navigator.web.d.ts.map +1 -1
  164. package/types/layouts/stack-utils/StackScreen.d.ts +1 -1
  165. package/types/layouts/stack-utils/StackScreen.d.ts.map +1 -1
  166. package/types/layouts/stack-utils/StackToolbar.d.ts +1 -1
  167. package/types/layouts/stack-utils/StackToolbar.d.ts.map +1 -1
  168. package/types/layouts/stack-utils/StackToolbarBottomHost.d.ts +1 -1
  169. package/types/layouts/stack-utils/StackToolbarBottomHost.d.ts.map +1 -1
  170. package/types/metro-config/getViteMetroPluginOptions.d.ts.map +1 -1
  171. package/types/metro-config/withOne.d.ts.map +1 -1
  172. package/types/platform/AndroidMenu.d.ts +2 -2
  173. package/types/platform/AndroidMenu.d.ts.map +1 -1
  174. package/types/platform/Animators.native.d.ts +2 -2
  175. package/types/platform/Animators.native.d.ts.map +1 -1
  176. package/types/platform/ArrangementView.d.ts +1 -1
  177. package/types/platform/ArrangementView.d.ts.map +1 -1
  178. package/types/platform/ArrangementView.native.d.ts +1 -1
  179. package/types/platform/ArrangementView.native.d.ts.map +1 -1
  180. package/types/platform/Containers.native.d.ts +34 -34
  181. package/types/platform/Containers.native.d.ts.map +1 -1
  182. package/types/platform/Menu.native.d.ts +2 -2
  183. package/types/platform/Menu.native.d.ts.map +1 -1
  184. package/types/platform/NavigationSplitView.native.d.ts +1 -1
  185. package/types/platform/NavigationSplitView.native.d.ts.map +1 -1
  186. package/types/platform/NavigationStack.native.d.ts +2 -2
  187. package/types/platform/NavigationStack.native.d.ts.map +1 -1
  188. package/types/platform/Pager.native.d.ts +1 -1
  189. package/types/platform/Pager.native.d.ts.map +1 -1
  190. package/types/platform/Popover.native.d.ts +1 -1
  191. package/types/platform/Popover.native.d.ts.map +1 -1
  192. package/types/platform/Sheet.native.d.ts +2 -2
  193. package/types/platform/Sheet.native.d.ts.map +1 -1
  194. package/types/platform/Tabs.native.d.ts +1 -1
  195. package/types/platform/Tabs.native.d.ts.map +1 -1
  196. package/types/platform/adaptive/ReservedRegions.d.ts +1 -1
  197. package/types/platform/adaptive/ReservedRegions.d.ts.map +1 -1
  198. package/types/platform/adaptive/ReservedRegions.native.d.ts +1 -1
  199. package/types/platform/adaptive/ReservedRegions.native.d.ts.map +1 -1
  200. package/types/platform/compose.android.d.ts +101 -101
  201. package/types/platform/compose.android.d.ts.map +1 -1
  202. package/types/platform/effects/Blur.d.ts +1 -1
  203. package/types/platform/effects/Blur.d.ts.map +1 -1
  204. package/types/platform/effects/Blur.native.d.ts +1 -1
  205. package/types/platform/effects/Blur.native.d.ts.map +1 -1
  206. package/types/platform/effects/EdgeFade.d.ts +1 -1
  207. package/types/platform/effects/EdgeFade.d.ts.map +1 -1
  208. package/types/platform/effects/EdgeFade.native.d.ts +1 -1
  209. package/types/platform/effects/EdgeFade.native.d.ts.map +1 -1
  210. package/types/platform/effects/Mask.d.ts +1 -1
  211. package/types/platform/effects/Mask.d.ts.map +1 -1
  212. package/types/platform/effects/Mask.native.d.ts +1 -1
  213. package/types/platform/effects/Mask.native.d.ts.map +1 -1
  214. package/types/platform/generated/Controls.native.d.ts +55 -55
  215. package/types/platform/generated/Controls.native.d.ts.map +1 -1
  216. package/types/platform/index.native.d.ts +58 -58
  217. package/types/platform/menu/index.native.d.ts +1 -1
  218. package/types/platform/menu/index.native.d.ts.map +1 -1
  219. package/types/platform/native-source/view.native.d.ts +1 -1
  220. package/types/platform/native-source/view.native.d.ts.map +1 -1
  221. package/types/platform/split-view/split-view.d.ts +2 -2
  222. package/types/platform/split-view/split-view.d.ts.map +1 -1
  223. package/types/platform/split-view/split-view.native.d.ts +3 -3
  224. package/types/platform/split-view/split-view.native.d.ts.map +1 -1
  225. package/types/platform/swift/index.native.d.ts +1 -1
  226. package/types/platform/swift/index.native.d.ts.map +1 -1
  227. package/types/platform/toolbar/index.native.d.ts +2 -2
  228. package/types/platform/toolbar/index.native.d.ts.map +1 -1
  229. package/types/platform/ui/Icon.android.d.ts +1 -1
  230. package/types/platform/ui/Icon.android.d.ts.map +1 -1
  231. package/types/platform/ui/Image.d.ts +1 -1
  232. package/types/platform/ui/Image.d.ts.map +1 -1
  233. package/types/platform/ui/Image.native.d.ts +1 -1
  234. package/types/platform/ui/Image.native.d.ts.map +1 -1
  235. package/types/platform/ui/Map.android.d.ts +1 -1
  236. package/types/platform/ui/Map.android.d.ts.map +1 -1
  237. package/types/platform/ui/Map.ios.d.ts +1 -1
  238. package/types/platform/ui/Map.ios.d.ts.map +1 -1
  239. package/types/platform/ui/PictureInPicture.d.ts +1 -1
  240. package/types/platform/ui/PictureInPicture.d.ts.map +1 -1
  241. package/types/platform/ui/PictureInPicture.native.d.ts +1 -1
  242. package/types/platform/ui/PictureInPicture.native.d.ts.map +1 -1
  243. package/types/platform/ui/Portal.d.ts +2 -2
  244. package/types/platform/ui/Portal.d.ts.map +1 -1
  245. package/types/platform/ui/Portal.native.d.ts +2 -2
  246. package/types/platform/ui/Portal.native.d.ts.map +1 -1
  247. package/types/platform/universal/TextInput/index.android.d.ts +1 -1
  248. package/types/platform/universal/TextInput/index.android.d.ts.map +1 -1
  249. package/types/platform/universal/TextInput/index.d.ts +1 -1
  250. package/types/platform/universal/TextInput/index.d.ts.map +1 -1
  251. package/types/platform/universal/TextInput/index.ios.d.ts +1 -1
  252. package/types/platform/universal/TextInput/index.ios.d.ts.map +1 -1
  253. package/types/platform/web/DomView.d.ts +1 -1
  254. package/types/platform/web/DomView.d.ts.map +1 -1
  255. package/types/platform/zoom/index.native.d.ts +3 -3
  256. package/types/platform/zoom/index.native.d.ts.map +1 -1
  257. package/types/router/Route.d.ts +1 -1
  258. package/types/router/Route.d.ts.map +1 -1
  259. package/types/router/RouteInfoContext.d.ts +1 -1
  260. package/types/router/RouteInfoContext.d.ts.map +1 -1
  261. package/types/router/linkingConfig.d.ts.map +1 -1
  262. package/types/router/web/WebStackView.d.ts +1 -1
  263. package/types/router/web/WebStackView.d.ts.map +1 -1
  264. package/types/safe-area-context/NativeSafeAreaProvider.d.ts +1 -1
  265. package/types/safe-area-context/NativeSafeAreaProvider.d.ts.map +1 -1
  266. package/types/safe-area-context/NativeSafeAreaProvider.native.d.ts +2 -1
  267. package/types/safe-area-context/NativeSafeAreaProvider.native.d.ts.map +1 -1
  268. package/types/safe-area-context/SafeAreaContext.d.ts +1 -1
  269. package/types/safe-area-context/SafeAreaContext.d.ts.map +1 -1
  270. package/types/server/ServerContextScript.d.ts +1 -1
  271. package/types/server/ServerContextScript.d.ts.map +1 -1
  272. package/types/ui/TabList.d.ts +1 -1
  273. package/types/ui/TabList.d.ts.map +1 -1
  274. package/types/ui/TabSlot.d.ts +3 -3
  275. package/types/ui/TabSlot.d.ts.map +1 -1
  276. package/types/ui/TabTrigger.d.ts +1 -1
  277. package/types/ui/TabTrigger.d.ts.map +1 -1
  278. package/types/ui/Tabs.d.ts +1 -1
  279. package/types/ui/Tabs.d.ts.map +1 -1
  280. package/types/useLoader.d.ts.map +1 -1
  281. package/types/utils/cleanUrl.d.ts +1 -0
  282. package/types/utils/cleanUrl.d.ts.map +1 -1
  283. package/types/views/ErrorBoundary.d.ts +2 -1
  284. package/types/views/ErrorBoundary.d.ts.map +1 -1
  285. package/types/views/ErrorBoundary.web.d.ts +2 -1
  286. package/types/views/ErrorBoundary.web.d.ts.map +1 -1
  287. package/types/views/LoadProgressBar.d.ts +1 -1
  288. package/types/views/LoadProgressBar.d.ts.map +1 -1
  289. package/types/views/LoadProgressBar.web.d.ts +2 -1
  290. package/types/views/LoadProgressBar.web.d.ts.map +1 -1
  291. package/types/views/Navigator.d.ts +6 -6
  292. package/types/views/Navigator.d.ts.map +1 -1
  293. package/types/views/RootErrorBoundary.d.ts +1 -1
  294. package/types/views/RootErrorBoundary.d.ts.map +1 -1
  295. package/types/views/RootErrorBoundary.web.d.ts +1 -1
  296. package/types/views/RootErrorBoundary.web.d.ts.map +1 -1
  297. package/types/views/RouteErrorView.d.ts +1 -1
  298. package/types/views/RouteErrorView.d.ts.map +1 -1
  299. package/types/views/RouteErrorView.web.d.ts +1 -1
  300. package/types/views/RouteErrorView.web.d.ts.map +1 -1
  301. package/types/views/Try.d.ts +1 -1
  302. package/types/views/Try.d.ts.map +1 -1
  303. package/types/vite/DevHead.d.ts +1 -1
  304. package/types/vite/DevHead.d.ts.map +1 -1
  305. package/types/vite/one.d.ts.map +1 -1
  306. package/types/vite/types.d.ts +4 -4
  307. package/src/layouts/stack-navigator.ts +0 -13
@@ -5,7 +5,10 @@ import { createWebStackNavigator } from '../router/web/WebStackNavigator'
5
5
 
6
6
  export const createStackNavigator = createWebStackNavigator
7
7
 
8
- export function getStackNavigatorProps(children: ReactNode) {
8
+ export function getStackNavigatorProps(children: ReactNode, screenLayout?: unknown) {
9
9
  const headlessChildren = getCustomNavigatorChildren(children)
10
- return headlessChildren.length ? { headlessChildren } : {}
10
+ return {
11
+ ...(screenLayout ? { screenLayout } : null),
12
+ ...(headlessChildren.length ? { headlessChildren } : null),
13
+ }
11
14
  }
@@ -27,7 +27,7 @@ export function buildOneMetroResolverOverrides({
27
27
  defaultConfig: T
28
28
  ) => T {
29
29
  const require = module.createRequire(projectRoot + '/')
30
- const emptyPath = require.resolve('@vxrn/vite-plugin-metro/empty', {
30
+ const emptyPath = require.resolve('@vxrn/utils/empty', {
31
31
  paths: [projectRoot],
32
32
  })
33
33
  const projectPackagePath = path.join(projectRoot, 'package.json')
@@ -15,7 +15,7 @@ describe('getViteMetroPluginOptions babel-config coexistence', () => {
15
15
 
16
16
  beforeAll(() => {
17
17
  // place the fixture under the workspace root so Node can walk up to
18
- // node_modules and resolve `one/metro-entry` + `@vxrn/vite-plugin-metro/empty`
18
+ // node_modules and resolve `one/metro-entry` + `@vxrn/utils/empty`
19
19
  const workspaceRoot = path.resolve(__dirname, '../../../../')
20
20
  tmpDir = fs.mkdtempSync(path.join(workspaceRoot, '.tmp-one-vite-metro-test-'))
21
21
  fs.writeFileSync(
@@ -29,7 +29,7 @@ describe('getViteMetroPluginOptions babel-config coexistence', () => {
29
29
  fs.rmSync(tmpDir, { recursive: true, force: true })
30
30
  })
31
31
 
32
- it('explains the missing Metro preset in an isolated app', () => {
32
+ it('explains the missing Metro plugin in an isolated app', () => {
33
33
  const isolatedApp = fs.mkdtempSync(path.join(os.tmpdir(), 'one-metro-no-preset-'))
34
34
  try {
35
35
  expect(() =>
@@ -37,7 +37,9 @@ describe('getViteMetroPluginOptions babel-config coexistence', () => {
37
37
  projectRoot: isolatedApp,
38
38
  relativeRouterRoot: 'app',
39
39
  })
40
- ).toThrow(/Metro requires babel-preset-expo.*Install it/)
40
+ ).toThrow(
41
+ /Metro requires @vxrn\/vite-plugin-metro and babel-preset-expo in the app.*Install with/
42
+ )
41
43
  } finally {
42
44
  fs.rmSync(isolatedApp, { recursive: true, force: true })
43
45
  }
@@ -21,11 +21,17 @@ export function getViteMetroPluginOptions({
21
21
  setupFile?: string | { native?: string; ios?: string; android?: string }
22
22
  }): Parameters<typeof metroPlugin>[0] {
23
23
  const require = createRequire(projectRoot + '/')
24
- try {
25
- require.resolve('babel-preset-expo')
26
- } catch {
24
+ const missing = ['@vxrn/vite-plugin-metro', 'babel-preset-expo'].filter((name) => {
25
+ try {
26
+ require.resolve(name)
27
+ return false
28
+ } catch {
29
+ return true
30
+ }
31
+ })
32
+ if (missing.length) {
27
33
  throw new Error(
28
- `[one] Metro requires babel-preset-expo in the app at ${projectRoot}. Install it with your package manager, or remove native.bundler: 'metro' to use One's default native bundler.`
34
+ `[one] Metro requires ${missing.join(' and ')} in the app at ${projectRoot}. Install with your package manager, or remove native.bundler: 'metro' to use One's default native bundler.`
29
35
  )
30
36
  }
31
37
 
@@ -1,5 +1,4 @@
1
1
  import path from 'node:path'
2
- import { buildMetroConfigInputFromViteConfig } from '@vxrn/vite-plugin-metro'
3
2
  import { getViteMetroPluginOptions } from './getViteMetroPluginOptions'
4
3
  import { loadUserOneOptions } from '../vite/loadConfig'
5
4
 
@@ -93,6 +92,9 @@ export async function withOne(
93
92
  root: path.resolve(loaded?.config?.config?.root ?? projectRoot),
94
93
  } as any
95
94
 
95
+ // imported after getViteMetroPluginOptions so a missing optional peer gets its
96
+ // install message instead of a bare module-not-found
97
+ const { buildMetroConfigInputFromViteConfig } = await import('@vxrn/vite-plugin-metro')
96
98
  const { defaultConfig } = await buildMetroConfigInputFromViteConfig(viteConfig, {
97
99
  ...metroPluginOptions,
98
100
  mainModuleName: 'one/metro-entry',
@@ -13,7 +13,7 @@ import {
13
13
  writeFileSync,
14
14
  } from 'node:fs'
15
15
  import { tmpdir } from 'node:os'
16
- import { dirname, join, resolve } from 'node:path'
16
+ import { dirname, join, relative, resolve } from 'node:path'
17
17
  import { fileURLToPath } from 'node:url'
18
18
  import { describe, expect, it } from 'vitest'
19
19
  import { auditUnpackedManifest, findForbiddenDependencies } from './closure'
@@ -235,6 +235,15 @@ console.log('one/native ok ' + url);
235
235
  const appDir = join(tmp, 'app')
236
236
  mkdirSync(join(appDir, 'app'), { recursive: true })
237
237
  writeFileSync(join(appDir, '.watchmanconfig'), '{}\n')
238
+ // the ejected babel config runs one/babel-preset, whose babel packages are
239
+ // optional peers the app installs (docs/metro-mode)
240
+ const onePeers = JSON.parse(readFileSync(join(oneDir, 'package.json'), 'utf8'))
241
+ .peerDependencies as Record<string, string>
242
+ const babelPresetPeers = Object.fromEntries(
243
+ Object.entries(onePeers).filter(
244
+ ([name]) => name.startsWith('@babel/') || name.startsWith('babel-')
245
+ )
246
+ )
238
247
 
239
248
  writeFileSync(
240
249
  join(appDir, 'package.json'),
@@ -247,12 +256,13 @@ console.log('one/native ok ' + url);
247
256
  vxrn: `file:${packedClosure.vxrn}`,
248
257
  '@vxrn/vite-plugin-metro': `file:${packedClosure['@vxrn/vite-plugin-metro']}`,
249
258
  '@vxrn/utils': `file:${packedClosure['@vxrn/utils']}`,
250
- react: '19.2.3',
251
- 'react-native': '0.87.1',
259
+ react: '19.3.0',
260
+ 'react-native': '0.88.0',
252
261
  'react-native-web': '^0.21.2',
253
262
  vite: '^8.2.2',
254
- metro: '^0.87.0',
255
- 'metro-config': '^0.87.0',
263
+ metro: '^0.87.1',
264
+ 'metro-config': '^0.87.1',
265
+ ...babelPresetPeers,
256
266
  },
257
267
  overrides: Object.fromEntries(
258
268
  Object.entries(packedClosure).map(([name, tarball]) => [
@@ -379,7 +389,7 @@ if (!config.dependencies['react-native-nitro-modules']) {
379
389
  ['-e', "require('./metro.config.cjs').catch((error) => { console.error(error.message); process.exitCode = 1 })"],
380
390
  { cwd: appDir, encoding: 'utf8' }
381
391
  )
382
- ).toThrow(/Metro requires babel-preset-expo.*Install it/)
392
+ ).toThrow(/Metro requires babel-preset-expo in the app.*Install with/)
383
393
 
384
394
  const expoPresetVersion = JSON.parse(
385
395
  readFileSync(join(workspaceRoot, '../node_modules/babel-preset-expo/package.json'), 'utf8')
@@ -440,4 +450,56 @@ if (!config.dependencies['react-native-nitro-modules']) {
440
450
  process.chdir(previousCwd)
441
451
  }
442
452
  })
453
+
454
+ it('packs every files entry and non-dist export target of each published package', () => {
455
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'one-packed-files-')))
456
+ // workspaceRoot here is the packages directory (see packWorkspaceClosure).
457
+ for (const entry of readdirSync(workspaceRoot, { withFileTypes: true })) {
458
+ if (!entry.isDirectory()) continue
459
+ const packageDir = join(workspaceRoot, entry.name)
460
+ const manifestPath = join(packageDir, 'package.json')
461
+ if (!existsSync(manifestPath)) continue
462
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
463
+ if (manifest.private) continue
464
+ const extracted = unpack(packToDir(packageDir, tmp), join(tmp, entry.name))
465
+ const packed: string[] = []
466
+ const collect = (dir: string) => {
467
+ for (const child of readdirSync(dir, { withFileTypes: true })) {
468
+ if (child.isDirectory()) collect(join(dir, child.name))
469
+ else packed.push(relative(extracted, join(dir, child.name)))
470
+ }
471
+ }
472
+ collect(extracted)
473
+ // npm silently ignores files entries that match nothing, so a stale
474
+ // entry looks packed until something references the missing path.
475
+ for (const pattern of manifest.files ?? []) {
476
+ // dist and types only exist after the release build; everything
477
+ // else must already match at least one packed path.
478
+ if (pattern === 'dist' || pattern === 'types') continue
479
+ expect(
480
+ packed.some((file) => file === pattern || file.startsWith(`${pattern}/`)),
481
+ `${manifest.name}: files entry ${pattern} packs nothing`
482
+ ).toBe(true)
483
+ }
484
+ const targets = new Set<string>()
485
+ const walk = (value: unknown) => {
486
+ if (typeof value === 'string' && value.startsWith('./') && !value.includes('*')) {
487
+ if (value !== './') targets.add(value.slice(2))
488
+ } else if (value && typeof value === 'object') {
489
+ // comment keys (// ...) carry placeholder values, not targets.
490
+ Object.entries(value).forEach(([key, entry]) => {
491
+ if (!key.startsWith('//')) walk(entry)
492
+ })
493
+ }
494
+ }
495
+ walk(manifest.exports)
496
+ walk(manifest.bin)
497
+ for (const target of targets) {
498
+ if (target.startsWith('dist/') || target.startsWith('types/')) continue
499
+ expect(packed, `${manifest.name}: export target ${target} is missing`).toContain(
500
+ target
501
+ )
502
+ }
503
+ }
504
+ })
443
505
  }, 180000)
@@ -0,0 +1,21 @@
1
+ // guards for what one ships into real android app builds: the native
2
+ // library must stay 16 kb page aligned.
3
+ import { readFileSync } from 'node:fs'
4
+ import { dirname, join, resolve } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { describe, expect, it } from 'vitest'
7
+
8
+ const here = dirname(fileURLToPath(import.meta.url))
9
+ const oneDir = resolve(here, '../..')
10
+
11
+ describe('shipped android artifacts', () => {
12
+ it('builds the native library 16 kb page aligned', () => {
13
+ // google play requires 16 kb page support for targetSdk 35+, and the
14
+ // pinned ndk 27 defaults to 4 kb. react native's gradle plugin injects
15
+ // this flag into app modules only, never libraries, so the library
16
+ // must pass it to cmake itself or libOne.so fails to load on 16 kb
17
+ // page devices.
18
+ const gradle = readFileSync(join(oneDir, 'android/build.gradle'), 'utf8')
19
+ expect(gradle).toContain('-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON')
20
+ })
21
+ })
@@ -0,0 +1,81 @@
1
+ // guards for what one ships into real ios app builds: the pod must
2
+ // carry a privacy manifest covering the required-reason apis its
3
+ // sources use, and reference only resources that exist.
4
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
5
+ import { dirname, join, resolve } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { describe, expect, it } from 'vitest'
8
+
9
+ const here = dirname(fileURLToPath(import.meta.url))
10
+ const oneDir = resolve(here, '../..')
11
+
12
+ function readSources(dir: string): string[] {
13
+ const out: string[] = []
14
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
15
+ if (entry.name === 'Tests') continue
16
+ const full = join(dir, entry.name)
17
+ if (entry.isDirectory()) out.push(...readSources(full))
18
+ else if (/\.(swift|m|mm|h|cpp)$/.test(entry.name)) out.push(readFileSync(full, 'utf8'))
19
+ }
20
+ return out
21
+ }
22
+
23
+ describe('shipped ios artifacts', () => {
24
+ it('ships a privacy manifest covering the required-reason apis in use', () => {
25
+ const manifestPath = join(oneDir, 'ios/PrivacyInfo.xcprivacy')
26
+ expect(existsSync(manifestPath)).toBe(true)
27
+ const manifest = readFileSync(manifestPath, 'utf8')
28
+ expect(manifest).toContain('<plist version="1.0">')
29
+ // reasons mirror expo's shipped manifests for the same api surface:
30
+ // user defaults (expo-constants), boot time (expo-device), file
31
+ // timestamps and disk space (expo-file-system).
32
+ const expected: Record<string, string[]> = {
33
+ NSPrivacyAccessedAPICategoryUserDefaults: ['CA92.1'],
34
+ NSPrivacyAccessedAPICategorySystemBootTime: ['35F9.1'],
35
+ NSPrivacyAccessedAPICategoryFileTimestamp: ['0A2A.1', '3B52.1'],
36
+ NSPrivacyAccessedAPICategoryDiskSpace: ['E174.1', '85F4.1'],
37
+ }
38
+ for (const [category, reasons] of Object.entries(expected)) {
39
+ expect(manifest).toContain(category)
40
+ for (const reason of reasons) expect(manifest).toContain(reason)
41
+ }
42
+ // every declared category must have a use in the shipped sources, and
43
+ // no other required-reason category may appear without one.
44
+ const sources = readSources(join(oneDir, 'ios'))
45
+ const uses: Record<string, RegExp> = {
46
+ NSPrivacyAccessedAPICategoryUserDefaults: /UserDefaults|AppStorage/,
47
+ NSPrivacyAccessedAPICategorySystemBootTime: /systemUptime|kern\.boottime/,
48
+ NSPrivacyAccessedAPICategoryFileTimestamp:
49
+ /attributesOfItem|\.modificationDate|\.creationDate|\.fileCreationDate/,
50
+ NSPrivacyAccessedAPICategoryDiskSpace:
51
+ /attributesOfFileSystem|systemFreeSize|systemSize|volumeAvailableCapacity/,
52
+ }
53
+ for (const [category, pattern] of Object.entries(uses)) {
54
+ expect(sources.some((source) => pattern.test(source))).toBe(true)
55
+ }
56
+ expect(sources.some((source) => /activeKeyboards/.test(source))).toBe(false)
57
+ // the pod must bundle the manifest, and the npm tarball must include it.
58
+ const podspec = readFileSync(join(oneDir, 'One.podspec'), 'utf8')
59
+ expect(podspec).toContain('PrivacyInfo.xcprivacy')
60
+ const files: string[] = JSON.parse(
61
+ readFileSync(join(oneDir, 'package.json'), 'utf8')
62
+ ).files
63
+ expect(files).toContain('ios')
64
+ })
65
+
66
+ it('references only podspec headers that exist', () => {
67
+ const podspec = readFileSync(join(oneDir, 'One.podspec'), 'utf8')
68
+ const headers = podspec.match(/'ios\/[^']+\.h'/g) ?? []
69
+ expect(headers.length).toBeGreaterThan(0)
70
+ for (const quoted of headers) {
71
+ expect(existsSync(join(oneDir, quoted.slice(1, -1)))).toBe(true)
72
+ }
73
+ for (const generated of [
74
+ 'nitrogen/generated/ios/One+autolinking.rb',
75
+ 'nitrogen/generated/ios/One-Swift-Cxx-Bridge.hpp',
76
+ 'schema.json',
77
+ ]) {
78
+ expect(existsSync(join(oneDir, generated))).toBe(true)
79
+ }
80
+ })
81
+ })
@@ -1,8 +1,52 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { findFocusedRoute } from '@react-navigation/core'
2
+ import { createElement } from 'react'
3
+ import { renderToString } from 'react-dom/server'
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { useLinking } from '../fork/useLinking'
2
6
  import { getMockContext } from '../testing-utils'
3
7
  import { getLinkingConfig } from './getLinkingConfig'
4
8
  import { getRoutes } from './getRoutes'
5
- import { getResolvedLinking, resetLinking, setupLinking } from './linkingConfig'
9
+ import {
10
+ getResolvedLinking,
11
+ getSSRInitialState,
12
+ resetLinking,
13
+ setupLinking,
14
+ } from './linkingConfig'
15
+
16
+ const browser = vi.hoisted<{
17
+ location: URL
18
+ history: {
19
+ state: {
20
+ __tempLocation?: { pathname: string; search?: string; hash?: string }
21
+ __tempKey?: string
22
+ } | null
23
+ }
24
+ }>(() => {
25
+ const browser = {
26
+ history: { state: null },
27
+ location: new URL('https://example.test/'),
28
+ }
29
+ Object.defineProperty(globalThis, 'window', {
30
+ configurable: true,
31
+ value: browser,
32
+ })
33
+ return browser
34
+ })
35
+
36
+ beforeEach(() => {
37
+ browser.history.state = null
38
+ resetLinking()
39
+ })
40
+
41
+ const nestedRoutes = () =>
42
+ getRoutes(
43
+ getMockContext([
44
+ '_layout.tsx',
45
+ 'home/_layout.tsx',
46
+ 'home/[projectId]/_layout.tsx',
47
+ 'home/[projectId]/files/[...path].tsx',
48
+ ])
49
+ )!
6
50
 
7
51
  describe('setupLinking', () => {
8
52
  const routes = () => getRoutes(getMockContext(['_layout.tsx', 'index.tsx']))!
@@ -39,3 +83,101 @@ describe('setupLinking', () => {
39
83
  )
40
84
  })
41
85
  })
86
+
87
+ describe.each([
88
+ { name: 'client', initialize: setupLinking },
89
+ { name: 'SSR', initialize: getSSRInitialState },
90
+ ])('$name initial location', ({ initialize }) => {
91
+ it.each([
92
+ ['/home/retained/files/first/second#details', 'details'],
93
+ ['/home/retained/files/first/second?mode=local#details', 'details'],
94
+ ['/home/retained/files/first/second?mode=left%23right#part%202', 'part%202'],
95
+ ['/home/retained/files/first/second?mode=local', undefined],
96
+ ])('round-trips %s through the actual route parser', (href, fragment) => {
97
+ const tree = nestedRoutes()
98
+ const state = initialize(tree, new URL(href, 'https://example.test'))!
99
+ const focused = findFocusedRoute(state)!
100
+ const linking = getLinkingConfig(tree, true)
101
+
102
+ expect(focused.params).toMatchObject({
103
+ projectId: 'retained',
104
+ path: ['first', 'second'],
105
+ })
106
+ if (fragment === undefined) {
107
+ expect(focused.params).not.toHaveProperty('#')
108
+ } else {
109
+ expect(focused.params).toHaveProperty('#', fragment)
110
+ }
111
+ expect(linking.getPathFromState!(state, linking.config)).toBe(href)
112
+ })
113
+ })
114
+
115
+ it('keeps fragment-specific SSR cache entries separate and clears the fragment', () => {
116
+ const tree = nestedRoutes()
117
+ const base = 'https://example.test/home/retained/files/first/second?mode=local'
118
+ const first = getSSRInitialState(tree, new URL(`${base}#first`))!
119
+ const second = getSSRInitialState(tree, new URL(`${base}#second`))!
120
+ const cleared = getSSRInitialState(tree, new URL(base))!
121
+
122
+ expect(findFocusedRoute(first)?.params).toHaveProperty('#', 'first')
123
+ expect(findFocusedRoute(second)?.params).toHaveProperty('#', 'second')
124
+ expect(findFocusedRoute(cleared)?.params).not.toHaveProperty('#')
125
+ expect(second).not.toBe(first)
126
+ expect(cleared).not.toBe(first)
127
+ expect(getSSRInitialState(tree, new URL(`${base}#first`))).toBe(first)
128
+ })
129
+
130
+ it('restores a masked initial location with its own query and fragment', () => {
131
+ browser.history.state = {
132
+ __tempLocation: {
133
+ pathname: '/home/retained/files/first/second',
134
+ search: '?mode=local',
135
+ hash: '#masked',
136
+ },
137
+ }
138
+ const tree = nestedRoutes()
139
+ const state = setupLinking(tree, new URL('https://example.test/display#visible'))!
140
+ const linking = getLinkingConfig(tree, true)
141
+
142
+ expect(linking.getPathFromState!(state, linking.config)).toBe(
143
+ '/home/retained/files/first/second?mode=local#masked'
144
+ )
145
+ })
146
+
147
+ it('keeps the visible fragment when a mask is configured to unmask on reload', () => {
148
+ browser.history.state = {
149
+ __tempLocation: { pathname: '/other', hash: '#masked' },
150
+ __tempKey: 'unmask',
151
+ }
152
+ const state = setupLinking(
153
+ nestedRoutes(),
154
+ new URL('https://example.test/home/retained/files/first/second#visible')
155
+ )!
156
+
157
+ expect(findFocusedRoute(state)?.params).toHaveProperty('#', 'visible')
158
+ })
159
+
160
+ it('restores the same masked fragment through the browser linking hook', async () => {
161
+ browser.history.state = {
162
+ __tempLocation: {
163
+ pathname: '/home/retained/files/first/second',
164
+ search: '?mode=local',
165
+ hash: '#masked',
166
+ },
167
+ }
168
+ const linking = getLinkingConfig(nestedRoutes(), true)
169
+ let getInitialState: ReturnType<typeof useLinking>['getInitialState'] | undefined
170
+ function Harness() {
171
+ const linkingState = useLinking({ current: null }, linking)
172
+ getInitialState = linkingState.getInitialState
173
+ return null
174
+ }
175
+
176
+ renderToString(createElement(Harness))
177
+ if (!getInitialState) throw new Error('linking hook did not initialize')
178
+ const state = (await getInitialState())!
179
+
180
+ expect(linking.getPathFromState!(state, linking.config)).toBe(
181
+ '/home/retained/files/first/second?mode=local#masked'
182
+ )
183
+ })
@@ -72,7 +72,8 @@ export function getSSRInitialState(
72
72
  ensureBaseLinkingConfig(routeNode, linking)
73
73
  if (!cachedBaseLinkingConfig) return undefined
74
74
 
75
- const path = initialLocation.pathname + (initialLocation.search || '')
75
+ const path =
76
+ initialLocation.pathname + (initialLocation.search || '') + initialLocation.hash
76
77
  if (ssrStateCache.has(path)) return ssrStateCache.get(path)
77
78
 
78
79
  const state = cachedBaseLinkingConfig.getStateFromPath?.(
@@ -101,14 +102,16 @@ export function setupLinking(
101
102
  if (initialLocation) {
102
103
  linkingConfig.getInitialURL = () => initialLocation.toString()
103
104
 
104
- let path = initialLocation.pathname + (initialLocation.search || '')
105
+ let path =
106
+ initialLocation.pathname + (initialLocation.search || '') + initialLocation.hash
105
107
 
106
108
  if (isWebClient) {
107
109
  const historyState = window.history.state
108
110
  if (historyState?.__tempLocation?.pathname && !historyState.__tempKey) {
109
111
  path =
110
112
  historyState.__tempLocation.pathname +
111
- (historyState.__tempLocation.search || '')
113
+ (historyState.__tempLocation.search || '') +
114
+ (historyState.__tempLocation.hash || '')
112
115
  }
113
116
  }
114
117
 
package/src/useLoader.ts CHANGED
@@ -12,9 +12,7 @@ import {
12
12
  getClientMatchesSnapshot,
13
13
  updateMatchLoaderData,
14
14
  } from './useMatches'
15
- import { getLoaderPath } from './utils/cleanUrl'
16
- import { getURL } from './getURL'
17
- import { LOADER_JS_POSTFIX_UNCACHED } from './constants'
15
+ import { getLoaderPath, getNativeLoaderPath } from './utils/cleanUrl'
18
16
  import { dynamicImport } from './utils/dynamicImport'
19
17
  import { weakKey } from './utils/weakKey'
20
18
  import { useServerContext } from './vite/one-server-only'
@@ -513,18 +511,8 @@ export function useLoaderState<
513
511
  // dev: ?platform=native tells vite plugin to return CJS
514
512
  nativeLoaderJSUrl = `${getLoaderPath(currentPath, true)}?platform=native`
515
513
  } else {
516
- // prod: request the .native.js static file directly
517
- // use uncached postfix since metro can't inline ONE_CACHE_KEY.
518
- // must use the static import — a dynamic require('./getURL') is
519
- // left as a runtime string require by metro and resolves to
520
- // undefined in the prod native bundle (crashes the route).
521
- const base = getURL()
522
- const cleanedPath = currentPath
523
- .slice(1)
524
- .replace(/\/$/, '')
525
- .replaceAll('_', '__')
526
- .replaceAll('/', '_')
527
- nativeLoaderJSUrl = `${base}/assets/${cleanedPath}${LOADER_JS_POSTFIX_UNCACHED.replace('.js', '.native.js')}`
514
+ // prod: the uncached .native.js file, since metro can't inline ONE_CACHE_KEY
515
+ nativeLoaderJSUrl = getNativeLoaderPath(currentPath)
528
516
  }
529
517
 
530
518
  try {
@@ -1,7 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest'
2
+ import { LOADER_JS_POSTFIX, NATIVE_LOADER_JS_POSTFIX_UNCACHED } from '../constants'
2
3
  import {
3
4
  encodeReservedFilenameChars,
4
5
  getLoaderPath,
6
+ getNativeLoaderPath,
5
7
  getPathFromLoaderPath,
6
8
  getPreloadPath,
7
9
  } from './cleanUrl'
@@ -153,6 +155,32 @@ describe('NTFS-reserved characters in route-derived names', () => {
153
155
  })
154
156
  })
155
157
 
158
+ describe('getNativeLoaderPath', () => {
159
+ // the uncached .native.js file buildPage writes next to each loader
160
+ const builtNativeLoader = (path: string) =>
161
+ getLoaderPath(path, false).replace(LOADER_JS_POSTFIX, NATIVE_LOADER_JS_POSTFIX_UNCACHED)
162
+
163
+ it.each([
164
+ '/',
165
+ '/docs/intro',
166
+ '/my_page/test',
167
+ '/events/2026-10-17T10:00',
168
+ '/posts/YWJj==',
169
+ '/blog/a:b*c',
170
+ ])('requests the file the build writes for %s', (path) => {
171
+ expect(getNativeLoaderPath(path).endsWith(builtNativeLoader(path))).toBe(true)
172
+ })
173
+
174
+ it('encodes reserved characters in route params', () => {
175
+ expect(getNativeLoaderPath('/events/2026-10-17T10:00')).toMatch(
176
+ /\/assets\/events_2026-10-17T10=3a00_vxrn_loader\.native\.js$/
177
+ )
178
+ expect(getNativeLoaderPath('/posts/YWJj==')).toMatch(
179
+ /\/assets\/posts_YWJj=3d=3d_vxrn_loader\.native\.js$/
180
+ )
181
+ })
182
+ })
183
+
156
184
  describe('getPathFromLoaderPath', () => {
157
185
  it('strips /_one/assets prefix', () => {
158
186
  expect(getPathFromLoaderPath('/_one/assets/docs_intro_999_vxrn_loader.js')).toBe(
@@ -3,6 +3,7 @@ import {
3
3
  isNative,
4
4
  LOADER_JS_POSTFIX,
5
5
  LOADER_JS_POSTFIX_REGEX,
6
+ NATIVE_LOADER_JS_POSTFIX_UNCACHED,
6
7
  PRELOAD_JS_POSTFIX,
7
8
  } from '../constants'
8
9
  import { getURL } from '../getURL'
@@ -56,12 +57,18 @@ export function getLoaderPath(
56
57
  const baseURL = includeUrl ? getURL() : ''
57
58
  const devPath = process.env.NODE_ENV === 'development' ? '/_one' : ''
58
59
 
59
- const currentPathUrl = new URL(currentPath, 'http://example.com')
60
-
61
- const cleanedUrl = cleanUrl(currentPathUrl.pathname.slice(1))
62
60
  const cacheBustSegment = cacheBust ? `_refetch_${cacheBust}_` : ''
63
61
 
64
- return `${baseURL}${devPath}/assets/${cleanedUrl}${cacheBustSegment}${LOADER_JS_POSTFIX}`
62
+ return `${baseURL}${devPath}/assets/${getLoaderFileStem(currentPath)}${cacheBustSegment}${LOADER_JS_POSTFIX}`
63
+ }
64
+
65
+ // native prod reads the uncached .native.js copy the build writes beside each loader
66
+ export function getNativeLoaderPath(currentPath: string) {
67
+ return `${getURL()}/assets/${getLoaderFileStem(currentPath)}${NATIVE_LOADER_JS_POSTFIX_UNCACHED}`
68
+ }
69
+
70
+ function getLoaderFileStem(currentPath: string) {
71
+ return cleanUrl(new URL(currentPath, 'http://example.com').pathname.slice(1))
65
72
  }
66
73
 
67
74
  export function getPathFromLoaderPath(loaderPath: string) {
package/src/vite/one.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { configureVXRNCompilerPlugin } from '@vxrn/compiler'
2
2
  import { resolvePath } from '@vxrn/resolve'
3
- import { type MetroPluginOptions, getPlatformEnvDefine } from '@vxrn/vite-plugin-metro'
3
+ import type { MetroPluginOptions } from '@vxrn/vite-plugin-metro'
4
+ import { getPlatformEnvDefine } from '@vxrn/utils'
4
5
  import events from 'node:events'
5
6
  import { existsSync, readFileSync } from 'node:fs'
6
7
  import path from 'node:path'
@@ -3,7 +3,7 @@ import { join, resolve } from 'node:path'
3
3
  import { pathToFileURL } from 'node:url'
4
4
  import { resolvePath } from '@vxrn/resolve'
5
5
  import { cloudflare } from '@cloudflare/vite-plugin'
6
- import { getPlatformEnvDefine } from '@vxrn/vite-plugin-metro'
6
+ import { getPlatformEnvDefine } from '@vxrn/utils'
7
7
  import { exactRegex } from 'rolldown/filter'
8
8
  import { type Plugin, type PluginOption } from 'vite'
9
9
  import { CACHE_KEY } from '../../constants'
@@ -285,7 +285,7 @@ export function createWorkerdDevPlugins(
285
285
  ? ['/bundledDevClient.mjs', '/assets/_virtual_one-entry.js']
286
286
  : ['/@vite/client', virtalEntryIdClient]
287
287
 
288
- const empty = resolvePath('@vxrn/vite-plugin-metro/empty', root)
288
+ const empty = resolvePath('@vxrn/utils/empty', root)
289
289
  const absorbed = absorbedPackageAliases(root, 'web')
290
290
 
291
291
  const preparePlugin: Plugin = {
package/src/vite/types.ts CHANGED
@@ -674,11 +674,11 @@ export namespace One {
674
674
  api?: VXRNBuildOptions
675
675
 
676
676
  /**
677
- * maximum number of static pages to build concurrently.
678
- * lower this when builds share a machine with other CPU-heavy work.
679
- * `ONE_BUILD_CONCURRENCY` overrides this value.
677
+ * maximum number of static pages to build concurrently. two keeps peak
678
+ * memory near half of eight workers for a modest slowdown; raise it on a
679
+ * machine with memory to spare. `ONE_BUILD_CONCURRENCY` overrides this value.
680
680
  *
681
- * @default Math.min(available CPU cores, 8)
681
+ * @default Math.min(available CPU cores, 2)
682
682
  */
683
683
  concurrency?: number
684
684
 
package/types/Frozen.d.ts CHANGED
@@ -2,5 +2,5 @@ import { type ReactNode } from 'react';
2
2
  export declare function Frozen({ on, children }: {
3
3
  on?: boolean;
4
4
  children: ReactNode;
5
- }): import("react/jsx-runtime").JSX.Element;
5
+ }): import("react").JSX.Element;
6
6
  //# sourceMappingURL=Frozen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Frozen.d.ts","sourceRoot":"","sources":["../src/Frozen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,wBAAgB,MAAM,CAAC,EAAE,EAAU,EAAE,QAAQ,EAAE,EAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,2CASrF"}
1
+ {"version":3,"file":"Frozen.d.ts","sourceRoot":"","sources":["../src/Frozen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,wBAAgB,MAAM,CAAC,EAAE,EAAU,EAAE,QAAQ,EAAE,EAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+BASrF"}