vxrn 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.
Files changed (208) hide show
  1. package/dist/config/getAdditionalViteConfig.mjs +1 -4
  2. package/dist/config/getAdditionalViteConfig.mjs.map +1 -1
  3. package/dist/config/getAdditionalViteConfig.native.js +1 -4
  4. package/dist/config/getAdditionalViteConfig.native.js.map +1 -1
  5. package/dist/config/getReactNativePlugins.mjs +1 -5
  6. package/dist/config/getReactNativePlugins.mjs.map +1 -1
  7. package/dist/config/getReactNativePlugins.native.js +1 -5
  8. package/dist/config/getReactNativePlugins.native.js.map +1 -1
  9. package/dist/exports/build.mjs +5 -0
  10. package/dist/exports/build.mjs.map +1 -1
  11. package/dist/exports/build.native.js +5 -0
  12. package/dist/exports/build.native.js.map +1 -1
  13. package/dist/exports/createServer.mjs +30 -25
  14. package/dist/exports/createServer.mjs.map +1 -1
  15. package/dist/exports/createServer.native.js +6 -3
  16. package/dist/exports/createServer.native.js.map +1 -1
  17. package/dist/exports/prebuild.mjs +25 -1
  18. package/dist/exports/prebuild.mjs.map +1 -1
  19. package/dist/exports/prebuild.native.js +30 -1
  20. package/dist/exports/prebuild.native.js.map +1 -1
  21. package/dist/exports/serve.mjs +2 -2
  22. package/dist/exports/serve.mjs.map +1 -1
  23. package/dist/exports/serve.native.js +2 -2
  24. package/dist/exports/serve.native.js.map +1 -1
  25. package/dist/exports/serveStaticAssets.mjs +43 -11
  26. package/dist/exports/serveStaticAssets.mjs.map +1 -1
  27. package/dist/exports/serveStaticAssets.native.js +69 -6
  28. package/dist/exports/serveStaticAssets.native.js.map +1 -1
  29. package/dist/index.js +1 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1 -2
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/index.native.js +1 -2
  34. package/dist/index.native.js.map +1 -1
  35. package/dist/patches/builtInDepPatches.mjs +14 -0
  36. package/dist/patches/builtInDepPatches.mjs.map +1 -1
  37. package/dist/patches/builtInDepPatches.native.js +14 -0
  38. package/dist/patches/builtInDepPatches.native.js.map +1 -1
  39. package/dist/plugins/clientInjectPlugin.mjs +2 -62
  40. package/dist/plugins/clientInjectPlugin.mjs.map +1 -1
  41. package/dist/plugins/clientInjectPlugin.native.js +2 -75
  42. package/dist/plugins/clientInjectPlugin.native.js.map +1 -1
  43. package/dist/plugins/reactNativeDevAssetPlugin.mjs +1 -3
  44. package/dist/plugins/reactNativeDevAssetPlugin.mjs.map +1 -1
  45. package/dist/plugins/reactNativeDevAssetPlugin.native.js +1 -3
  46. package/dist/plugins/reactNativeDevAssetPlugin.native.js.map +1 -1
  47. package/dist/plugins/reactNativeDevServer.mjs +55 -77
  48. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  49. package/dist/plugins/reactNativeDevServer.native.js +73 -84
  50. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  51. package/dist/plugins/serverExtensions.test.mjs +12 -6
  52. package/dist/plugins/serverExtensions.test.mjs.map +1 -1
  53. package/dist/plugins/serverExtensions.test.native.js +12 -6
  54. package/dist/plugins/serverExtensions.test.native.js.map +1 -1
  55. package/dist/rn-commands/bundle/buildBundle.mjs +11 -35
  56. package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
  57. package/dist/rn-commands/bundle/buildBundle.native.js +9 -36
  58. package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
  59. package/dist/runtime/hmr-client.mjs +125 -0
  60. package/dist/runtime/hmr-client.mjs.map +1 -0
  61. package/dist/runtime/hmr-client.native.js +197 -0
  62. package/dist/runtime/hmr-client.native.js.map +1 -0
  63. package/dist/runtime/hmr-runtime.mjs +162 -0
  64. package/dist/runtime/hmr-runtime.mjs.map +1 -0
  65. package/dist/runtime/hmr-runtime.native.js +348 -0
  66. package/dist/runtime/hmr-runtime.native.js.map +1 -0
  67. package/dist/runtime/hmr-types.mjs +2 -0
  68. package/dist/runtime/hmr-types.mjs.map +1 -0
  69. package/dist/runtime/hmr-types.native.js +2 -0
  70. package/dist/runtime/hmr-types.native.js.map +1 -0
  71. package/dist/runtime/native-prelude.mjs +97 -0
  72. package/dist/runtime/native-prelude.mjs.map +1 -0
  73. package/dist/runtime/native-prelude.native.js +97 -0
  74. package/dist/runtime/native-prelude.native.js.map +1 -0
  75. package/dist/runtime/react-refresh-utils.mjs +19 -0
  76. package/dist/runtime/react-refresh-utils.mjs.map +1 -0
  77. package/dist/runtime/react-refresh-utils.native.js +24 -0
  78. package/dist/runtime/react-refresh-utils.native.js.map +1 -0
  79. package/dist/utils/createNativeDevEngine.mjs +661 -0
  80. package/dist/utils/createNativeDevEngine.mjs.map +1 -0
  81. package/dist/utils/createNativeDevEngine.native.js +702 -0
  82. package/dist/utils/createNativeDevEngine.native.js.map +1 -0
  83. package/dist/utils/patches.mjs +6 -2
  84. package/dist/utils/patches.mjs.map +1 -1
  85. package/dist/utils/patches.native.js +6 -2
  86. package/dist/utils/patches.native.js.map +1 -1
  87. package/dist/utils/scanDepsToOptimize.mjs +4 -3
  88. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  89. package/dist/utils/scanDepsToOptimize.native.js +4 -3
  90. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  91. package/expo-plugin.cjs +122 -0
  92. package/package.json +15 -19
  93. package/src/config/getAdditionalViteConfig.ts +1 -3
  94. package/src/config/getReactNativePlugins.ts +0 -6
  95. package/src/exports/build.ts +5 -0
  96. package/src/exports/createServer.ts +7 -2
  97. package/src/exports/prebuild.ts +45 -0
  98. package/src/exports/serve.ts +2 -1
  99. package/src/exports/serveStaticAssets.ts +67 -4
  100. package/src/index.ts +0 -2
  101. package/src/patches/builtInDepPatches.ts +29 -0
  102. package/src/plugins/clientInjectPlugin.ts +2 -109
  103. package/src/plugins/reactNativeDevAssetPlugin.ts +0 -21
  104. package/src/plugins/reactNativeDevServer.ts +57 -84
  105. package/src/plugins/serverExtensions.test.ts +6 -8
  106. package/src/rn-commands/bundle/buildBundle.ts +9 -62
  107. package/src/runtime/hmr-client.ts +215 -0
  108. package/src/runtime/hmr-runtime.ts +276 -0
  109. package/src/runtime/hmr-types.ts +84 -0
  110. package/src/runtime/native-prelude.ts +110 -0
  111. package/src/runtime/react-refresh-utils.ts +36 -0
  112. package/src/types.ts +22 -4
  113. package/src/utils/createNativeDevEngine.ts +942 -0
  114. package/src/utils/patches.ts +36 -18
  115. package/src/utils/scanDepsToOptimize.ts +2 -3
  116. package/types/config/getAdditionalViteConfig.d.ts.map +1 -1
  117. package/types/config/getOptionsFilled.d.ts +2 -18
  118. package/types/config/getOptionsFilled.d.ts.map +1 -1
  119. package/types/config/getReactNativePlugins.d.ts.map +1 -1
  120. package/types/exports/build.d.ts +1 -9
  121. package/types/exports/build.d.ts.map +1 -1
  122. package/types/exports/createServer.d.ts.map +1 -1
  123. package/types/exports/prebuild.d.ts.map +1 -1
  124. package/types/exports/serve.d.ts +2 -1
  125. package/types/exports/serve.d.ts.map +1 -1
  126. package/types/exports/serveStaticAssets.d.ts +12 -1
  127. package/types/exports/serveStaticAssets.d.ts.map +1 -1
  128. package/types/index.d.ts +0 -1
  129. package/types/index.d.ts.map +1 -1
  130. package/types/patches/builtInDepPatches.d.ts.map +1 -1
  131. package/types/plugins/clientInjectPlugin.d.ts +1 -7
  132. package/types/plugins/clientInjectPlugin.d.ts.map +1 -1
  133. package/types/plugins/reactNativeDevAssetPlugin.d.ts.map +1 -1
  134. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  135. package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
  136. package/types/runtime/hmr-client.d.ts +40 -0
  137. package/types/runtime/hmr-client.d.ts.map +1 -0
  138. package/types/runtime/hmr-runtime.d.ts +69 -0
  139. package/types/runtime/hmr-runtime.d.ts.map +1 -0
  140. package/types/runtime/hmr-types.d.ts +76 -0
  141. package/types/runtime/hmr-types.d.ts.map +1 -0
  142. package/types/runtime/native-prelude.d.ts +11 -0
  143. package/types/runtime/native-prelude.d.ts.map +1 -0
  144. package/types/runtime/react-refresh-utils.d.ts +3 -0
  145. package/types/runtime/react-refresh-utils.d.ts.map +1 -0
  146. package/types/types.d.ts +15 -1
  147. package/types/types.d.ts.map +1 -1
  148. package/types/utils/createNativeDevEngine.d.ts +42 -0
  149. package/types/utils/createNativeDevEngine.d.ts.map +1 -0
  150. package/types/utils/patches.d.ts.map +1 -1
  151. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
  152. package/dist/config/getReactNativeBuildConfig.mjs +0 -200
  153. package/dist/config/getReactNativeBuildConfig.mjs.map +0 -1
  154. package/dist/config/getReactNativeBuildConfig.native.js +0 -204
  155. package/dist/config/getReactNativeBuildConfig.native.js.map +0 -1
  156. package/dist/plugins/reactNativeHMRPlugin.mjs +0 -120
  157. package/dist/plugins/reactNativeHMRPlugin.mjs.map +0 -1
  158. package/dist/plugins/reactNativeHMRPlugin.native.js +0 -151
  159. package/dist/plugins/reactNativeHMRPlugin.native.js.map +0 -1
  160. package/dist/utils/filterPluginsForNative.mjs +0 -27
  161. package/dist/utils/filterPluginsForNative.mjs.map +0 -1
  162. package/dist/utils/filterPluginsForNative.native.js +0 -33
  163. package/dist/utils/filterPluginsForNative.native.js.map +0 -1
  164. package/dist/utils/getReactNativeBundle.mjs +0 -104
  165. package/dist/utils/getReactNativeBundle.mjs.map +0 -1
  166. package/dist/utils/getReactNativeBundle.native.js +0 -135
  167. package/dist/utils/getReactNativeBundle.native.js.map +0 -1
  168. package/dist/utils/hotUpdateCache.mjs +0 -3
  169. package/dist/utils/hotUpdateCache.mjs.map +0 -1
  170. package/dist/utils/hotUpdateCache.native.js +0 -3
  171. package/dist/utils/hotUpdateCache.native.js.map +0 -1
  172. package/dist/utils/isBuildingNativeBundle.mjs +0 -6
  173. package/dist/utils/isBuildingNativeBundle.mjs.map +0 -1
  174. package/dist/utils/isBuildingNativeBundle.native.js +0 -7
  175. package/dist/utils/isBuildingNativeBundle.native.js.map +0 -1
  176. package/dist/utils/swapPrebuiltReactModules.mjs +0 -168
  177. package/dist/utils/swapPrebuiltReactModules.mjs.map +0 -1
  178. package/dist/utils/swapPrebuiltReactModules.native.js +0 -181
  179. package/dist/utils/swapPrebuiltReactModules.native.js.map +0 -1
  180. package/dist/worker.mjs +0 -55
  181. package/dist/worker.mjs.map +0 -1
  182. package/dist/worker.native.js +0 -55
  183. package/dist/worker.native.js.map +0 -1
  184. package/react-native-template.js +0 -375
  185. package/src/config/getReactNativeBuildConfig.ts +0 -349
  186. package/src/plugins/reactNativeHMRPlugin.ts +0 -237
  187. package/src/utils/filterPluginsForNative.ts +0 -55
  188. package/src/utils/getReactNativeBundle.ts +0 -243
  189. package/src/utils/hotUpdateCache.ts +0 -1
  190. package/src/utils/isBuildingNativeBundle.ts +0 -7
  191. package/src/utils/swapPrebuiltReactModules.ts +0 -341
  192. package/src/worker.ts +0 -90
  193. package/types/config/getReactNativeBuildConfig.d.ts +0 -72
  194. package/types/config/getReactNativeBuildConfig.d.ts.map +0 -1
  195. package/types/plugins/reactNativeHMRPlugin.d.ts +0 -10
  196. package/types/plugins/reactNativeHMRPlugin.d.ts.map +0 -1
  197. package/types/utils/filterPluginsForNative.d.ts +0 -8
  198. package/types/utils/filterPluginsForNative.d.ts.map +0 -1
  199. package/types/utils/getReactNativeBundle.d.ts +0 -12
  200. package/types/utils/getReactNativeBundle.d.ts.map +0 -1
  201. package/types/utils/hotUpdateCache.d.ts +0 -2
  202. package/types/utils/hotUpdateCache.d.ts.map +0 -1
  203. package/types/utils/isBuildingNativeBundle.d.ts +0 -3
  204. package/types/utils/isBuildingNativeBundle.d.ts.map +0 -1
  205. package/types/utils/swapPrebuiltReactModules.d.ts +0 -9
  206. package/types/utils/swapPrebuiltReactModules.d.ts.map +0 -1
  207. package/types/worker.d.ts +0 -13
  208. package/types/worker.d.ts.map +0 -1
