one 1.15.10 → 1.16.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.
Files changed (288) hide show
  1. package/dist/cjs/cli/build.cjs +98 -58
  2. package/dist/cjs/cli/build.native.js +106 -63
  3. package/dist/cjs/cli/build.native.js.map +1 -1
  4. package/dist/cjs/cli/buildPage.cjs +3 -3
  5. package/dist/cjs/cli/buildPage.native.js +3 -3
  6. package/dist/cjs/cli/buildPage.native.js.map +1 -1
  7. package/dist/cjs/constants.cjs +2 -0
  8. package/dist/cjs/constants.native.js +2 -0
  9. package/dist/cjs/constants.native.js.map +1 -1
  10. package/dist/cjs/createAPIRoute.native.js.map +1 -1
  11. package/dist/cjs/createApp.cjs +2 -6
  12. package/dist/cjs/createHandleRequest.cjs +2 -2
  13. package/dist/cjs/createHandleRequest.native.js +2 -2
  14. package/dist/cjs/createHandleRequest.native.js.map +1 -1
  15. package/dist/cjs/hooks.cjs +5 -1
  16. package/dist/cjs/hooks.native.js +7 -1
  17. package/dist/cjs/hooks.native.js.map +1 -1
  18. package/dist/cjs/index.native.js.map +1 -1
  19. package/dist/cjs/metro-config/getViteMetroPluginOptions.cjs +10 -7
  20. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js +13 -10
  21. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  22. package/dist/cjs/metro-config/getViteMetroPluginOptions.test.cjs +23 -0
  23. package/dist/cjs/metro-config/getViteMetroPluginOptions.test.native.js +26 -0
  24. package/dist/cjs/metro-config/getViteMetroPluginOptions.test.native.js.map +1 -0
  25. package/dist/cjs/router/Route.cjs +21 -1
  26. package/dist/cjs/router/Route.native.js +22 -1
  27. package/dist/cjs/router/Route.native.js.map +1 -1
  28. package/dist/cjs/router/interceptRoutes.cjs +6 -6
  29. package/dist/cjs/router/interceptRoutes.native.js +6 -6
  30. package/dist/cjs/router/interceptRoutes.native.js.map +1 -1
  31. package/dist/cjs/router/params.cjs +46 -0
  32. package/dist/cjs/router/params.native.js +70 -0
  33. package/dist/cjs/router/params.native.js.map +1 -0
  34. package/dist/cjs/router/useScreens.cjs +3 -0
  35. package/dist/cjs/router/useScreens.native.js +2 -0
  36. package/dist/cjs/router/useScreens.native.js.map +1 -1
  37. package/dist/cjs/serve-worker.native.js.map +1 -1
  38. package/dist/cjs/server/getServerManifest.cjs +6 -6
  39. package/dist/cjs/server/getServerManifest.native.js +6 -6
  40. package/dist/cjs/server/getServerManifest.native.js.map +1 -1
  41. package/dist/cjs/server/oneServe.cjs +9 -5
  42. package/dist/cjs/server/oneServe.native.js +9 -5
  43. package/dist/cjs/server/oneServe.native.js.map +1 -1
  44. package/dist/cjs/server/workerHandler.cjs +2 -2
  45. package/dist/cjs/server/workerHandler.native.js +2 -2
  46. package/dist/cjs/server/workerHandler.native.js.map +1 -1
  47. package/dist/cjs/skewProtection.cjs +48 -7
  48. package/dist/cjs/skewProtection.native.js +48 -7
  49. package/dist/cjs/skewProtection.native.js.map +1 -1
  50. package/dist/cjs/useLoader.cjs +67 -69
  51. package/dist/cjs/useLoader.native.js +113 -117
  52. package/dist/cjs/useLoader.native.js.map +1 -1
  53. package/dist/cjs/utils/dynamicImport.cjs +3 -1
  54. package/dist/cjs/utils/dynamicImport.native.js +22 -1
  55. package/dist/cjs/utils/dynamicImport.native.js.map +1 -1
  56. package/dist/cjs/utils/toAbsolute.cjs +5 -2
  57. package/dist/cjs/utils/toAbsolute.native.js +6 -1
  58. package/dist/cjs/utils/toAbsolute.native.js.map +1 -1
  59. package/dist/cjs/utils/workerImport.cjs +1 -1
  60. package/dist/cjs/utils/workerImport.native.js +1 -1
  61. package/dist/cjs/utils/workerImport.native.js.map +1 -1
  62. package/dist/cjs/views/RootErrorBoundary.cjs +114 -117
  63. package/dist/cjs/views/RootErrorBoundary.native.js +126 -146
  64. package/dist/cjs/views/RootErrorBoundary.native.js.map +1 -1
  65. package/dist/cjs/views/Try.cjs +17 -18
  66. package/dist/cjs/views/Try.native.js +28 -42
  67. package/dist/cjs/views/Try.native.js.map +1 -1
  68. package/dist/cjs/vite/plugins/criticalCSSPlugin.cjs +2 -1
  69. package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js +2 -1
  70. package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
  71. package/dist/cjs/vite/plugins/criticalCSSPlugin.test.cjs +26 -20
  72. package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js +28 -20
  73. package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
  74. package/dist/cjs/vite/plugins/imageDataPlugin.cjs +3 -2
  75. package/dist/cjs/vite/plugins/imageDataPlugin.native.js +3 -2
  76. package/dist/cjs/vite/plugins/imageDataPlugin.native.js.map +1 -1
  77. package/dist/cjs/vite/plugins/imageDataPlugin.test.cjs +34 -69
  78. package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js +34 -69
  79. package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
  80. package/dist/cjs/vite/plugins/sourceInspectorPlugin.cjs +20 -2
  81. package/dist/cjs/vite/plugins/sourceInspectorPlugin.native.js +23 -2
  82. package/dist/cjs/vite/plugins/sourceInspectorPlugin.native.js.map +1 -1
  83. package/dist/cjs/vite/plugins/sourceInspectorPlugin.test.cjs +21 -0
  84. package/dist/cjs/vite/plugins/sourceInspectorPlugin.test.native.js +28 -0
  85. package/dist/cjs/vite/plugins/sourceInspectorPlugin.test.native.js.map +1 -0
  86. package/dist/cjs/vite/resolveResponse.cjs +7 -2
  87. package/dist/cjs/vite/resolveResponse.native.js +7 -2
  88. package/dist/cjs/vite/resolveResponse.native.js.map +1 -1
  89. package/dist/esm/cli/build.mjs +100 -60
  90. package/dist/esm/cli/build.mjs.map +1 -1
  91. package/dist/esm/cli/build.native.js +108 -65
  92. package/dist/esm/cli/build.native.js.map +1 -1
  93. package/dist/esm/cli/buildPage.mjs +4 -4
  94. package/dist/esm/cli/buildPage.mjs.map +1 -1
  95. package/dist/esm/cli/buildPage.native.js +4 -4
  96. package/dist/esm/cli/buildPage.native.js.map +1 -1
  97. package/dist/esm/constants.mjs +2 -1
  98. package/dist/esm/constants.mjs.map +1 -1
  99. package/dist/esm/constants.native.js +2 -1
  100. package/dist/esm/constants.native.js.map +1 -1
  101. package/dist/esm/createAPIRoute.mjs.map +1 -1
  102. package/dist/esm/createAPIRoute.native.js.map +1 -1
  103. package/dist/esm/createApp.mjs +2 -6
  104. package/dist/esm/createApp.mjs.map +1 -1
  105. package/dist/esm/createHandleRequest.mjs +2 -2
  106. package/dist/esm/createHandleRequest.mjs.map +1 -1
  107. package/dist/esm/createHandleRequest.native.js +2 -2
  108. package/dist/esm/createHandleRequest.native.js.map +1 -1
  109. package/dist/esm/hooks.mjs +5 -1
  110. package/dist/esm/hooks.mjs.map +1 -1
  111. package/dist/esm/hooks.native.js +7 -1
  112. package/dist/esm/hooks.native.js.map +1 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/index.mjs.map +1 -1
  115. package/dist/esm/index.native.js.map +1 -1
  116. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs +9 -7
  117. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs.map +1 -1
  118. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js +12 -10
  119. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  120. package/dist/esm/metro-config/getViteMetroPluginOptions.test.mjs +24 -0
  121. package/dist/esm/metro-config/getViteMetroPluginOptions.test.mjs.map +1 -0
  122. package/dist/esm/metro-config/getViteMetroPluginOptions.test.native.js +24 -0
  123. package/dist/esm/metro-config/getViteMetroPluginOptions.test.native.js.map +1 -0
  124. package/dist/esm/router/Route.mjs +21 -1
  125. package/dist/esm/router/Route.mjs.map +1 -1
  126. package/dist/esm/router/Route.native.js +22 -1
  127. package/dist/esm/router/Route.native.js.map +1 -1
  128. package/dist/esm/router/interceptRoutes.mjs +7 -7
  129. package/dist/esm/router/interceptRoutes.mjs.map +1 -1
  130. package/dist/esm/router/interceptRoutes.native.js +7 -7
  131. package/dist/esm/router/interceptRoutes.native.js.map +1 -1
  132. package/dist/esm/router/params.mjs +21 -0
  133. package/dist/esm/router/params.mjs.map +1 -0
  134. package/dist/esm/router/params.native.js +42 -0
  135. package/dist/esm/router/params.native.js.map +1 -0
  136. package/dist/esm/router/useScreens.mjs +3 -0
  137. package/dist/esm/router/useScreens.mjs.map +1 -1
  138. package/dist/esm/router/useScreens.native.js +2 -0
  139. package/dist/esm/router/useScreens.native.js.map +1 -1
  140. package/dist/esm/serve-worker.mjs.map +1 -1
  141. package/dist/esm/serve-worker.native.js.map +1 -1
  142. package/dist/esm/server/getServerManifest.mjs +6 -6
  143. package/dist/esm/server/getServerManifest.mjs.map +1 -1
  144. package/dist/esm/server/getServerManifest.native.js +6 -6
  145. package/dist/esm/server/getServerManifest.native.js.map +1 -1
  146. package/dist/esm/server/oneServe.mjs +11 -7
  147. package/dist/esm/server/oneServe.mjs.map +1 -1
  148. package/dist/esm/server/oneServe.native.js +11 -7
  149. package/dist/esm/server/oneServe.native.js.map +1 -1
  150. package/dist/esm/server/workerHandler.mjs +2 -2
  151. package/dist/esm/server/workerHandler.mjs.map +1 -1
  152. package/dist/esm/server/workerHandler.native.js +2 -2
  153. package/dist/esm/server/workerHandler.native.js.map +1 -1
  154. package/dist/esm/skewProtection.mjs +48 -8
  155. package/dist/esm/skewProtection.mjs.map +1 -1
  156. package/dist/esm/skewProtection.native.js +48 -8
  157. package/dist/esm/skewProtection.native.js.map +1 -1
  158. package/dist/esm/useLoader.mjs +64 -66
  159. package/dist/esm/useLoader.mjs.map +1 -1
  160. package/dist/esm/useLoader.native.js +112 -116
  161. package/dist/esm/useLoader.native.js.map +1 -1
  162. package/dist/esm/utils/dynamicImport.mjs +1 -1
  163. package/dist/esm/utils/dynamicImport.mjs.map +1 -1
  164. package/dist/esm/utils/dynamicImport.native.js +20 -1
  165. package/dist/esm/utils/dynamicImport.native.js.map +1 -1
  166. package/dist/esm/utils/toAbsolute.mjs +3 -1
  167. package/dist/esm/utils/toAbsolute.mjs.map +1 -1
  168. package/dist/esm/utils/toAbsolute.native.js +5 -1
  169. package/dist/esm/utils/toAbsolute.native.js.map +1 -1
  170. package/dist/esm/utils/workerImport.mjs +2 -2
  171. package/dist/esm/utils/workerImport.mjs.map +1 -1
  172. package/dist/esm/utils/workerImport.native.js +2 -2
  173. package/dist/esm/utils/workerImport.native.js.map +1 -1
  174. package/dist/esm/views/RootErrorBoundary.mjs +114 -117
  175. package/dist/esm/views/RootErrorBoundary.mjs.map +1 -1
  176. package/dist/esm/views/RootErrorBoundary.native.js +126 -146
  177. package/dist/esm/views/RootErrorBoundary.native.js.map +1 -1
  178. package/dist/esm/views/Try.mjs +17 -18
  179. package/dist/esm/views/Try.mjs.map +1 -1
  180. package/dist/esm/views/Try.native.js +28 -42
  181. package/dist/esm/views/Try.native.js.map +1 -1
  182. package/dist/esm/vite/plugins/criticalCSSPlugin.mjs +2 -1
  183. package/dist/esm/vite/plugins/criticalCSSPlugin.mjs.map +1 -1
  184. package/dist/esm/vite/plugins/criticalCSSPlugin.native.js +2 -1
  185. package/dist/esm/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
  186. package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs +26 -20
  187. package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs.map +1 -1
  188. package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js +28 -20
  189. package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
  190. package/dist/esm/vite/plugins/imageDataPlugin.mjs +4 -3
  191. package/dist/esm/vite/plugins/imageDataPlugin.mjs.map +1 -1
  192. package/dist/esm/vite/plugins/imageDataPlugin.native.js +4 -3
  193. package/dist/esm/vite/plugins/imageDataPlugin.native.js.map +1 -1
  194. package/dist/esm/vite/plugins/imageDataPlugin.test.mjs +34 -69
  195. package/dist/esm/vite/plugins/imageDataPlugin.test.mjs.map +1 -1
  196. package/dist/esm/vite/plugins/imageDataPlugin.test.native.js +34 -69
  197. package/dist/esm/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
  198. package/dist/esm/vite/plugins/sourceInspectorPlugin.mjs +19 -3
  199. package/dist/esm/vite/plugins/sourceInspectorPlugin.mjs.map +1 -1
  200. package/dist/esm/vite/plugins/sourceInspectorPlugin.native.js +22 -3
  201. package/dist/esm/vite/plugins/sourceInspectorPlugin.native.js.map +1 -1
  202. package/dist/esm/vite/plugins/sourceInspectorPlugin.test.mjs +22 -0
  203. package/dist/esm/vite/plugins/sourceInspectorPlugin.test.mjs.map +1 -0
  204. package/dist/esm/vite/plugins/sourceInspectorPlugin.test.native.js +26 -0
  205. package/dist/esm/vite/plugins/sourceInspectorPlugin.test.native.js.map +1 -0
  206. package/dist/esm/vite/resolveResponse.mjs +7 -2
  207. package/dist/esm/vite/resolveResponse.mjs.map +1 -1
  208. package/dist/esm/vite/resolveResponse.native.js +7 -2
  209. package/dist/esm/vite/resolveResponse.native.js.map +1 -1
  210. package/expo-plugin.cjs +1 -0
  211. package/package.json +15 -10
  212. package/react-native-commands.cjs +1 -0
  213. package/src/cli/build.ts +156 -99
  214. package/src/cli/buildPage.ts +4 -4
  215. package/src/constants.ts +15 -0
  216. package/src/createAPIRoute.ts +35 -2
  217. package/src/createApp.tsx +2 -6
  218. package/src/createHandleRequest.ts +6 -2
  219. package/src/hooks.tsx +9 -1
  220. package/src/index.ts +8 -1
  221. package/src/metro-config/getViteMetroPluginOptions.test.ts +34 -0
  222. package/src/metro-config/getViteMetroPluginOptions.ts +14 -11
  223. package/src/router/Route.tsx +52 -2
  224. package/src/router/interceptRoutes.ts +7 -7
  225. package/src/router/params.ts +32 -0
  226. package/src/router/useScreens.tsx +18 -0
  227. package/src/serve-worker.ts +4 -2
  228. package/src/server/getServerManifest.ts +11 -7
  229. package/src/server/oneServe.ts +12 -14
  230. package/src/server/workerHandler.ts +13 -2
  231. package/src/skewProtection.ts +45 -5
  232. package/src/useLoader.ts +6 -4
  233. package/src/utils/dynamicImport.ts +2 -2
  234. package/src/utils/toAbsolute.ts +5 -0
  235. package/src/utils/workerImport.ts +2 -2
  236. package/src/views/RootErrorBoundary.tsx +18 -0
  237. package/src/views/Try.tsx +18 -0
  238. package/src/vite/plugins/criticalCSSPlugin.test.ts +34 -24
  239. package/src/vite/plugins/criticalCSSPlugin.ts +3 -2
  240. package/src/vite/plugins/imageDataPlugin.test.ts +39 -83
  241. package/src/vite/plugins/imageDataPlugin.ts +5 -4
  242. package/src/vite/plugins/sourceInspectorPlugin.test.ts +40 -0
  243. package/src/vite/plugins/sourceInspectorPlugin.ts +30 -2
  244. package/src/vite/resolveResponse.ts +6 -2
  245. package/types/cli/build.d.ts.map +1 -1
  246. package/types/constants.d.ts +9 -0
  247. package/types/constants.d.ts.map +1 -1
  248. package/types/createAPIRoute.d.ts +28 -3
  249. package/types/createAPIRoute.d.ts.map +1 -1
  250. package/types/createApp.d.ts.map +1 -1
  251. package/types/createHandleRequest.d.ts +1 -1
  252. package/types/createHandleRequest.d.ts.map +1 -1
  253. package/types/headless-server.d.ts +1 -1
  254. package/types/hooks.d.ts.map +1 -1
  255. package/types/index.d.ts +1 -1
  256. package/types/index.d.ts.map +1 -1
  257. package/types/metro-config/getViteMetroPluginOptions.d.ts +5 -0
  258. package/types/metro-config/getViteMetroPluginOptions.d.ts.map +1 -1
  259. package/types/metro-config/getViteMetroPluginOptions.test.d.ts +2 -0
  260. package/types/metro-config/getViteMetroPluginOptions.test.d.ts.map +1 -0
  261. package/types/router/Route.d.ts +1 -0
  262. package/types/router/Route.d.ts.map +1 -1
  263. package/types/router/params.d.ts +3 -0
  264. package/types/router/params.d.ts.map +1 -0
  265. package/types/router/useScreens.d.ts.map +1 -1
  266. package/types/serve-worker.d.ts +5 -3
  267. package/types/serve-worker.d.ts.map +1 -1
  268. package/types/server/getServerManifest.d.ts.map +1 -1
  269. package/types/server/oneServe.d.ts.map +1 -1
  270. package/types/server/workerHandler.d.ts +1 -1
  271. package/types/server/workerHandler.d.ts.map +1 -1
  272. package/types/skewProtection.d.ts +1 -0
  273. package/types/skewProtection.d.ts.map +1 -1
  274. package/types/useLoader.d.ts.map +1 -1
  275. package/types/utils/dynamicImport.d.ts +2 -0
  276. package/types/utils/dynamicImport.d.ts.map +1 -1
  277. package/types/utils/toAbsolute.d.ts +3 -0
  278. package/types/utils/toAbsolute.d.ts.map +1 -1
  279. package/types/views/RootErrorBoundary.d.ts.map +1 -1
  280. package/types/views/Try.d.ts.map +1 -1
  281. package/types/vite/plugins/criticalCSSPlugin.d.ts.map +1 -1
  282. package/types/vite/plugins/imageDataPlugin.d.ts.map +1 -1
  283. package/types/vite/plugins/sourceInspectorPlugin.d.ts +2 -0
  284. package/types/vite/plugins/sourceInspectorPlugin.d.ts.map +1 -1
  285. package/types/vite/plugins/sourceInspectorPlugin.test.d.ts +2 -0
  286. package/types/vite/plugins/sourceInspectorPlugin.test.d.ts.map +1 -0
  287. package/types/vite/resolveResponse.d.ts +1 -1
  288. package/types/vite/resolveResponse.d.ts.map +1 -1
