vxrn 1.21.1 → 1.21.3

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.
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { getNativeTransformConfig, wrapNativeBundleModuleScope } from "./createNativeDevEngine.native.js";
2
+ import { getHermesSWCIncludes, getNativeTransformConfig, hmrClientNoopPlugin, vxrnCompilerPlugin, wrapNativeBundleModuleScope } from "./createNativeDevEngine.native.js";
3
3
  var root = "/tmp/vxrn-native-env-define-test-nonexistent";
4
4
  describe("getNativeTransformConfig platform env defines", function () {
5
5
  var _loop = function (_i2, _iter2) {
@@ -50,4 +50,60 @@ globalThis.__rolldown_runtime__ = {};
50
50
  expect(wrapNativeBundleModuleScope(input)).toBe(input);
51
51
  });
52
52
  });
53
+ describe("getHermesSWCIncludes", function () {
54
+ var CLASS_SET = ["transform-classes", "transform-class-properties", "transform-class-static-block", "transform-private-methods", "transform-private-property-in-object"];
55
+ it("always includes the full Hermes class-transform set (dev and prod)", function () {
56
+ expect(getHermesSWCIncludes(true)).toEqual(expect.arrayContaining(CLASS_SET));
57
+ expect(getHermesSWCIncludes(false)).toEqual(expect.arrayContaining(CLASS_SET));
58
+ });
59
+ it("adds transform-async-to-generator only in production", function () {
60
+ expect(getHermesSWCIncludes(true)).not.toContain("transform-async-to-generator");
61
+ expect(getHermesSWCIncludes(false)).toContain("transform-async-to-generator");
62
+ });
63
+ });
64
+ describe("hmrClientNoopPlugin", function () {
65
+ var plugin = hmrClientNoopPlugin();
66
+ var resolveId = plugin.resolveId;
67
+ var load = plugin.load;
68
+ var VIRTUAL_ID = "\0vxrn-hmr-client-noop";
69
+ it.each([["react-native/Libraries/Utilities/HMRClient", void 0], ["../Utilities/HMRClient", "/project/node_modules/react-native/Libraries/Core.js"], ["../../Utilities/HMRClient.js", "/project/react-native/Libraries/Core.js"], ["..\\Utilities\\HMRClient.js", "C:\\project\\react-native\\Libraries\\Core.js"], ["../Utilities/HMRClient.ts", "/project/node_modules/react-native/Core.js"], ["../Utilities/HMRClient.tsx", "/project/node_modules/react-native/Core.js"], ["../Utilities/HMRClient.cjs", "/project/node_modules/react-native/Core.js"]])("aliases RN HMRClient specifier %j to the no-op virtual module", function (source, importer) {
70
+ expect(resolveId(source, importer)).toEqual({
71
+ id: VIRTUAL_ID,
72
+ external: false
73
+ });
74
+ });
75
+ it.each(["react-native/Libraries/Utilities/HMRClientRegistry", "some/MyUtilities/HMRClient", "react-native/Libraries/Core/setUpDeveloperTools", "react"])("does not touch unrelated specifier %j", function (source) {
76
+ expect(resolveId(source)).toBeUndefined();
77
+ });
78
+ it("does not alias an app-authored Utilities/HMRClient module", function () {
79
+ expect(resolveId("../Utilities/HMRClient", "/project/src/App.tsx")).toBeUndefined();
80
+ });
81
+ it("loads a no-op module exposing every HMRClient method RN calls", function () {
82
+ var result = load(VIRTUAL_ID);
83
+ expect(result === null || result === void 0 ? void 0 : result.moduleType).toBe("js");
84
+ for (var _i = 0, _iter = ["setup", "enable", "disable", "registerBundle", "log", "isEnabled"]; _i < _iter.length; _i++) {
85
+ var method = _iter[_i];
86
+ expect(result.code).toContain(method);
87
+ }
88
+ expect(result.code).toContain("export default HMRClient");
89
+ });
90
+ it("does not load unrelated ids", function () {
91
+ expect(load("\0some-other-virtual")).toBeUndefined();
92
+ });
93
+ });
94
+ describe("vxrnCompilerPlugin React Refresh registration", function () {
95
+ it("keeps initial-bundle registrations visible to rolldown", async function () {
96
+ var previousNodeEnv = process.env.NODE_ENV;
97
+ process.env.NODE_ENV = "test";
98
+ try {
99
+ var transform = vxrnCompilerPlugin("ios", true).transform;
100
+ var code = (await transform("export const marker = \"$RefreshReg$(\"; export function Probe() { return <div>probe</div> }", "/project/src/Probe.tsx")).code;
101
+ expect(code).toContain("var __vxrnRefreshReg = globalThis.$RefreshReg$");
102
+ expect(code).toContain("__vxrnRefreshReg(");
103
+ expect(code).toContain("\"$RefreshReg$(\"");
104
+ } finally {
105
+ process.env.NODE_ENV = previousNodeEnv;
106
+ }
107
+ });
108
+ });
53
109
  //# sourceMappingURL=createNativeDevEngine.test.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["describe","expect","it","getNativeTransformConfig","wrapNativeBundleModuleScope","root","_loop","_i2","_iter2","_loop2","_i12","_iter12","dev","platform","define","toBe","JSON","stringify","envObject","parse","TAMAGUI_TARGET","TAMAGUI_ENVIRONMENT","_i1","_iter1","length","_i","_iter","RUNTIME_MARKER","out","openIdx","indexOf","toBeGreaterThan","toBeLessThan","trimEnd","endsWith","Function","not","toThrow","input"],"sources":["createNativeDevEngine.test.native.js"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { getNativeTransformConfig, wrapNativeBundleModuleScope } from \"./createNativeDevEngine\";\n\nvar root = \"/tmp/vxrn-native-env-define-test-nonexistent\";\ndescribe(\"getNativeTransformConfig platform env defines\", function() {\n\tvar _loop = function(_i2, _iter2) {\n\t\tvar _loop2 = function(_i12, _iter12) {\n\t\t\tvar dev = _iter12[_i12];\n\t\t\tit(`injects TAMAGUI_TARGET=native for ${platform} (dev=${dev})`, function() {\n\t\t\t\tvar { define } = getNativeTransformConfig(platform, dev, root);\n\t\t\t\texpect(define[\"import.meta.env.TAMAGUI_TARGET\"]).toBe(\"\\\"native\\\"\");\n\t\t\t\texpect(define[\"process.env.TAMAGUI_TARGET\"]).toBe(\"\\\"native\\\"\");\n\t\t\t\texpect(define[\"import.meta.env.TAMAGUI_ENVIRONMENT\"]).toBe(JSON.stringify(platform));\n\t\t\t\texpect(define[\"import.meta.env.VITE_ENVIRONMENT\"]).toBe(JSON.stringify(platform));\n\t\t\t\texpect(define[\"import.meta.env.VITE_NATIVE\"]).toBe(\"\\\"1\\\"\");\n\t\t\t\texpect(define[\"import.meta.env.EXPO_OS\"]).toBe(JSON.stringify(platform));\n\t\t\t\tvar envObject = JSON.parse(define[\"import.meta.env\"]);\n\t\t\t\texpect(envObject.TAMAGUI_TARGET).toBe(\"native\");\n\t\t\t\texpect(envObject.TAMAGUI_ENVIRONMENT).toBe(platform);\n\t\t\t});\n\t\t};\n\t\tvar platform = _iter2[_i2];\n\t\tfor (var _i1 = 0, _iter1 = [true, false]; _i1 < _iter1.length; _i1++) _loop2(_i1, _iter1);\n\t};\n\tfor (var _i = 0, _iter = [\"ios\", \"android\"]; _i < _iter.length; _i++) _loop(_i, _iter);\n});\ndescribe(\"wrapNativeBundleModuleScope\", function() {\n\tvar RUNTIME_MARKER = \"//#region \\\\0rolldown/runtime.js\";\n\tit(\"wraps module code after the prelude so top-level vars do not leak to global\", function() {\n\t\tvar out = wrapNativeBundleModuleScope(`globalThis.global = globalThis;\nglobalThis.__DEV__ = true;\n${RUNTIME_MARKER}\nvar fetch_hot, fetch$1, Headers, Request, Response$1;\nglobalThis.__rolldown_runtime__ = {};\n\n//# sourceMappingURL=x.js.map`);\n\t\tvar openIdx = out.indexOf(\";(function() {\");\n\t\texpect(openIdx).toBeGreaterThan(-1);\n\t\texpect(out.indexOf(\"globalThis.__DEV__\")).toBeLessThan(openIdx);\n\t\texpect(out.indexOf(\"var fetch_hot\")).toBeGreaterThan(openIdx);\n\t\texpect(out.indexOf(\"})();\")).toBeLessThan(out.indexOf(\"//# sourceMappingURL\"));\n\t\texpect(out.trimEnd().endsWith(\"//# sourceMappingURL=x.js.map\")).toBe(true);\n\t\texpect(function() {\n\t\t\treturn new Function(out);\n\t\t}).not.toThrow();\n\t});\n\tit(\"is a no-op when the runtime marker is absent (e.g. prod bundle)\", function() {\n\t\tvar input = \"var x = 1;\\nconsole.log(x);\\n\";\n\t\texpect(wrapNativeBundleModuleScope(input)).toBe(input);\n\t});\n});\n\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["describe","expect","it","getHermesSWCIncludes","getNativeTransformConfig","hmrClientNoopPlugin","vxrnCompilerPlugin","wrapNativeBundleModuleScope","root","_loop","_i2","_iter2","_loop2","_i12","_iter12","dev","platform","define","toBe","JSON","stringify","envObject","parse","TAMAGUI_TARGET","TAMAGUI_ENVIRONMENT","_i1","_iter1","length","_i","_iter","RUNTIME_MARKER","out","openIdx","indexOf","toBeGreaterThan","toBeLessThan","trimEnd","endsWith","Function","not","toThrow","input","CLASS_SET","toEqual","arrayContaining","toContain","plugin","resolveId","load","VIRTUAL_ID","each","source","importer","id","external","toBeUndefined","result","moduleType","method","code","previousNodeEnv","process","env","NODE_ENV","transform"],"sources":["createNativeDevEngine.test.native.js"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { getHermesSWCIncludes, getNativeTransformConfig, hmrClientNoopPlugin, vxrnCompilerPlugin, wrapNativeBundleModuleScope } from \"./createNativeDevEngine\";\n\nvar root = \"/tmp/vxrn-native-env-define-test-nonexistent\";\ndescribe(\"getNativeTransformConfig platform env defines\", function() {\n\tvar _loop = function(_i2, _iter2) {\n\t\tvar _loop2 = function(_i12, _iter12) {\n\t\t\tvar dev = _iter12[_i12];\n\t\t\tit(`injects TAMAGUI_TARGET=native for ${platform} (dev=${dev})`, function() {\n\t\t\t\tvar { define } = getNativeTransformConfig(platform, dev, root);\n\t\t\t\texpect(define[\"import.meta.env.TAMAGUI_TARGET\"]).toBe(\"\\\"native\\\"\");\n\t\t\t\texpect(define[\"process.env.TAMAGUI_TARGET\"]).toBe(\"\\\"native\\\"\");\n\t\t\t\texpect(define[\"import.meta.env.TAMAGUI_ENVIRONMENT\"]).toBe(JSON.stringify(platform));\n\t\t\t\texpect(define[\"import.meta.env.VITE_ENVIRONMENT\"]).toBe(JSON.stringify(platform));\n\t\t\t\texpect(define[\"import.meta.env.VITE_NATIVE\"]).toBe(\"\\\"1\\\"\");\n\t\t\t\texpect(define[\"import.meta.env.EXPO_OS\"]).toBe(JSON.stringify(platform));\n\t\t\t\tvar envObject = JSON.parse(define[\"import.meta.env\"]);\n\t\t\t\texpect(envObject.TAMAGUI_TARGET).toBe(\"native\");\n\t\t\t\texpect(envObject.TAMAGUI_ENVIRONMENT).toBe(platform);\n\t\t\t});\n\t\t};\n\t\tvar platform = _iter2[_i2];\n\t\tfor (var _i1 = 0, _iter1 = [true, false]; _i1 < _iter1.length; _i1++) _loop2(_i1, _iter1);\n\t};\n\tfor (var _i = 0, _iter = [\"ios\", \"android\"]; _i < _iter.length; _i++) _loop(_i, _iter);\n});\ndescribe(\"wrapNativeBundleModuleScope\", function() {\n\tvar RUNTIME_MARKER = \"//#region \\\\0rolldown/runtime.js\";\n\tit(\"wraps module code after the prelude so top-level vars do not leak to global\", function() {\n\t\tvar out = wrapNativeBundleModuleScope(`globalThis.global = globalThis;\nglobalThis.__DEV__ = true;\n${RUNTIME_MARKER}\nvar fetch_hot, fetch$1, Headers, Request, Response$1;\nglobalThis.__rolldown_runtime__ = {};\n\n//# sourceMappingURL=x.js.map`);\n\t\tvar openIdx = out.indexOf(\";(function() {\");\n\t\texpect(openIdx).toBeGreaterThan(-1);\n\t\texpect(out.indexOf(\"globalThis.__DEV__\")).toBeLessThan(openIdx);\n\t\texpect(out.indexOf(\"var fetch_hot\")).toBeGreaterThan(openIdx);\n\t\texpect(out.indexOf(\"})();\")).toBeLessThan(out.indexOf(\"//# sourceMappingURL\"));\n\t\texpect(out.trimEnd().endsWith(\"//# sourceMappingURL=x.js.map\")).toBe(true);\n\t\texpect(function() {\n\t\t\treturn new Function(out);\n\t\t}).not.toThrow();\n\t});\n\tit(\"is a no-op when the runtime marker is absent (e.g. prod bundle)\", function() {\n\t\tvar input = \"var x = 1;\\nconsole.log(x);\\n\";\n\t\texpect(wrapNativeBundleModuleScope(input)).toBe(input);\n\t});\n});\ndescribe(\"getHermesSWCIncludes\", function() {\n\tvar CLASS_SET = [\n\t\t\"transform-classes\",\n\t\t\"transform-class-properties\",\n\t\t\"transform-class-static-block\",\n\t\t\"transform-private-methods\",\n\t\t\"transform-private-property-in-object\"\n\t];\n\tit(\"always includes the full Hermes class-transform set (dev and prod)\", function() {\n\t\texpect(getHermesSWCIncludes(true)).toEqual(expect.arrayContaining(CLASS_SET));\n\t\texpect(getHermesSWCIncludes(false)).toEqual(expect.arrayContaining(CLASS_SET));\n\t});\n\tit(\"adds transform-async-to-generator only in production\", function() {\n\t\texpect(getHermesSWCIncludes(true)).not.toContain(\"transform-async-to-generator\");\n\t\texpect(getHermesSWCIncludes(false)).toContain(\"transform-async-to-generator\");\n\t});\n});\ndescribe(\"hmrClientNoopPlugin\", function() {\n\tvar plugin = hmrClientNoopPlugin();\n\tvar resolveId = plugin.resolveId;\n\tvar load = plugin.load;\n\tvar VIRTUAL_ID = \"\\0vxrn-hmr-client-noop\";\n\tit.each([\n\t\t[\"react-native/Libraries/Utilities/HMRClient\", void 0],\n\t\t[\"../Utilities/HMRClient\", \"/project/node_modules/react-native/Libraries/Core.js\"],\n\t\t[\"../../Utilities/HMRClient.js\", \"/project/react-native/Libraries/Core.js\"],\n\t\t[\"..\\\\Utilities\\\\HMRClient.js\", \"C:\\\\project\\\\react-native\\\\Libraries\\\\Core.js\"],\n\t\t[\"../Utilities/HMRClient.ts\", \"/project/node_modules/react-native/Core.js\"],\n\t\t[\"../Utilities/HMRClient.tsx\", \"/project/node_modules/react-native/Core.js\"],\n\t\t[\"../Utilities/HMRClient.cjs\", \"/project/node_modules/react-native/Core.js\"]\n\t])(\"aliases RN HMRClient specifier %j to the no-op virtual module\", function(source, importer) {\n\t\texpect(resolveId(source, importer)).toEqual({\n\t\t\tid: VIRTUAL_ID,\n\t\t\texternal: false\n\t\t});\n\t});\n\tit.each([\n\t\t\"react-native/Libraries/Utilities/HMRClientRegistry\",\n\t\t\"some/MyUtilities/HMRClient\",\n\t\t\"react-native/Libraries/Core/setUpDeveloperTools\",\n\t\t\"react\"\n\t])(\"does not touch unrelated specifier %j\", function(source) {\n\t\texpect(resolveId(source)).toBeUndefined();\n\t});\n\tit(\"does not alias an app-authored Utilities/HMRClient module\", function() {\n\t\texpect(resolveId(\"../Utilities/HMRClient\", \"/project/src/App.tsx\")).toBeUndefined();\n\t});\n\tit(\"loads a no-op module exposing every HMRClient method RN calls\", function() {\n\t\tvar result = load(VIRTUAL_ID);\n\t\texpect(result === null || result === void 0 ? void 0 : result.moduleType).toBe(\"js\");\n\t\tfor (var _i = 0, _iter = [\n\t\t\t\"setup\",\n\t\t\t\"enable\",\n\t\t\t\"disable\",\n\t\t\t\"registerBundle\",\n\t\t\t\"log\",\n\t\t\t\"isEnabled\"\n\t\t]; _i < _iter.length; _i++) {\n\t\t\tvar method = _iter[_i];\n\t\t\texpect(result.code).toContain(method);\n\t\t}\n\t\texpect(result.code).toContain(\"export default HMRClient\");\n\t});\n\tit(\"does not load unrelated ids\", function() {\n\t\texpect(load(\"\\0some-other-virtual\")).toBeUndefined();\n\t});\n});\ndescribe(\"vxrnCompilerPlugin React Refresh registration\", function() {\n\tit(\"keeps initial-bundle registrations visible to rolldown\", async function() {\n\t\tvar previousNodeEnv = process.env.NODE_ENV;\n\t\tprocess.env.NODE_ENV = \"test\";\n\t\ttry {\n\t\t\tvar transform = vxrnCompilerPlugin(\"ios\", true).transform;\n\t\t\tvar code = (await transform(\"export const marker = \\\"$RefreshReg$(\\\"; export function Probe() { return <div>probe</div> }\", \"/project/src/Probe.tsx\")).code;\n\t\t\texpect(code).toContain(\"var __vxrnRefreshReg = globalThis.$RefreshReg$\");\n\t\t\texpect(code).toContain(\"__vxrnRefreshReg(\");\n\t\t\texpect(code).toContain(\"\\\"$RefreshReg$(\\\"\");\n\t\t} finally {\n\t\t\tprocess.env.NODE_ENV = previousNodeEnv;\n\t\t}\n\t});\n});\n\n"],"mappings":"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","ignoreList":[]}