@@ -62,6 +62,12 @@ export const prebuild = async ({
62
62
  )
63
63
  }
64
64
 
65
+ // validate swift 6 workaround was injected into Podfile (outside prebuild try/catch
66
+ // so a validation error doesn't produce a misleading "Is expo listed?" message)
67
+ if (!platform || platform === 'ios') {
68
+ validateSwift6Workaround(root)
69
+ }
70
+
65
71
  if (!platform || platform === 'ios') {
66
72
  console.info(
67
73
  `
@@ -206,6 +212,45 @@ Android:
206
212
  }
207
213
  }
208
214
 
215
+ function validateSwift6Workaround(root: string) {
216
+ const podfilePath = path.join(root, 'ios', 'Podfile')
217
+ if (!FSExtra.existsSync(podfilePath)) return
218
+
219
+ // check opt-out
220
+ const propsPath = path.join(root, 'ios', 'Podfile.properties.json')
221
+ if (FSExtra.existsSync(propsPath)) {
222
+ try {
223
+ const props = JSON.parse(FSExtra.readFileSync(propsPath, 'utf8'))
224
+ if (props['one.disableSwift6Workaround'] === 'true') return
225
+ } catch {}
226
+ }
227
+
228
+ // check version — only needed for <56
229
+ try {
230
+ const emcPkgPath = resolvePath('expo-modules-core/package.json', root)
231
+ const emcVersion = JSON.parse(FSExtra.readFileSync(emcPkgPath, 'utf8')).version
232
+ const major = Number.parseInt(emcVersion.match(/^(\d+)/)?.[1] || '0', 10)
233
+ if (major >= 56) return
234
+ } catch {}
235
+
236
+ const podfile = FSExtra.readFileSync(podfilePath, 'utf8')
237
+ if (!podfile.includes('SWIFT_STRICT_CONCURRENCY')) {
238
+ console.warn(
239
+ colors.yellow(`
240
+ ⚠️ Your ios/Podfile is missing the Swift 6 workaround for expo-modules-core.
241
+ Without this, your iOS build will fail with Swift concurrency errors.
242
+
243
+ Fix: add "vxrn/expo-plugin" to your app.json plugins and re-run prebuild.
244
+
245
+ To disable this check: set "one.disableSwift6Workaround": "true"
246
+ in ios/Podfile.properties.json.
247
+
248
+ See: expo/expo#43199
249
+ `)
250
+ )
251
+ }
252
+ }
253
+
209
254
  function isMissingCliDependency(error) {
210
255
  return (
211
256
  error.code === 'MODULE_NOT_FOUND' &&
@@ -6,7 +6,8 @@ import { applyCompression, createProdServer } from './createServer'
6
6
  export { loadEnv } from '../exports/loadEnv'
7
7
  export * from '../utils/getServerEntry'
8
8
  export { createProdServer, applyCompression } from './createServer'
9
- export { serveStaticAssets } from './serveStaticAssets'
9
+ export { serveStaticAssets, compileCacheRules } from './serveStaticAssets'
10
+ export type { CompiledCacheRules } from './serveStaticAssets'
10
11
 
11
12
  export const serve = async ({
12
13
  afterRegisterRoutes,
@@ -1,24 +1,87 @@
1
1
  import { serveStatic } from '@hono/node-server/serve-static'
2
2
  import type { Context } from 'hono'
3
+ import micromatch from 'micromatch'
3
4
 
4
5
  // hashed assets can be cached forever, html must revalidate
5
- const hashedAssetRe = /[.-][a-zA-Z0-9_-]{8,}\.\w+$/
6
+ const hashedAssetRe = /[.-](?=[a-zA-Z0-9_-]*\d)[a-zA-Z0-9_-]{8,}\.\w+$/
7
+
8
+ export type CompiledCacheRules = { re: RegExp; values: string[] }
9
+
10
+ /**
11
+ * compile a cacheControl config (glob → header) into a single regex.
12
+ * each unique header value gets a capturing group — one regex.test()
13
+ * per request, then the matched group index maps to the header value.
14
+ */
15
+ export function compileCacheRules(
16
+ cacheControl: Record<string, string>
17
+ ): CompiledCacheRules {
18
+ // group patterns by header value
19
+ const groups = new Map<string, string[]>()
20
+ for (const [pattern, value] of Object.entries(cacheControl)) {
21
+ let arr = groups.get(value)
22
+ if (!arr) {
23
+ arr = []
24
+ groups.set(value, arr)
25
+ }
26
+ arr.push(pattern)
27
+ }
28
+
29
+ // each group becomes a capturing group: (pat1|pat2)|(pat3)
30
+ // matched group index → values[index]
31
+ const values: string[] = []
32
+ const groupSources: string[] = []
33
+
34
+ for (const [value, patterns] of groups) {
35
+ values.push(value)
36
+ const sources = patterns.map((p) => micromatch.makeRe(p).source)
37
+ groupSources.push(`(${sources.join('|')})`)
38
+ }
39
+
40
+ const re = new RegExp(groupSources.join('|'))
41
+ return { re, values }
42
+ }
6
43
 
7
44
  export async function serveStaticAssets({
8
45
  context,
9
46
  next,
10
47
  outDir = 'dist',
48
+ cacheRules,
11
49
  }: {
12
50
  context: Context
13
51
  next?: () => Promise<void>
14
52
  outDir?: string
53
+ cacheRules?: CompiledCacheRules
15
54
  }) {
16
55
  let didCallNext = false
17
56
 
57
+ const root = `./${outDir}/client`
58
+ // path.join normalizes "./" away, so pre-compute the normalized prefix
59
+ const rootPrefix = `${outDir}/client/`
60
+
18
61
  const response = await serveStatic({
19
- root: `./${outDir}/client`,
20
- onFound: (path, c) => {
21
- if (hashedAssetRe.test(path)) {
62
+ root,
63
+ onFound: (fsPath, c) => {
64
+ // onFound receives the joined fs path (e.g. "dist/client/foo.js")
65
+ // strip root prefix to get the URL-relative path for glob matching
66
+ const path = fsPath.startsWith(rootPrefix)
67
+ ? fsPath.slice(rootPrefix.length)
68
+ : fsPath
69
+
70
+ // single regex test for all custom rules
71
+ if (cacheRules) {
72
+ const m = cacheRules.re.exec(path)
73
+ if (m) {
74
+ // find which capturing group matched
75
+ for (let i = 1; i < m.length; i++) {
76
+ if (m[i] !== undefined) {
77
+ c.header('Cache-Control', cacheRules.values[i - 1]!)
78
+ return
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ if (hashedAssetRe.test(fsPath)) {
22
85
  c.header('Cache-Control', 'public, immutable, max-age=31536000')
23
86
  } else {
24
87
  c.header('Cache-Control', 'public, max-age=0, must-revalidate')
package/src/index.ts CHANGED
@@ -25,5 +25,3 @@ export * from './plugins/rollupRemoveUnusedImports'
25
25
  export * from './plugins/autoDepOptimizePlugin'
26
26
 
27
27
  export * from './types'
28
-
29
- export { prebuildReactNativeModules } from './utils/swapPrebuiltReactModules'
@@ -28,6 +28,35 @@ export const builtInDepPatches: DepPatch[] = [
28
28
  },
29
29
  },
30
30
 
31
+ // @react-navigation/core 7.17 renamed createComponentForStaticNavigation to
32
+ // createComponentForStaticConfigDeprecated, breaking @react-navigation/native
33
+ // which still imports the old name. re-export the old name as an alias.
34
+ {
35
+ module: '@react-navigation/core',
36
+ patchFiles: {
37
+ 'lib/module/index.js': (contents) => {
38
+ assertString(contents)
39
+ // only patch if the old export is missing and the new one exists
40
+ if (contents.includes('createComponentForStaticNavigation')) return
41
+ if (!contents.includes('createComponentForStaticConfigDeprecated')) return
42
+ return contents.replace(
43
+ /export \{ createComponentForStaticConfigDeprecated as createComponentForStaticConfig,/,
44
+ 'export { createComponentForStaticConfigDeprecated as createComponentForStaticConfig, createComponentForStaticConfigDeprecated as createComponentForStaticNavigation,'
45
+ )
46
+ },
47
+
48
+ 'lib/commonjs/index.js': (contents) => {
49
+ if (!contents) return
50
+ if (contents.includes('createComponentForStaticNavigation')) return
51
+ if (!contents.includes('createComponentForStaticConfigDeprecated')) return
52
+ return contents.replace(
53
+ /createComponentForStaticConfigDeprecated as createComponentForStaticConfig,/,
54
+ 'createComponentForStaticConfigDeprecated as createComponentForStaticConfig, createComponentForStaticConfigDeprecated as createComponentForStaticNavigation,'
55
+ )
56
+ },
57
+ },
58
+ },
59
+
31
60
  // react-native-web doesn't export unstable_batchedUpdates but react-native does,
32
61
  // so libraries like @legendapp/list break when aliased to rnw on web
33
62
  {
@@ -1,12 +1,5 @@
1
- import path from 'node:path'
2
-
3
- import type { HmrOptions, Plugin, ResolvedConfig, UserConfig } from 'vite'
4
- import { isNativeEnvironment } from '../utils/environmentUtils'
5
- import { getResolvedConfig, setResolvedConfig } from './getResolvedConfigSubset'
6
-
7
- let config: ResolvedConfig | null
8
-
9
- const isObject = (x: any): x is object => x && typeof x === 'object'
1
+ import type { Plugin } from 'vite'
2
+ import { setResolvedConfig } from './getResolvedConfigSubset'
10
3
 
11
4
  export function getServerConfigPlugin() {
12
5
  return {
@@ -22,103 +15,3 @@ export function getServerConfigPlugin() {
22
15
  },
23
16
  } satisfies Plugin
24
17
  }
25
-
26
- /**
27
- * some values used by the client needs to be dynamically injected by the server
28
- * @server-only
29
- */
30
- export function nativeClientInjectPlugin(): Plugin {
31
- let injectConfigValues: (code: string) => string
32
-
33
- return {
34
- name: 'vite:native-client-inject',
35
-
36
- async buildStart() {
37
- const config = getResolvedConfig()
38
-
39
- const resolvedServerHostname = config.server.host || '127.0.0.1'
40
- const resolvedServerPort = config.server!.port! || 5173
41
- const devBase = config.base || '/'
42
-
43
- const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}`
44
-
45
- let hmrConfig = config.server!.hmr
46
- hmrConfig = isObject(hmrConfig) ? (hmrConfig as HmrOptions) : undefined
47
-
48
- const host = hmrConfig?.host || null
49
- const protocol = hmrConfig?.protocol || null
50
- const timeout = hmrConfig?.timeout || 30000
51
- const overlay = hmrConfig?.overlay !== false
52
- const isHmrServerSpecified = !!hmrConfig?.server
53
-
54
- // hmr.clientPort -> hmr.port
55
- // -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port
56
- let port = hmrConfig?.clientPort || hmrConfig?.port || null
57
- if (config.server.middlewareMode && !isHmrServerSpecified) {
58
- port ||= 24678
59
- }
60
-
61
- let directTarget = hmrConfig?.host || resolvedServerHostname
62
- directTarget += `:${hmrConfig?.port || resolvedServerPort}`
63
- directTarget += devBase
64
-
65
- let hmrBase = devBase
66
- if (hmrConfig?.path) {
67
- hmrBase = path.posix.join(hmrBase, hmrConfig.path)
68
- }
69
-
70
- const serializedDefines = serializeDefine(config.define || {})
71
-
72
- const modeReplacement = escapeReplacement(config.mode!)
73
- const baseReplacement = escapeReplacement(devBase)
74
- const definesReplacement = () => serializedDefines
75
- const serverHostReplacement = escapeReplacement(serverHost)
76
- const hmrProtocolReplacement = escapeReplacement(protocol)
77
- const hmrHostnameReplacement = escapeReplacement(host)
78
- const hmrPortReplacement = escapeReplacement(port)
79
- const hmrDirectTargetReplacement = escapeReplacement(directTarget)
80
- const hmrBaseReplacement = escapeReplacement(hmrBase)
81
- const hmrTimeoutReplacement = escapeReplacement(timeout)
82
- const hmrEnableOverlayReplacement = escapeReplacement(overlay)
83
-
84
- injectConfigValues = (code: string) => {
85
- return code
86
- .replace(`__MODE__`, modeReplacement)
87
- .replace(/__BASE__/g, baseReplacement)
88
- .replace(`__DEFINES__`, definesReplacement)
89
- .replace(`__SERVER_HOST__`, serverHostReplacement)
90
- .replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement)
91
- .replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement)
92
- .replace(`__HMR_PORT__`, hmrPortReplacement)
93
- .replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement)
94
- .replace(`__HMR_BASE__`, hmrBaseReplacement)
95
- .replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement)
96
- .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement)
97
- }
98
- },
99
-
100
- applyToEnvironment(environment) {
101
- return isNativeEnvironment(environment)
102
- },
103
-
104
- transform(code, id) {
105
- if (id.includes('vite-native-client/dist/esm/client.')) {
106
- return injectConfigValues(code)
107
- }
108
- },
109
- }
110
- }
111
-
112
- function escapeReplacement(value: string | number | boolean | null) {
113
- const jsonValue = JSON.stringify(value)
114
- return () => jsonValue
115
- }
116
-
117
- function serializeDefine(define: Record<string, any>): string {
118
- let res = `{`
119
- for (const key in define) {
120
- const val = define[key]
121
- res += `${JSON.stringify(key)}: ${typeof val === 'string' ? `(${val})` : JSON.stringify(val)}, `
122
- }
123
- return res + `}`
124
- }
@@ -5,20 +5,6 @@ import type { Plugin, ResolvedConfig } from 'vite'
5
5
  import colors from 'picocolors'
