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
@@ -0,0 +1,942 @@
1
+ /**
2
+ * Creates a rolldown DevEngine for native React Native bundle serving.
3
+ * Uses rolldown's experimental dev() API with ESM output.
4
+ *
5
+ * Inspired by rollipop's architecture:
6
+ * https://github.com/leegeunhyeok/rollipop
7
+ */
8
+
9
+ import { writeFileSync } from 'node:fs'
10
+ import { basename, dirname, extname, join, relative } from 'node:path'
11
+ import type { InputOptions, OutputOptions, Plugin, RolldownOutput } from 'rolldown'
12
+ import { DEFAULT_ASSET_EXTS } from '../constants/defaults'
13
+ import { getNativePrelude } from '../runtime/native-prelude'
14
+
15
+ // files that contain Flow syntax and need stripping
16
+ const FLOW_FILE_PATTERN = /node_modules[\\/](?:react-native|@react-native)[\\/].*\.js$/
17
+
18
+ interface NativeDevEngineOptions {
19
+ root: string
20
+ port: number
21
+ host?: string
22
+ platform: 'ios' | 'android'
23
+ serverUrl?: string
24
+ plugins?: Plugin[]
25
+ onHmrUpdate?: (update: { type: string; code?: string }) => void
26
+ }
27
+
28
+ interface NativeDevEngineResult {
29
+ engine: any
30
+ getBundle: () => Promise<{ code: string; map?: string }>
31
+ close: () => Promise<void>
32
+ }
33
+
34
+ // shared resolve extensions for native builds
35
+ function getResolveExtensions(platform: 'ios' | 'android'): string[] {
36
+ const platformExts =
37
+ platform === 'ios'
38
+ ? ['.ios.tsx', '.ios.ts', '.ios.jsx', '.ios.js']
39
+ : ['.android.tsx', '.android.ts', '.android.jsx', '.android.js']
40
+ const nativeExts = ['.native.tsx', '.native.ts', '.native.jsx', '.native.js']
41
+ const defaultExts = ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json']
42
+ return [...platformExts, ...nativeExts, ...defaultExts]
43
+ }
44
+
45
+ // shared rolldown resolve config for native builds
46
+ function getNativeResolveConfig(platform: 'ios' | 'android') {
47
+ return {
48
+ extensions: getResolveExtensions(platform),
49
+ conditionNames: ['react-native', 'import', 'require', 'default'],
50
+ mainFields: ['react-native', 'module', 'main'],
51
+ }
52
+ }
53
+
54
+ // shared rolldown transform config for native builds
55
+ function getNativeTransformConfig(dev: boolean) {
56
+ return {
57
+ jsx: {
58
+ // use 'classic' mode (babel plugin-transform-react-jsx)
59
+ // 'automatic' has files where jsxDEV import fails to resolve
60
+ runtime: 'classic' as const,
61
+ },
62
+ define: {
63
+ 'process.env.NODE_ENV': dev ? '"development"' : '"production"',
64
+ 'process.env.VXRN_REACT_19': 'false',
65
+ __DEV__: dev ? 'true' : 'false',
66
+ },
67
+ // auto-inject React import for classic JSX (React.createElement)
68
+ inject: {
69
+ React: 'react',
70
+ },
71
+ }
72
+ }
73
+
74
+ // shared plugins used by both dev and prod native builds
75
+ function getNativePlugins(
76
+ root: string,
77
+ platform: string,
78
+ viteImportGlobPlugin: any,
79
+ dev: boolean
80
+ ): Plugin[] {
81
+ return [
82
+ // stub CSS imports — native doesn't support CSS and rolldown removed CSS bundling
83
+ cssStubPlugin(),
84
+ // handle import.meta.glob (used by One's route system)
85
+ viteImportGlobPlugin({ root }) as any,
86
+ // strip Flow types from react-native and @react-native packages
87
+ flowStripPlugin(),
88
+ // handle asset imports (.png, .jpg, .ttf, etc.)
89
+ assetPlugin({ root, platform }),
90
+ // @vxrn/compiler babel transforms: reanimated worklets, async generators,
91
+ // react-native codegen, react compiler — same pipeline as metro
92
+ vxrnCompilerPlugin(platform, dev),
93
+ // hermes compat: transform class properties and private fields
94
+ hermesCompatSWCPlugin(dev),
95
+ // downgrade polyfill "not configurable" errors to warnings (hermes v1)
96
+ polyfillErrorDowngradePlugin(),
97
+ // strip DevSettings in prod (dev-only native module)
98
+ stripDevSettingsPlugin(dev),
99
+ ]
100
+ }
101
+
102
+ // shared output options for native builds
103
+ function getNativeOutputOptions(prelude: string): OutputOptions {
104
+ return {
105
+ format: 'esm',
106
+ sourcemap: true,
107
+ intro: prelude,
108
+ codeSplitting: false,
109
+ strictExecutionOrder: true,
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Post-process a native bundle to fix rolldown devMode output quirks.
115
+ * Most concerns have been moved to plugins/config:
116
+ * - VXRN_REACT_19 → handled by define in getNativeTransformConfig
117
+ * - polyfill error downgrade → polyfillErrorDowngradePlugin
118
+ * - DevSettings stripping → stripDevSettingsPlugin
119
+ */
120
+ function postProcessNativeBundle(code: string): string {
121
+ // rolldown devMode still emits ESM export statements that hermes can't parse.
122
+ // this is a rolldown behavior we can't configure away yet.
123
+ code = code.replace(/^\s*export\s*\{[^}]*\}\s*;?\s*$/gm, '')
124
+ // rolldown devMode runtime leaves some raw import.meta.hot references
125
+ // that aren't compiled through the normal plugin pipeline.
126
+ code = code.replace(/^if \(import\.meta\.hot\).*$/gm, '')
127
+ return code
128
+ }
129
+
130
+ /**
131
+ * Downlevel class fields in the rolldown runtime for Hermes compatibility.
132
+ * The runtime (\0rolldown/runtime.js) is injected directly into the output,
133
+ * bypassing hermesCompatSWCPlugin. We extract just that section (~5KB) and
134
+ * transform it rather than re-parsing the entire 6MB bundle.
135
+ */
136
+ async function downlevelClassFieldsInBundle(code: string): Promise<string> {
137
+ const startMarker = '//#region \\0rolldown/runtime.js'
138
+ const endMarker = '//#endregion'
139
+
140
+ const startIdx = code.indexOf(startMarker)
141
+ if (startIdx === -1) return code
142
+
143
+ const endIdx = code.indexOf(endMarker, startIdx)
144
+ if (endIdx === -1) return code
145
+
146
+ const runtimeEnd = endIdx + endMarker.length
147
+ const runtimeSection = code.slice(startIdx, runtimeEnd)
148
+
149
+ try {
150
+ const swc = await import('@swc/core')
151
+ const result = await swc.transform(runtimeSection, {
152
+ filename: 'rolldown-runtime.js',
153
+ configFile: false,
154
+ swcrc: false,
155
+ sourceMaps: false,
156
+ inputSourceMap: false,
157
+ isModule: false,
158
+ env: {
159
+ targets: { node: 9999 },
160
+ include: [
161
+ 'transform-class-properties',
162
+ 'transform-class-static-block',
163
+ 'transform-private-methods',
164
+ 'transform-private-property-in-object',
165
+ ],
166
+ },
167
+ jsc: {
168
+ parser: { syntax: 'ecmascript' },
169
+ transform: { react: { runtime: 'preserve' } },
170
+ externalHelpers: false,
171
+ assumptions: {
172
+ setPublicClassFields: true,
173
+ privateFieldsAsProperties: true,
174
+ },
175
+ },
176
+ })
177
+ return code.slice(0, startIdx) + result.code + code.slice(runtimeEnd)
178
+ } catch (err) {
179
+ console.warn('[vxrn] downlevelClassFieldsInBundle failed, returning original:', err)
180
+ return code
181
+ }
182
+ }
183
+
184
+ export async function createNativeDevEngine(
185
+ options: NativeDevEngineOptions
186
+ ): Promise<NativeDevEngineResult> {
187
+ const {
188
+ root,
189
+ port,
190
+ host = 'localhost',
191
+ platform,
192
+ serverUrl,
193
+ plugins: userPlugins = [],
194
+ onHmrUpdate,
195
+ } = options
196
+
197
+ const { dev, viteImportGlobPlugin } = await import('rolldown/experimental')
198
+
199
+ const hmrRuntimeSource = getHmrRuntimeSource()
200
+
201
+ const prelude = getNativePrelude({
202
+ dev: true,
203
+ platform,
204
+ serverUrl: serverUrl || `http://${host}:${port}`,
205
+ })
206
+
207
+ // generate a real entry file on disk (virtual modules can't use import.meta.glob)
208
+ const entryFile = generateNativeEntry(root)
209
+
210
+ let currentBundle: { code: string; map?: string } | null = null
211
+ let bundleResolve: ((value: any) => void) | null = null
212
+ let bundlePromise: Promise<any> | null = null
213
+
214
+ const resolvedHost = host === '0.0.0.0' ? 'localhost' : host
215
+
216
+ const inputOptions: InputOptions = {
217
+ input: entryFile,
218
+ cwd: root,
219
+ platform: 'neutral',
220
+ resolve: getNativeResolveConfig(platform),
221
+ transform: getNativeTransformConfig(true),
222
+
223
+ experimental: {
224
+ devMode: { implement: hmrRuntimeSource, host, port },
225
+ incrementalBuild: true,
226
+ },
227
+
228
+ treeshake: false,
229
+ // some react-native ecosystem packages import symbols that don't exist in
230
+ // the declared entry (e.g. @react-navigation/elements imports NavigationProvider
231
+ // from @react-navigation/native which doesn't export it). metro silently shims
232
+ // these — rolldown needs an explicit opt-in.
233
+ shimMissingExports: true,
234
+
235
+ moduleTypes: {
236
+ '.js': 'jsx',
237
+ },
238
+
239
+ plugins: [
240
+ ...getNativePlugins(root, platform, viteImportGlobPlugin, true),
241
+
242
+ // add import.meta.hot.accept() to user files for HMR boundaries
243
+ // rolldown compiles import.meta.hot -> createModuleHotContext at build time
244
+ nativeReactRefreshPlugin(),
245
+
246
+ ...userPlugins,
247
+ ],
248
+ }
249
+
250
+ const outputOptions: OutputOptions = {
251
+ ...getNativeOutputOptions(prelude),
252
+ // connect HMR WebSocket using RN's WebSocket module (not the global)
253
+ outro: `
254
+ try {
255
+ var __WS = (init_WebSocket(), __toCommonJS(WebSocket_exports)).default;
256
+ var __hmrUrl = 'ws://${resolvedHost}:${port}/hot';
257
+ var __hmrWS = new __WS(__hmrUrl);
258
+ __hmrWS.onmessage = function(event) {
259
+ try {
260
+ var msg = JSON.parse(event.data);
261
+ var g = typeof global !== 'undefined' ? global : globalThis;
262
+ if (msg.type === 'hmr:update' && msg.code) {
263
+ if (g.globalEvalWithSourceUrl) g.globalEvalWithSourceUrl(msg.code);
264
+ else (0, eval)(msg.code);
265
+ setTimeout(function() {
266
+ try { if (g.__ReactRefresh) g.__ReactRefresh.performReactRefresh(); } catch(e) {}
267
+ }, 50);
268
+ } else if (msg.type === 'hmr:reload') {
269
+ var ds = g.__turboModuleProxy ? g.__turboModuleProxy('DevSettings') : null;
270
+ if (ds && ds.reload) ds.reload();
271
+ }
272
+ } catch(e) { console.error('[vxrn] HMR eval error:', e); }
273
+ };
274
+ __hmrWS.onopen = function() {
275
+ if (typeof __rolldown_runtime__ !== 'undefined' && __rolldown_runtime__.setup) {
276
+ __rolldown_runtime__.setup(__hmrWS, __hmrUrl.replace('ws://', 'http://'));
277
+ }
278
+ };
279
+ __hmrWS.onerror = function(e) { console.warn('[vxrn] HMR connection error:', e.message || e); };
280
+ } catch(e) {}
281
+ `,
282
+ }
283
+
284
+ const engine = await dev(inputOptions, outputOptions, {
285
+ onOutput: async (result) => {
286
+ if (result instanceof Error) {
287
+ console.error('[vxrn] native bundle error:', result.message)
288
+ return
289
+ }
290
+
291
+ const output = result as RolldownOutput
292
+ const chunk = output.output.find((o) => o.type === 'chunk' && o.isEntry)
293
+ if (chunk && 'code' in chunk) {
294
+ let code = postProcessNativeBundle(chunk.code)
295
+
296
+ // downlevel class fields from the rolldown runtime (virtual module
297
+ // skipped by the per-file SWC plugin) so old Hermes can parse them
298
+ code = await downlevelClassFieldsInBundle(code)
299
+
300
+ // register a no-op HMRClient so RN's native side doesn't error when calling HMRClient.setup()
301
+ // our actual HMR is handled via the outro WebSocket connection
302
+ const hmrClientStub = `registerCallableModule("HMRClient",{setup:function(){},enable:function(){},disable:function(){},registerBundle:function(){},log:function(){}})`
303
+ code = code.replace(
304
+ /registerCallableModule\s*\(\s*["']AppRegistry["']/,
305
+ (match) => hmrClientStub + ',' + match
306
+ )
307
+
308
+ currentBundle = {
309
+ code,
310
+ map: chunk.map?.toString(),
311
+ }
312
+ console.info(
313
+ `[vxrn] native bundle ready (${Math.round(chunk.code.length / 1024)}KB)`
314
+ )
315
+ if (bundleResolve) {
316
+ bundleResolve(currentBundle)
317
+ bundleResolve = null
318
+ bundlePromise = null
319
+ }
320
+ }
321
+ },
322
+
323
+ onHmrUpdates: async (result) => {
324
+ if (result instanceof Error) {
325
+ console.error('[vxrn] HMR error:', result.message)
326
+ onHmrUpdate?.({ type: 'hmr:error' })
327
+ return
328
+ }
329
+ const updates = (result as any).updates || []
330
+
331
+ for (const item of updates) {
332
+ const update = item.update || item
333
+ if (update.type === 'Patch' && update.code) {
334
+ onHmrUpdate?.({ type: 'hmr:update', code: update.code })
335
+ } else if (update.type === 'FullReload') {
336
+ onHmrUpdate?.({ type: 'hmr:reload' })
337
+ }
338
+ }
339
+
340
+ if (updates.length === 0) {
341
+ onHmrUpdate?.({ type: 'hmr:reload' })
342
+ }
343
+ },
344
+
345
+ rebuildStrategy: 'auto',
346
+ watch: {},
347
+ })
348
+
349
+ await engine.run()
350
+
351
+ // modules are registered via WebSocket messages from the HMR client
352
+ // (the devMode runtime sends hmr:module-registered messages)
353
+
354
+ return {
355
+ engine,
356
+
357
+ async getBundle() {
358
+ if (currentBundle) return currentBundle
359
+ if (!bundlePromise) {
360
+ let timeoutId: ReturnType<typeof setTimeout>
361
+ bundlePromise = new Promise((resolve, reject) => {
362
+ bundleResolve = (value) => {
363
+ clearTimeout(timeoutId)
364
+ resolve(value)
365
+ }
366
+ timeoutId = setTimeout(
367
+ () => reject(new Error('[vxrn] bundle build timed out after 120s')),
368
+ 120_000
369
+ )
370
+ })
371
+ }
372
+ await engine.ensureLatestBuildOutput()
373
+ if (currentBundle) return currentBundle
374
+ return bundlePromise
375
+ },
376
+
377
+ async close() {
378
+ await engine.close()
379
+ },
380
+ }
381
+ }
382
+
383
+ // --- production build ---
384
+
385
+ interface NativeBuildOptions {
386
+ root: string
387
+ platform: 'ios' | 'android'
388
+ dev?: boolean
389
+ serverUrl?: string
390
+ plugins?: Plugin[]
391
+ }
392
+
393
+ export async function buildNativeBundle(
394
+ options: NativeBuildOptions
395
+ ): Promise<{ code: string; map?: string }> {
396
+ const { root, platform, dev = false, serverUrl, plugins: userPlugins = [] } = options
397
+
398
+ const { build } = await import('rolldown')
399
+ const { viteImportGlobPlugin } = await import('rolldown/experimental')
400
+
401
+ const prelude = getNativePrelude({
402
+ dev,
403
+ platform,
404
+ serverUrl,
405
+ })
406
+
407
+ const entryFile = generateNativeEntry(root, { dev })
408
+
409
+ const result = await build({
410
+ input: entryFile,
411
+ cwd: root,
412
+ platform: 'neutral',
413
+ resolve: getNativeResolveConfig(platform),
414
+ transform: getNativeTransformConfig(dev),
415
+ treeshake: !dev,
416
+ shimMissingExports: true,
417
+ moduleTypes: { '.js': 'jsx' },
418
+ plugins: [
419
+ ...getNativePlugins(root, platform, viteImportGlobPlugin, dev),
420
+ ...userPlugins,
421
+ ],
422
+ output: getNativeOutputOptions(prelude),
423
+ })
424
+ const chunk = result.output.find((o) => o.type === 'chunk' && o.isEntry)
425
+
426
+ if (!chunk || !('code' in chunk)) {
427
+ throw new Error('[vxrn] production build produced no output')
428
+ }
429
+
430
+ let code = postProcessNativeBundle(chunk.code)
431
+ code = await downlevelClassFieldsInBundle(code)
432
+ return { code, map: chunk.map?.toString() }
433
+ }
434
+
435
+ function generateNativeEntry(root: string, opts?: { dev?: boolean }): string {
436
+ const isDev = opts?.dev !== false
437
+ // write entry at project root so import.meta.glob('./app/...') resolves correctly
438
+ const entryPath = join(root, '.vxrn-entry-native.tsx')
439
+
440
+ const refreshSetup = isDev
441
+ ? `
442
+ // react-refresh/runtime MUST initialize before React loads
443
+ import RefreshRuntime from 'react-refresh/runtime';
444
+ RefreshRuntime.injectIntoGlobalHook(globalThis);
445
+ globalThis.__ReactRefresh = RefreshRuntime;
446
+ globalThis.$RefreshReg$ = function(type, id) {
447
+ RefreshRuntime.register(type, id);
448
+ };
449
+ globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
450
+ `
451
+ : ''
452
+
453
+ const entryCode = `
454
+ // auto-generated native entry for rolldown
455
+ ${refreshSetup}
456
+ import { createApp } from 'one';
457
+
458
+ var _routes = import.meta.glob(['./app/**/*.tsx', './app/**/*.ts', '!./app/**/*+api.*', '!./app/**/*.test.*', '!./app/**/*.d.ts'], { exhaustive: true });
459
+ // fix route keys: One expects '/app/...' prefix but import.meta.glob returns './app/...'
460
+ var routes = {};
461
+ Object.keys(_routes).forEach(function(key) {
462
+ routes[key.replace(/^\\./, '')] = _routes[key];
463
+ });
464
+
465
+ createApp({
466
+ routes: routes,
467
+ routerRoot: 'app',
468
+ flags: {},
469
+ });
470
+ `
471
+ writeFileSync(entryPath, entryCode)
472
+ return entryPath
473
+ }
474
+
475
+ // --- plugins ---
476
+
477
+ /**
478
+ * Stub CSS imports for native builds.
479
+ * Native doesn't support CSS and rolldown removed CSS bundling support.
480
+ * Without this, any `import './foo.css'` will cause a build error.
481
+ */
482
+ function cssStubPlugin(): Plugin {
483
+ return {
484
+ name: 'vxrn:css-stub',
485
+ load: {
486
+ handler(id) {
487
+ if (/\.css$/.test(id)) {
488
+ return { code: '', moduleType: 'js' as any }
489
+ }
490
+ },
491
+ },
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Pipe files through @vxrn/compiler's babel transforms.
497
+ * Handles reanimated worklet compilation, async generator downleveling,
498
+ * react-native codegen, and react compiler — same pipeline as metro.
499
+ * Auto-detects which transforms are needed per file.
500
+ */
501
+ function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
502
+ let compiler: typeof import('@vxrn/compiler') | null = null
503
+
504
+ return {
505
+ name: 'vxrn:compiler',
506
+ async transform(code, id) {
507
+ if (!/\.[cm]?[jt]sx?$/.test(id)) return
508
+ if (id.includes('\0') || id.includes('virtual:')) return
509
+
510
+ try {
511
+ if (!compiler) compiler = await import('@vxrn/compiler')
512
+
513
+ const props = {
514
+ id,
515
+ code,
516
+ development: dev,
517
+ environment: platform as 'ios' | 'android',
518
+ reactForRNVersion: '19' as const,
519
+ }
520
+
521
+ const babelOptions = compiler.getBabelOptions(props)
522
+ if (!babelOptions) return
523
+
524
+ const result = await compiler.transformBabel(id, code, babelOptions)
525
+
526
+ if (result?.code) {
527
+ return { code: result.code }
528
+ }
529
+ } catch (err: any) {
530
+ // log but don't crash — fallback to rolldown's own transform
531
+ if (dev) {
532
+ console.warn(`[vxrn:compiler] ${id}: ${err.message || err}`)
533
+ }
534
+ }
535
+ },
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Strip Flow types from react-native source files.
541
+ * Uses hermes-parser which is already a dep of react-native.
542
+ */
543
+ function flowStripPlugin(): Plugin {
544
+ return {
545
+ name: 'vxrn:flow-strip',
546
+ transform: {
547
+ async handler(code, id) {
548
+ if (!FLOW_FILE_PATTERN.test(id)) return
549
+
550
+ try {
551
+ const fft = await import('fast-flow-transform')
552
+ const result = await fft.default({
553
+ filename: id,
554
+ source: code,
555
+ sourcemap: true,
556
+ dialect: 'flow',
557
+ format: 'pretty',
558
+ })
559
+ // don't set moduleType - let rolldown's global moduleTypes config handle it
560
+ return { code: result.code, map: result.map }
561
+ } catch (err: any) {
562
+ console.warn(`[vxrn:flow-strip] ${id}: ${err.message}`)
563
+ }
564
+ },
565
+ },
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Handle asset imports (.png, .jpg, .ttf, etc.)
571
+ * Returns JS code that registers the asset with RN's AssetRegistry.
572
+ */
573
+ function assetPlugin(opts: { root: string; platform: string }): Plugin {
574
+ const assetRegex = new RegExp(`\\.(?:${DEFAULT_ASSET_EXTS.join('|')})$`)
575
+
576
+ return {
577
+ name: 'vxrn:asset',
578
+ load: {
579
+ async handler(id) {
580
+ if (!assetRegex.test(id)) return
581
+
582
+ const ext = extname(id).slice(1)
583
+ const name = basename(id, `.${ext}`)
584
+ const dir = dirname(id)
585
+ const relativePath = relative(opts.root, id)
586
+ const httpLocation = '/assets/' + dirname(relativePath)
587
+
588
+ // simple asset registration (TODO: scale detection like rollipop)
589
+ const assetData = {
590
+ __packager_asset: true,
591
+ name,
592
+ type: ext,
593
+ scales: [1],
594
+ httpServerLocation: httpLocation,
595
+ fileSystemLocation: dir,
596
+ hash: '',
597
+ width: undefined as number | undefined,
598
+ height: undefined as number | undefined,
599
+ }
600
+
601
+ // try to get image dimensions
602
+ if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].includes(ext)) {
603
+ try {
604
+ const { imageSize } = await import('image-size')
605
+ const dims = imageSize(id)
606
+ assetData.width = dims.width
607
+ assetData.height = dims.height
608
+ } catch {}
609
+ }
610
+
611
+ const code = `module.exports = require('react-native/Libraries/Image/AssetRegistry').registerAsset(${JSON.stringify(assetData)});`
612
+
613
+ return { code, moduleType: 'js' as any }
614
+ },
615
+ },
616
+ }
617
+ }
618
+
619
+ /**
620
+ * SWC transform for Hermes compatibility.
621
+ * Transforms class properties and private fields that Hermes doesn't support.
622
+ * Inspired by rollipop's swc-plugin.ts.
623
+ */
624
+ function hermesCompatSWCPlugin(dev: boolean): Plugin {
625
+ let swc: typeof import('@swc/core') | null = null
626
+
627
+ return {
628
+ name: 'vxrn:hermes-compat',
629
+ async transform(code, id) {
630
+ if (!/\.[cm]?[jt]sx?$/.test(id)) return
631
+ if (id.includes('\0') || id.includes('virtual:')) return
632
+ // skip files that don't need transformation
633
+ const hasClass = code.includes('class ') || code.includes('class{')
634
+ const hasAsync = !dev && code.includes('async ')
635
+ if (!hasClass && !hasAsync) return
636
+ // skip very large prebuilt files
637
+ if (code.length > 500_000) return
638
+
639
+ try {
640
+ if (!swc) swc = await import('@swc/core')
641
+
642
+ // hermes needs class properties downleveled; prod also needs
643
+ // classes and async-to-generator for bytecode compilation
644
+ const envIncludes = [
645
+ 'transform-class-properties',
646
+ 'transform-class-static-block',
647
+ 'transform-private-methods',
648
+ 'transform-private-property-in-object',
649
+ ...(!dev ? ['transform-classes', 'transform-async-to-generator'] : []),
650
+ ]
651
+
652
+ const result = await swc.transform(code, {
653
+ filename: id,
654
+ configFile: false,
655
+ swcrc: false,
656
+ sourceMaps: false,
657
+ inputSourceMap: false,
658
+ env: {
659
+ targets: { node: 9999 },
660
+ include: envIncludes,
661
+ },
662
+ jsc: {
663
+ parser: { syntax: 'typescript', tsx: true },
664
+ transform: { react: { runtime: 'preserve' } },
665
+ externalHelpers: false,
666
+ assumptions: {
667
+ setPublicClassFields: true,
668
+ privateFieldsAsProperties: true,
669
+ },
670
+ },
671
+ isModule: !id.endsWith('.cjs'),
672
+ })
673
+ return { code: result.code }
674
+ } catch (err: any) {
675
+ // don't crash on SWC transform errors (eg static blocks not supported)
676
+ }
677
+ },
678
+ }
679
+ }
680
+
681
+ /**
682
+ * Run @react-native/babel-plugin-codegen on native component spec files.
683
+ * Without this, native components (RNSScreen, SafeAreaView, etc.) get
684
+ * "Codegen didn't run" warnings and may not render correctly.
685
+ */
686
+ function codegenPlugin(): Plugin {
687
+ const NATIVE_COMPONENT_RE = /NativeComponent\.[jt]sx?$/
688
+ const SPEC_FILE_RE = /[/\\]specs?[/\\]/
689
+
690
+ return {
691
+ name: 'vxrn:codegen',
692
+ async transform(code, id) {
693
+ if (!NATIVE_COMPONENT_RE.test(id) && !SPEC_FILE_RE.test(id)) return
694
+
695
+ try {
696
+ const babel = await import('@babel/core')
697
+ const result = await babel.transformAsync(code, {
698
+ filename: id,
699
+ babelrc: false,
700
+ configFile: false,
701
+ compact: false,
702
+ plugins: ['@react-native/babel-plugin-codegen'],
703
+ sourceType: 'unambiguous',
704
+ })
705
+ if (result?.code) return { code: result.code }
706
+ } catch {}
707
+ },
708
+ }
709
+ }
710
+
711
+ /**
712
+ * Downgrade polyfill "not configurable" errors to warnings.
713
+ * hermes v1 has native fetch/Headers/etc that are non-configurable,
714
+ * so polyfill attempts throw errors that are noisy but harmless.
715
+ */
716
+ function polyfillErrorDowngradePlugin(): Plugin {
717
+ return {
718
+ name: 'vxrn:polyfill-error-downgrade',
719
+ transform(code, id) {
720
+ if (!id.includes('node_modules')) return
721
+ if (!code.includes('console.error("Failed to set polyfill.')) return
722
+
723
+ return {
724
+ code: code.replace(
725
+ /console\.error\(\s*"Failed to set polyfill\.\s*"\s*\+/g,
726
+ 'console.warn("Failed to set polyfill. " +'
727
+ ),
728
+ }
729
+ },
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Strip DevSettings reference in production builds.
735
+ * In production, NativeModules.getEnforcing('DevSettings') will fail
736
+ * since DevSettings is a dev-only module.
737
+ */
738
+ function stripDevSettingsPlugin(dev: boolean): Plugin {
739
+ return {
740
+ name: 'vxrn:strip-dev-settings',
741
+ transform(code, id) {
742
+ if (dev) return
743
+ if (!code.includes('DevSettings')) return
744
+
745
+ return {
746
+ code: code.replace(
747
+ /getEnforcing\s*\(\s*["']DevSettings["']\s*\)/g,
748
+ 'patched_getEnforcing_DevSettings_will_not_work_in_production()'
749
+ ),
750
+ }
751
+ },
752
+ }
753
+ }
754
+
755
+ /**
756
+ * React Refresh wrapper for native HMR.
757
+ * 1. Runs react-refresh/babel to add $RefreshReg$/$RefreshSig$ calls
758
+ * 2. Wraps each file with per-module $RefreshReg$ that includes the file path
759
+ * (react-refresh needs unique IDs like "filepath ComponentName")
760
+ * 3. Appends import.meta.hot.accept() for HMR boundaries
761
+ * 4. Schedules performReactRefresh() after module re-execution
762
+ */
763
+ function nativeReactRefreshPlugin(): Plugin {
764
+ return {
765
+ name: 'vxrn:react-refresh',
766
+ async transform(code, id) {
767
+ // only wrap user app files (not node_modules, not generated entry, not virtual)
768
+ if (id.includes('node_modules')) return
769
+ if (id.includes('.vxrn-entry-native')) return
770
+ if (id.startsWith('\0')) return
771
+ if (!/\.[tj]sx?$/.test(id)) return
772
+ // skip files that clearly have no components (raw source before JSX transform)
773
+ // check for JSX syntax (<), function keyword, or arrow functions (=>)
774
+ if (!/[<]|function\s|=>\s*[{(]/.test(code)) return
775
+
776
+ try {
777
+ // run react-refresh/babel to add $RefreshReg$ and $RefreshSig$
778
+ const babel = await import('@babel/core')
779
+
780
+ // babel needs parser plugins for TypeScript/JSX
781
+ const parserPlugins: any[] = []
782
+ if (id.endsWith('.tsx')) {
783
+ parserPlugins.push(['@babel/plugin-syntax-typescript', { isTSX: true }])
784
+ } else if (id.endsWith('.ts')) {
785
+ parserPlugins.push('@babel/plugin-syntax-typescript')
786
+ } else if (id.endsWith('.jsx')) {
787
+ parserPlugins.push('@babel/plugin-syntax-jsx')
788
+ }
789
+
790
+ const result = await babel.transformAsync(code, {
791
+ filename: id,
792
+ babelrc: false,
793
+ configFile: false,
794
+ compact: false,
795
+ plugins: [...parserPlugins, 'react-refresh/babel'],
796
+ sourceType: 'unambiguous',
797
+ })
798
+
799
+ if (result?.code) {
800
+ // escape the id for use in string literal
801
+ const escapedId = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
802
+
803
+ // wrap with per-file $RefreshReg$ that includes the file path as unique ID
804
+ // and schedule performReactRefresh() after HMR patch re-execution
805
+ const wrappedCode = `
806
+ var __prevRefreshReg = globalThis.$RefreshReg$;
807
+ var __prevRefreshSig = globalThis.$RefreshSig$;
808
+ if (globalThis.__ReactRefresh) {
809
+ globalThis.$RefreshReg$ = function(type, id) {
810
+ globalThis.__ReactRefresh.register(type, "${escapedId}" + " " + id);
811
+ };
812
+ globalThis.$RefreshSig$ = globalThis.__ReactRefresh.createSignatureFunctionForTransform;
813
+ }
814
+
815
+ ${result.code}
816
+
817
+ globalThis.$RefreshReg$ = __prevRefreshReg;
818
+ globalThis.$RefreshSig$ = __prevRefreshSig;
819
+ if (import.meta.hot) {
820
+ import.meta.hot.accept(function() {
821
+ if (globalThis.__ReactRefresh) {
822
+ setTimeout(function() { globalThis.__ReactRefresh.performReactRefresh(); }, 30);
823
+ }
824
+ });
825
+ }
826
+ `
827
+ return { code: wrappedCode }
828
+ }
829
+ } catch {
830
+ // if babel transform fails, just add the accept boundary
831
+ return {
832
+ code: code + `\nif (import.meta.hot) { import.meta.hot.accept(); }\n`,
833
+ }
834
+ }
835
+ },
836
+ }
837
+ }
838
+
839
+ // --- HMR runtime ---
840
+
841
+ function getHmrRuntimeSource(): string {
842
+ return `
843
+ // vxrn HMR runtime for rolldown devMode
844
+ var BaseDevRuntime = DevRuntime;
845
+
846
+ class ReactNativeDevRuntime extends BaseDevRuntime {
847
+ constructor() {
848
+ var _shared = { _socket: null, _queue: [] };
849
+ var clientId = 'rn-' + Date.now() + '-' + Math.random().toString(36).slice(2);
850
+ super({ send: function(msg) {
851
+ var s = JSON.stringify(msg);
852
+ if (_shared._socket && _shared._socket.readyState === 1) { _shared._socket.send(s); }
853
+ else { _shared._queue.push(s); }
854
+ }}, clientId);
855
+ this._shared = _shared;
856
+ this._socket = null;
857
+ this._queue = [];
858
+ this.moduleHotContexts = {};
859
+ }
860
+
861
+ createModuleHotContext(moduleId) {
862
+ var ctx = {
863
+ acceptCallbacks: [],
864
+ accept: function(cb) {
865
+ if (cb) ctx.acceptCallbacks.push({ deps: [moduleId], fn: cb });
866
+ },
867
+ invalidate: function() {},
868
+ on: function() {},
869
+ off: function() {},
870
+ send: function() {},
871
+ get refresh() { return globalThis.__ReactRefresh; },
872
+ get refreshUtils() {
873
+ return {
874
+ isReactRefreshBoundary: function(exports) {
875
+ if (!globalThis.__ReactRefresh) return false;
876
+ if (globalThis.__ReactRefresh.isLikelyComponentType(exports)) return true;
877
+ if (!exports || typeof exports !== 'object') return false;
878
+ var hasExports = false, allComponents = true;
879
+ for (var key in exports) {
880
+ hasExports = true;
881
+ if (key === '__esModule') continue;
882
+ if (!globalThis.__ReactRefresh.isLikelyComponentType(exports[key])) allComponents = false;
883
+ }
884
+ return hasExports && allComponents;
885
+ },
886
+ enqueueUpdate: function() {
887
+ if (globalThis.__ReactRefresh) {
888
+ setTimeout(function() { globalThis.__ReactRefresh.performReactRefresh(); }, 50);
889
+ }
890
+ }
891
+ };
892
+ }
893
+ };
894
+ this.moduleHotContexts[moduleId] = ctx;
895
+ return ctx;
896
+ }
897
+
898
+ applyUpdates(boundaries) {
899
+ for (var i = 0; i < boundaries.length; i++) {
900
+ var moduleId = boundaries[i][0];
901
+ var ctx = this.moduleHotContexts[moduleId];
902
+ if (ctx && ctx.acceptCallbacks) {
903
+ for (var j = 0; j < ctx.acceptCallbacks.length; j++) {
904
+ ctx.acceptCallbacks[j].fn(this.modules[moduleId].exports);
905
+ }
906
+ }
907
+ }
908
+ }
909
+
910
+ setup(socket, origin) {
911
+ if (this._socket) return;
912
+ this._socket = socket;
913
+ // also set the shared messenger socket so queued messages can flush
914
+ if (this._shared) this._shared._socket = socket;
915
+
916
+ var flushQueues = function() {
917
+ // flush messenger queue
918
+ if (this._shared && this._shared._queue.length) {
919
+ for (var i = 0; i < this._shared._queue.length; i++) socket.send(this._shared._queue[i]);
920
+ this._shared._queue = [];
921
+ }
922
+ // flush instance queue
923
+ for (var i = 0; i < this._queue.length; i++) socket.send(this._queue[i]);
924
+ this._queue = [];
925
+ }.bind(this);
926
+
927
+ if (socket.readyState === 1) {
928
+ flushQueues();
929
+ } else {
930
+ socket.addEventListener('open', function() {
931
+ flushQueues();
932
+ }, { once: true });
933
+ }
934
+
935
+ // HMR message handling is done by the outro WebSocket handler
936
+ // the runtime's setup() only needs to flush queued messages
937
+ }
938
+ }
939
+
940
+ globalThis.__rolldown_runtime__ = new ReactNativeDevRuntime();
941
+ `
942
+ }