package/expo-plugin.cjs CHANGED
@@ -19,6 +19,13 @@ function getPodfilePropsOptOut(propsPath, key) {
19
19
  }
20
20
 
21
21
  const plugin = (config, options = {}) => {
22
+ // fail fast on an Expo SDK mismatch before any native project is generated:
23
+ // one/vxrn pin expo-linking to a single SDK, and its native module is
24
+ // compiled against that SDK's expo-modules-core ABI. When the app's expo
25
+ // brings a newer expo-modules-core, the build/install/launch pipeline all
26
+ // succeed and the app then dies at ReactInstance creation with
27
+ // NoSuchMethodError in ExpoLinkingModule — invisible until runtime.
28
+ assertExpoModulesCoreMatchesExpoLinking(process.cwd())
22
29
  return withPlugins(config, [
23
30
  // auto-inject swift 6 workaround for expo-modules-core into Podfile (version-gated, opt-out-able)
24
31
  [
@@ -693,8 +700,12 @@ function addReactNativeScreensFix(input) {
693
700
  }
694
701
 
695
702
  function getExpoModulesCoreVersion(projectRoot) {
703
+ return getInstalledPackageVersion('expo-modules-core', projectRoot)
704
+ }
705
+
706
+ function getInstalledPackageVersion(packageName, projectRoot) {
696
707
  try {
697
- const pkgPath = require.resolve('expo-modules-core/package.json', {
708
+ const pkgPath = require.resolve(`${packageName}/package.json`, {
698
709
  paths: [projectRoot],
699
710
  })
700
711
  return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version
@@ -703,6 +714,44 @@ function getExpoModulesCoreVersion(projectRoot) {
703
714
  }
704
715
  }
705
716
 
717
+ /**
718
+ * expo-linking (pinned by one/vxrn to a single Expo SDK) calls into
719
+ * expo-modules-core's Kotlin/Swift ABI; mixing majors compiles and installs
720
+ * fine but crashes at ReactInstance creation (NoSuchMethodError in
721
+ * ExpoLinkingModule). The project-root resolution sees the copy native
722
+ * autolinking will actually compile, so comparing the two majors here turns
723
+ * an opaque runtime crash into an actionable prebuild error.
724
+ *
725
+ * Escape hatch: ONE_SKIP_EXPO_SDK_CHECK=1 (e.g. for intentionally patched
726
+ * setups). Silently passes when either package isn't resolvable (web-only
727
+ * projects never reach this plugin anyway).
728
+ */
729
+ function assertExpoModulesCoreMatchesExpoLinking(projectRoot) {
730
+ if (process.env.ONE_SKIP_EXPO_SDK_CHECK === '1') return
731
+
732
+ const expoModulesCoreVersion = getInstalledPackageVersion(
733
+ 'expo-modules-core',
734
+ projectRoot
735
+ )
736
+ const expoLinkingVersion = getInstalledPackageVersion('expo-linking', projectRoot)
737
+ if (!expoModulesCoreVersion || !expoLinkingVersion) return
738
+
739
+ const coreMajor = semverMajor(expoModulesCoreVersion)
740
+ const linkingMajor = semverMajor(expoLinkingVersion)
741
+ if (coreMajor === linkingMajor) return
742
+
743
+ throw new Error(
744
+ `[vxrn/one] Expo SDK mismatch: expo-modules-core@${expoModulesCoreVersion} is installed, ` +
745
+ `but the bundled native modules (expo-linking@${expoLinkingVersion}) are built against ` +
746
+ `expo-modules-core ${linkingMajor}.\n` +
747
+ `The app would build and launch, then crash at startup ` +
748
+ `(NoSuchMethodError in ExpoLinkingModule).\n` +
749
+ `Fix: align your expo SDK with the one this version of one targets (expo-modules-core ${linkingMajor}), ` +
750
+ `or upgrade one to a release targeting your SDK.\n` +
751
+ `To bypass intentionally: set ONE_SKIP_EXPO_SDK_CHECK=1`
752
+ )
753
+ }
754
+
706
755
  function semverMajor(version) {
707
756
  const match = version.match(/^(\d+)/)
708
757
  return match ? Number.parseInt(match[1], 10) : 0
@@ -851,6 +900,8 @@ function injectExpoUpdatesIosResourcesPatchIntoPodfile(podfile) {
851
900
  }
852
901
 
853
902
  module.exports = plugin
903
+ module.exports.assertExpoModulesCoreMatchesExpoLinking =
904
+ assertExpoModulesCoreMatchesExpoLinking
854
905
  module.exports.addReactNativeScreensFix = addReactNativeScreensFix
855
906
  module.exports.addSetCliPathToBundleReactNativeShellScript =
856
907
  addSetCliPathToBundleReactNativeShellScript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vxrn",
3
- "version": "1.21.1",
3
+ "version": "1.21.3",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,16 +60,16 @@
60
60
  "@expo/config-plugins": "~55.0.6",
61
61
  "@hono/node-server": "^1.19.14",
62
62
  "@react-native/dev-middleware": "^0.83.4",
63
- "@vxrn/compiler": "1.21.1",
64
- "@vxrn/debug": "1.21.1",
65
- "@vxrn/query-string": "1.21.1",
66
- "@vxrn/resolve": "1.21.1",
67
- "@vxrn/safe-area": "1.21.1",
68
- "@vxrn/url-parse": "1.21.1",
69
- "@vxrn/utils": "1.21.1",
70
- "@vxrn/vendor": "1.21.1",
71
- "@vxrn/vite-flow": "1.21.1",
72
- "@vxrn/vite-plugin-metro": "1.21.1",
63
+ "@vxrn/compiler": "1.21.3",
64
+ "@vxrn/debug": "1.21.3",
65
+ "@vxrn/query-string": "1.21.3",
66
+ "@vxrn/resolve": "1.21.3",
67
+ "@vxrn/safe-area": "1.21.3",
68
+ "@vxrn/url-parse": "1.21.3",
69
+ "@vxrn/utils": "1.21.3",
70
+ "@vxrn/vendor": "1.21.3",
71
+ "@vxrn/vite-flow": "1.21.3",
72
+ "@vxrn/vite-plugin-metro": "1.21.3",
73
73
  "citty": "^0.1.6",
74
74
  "dotenv": "^17.2.1",
75
75
  "dotenv-expand": "^12.0.2",
@@ -99,7 +99,7 @@
99
99
  "@types/find-node-modules": "^2.1.2",
100
100
  "@types/node": "^24.10.0",
101
101
  "@types/qrcode-terminal": "^0",
102
- "@types/ws": "^8.5.10",
102
+ "@types/ws": "^8.18.1",
103
103
  "depcheck": "^1.4.7",
104
104
  "vitest": "^4.1.0"
105
105
  },
@@ -54,7 +54,11 @@ export interface DevRuntimeInterface {
54
54
  createModuleHotContext(moduleId: string): void
55
55
  applyUpdates(boundaries: [string, string][]): void
56
56
  registerModule(id: string, exportsHolder: DevRuntimeModule['exportsHolder']): void
57
- loadExports(id: string): void
57
+ loadExports(id: string): DevRuntimeModule['exports']
58
+ }
59
+
60
+ declare global {
61
+ var __VXRN_ON_MODULE_UPDATED__: ((moduleId: string) => void) | undefined
58
62
  }
59
63
 
60
64
  // the base class is provided by rolldown's devMode runtime
@@ -72,7 +76,7 @@ class DevRuntime implements DevRuntimeInterface {
72
76
  registerModule(id: string, exportsHolder: DevRuntimeModule['exportsHolder']): void {
73
77
  throw new Error('registerModule should be implemented')
74
78
  }
75
- loadExports(id: string): void {
79
+ loadExports(id: string): DevRuntimeModule['exports'] {
76
80
  throw new Error('loadExports should be implemented')
77
81
  }
78
82
  }
@@ -75,20 +75,6 @@ if (typeof globalThis.URLSearchParams === 'undefined') {
75
75
  };
76
76
  }
77
77
 
78
- // suppress HMRClient.setup() error from native side
79
- // native calls HMRClient.setup() during bundle load but we don't use Metro HMR
80
- // this intercept catches the error and prevents the red screen
81
- var __origErrorHandler = globalThis.ErrorUtils && globalThis.ErrorUtils.getGlobalHandler && globalThis.ErrorUtils.getGlobalHandler();
82
- if (globalThis.ErrorUtils && globalThis.ErrorUtils.setGlobalHandler) {
83
- globalThis.ErrorUtils.setGlobalHandler(function(error, isFatal) {
84
- if (error && error.message && error.message.indexOf('HMRClient') !== -1) {
85
- // suppress HMRClient errors silently
86
- return;
87
- }
88
- if (__origErrorHandler) __origErrorHandler(error, isFatal);
89
- });
90
- }
91
-
92
78
  // ensure console exists before any library prelude touches it in release bundles
93
79
  globalThis.console = globalThis.console || {};
94
80
  var console = globalThis.console;
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import {
3
+ getHermesSWCIncludes,
3
4
  getNativeTransformConfig,
5
+ hmrClientNoopPlugin,
6
+ vxrnCompilerPlugin,
4
7
  wrapNativeBundleModuleScope,
5
8
  } from './createNativeDevEngine'
6
9
 
@@ -66,3 +69,109 @@ describe('wrapNativeBundleModuleScope', () => {
66
69
  expect(wrapNativeBundleModuleScope(input)).toBe(input)
67
70
  })
68
71
  })
72
+
73
+ describe('getHermesSWCIncludes', () => {
74
+ const CLASS_SET = [
75
+ 'transform-classes',
76
+ 'transform-class-properties',
77
+ 'transform-class-static-block',
78
+ 'transform-private-methods',
79
+ 'transform-private-property-in-object',
80
+ ]
81
+
82
+ it('always includes the full Hermes class-transform set (dev and prod)', () => {
83
+ // regression: transform-classes was missing in dev, leaving a half-transpiled
84
+ // class hierarchy Hermes crashes on at `new Subclass()`
85
+ expect(getHermesSWCIncludes(true)).toEqual(expect.arrayContaining(CLASS_SET))
86
+ expect(getHermesSWCIncludes(false)).toEqual(expect.arrayContaining(CLASS_SET))
87
+ })
88
+
89
+ it('adds transform-async-to-generator only in production', () => {
90
+ expect(getHermesSWCIncludes(true)).not.toContain('transform-async-to-generator')
91
+ expect(getHermesSWCIncludes(false)).toContain('transform-async-to-generator')
92
+ })
93
+ })
94
+
95
+ describe('hmrClientNoopPlugin', () => {
96
+ const plugin = hmrClientNoopPlugin()
97
+ const resolveId = plugin.resolveId as unknown as (
98
+ source: string,
99
+ importer?: string
100
+ ) => any
101
+ const load = plugin.load as unknown as (id: string) => any
102
+ const VIRTUAL_ID = '\0vxrn-hmr-client-noop'
103
+
104
+ it.each([
105
+ ['react-native/Libraries/Utilities/HMRClient', undefined],
106
+ ['../Utilities/HMRClient', '/project/node_modules/react-native/Libraries/Core.js'],
107
+ ['../../Utilities/HMRClient.js', '/project/react-native/Libraries/Core.js'],
108
+ // native Windows ids use backslashes
109
+ ['..\\Utilities\\HMRClient.js', 'C:\\project\\react-native\\Libraries\\Core.js'],
110
+ ['../Utilities/HMRClient.ts', '/project/node_modules/react-native/Core.js'],
111
+ ['../Utilities/HMRClient.tsx', '/project/node_modules/react-native/Core.js'],
112
+ ['../Utilities/HMRClient.cjs', '/project/node_modules/react-native/Core.js'],
113
+ ])(
114
+ 'aliases RN HMRClient specifier %j to the no-op virtual module',
115
+ (source, importer) => {
116
+ expect(resolveId(source, importer)).toEqual({ id: VIRTUAL_ID, external: false })
117
+ }
118
+ )
119
+
120
+ it.each([
121
+ // trailing letters (no boundary) must not match
122
+ 'react-native/Libraries/Utilities/HMRClientRegistry',
123
+ // the Utilities segment must start at a path boundary
124
+ 'some/MyUtilities/HMRClient',
125
+ // unrelated RN modules
126
+ 'react-native/Libraries/Core/setUpDeveloperTools',
127
+ 'react',
128
+ ])('does not touch unrelated specifier %j', (source) => {
129
+ expect(resolveId(source)).toBeUndefined()
130
+ })
131
+
132
+ it('does not alias an app-authored Utilities/HMRClient module', () => {
133
+ expect(resolveId('../Utilities/HMRClient', '/project/src/App.tsx')).toBeUndefined()
134
+ })
135
+
136
+ it('loads a no-op module exposing every HMRClient method RN calls', () => {
137
+ const result = load(VIRTUAL_ID)
138
+ expect(result?.moduleType).toBe('js')
139
+ for (const method of [
140
+ 'setup',
141
+ 'enable',
142
+ 'disable',
143
+ 'registerBundle',
144
+ 'log',
145
+ 'isEnabled',
146
+ ]) {
147
+ expect(result!.code).toContain(method)
148
+ }
149
+ expect(result!.code).toContain('export default HMRClient')
150
+ })
151
+
152
+ it('does not load unrelated ids', () => {
153
+ expect(load('\0some-other-virtual')).toBeUndefined()
154
+ })
155
+ })
156
+
157
+ describe('vxrnCompilerPlugin React Refresh registration', () => {
158
+ it('keeps initial-bundle registrations visible to rolldown', async () => {
159
+ const previousNodeEnv = process.env.NODE_ENV
160
+ process.env.NODE_ENV = 'test'
161
+ try {
162
+ const plugin = vxrnCompilerPlugin('ios', true)
163
+ const transform = plugin.transform as (code: string, id: string) => Promise<any>
164
+ const result = await transform(
165
+ 'export const marker = "$RefreshReg$("; export function Probe() { return <div>probe</div> }',
166
+ '/project/src/Probe.tsx'
167
+ )
168
+ const code = result.code as string
169
+
170
+ expect(code).toContain('var __vxrnRefreshReg = globalThis.$RefreshReg$')
171
+ expect(code).toContain('__vxrnRefreshReg(')
172
+ expect(code).toContain('"$RefreshReg$("')
173
+ } finally {
174
+ process.env.NODE_ENV = previousNodeEnv
175
+ }
176
+ })
177
+ })
@@ -17,6 +17,29 @@ import { getNativePrelude } from '../runtime/native-prelude'
17
17
  // files that contain Flow syntax and need stripping
18
18
  const FLOW_FILE_PATTERN = /node_modules[\\/](?:react-native|@react-native)[\\/].*\.js$/
19
19
 
20
+ // Hermes needs the whole class shape lowered *together*. downleveling only the
21
+ // class fields while leaving `class ... extends` as modern ES6 produces a
22
+ // half-transpiled hierarchy Hermes crashes on at `new Subclass()` (TypeError:
23
+ // Cannot read property 'prototype' of undefined). These must stay atomic across
24
+ // both SWC call sites and dev/prod. defining them once makes that a fact, not a
25
+ // convention (the original bug was `transform-classes` missing from one of two
26
+ // hand-copied include lists).
27
+ const HERMES_CLASS_TRANSFORMS = [
28
+ 'transform-classes',
29
+ 'transform-class-properties',
30
+ 'transform-class-static-block',
31
+ 'transform-private-methods',
32
+ 'transform-private-property-in-object',
33
+ ] as const
34
+
35
+ // prod-only: needed for hermesc bytecode AOT compilation, not the dev interpreter
36
+ const HERMES_PROD_TRANSFORMS = ['transform-async-to-generator'] as const
37
+
38
+ /** SWC `env.include` for Hermes-compatible downleveling; see HERMES_CLASS_TRANSFORMS. */
39
+ export function getHermesSWCIncludes(dev: boolean): string[] {
40
+ return [...HERMES_CLASS_TRANSFORMS, ...(dev ? [] : HERMES_PROD_TRANSFORMS)]
41
+ }
42
+
20
43
  interface NativeDevEngineOptions {
21
44
  root: string
22
45
  port: number
@@ -191,19 +214,28 @@ function getNativePlugins(
191
214
  serverFileExclusionPlugin(),
192
215
  // guard server-only / client-only / web-only / native-only imports
193
216
  environmentGuardPlugin(),
217
+ // alias RN's Metro HMR client to a no-op; vxrn drives HMR itself (the
218
+ // rolldown-runtime WebSocket); RN's client otherwise opens a /hot socket and
219
+ // red-boxes "unknown-message [object Object]" on every edit (new arch)
220
+ hmrClientNoopPlugin(),
194
221
  // stub CSS imports — native doesn't support CSS and rolldown removed CSS bundling
195
222
  cssStubPlugin(),
196
223
  // handle import.meta.glob (used by One's route system)
197
224
  viteImportGlobPlugin({ root }),
198
- // strip Flow types from react-native and @react-native packages
225
+ // @vxrn/compiler babel transforms: reanimated worklets, async generators,
226
+ // react-native codegen, react compiler, same pipeline as metro. runs before
227
+ // flowStripPlugin so react-native's Flow `.js` specs reach codegen with their
228
+ // type argument intact. stripping Flow first would erase it (which is why the
229
+ // codegen "didn't run for <Component>" warning fired).
230
+ vxrnCompilerPlugin(platform, dev),
231
+ // strip Flow from any react-native / @react-native `.js` the compiler didn't
232
+ // handle, the guaranteed safety net before rolldown's oxc core parse (which
233
+ // can't parse Flow). now downstream of the compiler, so codegen sees the types.
199
234
  flowStripPlugin(),
200
235
  // guard undefined native methods in NativeAnimatedHelper
201
236
  nativeAnimatedGuardPlugin(),
202
237
  // handle asset imports (.png, .jpg, .ttf, etc.)
203
238
  assetPlugin({ root, platform, assetsDest }),
204
- // @vxrn/compiler babel transforms: reanimated worklets, async generators,
205
- // react-native codegen, react compiler — same pipeline as metro
206
- vxrnCompilerPlugin(platform, dev),
207
239
  // hermes compat: transform class properties and private fields
208
240
  hermesCompatSWCPlugin(dev),
209
241
  ]
@@ -333,12 +365,8 @@ async function downlevelClassFieldsInBundle(code: string): Promise<string> {
333
365
  isModule: false,
334
366
  env: {
335
367
  targets: { node: 9999 },
336
- include: [
337
- 'transform-class-properties',
338
- 'transform-class-static-block',
339
- 'transform-private-methods',
340
- 'transform-private-property-in-object',
341
- ],
368
+ // dev-only runtime prelude: the class set only, no prod bytecode transforms
369
+ include: [...HERMES_CLASS_TRANSFORMS],
342
370
  },
343
371
  jsc: {
344
372
  parser: { syntax: 'ecmascript' },
@@ -470,14 +498,6 @@ try {
470
498
  // skipped by the per-file SWC plugin) so old Hermes can parse them
471
499
  code = await downlevelClassFieldsInBundle(code)
472
500
 
473
- // register a no-op HMRClient so RN's native side doesn't error when calling HMRClient.setup()
474
- // our actual HMR is handled via the outro WebSocket connection
475
- const hmrClientStub = `registerCallableModule("HMRClient",{setup:function(){},enable:function(){},disable:function(){},registerBundle:function(){},log:function(){}})`
476
- code = code.replace(
477
- /registerCallableModule\s*\(\s*["']AppRegistry["']/,
478
- (match) => hmrClientStub + ',' + match
479
- )
480
-
481
501
  // wrap module code in a function scope so top-level `var`s (e.g. RN
482
502
  // fetch.js's `Headers`/`Request`) don't leak as non-configurable
483
503
  // globals and break RN's polyfillGlobal (dev-only redbox). see fn doc.
@@ -505,7 +525,6 @@ try {
505
525
  return
506
526
  }
507
527
  const updates = (result as any).updates || []
508
-
509
528
  for (const item of updates) {
510
529
  const update = item.update || item
511
530
  if (update.type === 'Patch' && update.code) {
@@ -792,6 +811,49 @@ function environmentGuardPlugin(): Plugin {
792
811
  }
793
812
  }
794
813
 
814
+ /**
815
+ * alias react-native's Metro HMR client (`Libraries/Utilities/HMRClient`) to a
816
+ * no-op module.
817
+ *
818
+ * vxrn drives Fast Refresh itself over the rolldown-runtime WebSocket and never
819
+ * speaks Metro's `/hot` protocol. On the new architecture, react-native
820
+ * `registerCallableModule('HMRClient', require('./HMRClient'))`s its real client
821
+ * eagerly at startup before vxrn's late override runs, and `emplace` keeps
822
+ * that first registration. RN's client then opens a `MetroHMRClient` socket that
823
+ * receives vxrn's `hmr:*` frames it can't parse and red-boxes
824
+ * `unknown-message [object Object]` on every edit.
825
+ *
826
+ * neutralizing the module at its source means RN registers *this* no-op as the
827
+ * one-and-only `HMRClient` (working with `emplace`, so it's arch-agnostic) and
828
+ * the stray socket is never opened. The class-shaped surface
829
+ * (`setup`/`enable`/`disable`/`registerBundle`/`log`/`isEnabled`) mirrors the
830
+ * methods RN calls on it.
831
+ */
832
+ export function hmrClientNoopPlugin(): Plugin {
833
+ // match RN's HMRClient by module path, tolerating either separator (native
834
+ // Windows ids use `\`) and an optional js/ts extension
835
+ const RN_HMR_CLIENT_RE = /(^|[\\/])Utilities[\\/]HMRClient(\.[cm]?[jt]sx?)?$/
836
+ return {
837
+ name: 'vxrn:hmr-client-noop',
838
+ resolveId(source, importer) {
839
+ const fromReactNative =
840
+ source.startsWith('react-native/') ||
841
+ (importer != null && /(^|[\\/])react-native[\\/]/.test(importer))
842
+ if (fromReactNative && RN_HMR_CLIENT_RE.test(source)) {
843
+ return { id: '\0vxrn-hmr-client-noop', external: false }
844
+ }
845
+ },
846
+ load(id) {
847
+ if (id === '\0vxrn-hmr-client-noop') {
848
+ return {
849
+ code: `const HMRClient = { setup() {}, enable() {}, disable() {}, registerBundle() {}, log() {}, isEnabled() { return false } }\nexport default HMRClient`,
850
+ moduleType: 'js',
851
+ }
852
+ }
853
+ },
854
+ }
855
+ }
856
+
795
857
  /**
796
858
  * Stub CSS imports for native builds.
797
859
  * Native doesn't support CSS and rolldown removed CSS bundling support.
@@ -816,7 +878,7 @@ function cssStubPlugin(): Plugin {
816
878
  * react-native codegen, react compiler, and react-refresh (dev only) —
817
879
  * same pipeline as metro, single babel pass per file.
818
880
  */
819
- function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
881
+ export function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
820
882
  let compiler: typeof import('@vxrn/compiler') | null = null
821
883
 
822
884
  // whether a file is a user file that should get react-refresh wiring
@@ -852,7 +914,17 @@ function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
852
914
  const existingPlugins = babelOptions?.plugins || []
853
915
  babelOptions = {
854
916
  ...babelOptions,
855
- plugins: [...existingPlugins, 'react-refresh/babel'],
917
+ plugins: [
918
+ ...existingPlugins,
919
+ [
920
+ 'react-refresh/babel',
921
+ {
922
+ skipEnvCheck: true,
923
+ refreshReg: '__vxrnRefreshReg',
924
+ refreshSig: '__vxrnRefreshSig',
925
+ },
926
+ ],
927
+ ],
856
928
  }
857
929
  }
858
930
 
@@ -876,6 +948,9 @@ if (globalThis.__ReactRefresh) {
876
948
  };
877
949
  globalThis.$RefreshSig$ = globalThis.__ReactRefresh.createSignatureFunctionForTransform;
878
950
  }
951
+ // keep registration calls local so rolldown retains them in the initial bundle.
952
+ var __vxrnRefreshReg = globalThis.$RefreshReg$;
953
+ var __vxrnRefreshSig = globalThis.$RefreshSig$;
879
954
 
880
955
  ${out}
881
956
 
@@ -1024,15 +1099,9 @@ function hermesCompatSWCPlugin(dev: boolean): Plugin {
1024
1099
  try {
1025
1100
  if (!swc) swc = await import('@swc/core')
1026
1101
 
1027
- // hermes needs class properties downleveled; prod also needs
1028
- // classes and async-to-generator for bytecode compilation
1029
- const envIncludes = [
1030
- 'transform-class-properties',
1031
- 'transform-class-static-block',
1032
- 'transform-private-methods',
1033
- 'transform-private-property-in-object',
1034
- ...(!dev ? ['transform-classes', 'transform-async-to-generator'] : []),
1035
- ]
1102
+ // app modules: the Hermes class set (unconditional), plus async-to-generator
1103
+ // in prod only (see HERMES_CLASS_TRANSFORMS / HERMES_PROD_TRANSFORMS)
1104
+ const envIncludes = getHermesSWCIncludes(dev)
1036
1105
 
1037
1106
  const result = await swc.transform(code, {
1038
1107
  filename: id,
@@ -1131,6 +1200,16 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
1131
1200
  ctx.acceptCallbacks[j].fn(this.modules[moduleId].exports);
1132
1201
  }
1133
1202
  }
1203
+ // surface each committed module id to the framework hot-update hook (if one
1204
+ // is registered). RN's React Refresh can't repaint frameworks that re-wrap
1205
+ // route components away from the edited module's Refresh family (e.g. One),
1206
+ // and the web route-update event has no equivalent on the native /hot
1207
+ // socket, so this generic vxrn global is the bridge.
1208
+ try {
1209
+ if (globalThis.__VXRN_ON_MODULE_UPDATED__ && moduleId) globalThis.__VXRN_ON_MODULE_UPDATED__(moduleId);
1210
+ } catch (error) {
1211
+ console.error('[vxrn HMR]: module update hook failed', error);
1212
+ }
1134
1213
  }
1135
1214
  }
1136
1215
 
@@ -58,7 +58,10 @@ export interface DevRuntimeInterface {
58
58
  createModuleHotContext(moduleId: string): void;
59
59
  applyUpdates(boundaries: [string, string][]): void;
60
60
  registerModule(id: string, exportsHolder: DevRuntimeModule['exportsHolder']): void;
61
- loadExports(id: string): void;
61
+ loadExports(id: string): DevRuntimeModule['exports'];
62
+ }
63
+ declare global {
64
+ var __VXRN_ON_MODULE_UPDATED__: ((moduleId: string) => void) | undefined;
62
65
  }
63
66
  declare class DevRuntime implements DevRuntimeInterface {
64
67
  clientId: string;
@@ -67,7 +70,7 @@ declare class DevRuntime implements DevRuntimeInterface {
67
70
  createModuleHotContext(moduleId: string): void;
68
71
  applyUpdates(boundaries: [string, string][]): void;
69
72
  registerModule(id: string, exportsHolder: DevRuntimeModule['exportsHolder']): void;
70
- loadExports(id: string): void;
73
+ loadExports(id: string): DevRuntimeModule['exports'];
71
74
  }
72
75
  export type { DevRuntime };
73
76
  export interface DevRuntimeMessenger {