6
6
  import { isNativeEnvironment } from '../utils/environmentUtils'
7
7
 
8
- const IMAGE_ASSET_EXTS = [
9
- 'png',
10
- 'jpg',
11
- 'jpeg',
12
- 'bmp',
13
- 'gif',
14
- 'webp',
15
- 'psd',
16
- 'svg',
17
- 'tiff',
18
- 'ktx',
19
- ]
20
- const IMAGE_ASSET_EXTS_SET = new Set(IMAGE_ASSET_EXTS)
21
-
22
8
  const ASSET_DEST_DIR = 'assets'
23
9
  /** `/assets` is too common and might conflict with web, using another path for dev server in development. */
24
10
  const DEV_ASSET_DEST_PATH = '__vxrn_dev_native_assets'
@@ -173,10 +159,3 @@ export default asset;
173
159
  },
174
160
  }
175
161
  }
176
-
177
- function isAssetTypeAnImage(
178
- /** Normally an file extension without the leading dot. */
179
- type: string
180
- ) {
181
- return IMAGE_ASSET_EXTS_SET.has(type)
182
- }
@@ -5,15 +5,9 @@ import {
5
5
  removeConnectedNativeClient,
6
6
  } from '../utils/connectedNativeClients'
7
7
  import type { VXRNOptionsFilled } from '../config/getOptionsFilled'