package/src/cli/build.ts CHANGED
@@ -5,7 +5,13 @@ import { resolvePath } from '@vxrn/resolve'
5
5
  import FSExtra from 'fs-extra'
6
6
  import MicroMatch from 'micromatch'
7
7
  import type { OutputAsset, RolldownOutput } from 'rolldown'
8
- import { type InlineConfig, mergeConfig, build as viteBuild } from 'vite'
8
+ import {
9
+ createBuilder,
10
+ type InlineConfig,
11
+ mergeConfig,
12
+ normalizePath,
13
+ build as viteBuild,
14
+ } from 'vite'
9
15
  import {
10
16
  type ClientManifestEntry,
11
17
  fillOptions,
@@ -19,7 +25,7 @@ import { setServerGlobals } from '../server/setServerGlobals'
19
25
  import { getPathnameFromFilePath } from '../utils/getPathnameFromFilePath'
20
26
  import { getRouterRootFromOneOptions } from '../utils/getRouterRootFromOneOptions'
21
27
  import { isRolldown } from '../utils/isRolldown'
22
- import { toAbsolute } from '../utils/toAbsolute'
28
+ import { toAbsolute, toAbsoluteUrl } from '../utils/toAbsolute'
23
29
  import { buildVercelOutputDirectory } from '../vercel/build/buildVercelOutputDirectory'
24
30
  import { getManifest } from '../vite/getManifest'
25
31
  import { loadUserOneOptions } from '../vite/loadConfig'
@@ -380,24 +386,59 @@ export async function build(args: {
380
386
  const { optimizeDeps } = getOptimizeDeps('build')
381
387
  const { rolldownOptions: _rolldownOptions, ...optimizeDepsNoRolldown } = optimizeDeps
382
388
 
389
+ // unified mode: api + middleware routes share config with the SSR server build —
390
+ // same defines, plugins, externalization rules, no blanket noExternal: true.
391
+ // when off, keep the legacy path that derives from webBuildConfig with
392
+ // ssr.noExternal: true hard-coded (back-compat).
393
+ const serverOpts = oneOptions.build?.server
394
+ const isUnified =
395
+ typeof serverOpts === 'object' && serverOpts !== null && serverOpts.unified === true
396
+
383
397
  // clone plugin hooks so vite's wrapHookObject doesn't fail on reuse across builds
384
398
  // (vite defines non-configurable getters on hook objects during the first build)
385
- const clonedWebBuildConfig = clonePluginHooks(vxrnOutput.webBuildConfig)
399
+ let baseForApi = isUnified
400
+ ? clonePluginHooks(vxrnOutput.serverBuildConfig ?? vxrnOutput.webBuildConfig)
401
+ : clonePluginHooks(vxrnOutput.webBuildConfig)
402
+
403
+ if (isUnified) {
404
+ // serverBuildConfig has rolldownOptions.input: ['virtual:one-entry'] —
405
+ // vite mergeConfig concatenates that with the api build's input object,
406
+ // which rolldown then tries to iterate as a string list. strip it so the
407
+ // per-routes input map in buildCustomRoutes takes effect cleanly.
408
+ //
409
+ // also strip `omit-api-routes`, the server's transform plugin that empties
410
+ // any +api / _middleware file. correct for the SSR render bundle, wrong
411
+ // for api/middleware builds where those files ARE the entry points.
412
+ const clone: InlineConfig = {
413
+ ...baseForApi,
414
+ build: baseForApi.build ? { ...baseForApi.build } : undefined,
415
+ plugins: baseForApi.plugins
416
+ ? (baseForApi.plugins as any[]).filter(
417
+ (p) => p && typeof p === 'object' && p.name !== 'omit-api-routes'
418
+ )
419
+ : undefined,
420
+ }
421
+ if (clone.build && (clone.build as any).rolldownOptions) {
422
+ const ro = { ...((clone.build as any).rolldownOptions as any) }
423
+ delete ro.input
424
+ ;(clone.build as any).rolldownOptions = ro
425
+ }
426
+ if (clone.build) {
427
+ delete (clone.build as any).outDir
428
+ }
429
+ baseForApi = clone
430
+ }
386
431
 
387
- const apiBuildConfig = mergeConfig(
388
- // feels like this should build off the *server* build config not web
389
- clonedWebBuildConfig,
390
- {
391
- configFile: false,
392
- appType: 'custom',
393
- optimizeDeps: optimizeDepsNoRolldown,
394
- environments: {
395
- client: {
396
- optimizeDeps: { rolldownOptions: _rolldownOptions },
397
- },
432
+ const apiBuildConfig = mergeConfig(baseForApi, {
433
+ configFile: false,
434
+ appType: 'custom',
435
+ optimizeDeps: optimizeDepsNoRolldown,
436
+ environments: {
437
+ client: {
438
+ optimizeDeps: { rolldownOptions: _rolldownOptions },
398
439
  },
399
- } satisfies InlineConfig
400
- )
440
+ },
441
+ } satisfies InlineConfig)
401
442
 
402
443
  async function buildCustomRoutes(subFolder: string, routes: RouteInfo<string>[]) {
403
444
  const input = routes.reduce((entries, { page, file }) => {
@@ -413,19 +454,20 @@ export async function build(args: {
413
454
  appType: 'custom',
414
455
  configFile: false,
415
456
 
416
- // plugins: [
417
- // nodeExternals({
418
- // exclude: optimizeDeps.include,
419
- // }) as any,
420
- // ],
421
-
422
457
  define: vxrnOutput!.processEnvDefines,
423
458
 
424
- ssr: {
425
- noExternal: true,
426
- external: ['react', 'react-dom'],
427
- optimizeDeps: optimizeDepsNoRolldown,
428
- },
459
+ ssr: isUnified
460
+ ? {
461
+ // in unified mode let the base (serverBuildConfig) set ssr.noExternal
462
+ // — default is now ['react', 'react-dom'] instead of `true`, so
463
+ // rolldown can externalize the rest.
464
+ optimizeDeps: optimizeDepsNoRolldown,
465
+ }
466
+ : {
467
+ noExternal: true,
468
+ external: ['react', 'react-dom'],
469
+ optimizeDeps: optimizeDepsNoRolldown,
470
+ },
429
471
 
430
472
  environments: {
431
473
  ssr: {
@@ -456,7 +498,10 @@ export async function build(args: {
456
498
  // prevents it from shaking out the exports
457
499
  preserveEntrySignatures: 'strict',
458
500
  input: input,
459
- external: [],
501
+ // in unified mode, inherit externals from serverBuildConfig (user
502
+ // ssr.external / rolldownOptions.external). the legacy path resets
503
+ // them to [] so per-route files bundle everything.
504
+ ...(isUnified ? {} : { external: [] }),
460
505
  output: {
461
506
  entryFileNames: '[name]',
462
507
  exports: 'auto',
@@ -528,7 +573,7 @@ export async function build(args: {
528
573
  if (middlewareBuildInfo) {
529
574
  for (const middleware of manifest.middlewareRoutes) {
530
575
  const absoluteRoot = resolve(process.cwd(), options.root)
531
- const fullPath = join(absoluteRoot, routerRoot, middleware.file)
576
+ const fullPath = normalizePath(join(absoluteRoot, routerRoot, middleware.file))
532
577
  const outChunks = middlewareBuildInfo.output.filter((x) => x.type === 'chunk')
533
578
  const chunk = outChunks.find((x) => x.facadeModuleId === fullPath)
534
579
  if (!chunk) throw new Error(`internal err finding middleware`)
@@ -537,7 +582,7 @@ export async function build(args: {
537
582
  }
538
583
 
539
584
  // for the require Sitemap in getRoutes
540
- globalThis['require'] = createRequire(join(import.meta.url, '..'))
585
+ globalThis['require'] = createRequire(import.meta.dirname + '/')
541
586
 
542
587
  const assets: OutputAsset[] = []
543
588
 
@@ -607,7 +652,10 @@ export async function build(args: {
607
652
  // layout files start with _layout
608
653
  if (file.startsWith('_layout') && id.includes(`/${routerRoot}/`)) {
609
654
  // contextKey format is "./_layout.tsx" or "./subdir/_layout.tsx"
610
- const relativePath = relative(process.cwd(), id).replace(`${routerRoot}/`, '')
655
+ const relativePath = normalizePath(relative(process.cwd(), id)).replace(
656
+ `${routerRoot}/`,
657
+ ''
658
+ )
611
659
  const contextKey = `./${relativePath}`
612
660
  layoutServerPaths.set(contextKey, output.fileName)
613
661
  }
@@ -650,10 +698,8 @@ export async function build(args: {
650
698
  }
651
699
 
652
700
  // resolve the full module path for this route
653
- const routeModulePath = join(
654
- resolve(process.cwd(), options.root),
655
- routerRoot,
656
- foundRoute.file.slice(2)
701
+ const routeModulePath = normalizePath(
702
+ join(resolve(process.cwd(), options.root), routerRoot, foundRoute.file.slice(2))
657
703
  )
658
704
 
659
705
  // find the server chunk containing this route
@@ -942,7 +988,7 @@ export async function build(args: {
942
988
 
943
989
  let exported
944
990
  try {
945
- exported = await import(toAbsolute(serverJsPath))
991
+ exported = await import(toAbsoluteUrl(serverJsPath))
946
992
  } catch (err) {
947
993
  console.error(`Error importing page (original error)`, err)
948
994
  // err cause not showing in vite or something
@@ -1152,11 +1198,22 @@ export async function build(args: {
1152
1198
  }))
1153
1199
  }
1154
1200
 
1155
- // swap out for the built middleware path
1201
+ // swap out for the built middleware path.
1202
+ // page routes have compiled middleware paths attached via buildPage
1203
+ // (buildInfo.middlewares). api routes don't go through buildPage, so fall
1204
+ // back to the builtMiddlewares map keyed by the middleware source file.
1156
1205
  const buildInfo = builtRoutes.find((x) => x.routeFile === route.file)
1157
- if (built.middlewares && buildInfo?.middlewares) {
1206
+ if (built.middlewares) {
1158
1207
  for (const [index, mw] of built.middlewares.entries()) {
1159
- mw.contextKey = buildInfo.middlewares[index]
1208
+ const viaBuildInfo = buildInfo?.middlewares?.[index]
1209
+ if (viaBuildInfo) {
1210
+ mw.contextKey = viaBuildInfo
1211
+ continue
1212
+ }
1213
+ const viaMiddlewareMap = builtMiddlewares[mw.contextKey]
1214
+ if (viaMiddlewareMap) {
1215
+ mw.contextKey = viaMiddlewareMap
1216
+ }
1160
1217
  }
1161
1218
  }
1162
1219
 
@@ -1244,7 +1301,14 @@ export async function build(args: {
1244
1301
 
1245
1302
  case 'cloudflare': {
1246
1303
  // Generate lazy import functions - modules load on-demand, not all upfront
1247
- // Uses find_additional_modules in wrangler config to keep modules separate
1304
+ // The worker config keeps route modules separate so they can stay lazy.
1305
+ const workerSrcDir = join(options.root, outDir)
1306
+ const getWorkerSourceImportPath = (routeFile: string) => {
1307
+ const importPath = normalizePath(
1308
+ relative(workerSrcDir, join(options.root, routerRoot, routeFile))
1309
+ )
1310
+ return importPath.startsWith('.') ? importPath : `./${importPath}`
1311
+ }
1248
1312
  const pageRouteMap: string[] = []
1249
1313
  const apiRouteMap: string[] = []
1250
1314
  const middlewareRouteMap: string[] = []
@@ -1263,19 +1327,18 @@ export async function build(args: {
1263
1327
  // Generate lazy imports for API routes
1264
1328
  for (const route of buildInfoForWriting.manifest.apiRoutes) {
1265
1329
  if (route.file) {
1266
- // API files are built to dist/api/
1267
- // route.page is like "/api/hello", files are at "dist/api/api/hello.js"
1268
- // both vite and rolldown-vite replace brackets with underscores in output filenames
1269
- const apiFileName = route.page.slice(1).replace(/\[/g, '_').replace(/\]/g, '_')
1270
- const importPath = `./api/${apiFileName}.js`
1330
+ // Import API routes from source so the Cloudflare plugin can apply its
1331
+ // own unenv/CJS transforms instead of re-bundling pre-built chunks.
1332
+ const importPath = getWorkerSourceImportPath(route.file)
1271
1333
  apiRouteMap.push(` '${route.page}': () => import('${importPath}')`)
1272
1334
  }
1273
1335
  }
1274
1336
 
1275
1337
  // Generate lazy imports for middlewares
1276
- // The key must match the contextKey used to look up the middleware (e.g., "dist/middlewares/_middleware.js")
1277
- for (const [, builtPath] of Object.entries(builtMiddlewares)) {
1278
- const importPath = './' + builtPath.replace(new RegExp(`^${outDir}/`), '')
1338
+ // Keep the built output path as the lookup key, but import the source
1339
+ // file so the plugin owns the middleware dependency graph too.
1340
+ for (const [sourceFile, builtPath] of Object.entries(builtMiddlewares)) {
1341
+ const importPath = getWorkerSourceImportPath(sourceFile)
1279
1342
  middlewareRouteMap.push(` '${builtPath}': () => import('${importPath}')`)
1280
1343
  }
1281
1344
 
@@ -1334,7 +1397,7 @@ export default {
1334
1397
  }
1335
1398
 
1336
1399
  try {
1337
- const response = await server.fetch(request)
1400
+ const response = await server.fetch(request, env, ctx)
1338
1401
 
1339
1402
  // no route matched or 404 → try static assets
1340
1403
  if (!response || response.status === 404) {
@@ -1359,45 +1422,41 @@ export default {
1359
1422
  `
1360
1423
  await FSExtra.writeFile(workerSrcPath, workerCode)
1361
1424
 
1362
- // Bundle the worker using Vite/esbuild
1363
- // Cloudflare Workers with nodejs_compat supports Node.js built-ins
1425
+ const projectName = await getCloudflareProjectName(options.root)
1426
+ const userWranglerConfig = await loadUserWranglerConfig(options.root)
1427
+ const wranglerInputConfig = createCloudflareWranglerConfig(
1428
+ projectName,
1429
+ userWranglerConfig?.config
1430
+ )
1431
+ wranglerInputConfig.main = relative(join(options.root, outDir), workerSrcPath)
1432
+
1433
+ const wranglerInputPath = join(options.root, outDir, '_wrangler.input.jsonc')
1434
+ await FSExtra.writeFile(
1435
+ wranglerInputPath,
1436
+ `${JSON.stringify(wranglerInputConfig, null, 2)}\n`
1437
+ )
1438
+
1439
+ // Bundle the worker using Cloudflare's Vite plugin so we pick up unenv
1440
+ // polyfills and esmExternalRequirePlugin for Node-first CJS deps.
1364
1441
  console.info('\n [cloudflare] Bundling worker...')
1365
- await viteBuild({
1442
+ const { cloudflare } = await import('@cloudflare/vite-plugin')
1443
+ const builder = await createBuilder({
1366
1444
  root: options.root,
1367
1445
  mode: 'production',
1368
1446
  logLevel: 'warn',
1369
- build: {
1370
- outDir,
1371
- emptyOutDir: false,
1372
- // Use SSR mode with node target for proper Node.js module resolution
1373
- ssr: workerSrcPath,
1374
- rolldownOptions: {
1375
- external: [
1376
- // React Native dev tools - not needed in production
1377
- '@react-native/dev-middleware',
1378
- '@react-native/debugger-shell',
1379
- 'metro',
1380
- 'metro-core',
1381
- 'metro-runtime',
1382
- // Native modules that can't run in workers
1383
- /\.node$/,
1384
- ],
1385
- output: {
1386
- entryFileNames: 'worker.js',
1387
- format: 'es',
1388
- // Keep dynamic imports separate for lazy loading
1389
- inlineDynamicImports: false,
1390
- },
1391
- },
1392
- minify: true,
1393
- target: 'esnext',
1394
- },
1447
+ configFile: false,
1395
1448
  define: {
1396
1449
  'process.env.NODE_ENV': JSON.stringify('production'),
1397
1450
  'process.env.VITE_ENVIRONMENT': JSON.stringify('ssr'),
1451
+ 'process.env.ONE_CACHE_KEY': JSON.stringify(constants.CACHE_KEY),
1398
1452
  },
1453
+ plugins: [
1454
+ cloudflare({
1455
+ configPath: wranglerInputPath,
1456
+ viteEnvironment: { name: 'worker' },
1457
+ }),
1458
+ ],
1399
1459
  resolve: {
1400
- conditions: ['workerd', 'worker', 'node', 'module', 'default'],
1401
1460
  alias: [
1402
1461
  // rolldown can't parse react-native's Flow syntax; alias to react-native-web for ssr
1403
1462
  {
@@ -1418,37 +1477,35 @@ export default {
1418
1477
  },
1419
1478
  ],
1420
1479
  },
1421
- ssr: {
1422
- target: 'node',
1423
- noExternal: true,
1480
+ build: {
1481
+ outDir,
1482
+ emptyOutDir: false,
1483
+ rolldownOptions: {
1484
+ // Match the main web build behavior so RN packages that import
1485
+ // native-only symbols from react-native can still bundle against
1486
+ // the react-native-web alias in the worker graph.
1487
+ shimMissingExports: true,
1488
+ },
1424
1489
  },
1425
1490
  })
1491
+ const workerEnv = builder.environments.worker
1492
+ if (!workerEnv) {
1493
+ throw new Error('[one] plugin did not register "worker" environment')
1494
+ }
1495
+ await builder.build(workerEnv)
1426
1496
 
1427
1497
  // Clean up temp file
1428
1498
  await FSExtra.remove(workerSrcPath)
1429
-
1430
- // Use jsonc for wrangler config (recommended for new projects)
1431
- // Use assets with run_worker_first so all requests go through worker (enables middleware on SSG pages)
1432
- const projectName = await getCloudflareProjectName(options.root)
1433
- const userWranglerConfig = await loadUserWranglerConfig(options.root)
1434
- const wranglerConfig = createCloudflareWranglerConfig(
1435
- projectName,
1436
- userWranglerConfig?.config
1437
- )
1499
+ await FSExtra.remove(wranglerInputPath)
1438
1500
 
1439
1501
  if (userWranglerConfig) {
1440
1502
  console.info(
1441
- ` [cloudflare] Merging ${relative(options.root, userWranglerConfig.path)} into ${outDir}/wrangler.jsonc`
1503
+ ` [cloudflare] Merging ${relative(options.root, userWranglerConfig.path)} into ${outDir}/worker/wrangler.json`
1442
1504
  )
1443
1505
  }
1444
1506
 
1445
- await FSExtra.writeFile(
1446
- join(options.root, outDir, 'wrangler.jsonc'),
1447
- `${JSON.stringify(wranglerConfig, null, 2)}\n`
1448
- )
1449
-
1450
- postBuildLogs.push(`Cloudflare worker bundled at ${outDir}/worker.js`)
1451
- postBuildLogs.push(`To deploy: cd ${outDir} && wrangler deploy`)
1507
+ postBuildLogs.push(`Cloudflare worker bundled at ${outDir}/worker/index.js`)
1508
+ postBuildLogs.push(`To deploy: cd ${outDir}/worker && wrangler deploy`)
1452
1509
 
1453
1510
  break
1454
1511
  }
@@ -5,7 +5,7 @@ import { LOADER_JS_POSTFIX_UNCACHED } from '../constants'
5
5
  import type { LoaderProps } from '../types'
6
6
  import { getLoaderPath, getPreloadCSSPath, getPreloadPath } from '../utils/cleanUrl'
7
7
  import { isResponse } from '../utils/isResponse'
8
- import { toAbsolute } from '../utils/toAbsolute'
8
+ import { toAbsolute, toAbsoluteUrl } from '../utils/toAbsolute'
9
9
  import { replaceLoader } from '../vite/replaceLoader'
10
10
  import type { One, RouteInfo } from '../vite/types'
11
11
 
@@ -174,7 +174,7 @@ prefetchCSS()
174
174
  recordTiming('writeCSSPreload', performance.now() - t0)
175
175
 
176
176
  t0 = performance.now()
177
- const exported = await import(toAbsolute(serverJsPath))
177
+ const exported = await import(toAbsoluteUrl(serverJsPath))
178
178
  recordTiming('importServerModule', performance.now() - t0)
179
179
 
180
180
  const loaderProps: LoaderProps = { path, params }
@@ -195,7 +195,7 @@ prefetchCSS()
195
195
  // derive server dir from clientDir (e.g. dist/client -> dist/server)
196
196
  const serverDir = join(clientDir, '..', 'server')
197
197
  const layoutExported = await import(
198
- toAbsolute(join(serverDir, layoutServerPath))
198
+ toAbsoluteUrl(join(serverDir, layoutServerPath))
199
199
  )
200
200
  const layoutLoaderData = await layoutExported?.loader?.(loaderProps)
201
201
  return { contextKey: layout.contextKey, loaderData: layoutLoaderData }
@@ -478,7 +478,7 @@ params:\n\n${JSON.stringify(params || null, null, 2)}`
478
478
 
479
479
  async function getRender(serverEntry: string) {
480
480
  try {
481
- const serverImport = await import(serverEntry)
481
+ const serverImport = await import(toAbsoluteUrl(serverEntry))
482
482
 
483
483
  const render =
484
484
  serverImport.default.render ||
package/src/constants.ts CHANGED
@@ -3,10 +3,25 @@ import type { One } from './vite/types'
3
3
 
4
4
  export const isWebClient =
5
5
  process.env.TAMAGUI_TARGET !== 'native' && typeof window !== 'undefined'
6
+
6
7
  export const isWebServer =
7
8
  process.env.TAMAGUI_TARGET !== 'native' && typeof window === 'undefined'
9
+
8
10
  export const isNative = process.env.TAMAGUI_TARGET === 'native'
9
11
 
12
+ /**
13
+ * True only in a browser main-thread context with a navigable history —
14
+ * i.e. `window` AND `window.history` are available. Excludes native,
15
+ * SSR, web workers / service workers (no `window`), and exotic sandboxed
16
+ * environments where `window` exists but history is stripped. Use this
17
+ * for guarding any `window.history` / `window.location` access so
18
+ * intercept routes, URL masking, etc. don't assume a full browser.
19
+ */
20
+ export const hasWebHistory =
21
+ isWebClient &&
22
+ typeof window.history !== 'undefined' &&
23
+ typeof window.location !== 'undefined'
24
+
10
25
  export const CACHE_KEY = `${process.env.ONE_CACHE_KEY ?? Math.round(Math.random() * 100_000_000)}`
11
26
 
12
27
  export const LOADER_JS_POSTFIX_UNCACHED = `_vxrn_loader.js`
@@ -2,9 +2,40 @@ import type { OneRouter } from './interfaces/router'
2
2
 
3
3
  type MaybePromise<T> = T | Promise<T>
4
4
 
5
+ /**
6
+ * Minimal shape of the Cloudflare Workers `ExecutionContext`.
7
+ * Use `context.worker?.executionCtx?.waitUntil(promise)` from an API handler
8
+ * to keep the worker alive for fire-and-forget background work after the
9
+ * response has been sent.
10
+ */
11
+ export type WorkerExecutionContext = {
12
+ waitUntil: (promise: Promise<unknown>) => void
13
+ passThroughOnException: () => void
14
+ }
15
+
16
+ /** Cloudflare Workers `env` bindings. Shape is app-specific; `unknown` by default. */
17
+ export type WorkerEnv = Record<string, unknown>
18
+
19
+ /**
20
+ * Worker-runtime context surfaced on API handlers when running under an edge
21
+ * runtime (Cloudflare Workers). Undefined in dev/Node.
22
+ */
23
+ export type WorkerContext = {
24
+ env?: WorkerEnv
25
+ executionCtx?: WorkerExecutionContext
26
+ }
27
+
28
+ export type APIRouteContext<
29
+ Params extends Record<string, string> = Record<string, string>,
30
+ > = {
31
+ params: Params
32
+ /** Present on worker runtimes (Cloudflare Workers). Undefined in dev/Node. */
33
+ worker?: WorkerContext
34
+ }
35
+
5
36
  type APIRouteHandler<Params extends Record<string, string> = Record<string, string>> = (
6
37
  request: Request,
7
- context: { params: Params }
38
+ context: APIRouteContext<Params>
8
39
  ) => MaybePromise<Response>
9
40
 
10
41
  /**
@@ -15,8 +46,10 @@ type APIRouteHandler<Params extends Record<string, string> = Record<string, stri
15
46
  * // app/api/users/[id]+api.ts
16
47
  * import { createAPIRoute } from 'one'
17
48
  *
18
- * export const GET = createAPIRoute<'/api/users/[id]'>((request, { params }) => {
49
+ * export const GET = createAPIRoute<'/api/users/[id]'>((request, { params, worker }) => {
19
50
  * // params.id is typed as string
51
+ * // on cloudflare workers, worker.executionCtx.waitUntil() keeps background work alive
52
+ * worker?.executionCtx?.waitUntil(trackEvent(params.id))
20
53
  * return Response.json({ id: params.id })
21
54
  * })
22
55
  * ```
package/src/createApp.tsx CHANGED
@@ -8,6 +8,7 @@ import { render } from './render'
8
8
  import { initClientMatches } from './router/router'
9
9
  import { registerPreloadedRoute } from './router/useViteRoutes'
10
10
  import { setupSkewProtection } from './skewProtection'
11
+ import { handleSkewError } from './utils/dynamicImport'
11
12
  import { findRootLayout } from './utils/findRootLayout'
12
13
  import type { RenderAppProps } from './types'
13
14
  import { getServerHeadInsertions } from './useServerHeadInsertion'
@@ -241,12 +242,7 @@ export function createApp(options: CreateAppProps) {
241
242
  if (typeof window !== 'undefined' && process.env.ONE_SKEW_PROTECTION !== 'false') {
242
243
  window.addEventListener('vite:preloadError', (e) => {
243
244
  e.preventDefault()
244
- const key = '__one_skew_reload'
245
- const last = sessionStorage.getItem(key)
246
- if (!last || Date.now() - Number(last) > 10_000) {
247
- sessionStorage.setItem(key, String(Date.now()))
248
- window.location.reload()
249
- }
245
+ handleSkewError()
250
246
  })
251
247
  }
252
248
 
@@ -120,7 +120,9 @@ export async function resolveAPIRoute(
120
120
  handlers: RequestHandlers,
121
121
  request: Request,
122
122
  url: URL,
123
- route: RouteInfoCompiled
123
+ route: RouteInfoCompiled,
124
+ env?: unknown,
125
+ executionCtx?: unknown
124
126
  ) {
125
127
  const { pathname } = url
126
128
  const params = getRouteParams(pathname, route)
@@ -145,7 +147,9 @@ export async function resolveAPIRoute(
145
147
  },
146
148
  }),
147
149
  request,
148
- params || {}
150
+ params || {},
151
+ env,
152
+ executionCtx
149
153
  )
150
154
  } catch (err) {
151
155
  if (isResponse(err)) {
package/src/hooks.tsx CHANGED
@@ -2,6 +2,7 @@ import React, { createContext, useContext } from 'react'
2
2
  import type { OneRouter } from './interfaces/router'
3
3
  import { router } from './router/imperative-api'
4
4
  import { RouteParamsContext, useRouteNode } from './router/Route'
5
+ import { mergeDynamicParams } from './router/params'
5
6
  import { RouteInfoContext } from './router/RouteInfoContext'
6
7
  import { navigationRef, useStoreRootState, useStoreRouteInfo } from './router/router'
7
8
  import { getServerContext } from './vite/one-server-only'
@@ -177,7 +178,14 @@ export const useGlobalSearchParams = useActiveParams
177
178
  * ```
178
179
  */
179
180
  export function useParams<TParams extends object = SearchParams>(): Partial<TParams> {
180
- const params = React.useContext(RouteParamsContext) ?? {}
181
+ const contextParams = React.useContext(RouteParamsContext) ?? {}
182
+ const routeNode = useRouteNode()
183
+ const routeInfoParams = useRouteInfo().params
184
+
185
+ const params = React.useMemo(
186
+ () => mergeDynamicParams(contextParams, routeNode?.dynamic, routeInfoParams),
187
+ [contextParams, routeInfoParams, routeNode]
188
+ )
181
189
 
182
190
  return React.useMemo(() => {
183
191
  return Object.fromEntries(
package/src/index.ts CHANGED
@@ -53,7 +53,14 @@ export { onClientLoaderResolve } from './clientLoaderResolver'
53
53
  // middleware
54
54
  export { createMiddleware, type Middleware } from './createMiddleware'
55
55
  // api routes
56
- export { createAPIRoute, type APIRouteHandler } from './createAPIRoute'
56
+ export {
57
+ createAPIRoute,
58
+ type APIRouteContext,
59
+ type APIRouteHandler,
60
+ type WorkerContext,
61
+ type WorkerEnv,
62
+ type WorkerExecutionContext,
63
+ } from './createAPIRoute'
57
64
  export { getURL } from './getURL'
58
65
  export { Head } from './head'
59
66
  // for easier expo-router migration
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { normalizeReSource } from './getViteMetroPluginOptions'
3
+
4
+ // String.raw avoids double-escaping: String.raw`[\\/]` is the 5-char string [ \ \ / ]
5
+ // which is exactly what regex.source produces for /[\\/]/
6
+
7
+ describe('normalizeReSource', () => {
8
+ it(String.raw`[\\/] to \/`, () => {
9
+ expect(normalizeReSource(String.raw`[\\/]`)).toBe(String.raw`\/`)
10
+ })
11
+
12
+ it(String.raw`[^\\/] to [^/]`, () => {
13
+ expect(normalizeReSource(String.raw`[^\\/]`)).toBe('[^/]')
14
+ })
15
+
16
+ it('full Windows micromatch regex', () => {
17
+ // micromatch.makeRe('**/*.web.(ts|tsx)').source on Windows (picomatch 2.x)
18
+ const windowsSource = String.raw`^(?:(?:^|[\\/]|(?:(?:(?!(?:^|[\\/])\.).)*?)[\\/])(?!\.)(?=.)[^\\/]*?\.web\.(ts|tsx))$`
19
+ const posixSource = String.raw`^(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.web\.(ts|tsx))$`
20
+
21
+ expect(normalizeReSource(windowsSource)).toBe(posixSource)
22
+ })
23
+
24
+ it('no-op for POSIX regex', () => {
25
+ const posixSource = String.raw`^(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.web\.(ts|tsx))$`
26
+
27
+ expect(normalizeReSource(posixSource)).toBe(posixSource)
28
+ })
29
+
30
+ it('multiple groups in one source', () => {
31
+ const source = String.raw`a[\\/]b[\\/]c[^\\/]d`
32
+ expect(normalizeReSource(source)).toBe(String.raw`a\/b\/c[^/]d`)
33
+ })
34
+ })