vxrn 1.26.0 → 2.0.0-beta.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/patches/builtInDepPatches.mjs +23 -42
  2. package/dist/patches/builtInDepPatches.mjs.map +1 -1
  3. package/dist/patches/builtInDepPatches.native.js +23 -42
  4. package/dist/patches/builtInDepPatches.native.js.map +1 -1
  5. package/dist/plugins/reactNativeDevServer.mjs +88 -42
  6. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  7. package/dist/plugins/reactNativeDevServer.native.js +113 -44
  8. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  9. package/dist/rn-commands/bundle/buildBundle.mjs +29 -10
  10. package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
  11. package/dist/rn-commands/bundle/buildBundle.native.js +29 -10
  12. package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
  13. package/dist/utils/createNativeDevEngine.mjs +288 -95
  14. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  15. package/dist/utils/createNativeDevEngine.native.js +329 -95
  16. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  17. package/dist/utils/createNativeDevEngine.test.mjs +285 -23
  18. package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
  19. package/dist/utils/createNativeDevEngine.test.native.js +319 -23
  20. package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
  21. package/dist/utils/patches.test.mjs.map +1 -1
  22. package/dist/utils/patches.test.native.js.map +1 -1
  23. package/dist/utils/scanDepsToOptimize.mjs +1 -3
  24. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  25. package/dist/utils/scanDepsToOptimize.native.js +1 -3
  26. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  27. package/dist/utils/symbolicateNativeStack.mjs +66 -0
  28. package/dist/utils/symbolicateNativeStack.mjs.map +1 -0
  29. package/dist/utils/symbolicateNativeStack.native.js +70 -0
  30. package/dist/utils/symbolicateNativeStack.native.js.map +1 -0
  31. package/dist/utils/symbolicateNativeStack.test.mjs +96 -0
  32. package/dist/utils/symbolicateNativeStack.test.mjs.map +1 -0
  33. package/dist/utils/symbolicateNativeStack.test.native.js +96 -0
  34. package/dist/utils/symbolicateNativeStack.test.native.js.map +1 -0
  35. package/expo-plugin.cjs +12 -6
  36. package/package.json +13 -12
  37. package/src/patches/builtInDepPatches.ts +24 -48
  38. package/src/plugins/reactNativeDevServer.ts +124 -52
  39. package/src/rn-commands/bundle/buildBundle.ts +28 -9
  40. package/src/utils/createNativeDevEngine.test.ts +581 -193
  41. package/src/utils/createNativeDevEngine.ts +506 -123
  42. package/src/utils/patches.test.ts +1 -4
  43. package/src/utils/scanDepsToOptimize.ts +0 -3
  44. package/src/utils/symbolicateNativeStack.test.ts +78 -0
  45. package/src/utils/symbolicateNativeStack.ts +129 -0
  46. package/types/patches/builtInDepPatches.d.ts.map +1 -1
  47. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  48. package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
  49. package/types/utils/createNativeDevEngine.d.ts +22 -4
  50. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
  51. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
  52. package/types/utils/symbolicateNativeStack.d.ts +46 -0
  53. package/types/utils/symbolicateNativeStack.d.ts.map +1 -0
  54. package/types/utils/symbolicateNativeStack.test.d.ts +2 -0
  55. package/types/utils/symbolicateNativeStack.test.d.ts.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vxrn",
3
- "version": "1.26.0",
3
+ "version": "2.0.0-beta.33.1",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -65,16 +65,16 @@
65
65
  "@jridgewell/trace-mapping": "^0.3.25",
66
66
  "@react-native/codegen": "^0.86.2",
67
67
  "@react-native/dev-middleware": "^0.86.2",
68
- "@vxrn/compiler": "1.26.0",
69
- "@vxrn/debug": "1.26.0",
70
- "@vxrn/query-string": "1.26.0",
71
- "@vxrn/resolve": "1.26.0",
72
- "@vxrn/safe-area": "1.26.0",
73
- "@vxrn/url-parse": "1.26.0",
74
- "@vxrn/utils": "1.26.0",
75
- "@vxrn/vendor": "1.26.0",
76
- "@vxrn/vite-flow": "1.26.0",
77
- "@vxrn/vite-plugin-metro": "1.26.0",
68
+ "@vxrn/compiler": "2.0.0-beta.33.1",
69
+ "@vxrn/debug": "2.0.0-beta.33.1",
70
+ "@vxrn/query-string": "2.0.0-beta.33.1",
71
+ "@vxrn/resolve": "2.0.0-beta.33.1",
72
+ "@vxrn/safe-area": "2.0.0-beta.33.1",
73
+ "@vxrn/url-parse": "2.0.0-beta.33.1",
74
+ "@vxrn/utils": "2.0.0-beta.33.1",
75
+ "@vxrn/vendor": "2.0.0-beta.33.1",
76
+ "@vxrn/vite-flow": "2.0.0-beta.33.1",
77
+ "@vxrn/vite-plugin-metro": "2.0.0-beta.33.1",
78
78
  "citty": "^0.1.6",
