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
@@ -1,7 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { basename, dirname, extname, join, relative, resolve } from "node:path";
3
- import { copyFileSync, mkdirSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
4
4
  import { pathToFileURL } from "node:url";
5
+ import micromatch from "micromatch";
5
6
  import { loadEnv, normalizePath } from "vite";
6
7
  import { shouldStripFlow, transformHermesAsync } from "@vxrn/compiler";
7
8
  import { DEFAULT_ASSET_EXTS } from "../constants/defaults.mjs";
@@ -109,7 +110,12 @@ function getNativeTransformConfig(platform, dev, root) {
109
110
  }
110
111
  };
111
112
  }
112
- function getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest, onAsset, sourceMaps = false) {
113
+ function getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest, onAsset, sourceMaps = false, userPlugins = []) {
114
+ const context = {
115
+ root,
116
+ platform,
117
+ dev
118
+ };
113
119
  return [...(globalThis.__vxrnAddNativePlugins || []), serverFileExclusionPlugin(), environmentGuardPlugin(), hmrClientNoopPlugin(), cssStubPlugin(), viteImportGlobPlugin({
114
120
  root
115
121
  }), vxrnCompilerPlugin(platform, dev, root, sourceMaps), rnCodegenPlugin({
@@ -119,25 +125,38 @@ function getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest,
119
125
  platform,
120
126
  assetsDest,
121
127
  onAsset
122
- }), hermesCompatSWCPlugin(dev, sourceMaps), hermesLoopsPlugin(sourceMaps)];
128
+ }), hermesCompatSWCPlugin(dev, sourceMaps), hermesLoopsPlugin(sourceMaps), ...userPlugins].map(plugin => plugin.api?.vxrnNative ? plugin.api.vxrnNative(context) : plugin);
123
129
  }