8
- import { clearCachedBundle, getReactNativeBundle } from '../utils/getReactNativeBundle'
9
- import { hotUpdateCache } from '../utils/hotUpdateCache'
10
8
  import { URL } from 'node:url'
11
- import { existsSync } from 'node:fs'
12
- import { readFile, writeFile } from 'node:fs/promises'
13
9
  import { createDevMiddleware } from '@react-native/dev-middleware'
14
- import { runOnWorker } from '../worker'
15
- import { getCacheDir } from '../utils/getCacheDir'
16
- import { debounce } from 'perfect-debounce'
10
+ import { createNativeDevEngine } from '../utils/createNativeDevEngine'
17
11
 
18
12
  type ClientMessage = {
19
13
  type: 'client-log'
@@ -26,15 +20,12 @@ export function createReactNativeDevServerPlugin(
26
20
  Pick<VXRNOptionsFilled, 'cacheDir' | 'debugBundle' | 'debugBundlePaths' | 'entries'>
27
21
  >
28
22
  ): Plugin {
29
- let hmrSocket: WebSocket | null = null
30
-
31
23
  return {
32
24
  name: 'vite-plugin-react-native-server',
33
25
 
34
26
  configureServer(server: ViteDevServer) {
35
27
  const { host, port } = server.config.server
36
28
  const { root } = server.config
37
- const cacheDir = options?.cacheDir || getCacheDir(root)
38
29
  const hmrWSS = new WebSocketServer({ noServer: true })
39
30
  const clientWSS = new WebSocketServer({ noServer: true })
40
31
 
@@ -49,29 +40,41 @@ export function createReactNativeDevServerPlugin(
49
40
 
50
41
  // link up sockets
51
42
  server.httpServer?.on('upgrade', (req, socket, head) => {
43
+ const url = req.url || ''
44
+
52
45
  // devtools sockets
53
46
  for (const endpoint of devToolsSocketEndpoints) {
54
- if (req.url.startsWith(endpoint)) {
47
+ if (url.startsWith(endpoint)) {
55
48
  const wss = websocketEndpoints[endpoint]
56
49
  wss.handleUpgrade(req, socket, head, (ws) => {
57
50
  wss.emit('connection', ws, req)
58
51
  })
52
+ return
59
53
  }
60
54
  }
61
55
 
62
- // hmr socket
63
- if (
64
- req.url.startsWith(
65
- '/__hmr'
66
- ) /* TODO: handle '/__hmr?platform=ios' and android differently */
67
- ) {
56
+ // rolldown HMR socket (used by rolldown dev() HMR client)
57
+ if (url.startsWith('/hot')) {
68
58
  hmrWSS.handleUpgrade(req, socket, head, (ws) => {
59
+ // listen for module registration messages from client
60
+ ws.on('message', async (data: any) => {
61
+ try {
62
+ const msg = JSON.parse(data.toString())
63
+ if (msg.type === 'hmr:module-registered' && msg.modules) {
64
+ const currentEngine = devEngines['ios'] || devEngines['android']
65
+ if (currentEngine?.engine) {
66
+ await currentEngine.engine.registerModules('vxrn-dev', msg.modules)
67
+ }
68
+ }
69
+ } catch {}
70
+ })
69
71
  hmrWSS.emit('connection', ws, req)
70
72
  })
73
+ return
71
74
  }
72
75
 
73
76
  // client socket
74
- if (req.url === '/__client') {
77
+ if (url === '/__client') {
75
78
  clientWSS.handleUpgrade(req, socket, head, (ws) => {
76
79
  clientWSS.emit('connection', ws, req)
77
80
  })
@@ -131,24 +134,12 @@ export function createReactNativeDevServerPlugin(
131
134
  android: 'android',
132
135
  }
133
136
 
134
- // Handle React Native endpoints
135
- server.middlewares.use('/file', async (req, res) => {
136
- const url = new URL(req.url!, `http://${req.headers.host}`)
137
- const file = url.searchParams.get('file')
138
-
139
- if (file) {
140
- const source = hotUpdateCache.get(file)
141
- if (!source) {
142
- console.warn(`No hot source found for`, file)
143
- res.writeHead(200, { 'Content-Type': 'text/javascript' })
144
- res.end('')
145
- return
146
- }
147
-
148
- res.writeHead(200, { 'Content-Type': 'text/javascript' })
149
- res.end(source)
150
- }
151
- })
137
+ // rolldown DevEngine instances (per platform)
138
+ const devEngines: Record<
139
+ string,
140
+ Awaited<ReturnType<typeof createNativeDevEngine>> | null
141
+ > = {}
142
+ const devEngineCreating: Record<string, Promise<any> | null> = {}
152
143
 
153
144
  // React Native bundle handler
154
145
  const handleRNBundle: Connect.NextHandleFunction = async (req, res) => {
@@ -162,47 +153,39 @@ export function createReactNativeDevServerPlugin(
162
153
 
163
154
  try {
164
155
  const bundle = await (async () => {
165
- if (typeof options?.debugBundle === 'string' && options.debugBundlePaths) {
166
- const path = options.debugBundlePaths[platform]
167
- if (existsSync(path)) {
168
- console.info(` !!! - serving debug bundle from`, path)
169
- return await readFile(path, 'utf-8')
156
+ if (!devEngines[platform]) {
157
+ // prevent duplicate creation from concurrent requests
158
+ if (!devEngineCreating[platform]) {
159
+ devEngineCreating[platform] = (async () => {
160
+ try {
161
+ console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
162
+ devEngines[platform] = await createNativeDevEngine({
163
+ root,
164
+ port: port || 8081,
165
+ host: typeof host === 'string' ? host : 'localhost',
166
+ platform,
167
+ serverUrl: `http://${typeof host === 'string' && host !== '0.0.0.0' ? host : 'localhost'}:${port || 8081}`,
168
+ onHmrUpdate: (update) => {
169
+ const msg = JSON.stringify(update)
170
+ hmrWSS.clients.forEach((client: any) => {
171
+ if (client.readyState === 1) {
172
+ client.send(msg)
173
+ }
174
+ })
175
+ },
176
+ })
177
+ console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
178
+ } catch (err) {
179
+ // clear so next request retries instead of permanently failing
180
+ devEngineCreating[platform] = null
181
+ throw err
182
+ }
183
+ })()
170
184
  }
185
+ await devEngineCreating[platform]
171
186
  }
172
187
 
173
- const getRnBundleOptions = {
174
- root,
175
- cacheDir,
176
- server: {
177
- port: port,
178
- url: `http://${host}:${port}`,
179
- },
180
- entries: { native: options?.entries?.native || './src/entry-native.tsx' },
181
- ...options,
182
- }
183
-
184
- let outBundle = process.env.VXRN_WORKER_BUNDLE
185
- ? await runOnWorker('bundle-react-native', {
186
- options: getRnBundleOptions,
187
- platform,
188
- })
189
- : await getReactNativeBundle(getRnBundleOptions, platform, {
190
- mode: process.env.RN_SERVE_PROD_BUNDLE ? 'prod' : 'dev',
191
- })
192
-
193
- if (server.config.webSocketToken) {
194
- outBundle = `globalThis.__VITE_WS_TOKEN__ = "${server.config.webSocketToken}";\n${outBundle}`
195
- }
196
-
197
- if (options?.debugBundle && options.debugBundlePaths) {
198
- const path = options.debugBundlePaths[platform]
199
- if (!existsSync(path)) {
200
- console.info(` !!! - writing debug bundle to`, path)
201
- await writeFile(path, outBundle)
202
- }
203
- }
204
-
205
- return outBundle
188
+ return await devEngines[platform]!.getBundle().then((r) => r.code)
206
189
  })()
207
190
 
208
191
  res.writeHead(200, { 'Content-Type': 'text/javascript' })
@@ -238,16 +221,6 @@ export function createReactNativeDevServerPlugin(
238
221
  res.writeHead(200, { 'Content-Type': 'text/plain' })
239
222
  res.end('TODO')
240
223
  })
241
-
242
- // Clear bundle cache on file changes (debounced to avoid CPU spikes during builds)
243
- const debouncedClearCache = debounce(clearCachedBundle, 100)
244
- server.watcher.on('change', (path: string) => {
245
- // Skip clearing cache for dist files to avoid loops during builds
246
- if (path.includes('/dist/') || path.includes('\\dist\\')) {
247
- return
248
- }
249
- debouncedClearCache()
250
- })
251
224
  },
252
225
  }
253
226
  }
@@ -63,26 +63,24 @@ describe('platform-specific-resolve', () => {
63
63
  vi.restoreAllMocks()
64
64
  })
65
65
 
66
- it('errors when .server file is explicitly imported on client', async () => {
66
+ it('stubs .server file when explicitly imported on client', async () => {
67
67
  const plugin = await getPlatformResolvePlugin()
68
68
  const resolveId = plugin.resolveId as Function
69
69
 
70
70
  const ctx = createMockContext('client', '/src/db.server.ts')
71
71
 
72
- await expect(
73
- resolveId.call(ctx, './db.server', '/src/page.tsx', {})
74
- ).rejects.toThrow('.server file cannot be imported on client')
72
+ const result = await resolveId.call(ctx, './db.server', '/src/page.tsx', {})
73
+ expect(result).toEqual({ id: '\0server-only-stub:./db.server' })
75
74
  })
76
75
 
77
- it('errors when .server file is explicitly imported on ios', async () => {
76
+ it('stubs .server file when explicitly imported on ios', async () => {
78
77
  const plugin = await getPlatformResolvePlugin()
79
78
  const resolveId = plugin.resolveId as Function
80
79
 
81
80
  const ctx = createMockContext('ios', '/src/db.server.ts')
82
81
 
83
- await expect(
84
- resolveId.call(ctx, './db.server', '/src/page.tsx', {})
85
- ).rejects.toThrow('.server file cannot be imported on ios')
82
+ const result = await resolveId.call(ctx, './db.server', '/src/page.tsx', {})
83
+ expect(result).toEqual({ id: '\0server-only-stub:./db.server' })
86
84
  })
87
85
 
88
86
  it('allows .server file import on ssr', async () => {