79
79
  "dotenv": "^17.2.1",
80
80
  "dotenv-expand": "^12.0.2",
@@ -95,13 +95,14 @@
95
95
  "perfect-debounce": "^1.0.0",
96
96
  "picocolors": "^1.0.0",
97
97
  "pkg-types": "^1.0.3",
98
- "rolldown": "^1.2.4",
98
+ "rolldown": "^1.2.8",
99
99
  "semver": "^7.6.3",
100
100
  "vite": "^8.2.2",
101
101
  "ws": "^8.21.0"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@biomejs/biome": "2.3.3",
105
+ "@jridgewell/gen-mapping": "^0.3.5",
105
106
  "@react-native-community/cli": "^20.2.0",
106
107
  "@tamagui/build": "2.6.2",
107
108
  "@types/find-node-modules": "^2.1.2",
@@ -268,36 +268,20 @@ ${contents}
268
268
  },
269
269
  },
270
270
 
271
- // fix RNGH crash on RN 0.81+ new architecture
271
+ // fix RNGH crash on RN 0.86+ new architecture
272
272
  // getShadowNodeFromRef crashes when findHostInstance_DEPRECATED returns null
273
273
  {
274
274
  module: 'react-native-gesture-handler',
275
275
  patchFiles: {
276
- // no upper bound. this was capped at 2.30.0, but 2.32.0 still ships the
277
- // exact unsafe line in all three targets, so the cap silently dropped the
278
- // crash guard rather than retiring it. each replacement is a no-op when
279
- // its target is absent, so an uncapped range retires itself.
280
- version: '>=2.0.0',
276
+ version: '>=3.0.0',
281
277
 
282
278
  'lib/module/getShadowNodeFromRef.js': (contents) => {
283
279
  return contents?.replace(
284
- `return getInternalInstanceHandleFromPublicInstance(findHostInstance_DEPRECATED(ref)).stateNode.node;`,
285
- `const hostInstance = findHostInstance_DEPRECATED(ref);
286
- if (hostInstance === null || hostInstance === undefined) {
287
- return null;
288
- }
289
- const internalHandle = getInternalInstanceHandleFromPublicInstance(hostInstance);
290
- if (internalHandle === null || internalHandle === undefined) {
291
- return null;
292
- }
293
- return internalHandle.stateNode?.node ?? null;`
294
- )
295
- },
280
+ `const hostInstance = isAlreadyHostInstance ? ref : findHostInstance_DEPRECATED(ref);
296
281
 
297
- 'lib/commonjs/getShadowNodeFromRef.js': (contents) => {
298
- return contents?.replace(
299
- `return getInternalInstanceHandleFromPublicInstance(findHostInstance_DEPRECATED(ref)).stateNode.node;`,
300
- `const hostInstance = findHostInstance_DEPRECATED(ref);
282
+ // @ts-ignore Fabric
283
+ return getInternalInstanceHandleFromPublicInstance(hostInstance).stateNode.node;`,
284
+ `const hostInstance = isAlreadyHostInstance ? ref : findHostInstance_DEPRECATED(ref);
301
285
  if (hostInstance === null || hostInstance === undefined) {
302
286
  return null;
303
287
  }
@@ -312,10 +296,16 @@ ${contents}
312
296
  // metro uses src/ directly via "react-native" field in package.json
313
297
  'src/getShadowNodeFromRef.ts': (contents) => {
314
298
  return contents?.replace(
315
- `return getInternalInstanceHandleFromPublicInstance(
316
- findHostInstance_DEPRECATED(ref)
317
- ).stateNode.node;`,
318
- `const hostInstance = findHostInstance_DEPRECATED(ref);
299
+ `const hostInstance = isAlreadyHostInstance
300
+ ? ref
301
+ : findHostInstance_DEPRECATED(ref);
302
+
303
+ // @ts-ignore Fabric
304
+ return getInternalInstanceHandleFromPublicInstance(hostInstance).stateNode
305
+ .node;`,
306
+ `const hostInstance = isAlreadyHostInstance
307
+ ? ref
308
+ : findHostInstance_DEPRECATED(ref);
319
309
  if (hostInstance === null || hostInstance === undefined) {
320
310
  return null;
321
311
  }
@@ -507,10 +497,6 @@ install('URLSearchParams', () => URLSearchParams);
507
497
  {
508
498
  module: 'expo-video',
509
499
  patchFiles: {
510
- 'build/index.js': (contents) => {
511
- // bad type export that can't be auto-fixed
512
- return contents?.replace(`export { VideoThumbnail } from './VideoThumbnail';`, ``)
513
- },
514
500
  'build/**/*.js': ['jsx'],
515
501
  },
516
502
  },
@@ -632,17 +618,13 @@ export const addCustomSourceTransformer = resolveAssetSource.addCustomSourceTran
632
618
  },
633
619
 
634
620
  // a void TurboModule method that throws an Obj-C NSException crashes the app
635
- // with a SIGSEGV: ObjCTurboModule::performVoidMethodInvocation converts it to
636
- // a JS error (convertNSExceptionToJSError) on the TurboModule method queue,
637
- // which touches the Hermes runtime off the JS thread and corrupts the
638
- // single-threaded VM. only safe to build the JS error when the method runs
639
- // sync (on the JS thread); otherwise log. mirrors the isSync guard added for
640
- // value-returning methods in facebook/react-native#50193 (which never covered
641
- // the void path). see tests/test/IOS_PROD_CRASH.md
621
+ // when React Native rethrows it on the TurboModule queue. there is no JS
622
+ // caller for an async void method to receive an error, so log it instead.
623
+ // see tests/test/IOS_PROD_CRASH.md
642
624
  {
643
625
  module: 'react-native',
644
626
  patchFiles: {
645
- version: '>=0.81.0',
627
+ version: '>=0.86.0',
646
628
 
647
629
  'ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm': (
648
630
  contents
@@ -652,20 +634,14 @@ export const addCustomSourceTransformer = resolveAssetSource.addCustomSourceTran
652
634
  ` @try {
653
635
  [inv invokeWithTarget:strongModule];
654
636
  } @catch (NSException *exception) {
655
- throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr);
637
+ // Void methods are always async, re-throw instead of converting to
638
+ // JSError, same as the async branch in performMethodInvocation.
639
+ @throw exception;
656
640
  } @finally {`,
657
641
  ` @try {
658
642
  [inv invokeWithTarget:strongModule];
659
643
  } @catch (NSException *exception) {
660
- if (shouldVoidMethodsExecuteSync_) {
661
- // sync void methods run on the JS thread — safe to build the JS error.
662
- throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr);
663
- } else {
664
- // async void methods run on the TurboModule method queue; building a JS
665
- // error here touches the Hermes runtime off the JS thread and corrupts
666
- // the VM (SIGSEGV). there is no JS caller to receive it, so log instead.
667
- RCTLogError(@"Exception in %s.%s: %@", moduleName, methodNameStr.c_str(), exception.reason);
668
- }
644
+ RCTLogError(@"Exception in %s.%s: %@", moduleName, methodNameStr.c_str(), exception.reason);
669
645
  } @finally {`
670
646
  )
671
647
  },
@@ -11,6 +11,12 @@ import { readFile } from 'node:fs/promises'
11
11
  import { createDevMiddleware } from '@react-native/dev-middleware'
12
12
  import { createNativeDevEngine } from '../utils/createNativeDevEngine'
13
13
  import { getBoundPort } from '../utils/getBoundPort'
14
+ import {
15
+ getNativeFramePlatform,
16
+ isNativeBundleFrame,
17
+ symbolicateNativeStack,
18
+ type NativeStackFrame,
19
+ } from '../utils/symbolicateNativeStack'
14
20
 
15
21
  type ClientMessage = {
16
22
  type: 'client-log'
@@ -77,6 +83,7 @@ export function createReactNativeDevServerPlugin(
77
83
  > = {}
78
84
  const devEngineCreating: Record<string, Promise<unknown> | null> = {}
79
85
  const warnedProdBundleRequest = new Set<string>()
86
+ const pendingReloadPlatforms = new Set<'ios' | 'android'>()
80
87
 
81
88
  const devToolsSocketEndpoints = ['/inspector/device', '/inspector/debug']
82
89
  const reactNativeDevToolsUrl = `http://${host}:${getBoundPort(server)}`
@@ -204,8 +211,11 @@ export function createReactNativeDevServerPlugin(
204
211
  return
205
212
  }
206
213
  addConnectedNativeClient()
214
+ if (pendingReloadPlatforms.has(socket.vxrnPlatform)) {
215
+ socket.send(JSON.stringify({ type: 'hmr:reload' }))
216
+ }
207
217
 
208
- socket.on('message', async (message) => {
218
+ socket.on('message', (message) => {
209
219
  const value = message.toString()
210
220
  if (value === 'ping') {
211
221
  socket.send('pong')
@@ -219,8 +229,9 @@ export function createReactNativeDevServerPlugin(
219
229
  return
220
230
  }
221
231
  if (update.type === 'hmr:invalidate') {
222
- currentEngine.engine.triggerFullBuild()
223
- await currentEngine.engine.ensureLatestBuildOutput()
232
+ // the runtime could not accept an update, so restart it. the bundle
233
+ // it fetches on the way back settles the engine's pending work
234
+ // first, so it lands on the current output.
224
235
  socket.send(JSON.stringify({ type: 'hmr:reload' }))
225
236
  }
226
237
  })
@@ -267,6 +278,55 @@ export function createReactNativeDevServerPlugin(
267
278
  })
268
279
  })
269
280
 
281
+ const getDevEngine = async (platform: 'ios' | 'android') => {
282
+ while (!devEngines[platform]) {
283
+ // prevent duplicate creation from concurrent requests
284
+ if (!devEngineCreating[platform]) {
285
+ const creating = (async () => {
286
+ try {
287
+ console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
288
+ devEngines[platform] = await createNativeDevEngine({
289
+ root,
290
+ port: getBoundPort(server),
291
+ host: typeof host === 'string' ? host : 'localhost',
292
+ platform,
293
+ serverUrl: `http://${typeof host === 'string' && host !== '0.0.0.0' ? host : 'localhost'}:${getBoundPort(server)}`,
294
+ onHmrUpdate: (update) => {
295
+ const msg = JSON.stringify(update)
296
+ // an update with no clientId is for every client on the
297
+ // platform: an error, or a reload after a full rebuild
298
+ const target = 'clientId' in update ? update.clientId : undefined
299
+ if (update.type === 'hmr:reload' && !target) {
300
+ // keep this pending until the rebuilt bundle is served.
301
+ // a previous app instance can leave a live socket while
302
+ // its replacement is still mounting and connecting.
303
+ pendingReloadPlatforms.add(platform)
304
+ }
305
+ hmrWSS.clients.forEach((client) => {
306
+ const nativeClient = client as NativeHmrSocket
307
+ if (
308
+ nativeClient.readyState === 1 &&
309
+ nativeClient.vxrnPlatform === platform &&
310
+ (!target || nativeClient.vxrnClientId === target)
311
+ ) {
312
+ client.send(msg)
313
+ }
314
+ })
315
+ },
316
+ })
317
+ console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
318
+ } finally {
319
+ // clear so a failed creation can be retried.
320
+ devEngineCreating[platform] = null
321
+ }
322
+ })()
323
+ devEngineCreating[platform] = creating
324
+ }
325
+ await devEngineCreating[platform]
326
+ }
327
+ return devEngines[platform]!
328
+ }
329
+
270
330
  // React Native bundle handler
271
331
  const handleRNBundle: Connect.NextHandleFunction = async (req, res) => {
272
332
  const url = new URL(req.url!, `http://${req.headers.host}`)
@@ -281,7 +341,10 @@ export function createReactNativeDevServerPlugin(
281
341
  // here would mean a second engine per platform. production native bundles
282
342
  // come from the build command instead. say so rather than quietly serving
283
343
  // dev bytes to something that asked for production ones.
284
- if (url.searchParams.get('dev') === 'false' && !warnedProdBundleRequest.has(platform)) {
344
+ if (
345
+ url.searchParams.get('dev') === 'false' &&
346
+ !warnedProdBundleRequest.has(platform)
347
+ ) {
285
348
  warnedProdBundleRequest.add(platform)
286
349
  console.warn(
287
350
  `[vxrn] ${platform} bundle requested with dev=false, but the dev server only builds dev bundles. Serving a dev bundle. Use the build command for a production one.`
@@ -289,50 +352,20 @@ export function createReactNativeDevServerPlugin(
289
352
  }
290
353
 
291
354
  try {
292
- const bundle = await (async () => {
293
- if (!devEngines[platform]) {
294
- // prevent duplicate creation from concurrent requests
295
- if (!devEngineCreating[platform]) {
296
- devEngineCreating[platform] = (async () => {
297
- try {
298
- console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
299
- devEngines[platform] = await createNativeDevEngine({
300
- root,
301
- port: getBoundPort(server),
302
- host: typeof host === 'string' ? host : 'localhost',
303
- platform,
304
- serverUrl: `http://${typeof host === 'string' && host !== '0.0.0.0' ? host : 'localhost'}:${getBoundPort(server)}`,
305
- onHmrUpdate: (update) => {
306
- const msg = JSON.stringify(update)
307
- hmrWSS.clients.forEach((client) => {
308
- const nativeClient = client as NativeHmrSocket
309
- if (
310
- nativeClient.readyState === 1 &&
311
- nativeClient.vxrnPlatform === platform &&
312
- (update.type === 'hmr:error' ||
313
- nativeClient.vxrnClientId === update.clientId)
314
- ) {
315
- client.send(msg)
316
- }
317
- })
318
- },
319
- })
320
- console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
321
- } catch (err) {
322
- // clear so next request retries instead of permanently failing
323
- devEngineCreating[platform] = null
324
- throw err
325
- }
326
- })()
327
- }
328
- await devEngineCreating[platform]
329
- }
330
-
331
- return await devEngines[platform]!.getBundle().then((r) => r.code)
332
- })()
333
-
334
- res.writeHead(200, { 'Content-Type': 'text/javascript' })
335
- res.end(bundle)
355
+ const bundle = await (await getDevEngine(platform)).getBundle()
356
+ // a client that connects after this response starts from the current
357
+ // route map and does not need the pending reload intended for the
358
+ // previous runtime.
359
+ res.once('finish', () => pendingReloadPlatforms.delete(platform))
360
+
361
+ // a DevSettings reload requests this exact URL again. native URL
362
+ // loading may otherwise reuse the first response and restart the app
363
+ // on a route map from before a file was added or removed.
364
+ res.writeHead(200, {
365
+ 'Cache-Control': 'no-store',
366
+ 'Content-Type': 'text/javascript',
367
+ })
368
+ res.end(bundle.code)
336
369
  } catch (err) {
337
370
  console.error(` Error building React Native bundle`)
338
371
  console.error(err)
@@ -353,10 +386,49 @@ export function createReactNativeDevServerPlugin(
353
386
  }
354
387
  })
355
388
 
356
- // Symbolicate endpoint
357
- server.middlewares.use('/symbolicate', (_req, res) => {
358
- res.writeHead(200, { 'Content-Type': 'text/plain' })
359
- res.end('TODO')
389
+ // resolve a device stack back to authored files through the dev bundle's
390
+ // source map. react native posts here from LogBox and from console traces.
391
+ server.middlewares.use('/symbolicate', async (req, res) => {
392
+ try {
393
+ const chunks: Buffer[] = []
394
+ for await (const chunk of req) chunks.push(chunk as Buffer)
395
+ const body: { stack?: NativeStackFrame[] } = JSON.parse(
396
+ Buffer.concat(chunks).toString('utf8')
397
+ )
398
+ const stack = body.stack
399
+ if (!Array.isArray(stack)) {
400
+ throw new Error('expected a `stack` array in the request body')
401
+ }
402
+
403
+ const bundleFrame = stack.find((frame) => isNativeBundleFrame(frame.file))
404
+ if (!bundleFrame) {
405
+ // nothing in this stack came from the bundle, so there is nothing to
406
+ // resolve. hand it straight back rather than inventing frames.
407
+ res.writeHead(200, { 'Content-Type': 'application/json' })
408
+ res.end(JSON.stringify({ stack, codeFrame: null }))
409
+ return
410
+ }
411
+
412
+ const framePlatform = getNativeFramePlatform(bundleFrame.file)
413
+ const platform = validPlatforms[framePlatform ?? '']
414
+ if (!platform) {
415
+ throw new Error(
416
+ `could not tell which platform "${bundleFrame.file}" was bundled for`
417
+ )
418
+ }
419
+
420
+ const bundle = await (await getDevEngine(platform)).getBundle()
421
+ res.writeHead(200, { 'Content-Type': 'application/json' })
422
+ res.end(JSON.stringify(symbolicateNativeStack(stack, bundle.map)))
423
+ } catch (err) {
424
+ console.error(`[vxrn] symbolicate failed`, err)
425
+ res.writeHead(500, { 'Content-Type': 'application/json' })
426
+ res.end(
427
+ JSON.stringify({
428
+ error: err instanceof Error ? err.message : String(err),
429
+ })
430
+ )
431
+ }
360
432
  })
361
433
  },
362
434
  }
@@ -1,5 +1,5 @@
1
- import path from 'node:path'
2
1
  import FSExtra from 'fs-extra'
2
+ import { clearTransformCache } from '@vxrn/compiler'
3
3
  import {
4
4
  bundle as metroBundle,
5
5
  type BundleCommandArgs,
@@ -18,15 +18,29 @@ export async function buildBundle(
18
18
  const {
19
19
  platform, // Android is not supported yet.
20
20
  dev,
21
- // bundleEncoding, // Not supported, we are using utf8.
22
- // sourcemapUseAbsolutePath,
23
- // unstableTransformProfile,
24
- // resetCache, // Currently we are not using cache for production builds.
25
- // readGlobalCache,
26
- // entryFile, // Not supported. With VxRN, we are using a static entry file.
27
21
  bundleOutput,
22
+ // React Native's `--bundle-encoding` names a Node Buffer encoding.
23
+ bundleEncoding = 'utf8',
28
24
  assetsDest,
29
- // minify, // Minification is not supported.
25
+ // `--minify` overrides React Native's default of "minify unless this is a
26
+ // dev bundle". undefined means the flag was not passed.
27
+ minify,
28
+ // `--reset-cache` drops the compiler's transform cache, the only cache a
29
+ // rolldown production build reads.
30
+ resetCache,
31
+ // `--entry-file` names the React Native root module. One generates its own
32
+ // native entry from the route tree instead, and __vxrnNativeEntryFile is
33
+ // the supported way to point the build somewhere else.
34
+ // `--read-global-cache` / `--max-workers` / `--config` / `--transformer` /
35
+ // `--resolver-option` configure Metro's global cache, worker pool and
36
+ // resolver. A rolldown production build has none of them: it transforms
37
+ // from source in-process. React Native's own bundle command ignores
38
+ // `--read-global-cache` too.
39
+ // `--unstable-transform-profile` picks a JS engine to target. This pipeline
40
+ // always downlevels for Hermes, which is what every profile it accepts
41
+ // ('default', 'hermes', 'hermes-canary') can run.
42
+ // `--sourcemap-use-absolute-path`, `--sourcemap-sources-root`,
43
+ // `--asset-catalog-dest` and `--indexed-ram-bundle` are not implemented.
30
44
  } = args
31
45
 
32
46
  const { root } = ctx
@@ -53,6 +67,10 @@ export async function buildBundle(
53
67
  process.env.NODE_ENV = 'production'
54
68
  }
55
69
 
70
+ if (resetCache) {
71
+ clearTransformCache()
72
+ }
73
+
56
74
  console.info(`[vxrn] building native bundle for ${platform}...`)
57
75
  const nativeEntryFile = (globalThis as { __vxrnNativeEntryFile?: unknown })
58
76
  .__vxrnNativeEntryFile
@@ -67,6 +85,7 @@ export async function buildBundle(
67
85
  serverUrl: process.env.ONE_SERVER_URL,
68
86
  assetsDest,
69
87
  sourcemap: !!args.sourcemapOutput,
88
+ minify,
70
89
  })
71
90
  const builtBundle = result.code
72
91
 
@@ -76,7 +95,7 @@ export async function buildBundle(
76
95
  }
77
96
 
78
97
  console.info(`Writing bundle to ${bundleOutput}...`)
79
- FSExtra.writeFileSync(bundleOutput, builtBundle, { encoding: 'utf8' })
98
+ FSExtra.writeFileSync(bundleOutput, builtBundle, { encoding: bundleEncoding })
80
99
  console.info('Done.')
81
100
 
82
101
  // Prevent the process not getting exited for some unknown reason.