124
- function getNativeOutputOptions(prelude, sourcemap) {
130
+ function getNativeOutputOptions(prelude, sourcemap, minify) {
125
131
  return {
126
132
  format: "esm",
127
133
  sourcemap,
134
+ sourcemapPathTransform: (relativeSourcePath, sourcemapPath) => normalizePath(resolve(dirname(sourcemapPath), relativeSourcePath)),
128
135
  intro: prelude,
129
136
  codeSplitting: false,
130
- strictExecutionOrder: true
137
+ strictExecutionOrder: true,
138
+ minify: minify || "dce-only"
131
139
  };
132
140
  }
133
141
  function normalizeNativeCommonJSInterop(code) {
134
142
  return code.replace(/(\b__toESM(?:\$\d+)?\(\s*(?:require[\w$]*\(\)|__rolldown_runtime__\.loadExports\("(?:\\.|[^"\\])*"\))\s*),\s*1(\s*\))/g, "$1$2");
135
143
  }
144
+ function blankPreservingLines(match) {
145
+ return "\n".repeat(match.match(/\n/g)?.length ?? 0);
146
+ }
147
+ function countLines(code) {
148
+ return (code.match(/\n/g)?.length ?? 0) + 1;
149
+ }
150
+ function assertBundleLinesPreserved(before, after, stage) {
151
+ const beforeLines = countLines(before);
152
+ const afterLines = countLines(after);
153
+ if (beforeLines !== afterLines) throw new Error(`[vxrn] native bundle post-processing (${stage}) changed the line count (${beforeLines} -> ${afterLines}), which invalidates the source map. Every pass after Rolldown emits the map must preserve lines.`);
154
+ }
136
155
  function postProcessNativeBundle(code) {
137
156
  code = normalizeNativeCommonJSInterop(code);
138
157
  code = code.replace(/\btypeof\s+import\.meta\b/g, "\"object\"");
139
- code = code.replace(/^\s*export\s*\{[^}]*\}\s*;?\s*$/gm, "");
140
- code = code.replace(/^\s*export\s+default\s+([^;\n]+);?\s*$/gm, "$1;");
158
+ code = code.replace(/^[^\S\n]*export[^\S\n]*\{[^}]*\}[^\S\n]*;?[^\S\n]*$/gm, blankPreservingLines);
159
+ code = code.replace(/^([^\S\n]*)export[^\S\n]+default[^\S\n]+([^;\n]+);?[^\S\n]*$/gm, "$1$2;");
141
160
  code = code.replace(/^if \(import\.meta\.hot\).*$/gm, "");
142
161
  {
143
162
  const idx = code.indexOf("NativeAnimatedModule_default ?? NativeAnimatedTurboModule_default;");
@@ -150,7 +169,8 @@ function postProcessNativeBundle(code) {
150
169
  const afterMarker = code.indexOf("})();", idx);
151
170
  if (afterMarker !== -1) {
152
171
  const end = afterMarker + 5;
153
- code = code.slice(0, beforeMarker) + "NativeAnimatedModule = NativeAnimatedModule_default ?? NativeAnimatedTurboModule_default;" + code.slice(end);
172
+ const removed = code.slice(beforeMarker, end);
173
+ code = code.slice(0, beforeMarker) + "NativeAnimatedModule = NativeAnimatedModule_default ?? NativeAnimatedTurboModule_default;" + blankPreservingLines(removed) + code.slice(end);
154
174
  }
155
175
  }
156
176
  }
@@ -161,7 +181,7 @@ function postProcessNativeBundle(code) {
161
181
  function wrapNativeBundleModuleScope(code) {
162
182
  const idx = code.indexOf("//#region \\0rolldown/runtime.js");
163
183
  if (idx === -1) return code;
164
- return code.slice(0, idx) + ";(function() {\n" + code.slice(idx) + "\n})();\n";
184
+ return code.slice(0, idx) + ";(function() {" + code.slice(idx) + "\n})();\n";
165
185
  }
166
186
  async function downlevelClassFieldsInBundle(code) {
167
187
  const startMarker = "//#region \\0rolldown/runtime.js";
@@ -191,6 +211,19 @@ ${transformedCode}`;
191
211
  const linePreservingRuntime = transformed + "\n".repeat(originalNewlines - transformedNewlines);
192
212
  return code.slice(0, startIdx) + linePreservingRuntime + code.slice(runtimeEnd);
193
213
  }
214
+ function nativeBundlePostProcessPlugin() {
215
+ return {
216
+ name: "vxrn:native-post-process",
217
+ async renderChunk(code) {
218
+ const processed = await downlevelClassFieldsInBundle(postProcessNativeBundle(code));
219
+ assertBundleLinesPreserved(code, processed, "production");
220
+ return {
221
+ code: processed,
222
+ map: null
223
+ };
224
+ }
225
+ };
226
+ }
194
227
  async function createNativeDevEngine(options) {
195
228
  const {
196
229
  root,
@@ -218,7 +251,18 @@ async function createNativeDevEngine(options) {
218
251
  let bundleReject = null;
219
252
  let bundlePromise = null;
220
253
  const resolvedHost = host === "0.0.0.0" ? "localhost" : host;
221
- const engine = await dev({
254
+ const virtualEntry = nativeVirtualEntryPlugin(root, {
255
+ dev: true,
256
+ platform,
257
+ dynamicRoutes: true
258
+ });
259
+ let engineWork = Promise.resolve();
260
+ const queueEngineWork = work => {
261
+ const result = engineWork.then(work, work);
262
+ engineWork = result.catch(() => {});
263
+ return result;
264
+ };
265
+ const inputOptions = {
222
266
  input: VIRTUAL_NATIVE_ENTRY,
223
267
  cwd: root,
224
268
  platform: "neutral",
@@ -238,72 +282,46 @@ async function createNativeDevEngine(options) {
238
282
  moduleTypes: {
239
283
  ".js": "jsx"
240
284
  },
241
- plugins: [nativeVirtualEntryPlugin(root, {
242
- dev: true
243
- }), ...getNativePlugins(root, platform, viteImportGlobPlugin, true, void 0, assetRegistry.register, false), ...userPlugins]
244
- }, {
245
- ...getNativeOutputOptions(prelude, false),
285
+ plugins: [virtualEntry.plugin, ...getNativePlugins(root, platform, viteImportGlobPlugin, true, void 0, assetRegistry.register, true, userPlugins)]
286
+ };
287
+ const outputOptions = {
288
+ ...getNativeOutputOptions(prelude, true, false),
246
289
  outro: `
247
290
  try {
248
291
  var __WS = (init_WebSocket(), __toCommonJS(WebSocket_exports)).default;
249
292
  var __hmrUrl = 'ws://${resolvedHost}:${port}/hot?platform=${platform}&clientId=' + encodeURIComponent(__rolldown_runtime__.clientId);
250
- var __hmrWS = new __WS(__hmrUrl);
251
- __hmrWS.onmessage = function(event) {
252
- try {
253
- var msg = JSON.parse(event.data);
254
- var g = typeof global !== 'undefined' ? global : globalThis;
255
- if (msg.type === 'hmr:update' && msg.code) {
256
- var applied = __rolldown_runtime__.applyHmrUpdate(msg.code, msg.changedIds, msg.seq);
257
- if (!applied) {
258
- var updateSettings = g.__turboModuleProxy ? g.__turboModuleProxy('DevSettings') : null;
259
- if (updateSettings && updateSettings.reload) updateSettings.reload();
260
- }
261
- } else if (msg.type === 'hmr:reload') {
262
- var ds = g.__turboModuleProxy ? g.__turboModuleProxy('DevSettings') : null;
263
- if (ds && ds.reload) ds.reload();
264
- }
265
- } catch(e) { console.error('[vxrn] HMR eval error:', e); }
266
- };
267
- __hmrWS.onopen = function() {
268
- if (typeof __rolldown_runtime__ !== 'undefined' && __rolldown_runtime__.setup) {
269
- __rolldown_runtime__.setup(__hmrWS);
270
- }
271
- };
272
- __hmrWS.onerror = function(e) { console.warn('[vxrn] HMR connection error:', e.message || e); };
273
- } catch(e) {}
293
+ __rolldown_runtime__.setup(new __WS(__hmrUrl));
294
+ } catch(e) {
295
+ // a swallowed failure here leaves the app permanently without Fast Refresh and
296
+ // nothing on screen or in the terminal says so.
297
+ console.error('[vxrn] HMR client failed to start:', (e && e.message) || e);
298
+ }
274
299
  `
275
- }, {
300
+ };
301
+ let outputProcessed = Promise.resolve();
302
+ let engine;
303
+ let routeWatcher;
304
+ const rebuildIfRouteGraphChanged = () => queueEngineWork(async () => {
305
+ const {
306
+ files: knownRoutes,
307
+ scanFiles,
308
+ updateFiles
309
+ } = virtualEntry.routes;
310
+ const currentRoutes = scanFiles();
311
+ if (!(currentRoutes.size !== knownRoutes.size || [...currentRoutes].some(route => !knownRoutes.has(route)))) return false;
312
+ updateFiles(currentRoutes);
313
+ return true;
314
+ });
315
+ engine = await dev(inputOptions, outputOptions, {
276
316
  onOutput: async result => {
277
- if (result instanceof Error) {
278
- console.error("[vxrn] native bundle error:", result.message);
279
- if (!currentBundle) {
280
- firstBuildError ||= result;
281
- if (bundleReject) {
282
- const reject = bundleReject;
283
- bundleResolve = null;
284
- bundleReject = null;
285
- bundlePromise = null;
286
- reject(firstBuildError);
287
- }
288
- }
289
- return;
290
- }
291
- const chunk = result.output.find(o => o.type === "chunk" && o.isEntry);
292
- if (chunk && "code" in chunk) {
293
- firstBuildError = null;
294
- let code = postProcessNativeBundle(chunk.code);
295
- code = await downlevelClassFieldsInBundle(code);
296
- code = wrapNativeBundleModuleScope(code);
297
- currentBundle = {
298
- code
299
- };
300
- console.info(`[vxrn] native bundle ready (${Math.round(code.length / 1024)}KB)`);
301
- if (bundleResolve) {
302
- bundleResolve(currentBundle);
303
- bundleResolve = null;
304
- bundleReject = null;
305
- bundlePromise = null;
306
- }
317
+ let finishOutput = () => {};
318
+ outputProcessed = new Promise(resolve2 => {
319
+ finishOutput = resolve2;
320
+ });
321
+ try {
322
+ await handleOutput(result);
323
+ } finally {
324
+ finishOutput();
307
325
  }
308
326
  },
309
327
  onHmrUpdates: async result => {
@@ -314,6 +332,7 @@ try {
314
332
  });
315
333
  return;
316
334
  }
335
+ if (await rebuildIfRouteGraphChanged()) return;
317
336
  for (const {
318
337
  clientId,
319
338
  update
@@ -331,11 +350,74 @@ try {
331
350
  rebuildStrategy: "never",
332
351
  watch: {}
333
352
  });
353
+ async function handleOutput(result) {
354
+ if (result instanceof Error) {
355
+ console.error("[vxrn] native bundle error:", result.message);
356
+ if (/panic/i.test(result.message)) console.error("[vxrn] rolldown itself panicked. Fast Refresh will full-reload until you restart the dev server.");
357
+ if (!currentBundle) {
358
+ firstBuildError ||= result;
359
+ if (bundleReject) {
360
+ const reject = bundleReject;
361
+ bundleResolve = null;
362
+ bundleReject = null;
363
+ bundlePromise = null;
364
+ reject(firstBuildError);
365
+ }
366
+ }
367
+ return;
368
+ }
369
+ const chunk = result.output.find(o => o.type === "chunk" && o.isEntry);
370
+ if (chunk && "code" in chunk) {
371
+ firstBuildError = null;
372
+ let code = postProcessNativeBundle(chunk.code);
373
+ code = await downlevelClassFieldsInBundle(code);
374
+ assertBundleLinesPreserved(chunk.code, code, "dev");
375
+ code = wrapNativeBundleModuleScope(code);
376
+ if (!chunk.map) throw new Error("[vxrn] rolldown produced no source map for the dev bundle, so /symbolicate cannot resolve a frame");
377
+ currentBundle = {
378
+ code,
379
+ map: chunk.map.toString()
380
+ };
381
+ console.info(`[vxrn] native bundle ready (${Math.round(code.length / 1024)}KB)`);
382
+ if (bundleResolve) {
383
+ bundleResolve(currentBundle);
384
+ bundleResolve = null;
385
+ bundleReject = null;
386
+ bundlePromise = null;
387
+ }
388
+ }
389
+ }
334
390
  await engine.run();
391
+ let routeWatchTarget = virtualEntry.routes.routeRoot;
392
+ while (!statSync(routeWatchTarget, {
393
+ throwIfNoEntry: false
394
+ })?.isDirectory()) {
395
+ const parent = dirname(routeWatchTarget);
396
+ if (parent === routeWatchTarget) break;
397
+ routeWatchTarget = parent;
398
+ }
399
+ const normalizedRouteRoot = normalizePath(virtualEntry.routes.routeRoot);
400
+ routeWatcher = watch(routeWatchTarget, {
401
+ recursive: true
402
+ }, (_eventType, file) => {
403
+ if (file === null) return;
404
+ const changed = normalizePath(resolve(routeWatchTarget, file.toString()));
405
+ if (changed !== normalizedRouteRoot && !changed.startsWith(`${normalizedRouteRoot}/`)) return;
406
+ rebuildIfRouteGraphChanged().catch(error => {
407
+ console.error(`[vxrn] rebuilding the native route map failed`, error);
408
+ });
409
+ });
410
+ routeWatcher.on("error", error => {
411
+ console.error(`[vxrn] watching the native route map failed`, error);
412
+ });
413
+ await rebuildIfRouteGraphChanged();
335
414
  return {
336
415
  engine,
337
416
  async getBundle() {
338
- await engine.ensureLatestBuildOutput();
417
+ await queueEngineWork(async () => {
418
+ await engine.ensureLatestBuildOutput();
419
+ await outputProcessed;
420
+ });
339
421
  if (currentBundle) return currentBundle;
340
422
  if (firstBuildError) throw firstBuildError;
341
423
  if (!bundlePromise) {
@@ -363,6 +445,7 @@ try {
363
445
  return assetRegistry.resolve(pathname, hash);
364
446
  },
365
447
  async close() {
448
+ routeWatcher?.close();
366
449
  await engine.close();
367
450
  }
368
451
  };
@@ -376,7 +459,8 @@ async function buildNativeBundle(options) {
376
459
  entryFile,
377
460
  assetsDest,
378
461
  plugins: userPlugins = [],
379
- sourcemap = false
462
+ sourcemap = false,
463
+ minify = !dev
380
464
  } = options;
381
465
  const {
382
466
  build
@@ -404,21 +488,22 @@ async function buildNativeBundle(options) {
404
488
  ".js": "jsx"
405
489
  },
406
490
  plugins: [...(entryFile ? [] : [nativeVirtualEntryPlugin(root, {
407
- dev
408
- })]), ...getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest, void 0, sourcemap), ...userPlugins],
409
- output: getNativeOutputOptions(prelude, sourcemap)
491
+ dev,
492
+ platform
493
+ }).plugin]), ...getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest, void 0, sourcemap, userPlugins), nativeBundlePostProcessPlugin()],
494
+ output: getNativeOutputOptions(prelude, sourcemap, minify)
410
495
  })).output.find(o => o.type === "chunk" && o.isEntry);
411
496
  if (!chunk || !("code" in chunk)) throw new Error("[vxrn] production build produced no output");
412
- let code = postProcessNativeBundle(chunk.code);
413
- code = await downlevelClassFieldsInBundle(code);
497
+ if (sourcemap && !chunk.map) throw new Error("[vxrn] a source map was requested but rolldown produced none for the production bundle");
414
498
  return {
415
- code,
499
+ code: chunk.code,
416
500
  map: sourcemap ? chunk.map?.toString() : void 0
417
501
  };
418
502
  }
419
503
  const VIRTUAL_NATIVE_ENTRY = "virtual:native-entry";
420
504
  function nativeVirtualEntryPlugin(root, opts) {
421
505
  const isDev = opts?.dev !== false;
506
+ const useDynamicRouteRegistry = opts.dynamicRoutes === true;
422
507
  const resolvedId = normalizePath(resolve(root, "__virtual-native-entry.tsx"));
423
508
  const entryConfig = globalThis.__vxrnNativeEntryConfig || {};
424
509
  const routerRoot = entryConfig.routerRoot || "app";
@@ -433,8 +518,7 @@ function nativeVirtualEntryPlugin(root, opts) {
433
518
  return `import ${JSON.stringify(resolved)};`;
434
519
  })();
435
520
  const routeGlobs = [`./${routerRoot}/**/*.tsx`, `./${routerRoot}/**/*.ts`, `!./${routerRoot}/**/*+api.*`, `!./${routerRoot}/**/*.test.*`, `!./${routerRoot}/**/*.d.ts`, `!./${routerRoot}/**/*.server.*`, `!./${routerRoot}/**/_middleware.*`, `!./${routerRoot}/**/*.web.*`, ...(entryConfig.ignoredRouteFiles || []).map(pattern => `!./${routerRoot}/${pattern}`)];
436
- const entryCode = `
437
- ${isDev ? `
521
+ const refreshSetup = isDev ? `
438
522
  // react-refresh/runtime MUST initialize before React loads
439
523
  import RefreshRuntime from 'react-refresh/runtime';
440
524
  RefreshRuntime.injectIntoGlobalHook(globalThis);
@@ -443,22 +527,25 @@ globalThis.$RefreshReg$ = function(type, id) {
443
527
  RefreshRuntime.register(type, id);
444
528
  };
445
529
  globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
446
- ` : ""}
530
+ ` : "";
531
+ const entryCode = () => `
532
+ ${refreshSetup}
447
533
  import * as ReactNativeInitializeCore from 'react-native/Libraries/Core/InitializeCore';
448
534
  import NativeWebSocket from 'react-native/Libraries/WebSocket/WebSocket';
535
+ import DevSettings from 'react-native/Libraries/Utilities/DevSettings';
449
536
  ${setupFileImport}
537
+ ${routeImports}
450
538
  import { createApp } from 'one';
451
539
 
452
540
  void ReactNativeInitializeCore;
453
541
  globalThis.WebSocket = NativeWebSocket;
542
+ globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__ = function(reason) {
543
+ DevSettings.reload(reason);
544
+ };
454
545
 
455
- var _routes = import.meta.glob(${JSON.stringify(routeGlobs)}, { exhaustive: true });
456
- // fix route keys: One expects '/${routerRoot}/...' prefix but import.meta.glob returns './${routerRoot}/...'
457
- var routes = {};
458
- Object.keys(_routes).forEach(function(key) {
459
- var normalizedKey = key.replace(/^\\.\\//, '');
460
- routes['/' + normalizedKey] = _routes[key];
461
- });
546
+ // React Native reloads a dev bundle in the existing JS runtime. Advance One's
547
+ // route-cache version so a full reload cannot reuse the previous context.
548
+ globalThis.__vxrnVersion = (globalThis.__vxrnVersion || 0) + 1;
462
549
 
463
550
  createApp({
464
551
  routes: routes,
@@ -466,14 +553,83 @@ createApp({
466
553
  flags: ${JSON.stringify(flags)},
467
554
  linking: ${JSON.stringify(linking)},
468
555
  });
556
+
557
+ ${routeHmr}
469
558
  `;
559
+ const routeRoot = resolve(root, routerRoot);
560
+ const stripDot = pattern => pattern.replace(/^\.\//, "");
561
+ const includeGlobs = routeGlobs.filter(p => !p.startsWith("!")).map(stripDot);
562
+ const excludeGlobs = routeGlobs.filter(p => p.startsWith("!")).map(p => stripDot(p.slice(1)));
563
+ const isRouteFile = file => {
564
+ const relativeToRoot = normalizePath(relative(root, file));
565
+ return micromatch.isMatch(relativeToRoot, includeGlobs) && !micromatch.isMatch(relativeToRoot, excludeGlobs);
566
+ };
567
+ const scanFiles = () => {
568
+ const files = /* @__PURE__ */new Set();
569
+ const walkRouteRoot = dir => {
570
+ for (const entry of readdirSync(dir, {
571
+ withFileTypes: true
572
+ })) {
573
+ const child = resolve(dir, entry.name);
574
+ if (entry.isDirectory()) walkRouteRoot(child);else if (isRouteFile(child)) files.add(normalizePath(child));
575
+ }
576
+ };
577
+ if (statSync(routeRoot, {
578
+ throwIfNoEntry: false
579
+ })?.isDirectory()) walkRouteRoot(routeRoot);
580
+ return files;
581
+ };
582
+ const registrySpecifier = `./node_modules/.vxrn/native-routes-${opts.platform}.mjs`;
583
+ const registryPath = resolve(root, registrySpecifier);
584
+ const routes = {
585
+ routeRoot,
586
+ files: /* @__PURE__ */new Set(),
587
+ isRouteFile,
588
+ scanFiles,
589
+ updateFiles(files) {
590
+ routes.files = files;
591
+ if (!useDynamicRouteRegistry) return;
592
+ const source = `export const routes = {
593
+ ${[...files].sort().map(file => {
594
+ const routeKey = `/${normalizePath(relative(root, file))}`;
595
+ let importPath = normalizePath(relative(dirname(registryPath), file));
596
+ if (!importPath.startsWith(".")) importPath = `./${importPath}`;
597
+ return ` ${JSON.stringify(routeKey)}: () => import(${JSON.stringify(importPath)})`;
598
+ }).join(",\n")}
599
+ }
600
+ `;
601
+ mkdirSync(dirname(registryPath), {
602
+ recursive: true
603
+ });
604
+ if (!existsSync(registryPath) || readFileSync(registryPath, "utf8") !== source) writeFileSync(registryPath, source);
605
+ }
606
+ };
607
+ routes.updateFiles(routes.scanFiles());
608
+ const routeImports = useDynamicRouteRegistry ? `import { routes } from ${JSON.stringify(registrySpecifier)};` : `var _routes = import.meta.glob(${JSON.stringify(routeGlobs)}, { exhaustive: true });
609
+ // fix route keys: One expects '/${routerRoot}/...' prefix but import.meta.glob returns './${routerRoot}/...'
610
+ var routes = {};
611
+ Object.keys(_routes).forEach(function(key) {
612
+ var normalizedKey = key.replace(/^\\.\\//, '');
613
+ routes['/' + normalizedKey] = _routes[key];
614
+ });`;
615
+ const routeHmr = useDynamicRouteRegistry ? `if (import.meta.hot) {
616
+ import.meta.hot.accept(${JSON.stringify(registrySpecifier)}, function(next) {
617
+ if (globalThis.__VXRN_UPDATE_NATIVE_ROUTES__) {
618
+ globalThis.__VXRN_UPDATE_NATIVE_ROUTES__(next.routes);
619
+ }
620
+ });
621
+ }` : "";
470
622
  return {
471
- name: "vxrn:native-virtual-entry",
472
- resolveId(id) {
473
- if (id === VIRTUAL_NATIVE_ENTRY) return resolvedId;
474
- },
475
- load(id) {
476
- if (id === resolvedId) return entryCode;
623
+ routes,
624
+ plugin: {
625
+ name: "vxrn:native-virtual-entry",
626
+ resolveId(id) {
627
+ if (id === VIRTUAL_NATIVE_ENTRY) return resolvedId;
628
+ },
629
+ load(id) {
630
+ if (id !== resolvedId) return;
631
+ return entryCode();
632
+ }
477
633
  }
478
634
  };
479
635
  }
@@ -1154,14 +1310,51 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
1154
1310
  }
1155
1311
  }
1156
1312
 
1313
+ reload(reason) {
1314
+ var reloadNativeBundle = globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__;
1315
+ if (typeof reloadNativeBundle === 'function') {
1316
+ reloadNativeBundle(reason);
1317
+ return;
1318
+ }
1319
+ console.error('[vxrn HMR]: DevSettings.reload is unavailable');
1320
+ }
1321
+
1157
1322
  setup(socket) {
1158
1323
  if (this._socket) return;
1159
1324
  this._socket = socket;
1325
+ var runtime = this;
1326
+ // addEventListener rather than socket.onmessage: the bundle's WebSocket is
1327
+ // whatever the host platform provides, and a host that does not honor on*
1328
+ // property assignment would otherwise leave the app permanently without
1329
+ // Fast Refresh and say nothing.
1330
+ socket.addEventListener('message', function(event) {
1331
+ var message;
1332
+ try {
1333
+ message = JSON.parse(event.data);
1334
+ } catch (error) {
1335
+ return;
1336
+ }
1337
+ try {
1338
+ if (message.type === 'hmr:update') {
1339
+ // a patch that cannot be applied in place is the reload signal
1340
+ if (!runtime.applyHmrUpdate(message.code, message.changedIds, message.seq)) {
1341
+ runtime.reload('native HMR update could not be applied');
1342
+ }
1343
+ } else if (message.type === 'hmr:reload') {
1344
+ runtime.reload('native route map changed');
1345
+ }
1346
+ } catch (error) {
1347
+ console.error('[vxrn HMR]: failed to apply update', error);
1348
+ }
1349
+ });
1350
+ socket.addEventListener('error', function(event) {
1351
+ console.warn('[vxrn HMR]: connection error', (event && event.message) || event);
1352
+ });
1160
1353
  }
1161
1354
  }
1162
1355
 
1163
1356
  globalThis.__rolldown_runtime__ = new ReactNativeDevRuntime();
1164
1357
  `;
1165
1358
  }
1166
- export { buildNativeBundle, createNativeDevAssetRegistry, createNativeDevEngine, getHermesSWCIncludes, getHmrRuntimeSource, getNativeAssetData, getNativeTransformConfig, hermesCompatPlugin, hermesCompatSWCPlugin, hermesLoopsPlugin, hmrClientNoopPlugin, nativeAnimatedGuardPlugin, normalizeNativeCommonJSInterop, vxrnCompilerPlugin, wrapNativeBundleModuleScope };
1359
+ export { buildNativeBundle, createNativeDevAssetRegistry, createNativeDevEngine, getHermesSWCIncludes, getHmrRuntimeSource, getNativeAssetData, getNativeTransformConfig, hermesCompatPlugin, hermesCompatSWCPlugin, hermesLoopsPlugin, hmrClientNoopPlugin, nativeAnimatedGuardPlugin, normalizeNativeCommonJSInterop, postProcessNativeBundle, vxrnCompilerPlugin, wrapNativeBundleModuleScope };
1167
1360
  //# sourceMappingURL=createNativeDevEngine.mjs.map