Skip to content

Commit 95cb4a0

Browse files
authored
refactor(apple): evaluate Hermes from JS (#2435)
1 parent bbcf421 commit 95cb4a0

File tree

9 files changed

+126
-86
lines changed

9 files changed

+126
-86
lines changed

ios/app.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
import { cp_r, mkdir_p, rm_r } from "../scripts/utils/filesystem.mjs";
1414
import { generateAssetsCatalogs } from "./assetsCatalog.mjs";
1515
import { generateEntitlements } from "./entitlements.mjs";
16-
import { isBridgelessEnabled, isNewArchEnabled } from "./features.mjs";
16+
import {
17+
isBridgelessEnabled,
18+
isHermesEnabled,
19+
isNewArchEnabled,
20+
} from "./features.mjs";
1721
import { generateInfoPlist } from "./infoPlist.mjs";
1822
import { generateLocalizations, getProductName } from "./localizations.mjs";
1923
import { generatePrivacyManifest } from "./privacyManifest.mjs";
@@ -220,8 +224,6 @@ export function generateProject(
220224
fs
221225
);
222226
const reactNativeVersion = readPackageVersion(reactNativePath, fs);
223-
const useNewArch = isNewArchEnabled(options, reactNativeVersion);
224-
const useBridgeless = isBridgelessEnabled(options, reactNativeVersion);
225227

226228
/** @type {ProjectConfiguration} */
227229
const project = {
@@ -234,8 +236,9 @@ export function generateProject(
234236
reactNativeVersion,
235237
fs
236238
),
237-
useNewArch,
238-
useBridgeless,
239+
useHermes: isHermesEnabled(targetPlatform, reactNativeVersion, options),
240+
useNewArch: isNewArchEnabled(reactNativeVersion, options),
241+
useBridgeless: isBridgelessEnabled(reactNativeVersion, options),
239242
buildSettings: {},
240243
testsBuildSettings: {},
241244
uitestsBuildSettings: {},

ios/features.mjs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-check
2-
/** @import { JSONObject } from "../scripts/types.ts"; */
2+
/** @import { ApplePlatform, JSONObject } from "../scripts/types.ts"; */
33
import { v } from "../scripts/helpers.js";
44

55
/**
@@ -10,18 +10,18 @@ function supportsNewArch(reactNativeVersion) {
1010
}
1111

1212
/**
13-
* @param {JSONObject} options
1413
* @param {number} reactNativeVersion
14+
* @param {JSONObject} options
1515
* @returns {boolean}
1616
*/
17-
export function isNewArchEnabled(options, reactNativeVersion) {
17+
export function isNewArchEnabled(reactNativeVersion, options) {
1818
if (!supportsNewArch(reactNativeVersion)) {
1919
return false;
2020
}
2121

22-
const envVar = process.env["RCT_NEW_ARCH_ENABLED"];
23-
if (typeof envVar === "string") {
24-
return envVar !== "0";
22+
const newArchEnabled = process.env["RCT_NEW_ARCH_ENABLED"];
23+
if (typeof newArchEnabled === "string") {
24+
return newArchEnabled !== "0";
2525
}
2626

2727
if ("newArchEnabled" in options) {
@@ -37,12 +37,12 @@ export function isNewArchEnabled(options, reactNativeVersion) {
3737
}
3838

3939
/**
40-
* @param {JSONObject} options
4140
* @param {number} reactNativeVersion
41+
* @param {JSONObject} options
4242
* @returns {boolean}
4343
*/
44-
export function isBridgelessEnabled(options, reactNativeVersion) {
45-
if (isNewArchEnabled(options, reactNativeVersion)) {
44+
export function isBridgelessEnabled(reactNativeVersion, options) {
45+
if (isNewArchEnabled(reactNativeVersion, options)) {
4646
if (reactNativeVersion >= v(0, 74, 0)) {
4747
return options["bridgelessEnabled"] !== false;
4848
}
@@ -52,3 +52,24 @@ export function isBridgelessEnabled(options, reactNativeVersion) {
5252
}
5353
return false;
5454
}
55+
56+
/**
57+
* @param {ApplePlatform} platform
58+
* @param {number} reactNativeVersion
59+
* @param {JSONObject} options
60+
* @returns {boolean | "from-source"}
61+
*/
62+
export function isHermesEnabled(platform, reactNativeVersion, options) {
63+
const useHermes = process.env["USE_HERMES"];
64+
const enabled =
65+
typeof useHermes === "string"
66+
? useHermes === "1"
67+
: options["hermesEnabled"] === true;
68+
69+
// Hermes prebuilds for visionOS was introduced in 0.76
70+
if (enabled && platform === "visionos" && reactNativeVersion < v(0, 76, 0)) {
71+
return "from-source";
72+
}
73+
74+
return enabled;
75+
}

ios/pod_helpers.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,9 @@ def find_file(file_name, current_dir)
2828
end
2929

3030
def use_hermes?(options)
31-
use_hermes = ENV.fetch('USE_HERMES', nil)
32-
return use_hermes == '1' unless use_hermes.nil?
33-
34-
# Hermes prebuilds for visionOS was introduced in 0.76
35-
is_visionos = options[:path].end_with?('react-native-visionos')
36-
ENV['RCT_BUILD_HERMES_FROM_SOURCE'] = 'true' if is_visionos && options[:version] < v(0, 76, 0)
37-
38-
options[:hermes_enabled] == true
31+
use_hermes = options[:use_hermes]
32+
ENV['RCT_BUILD_HERMES_FROM_SOURCE'] = 'true' if use_hermes == 'from-source'
33+
use_hermes != false
3934
end
4035

4136
def use_new_architecture!(options, react_native_version)

ios/test_app.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def use_react_native!(project_root, project, options)
111111
app_path: find_file('package.json', project_root).parent.to_s,
112112
path: react_native_path.relative_path_from(project_root).to_s,
113113
rta_project_root: project_root,
114+
use_hermes: project[:use_hermes],
114115
use_new_arch: project[:use_new_arch],
115116
use_bridgeless: project[:use_bridgeless],
116117
version: project[:react_native_version])
@@ -168,6 +169,7 @@ def make_project!(project_root, target_platform, options)
168169
:react_native_version => project['reactNativeVersion'],
169170
:react_native_host_path => project['reactNativeHostPath'],
170171
:community_autolinking_script_path => project['communityAutolinkingScriptPath'],
172+
:use_hermes => project['useHermes'],
171173
:use_new_arch => project['useNewArch'],
172174
:use_bridgeless => project['useBridgeless'],
173175
:code_sign_identity => build_settings[CODE_SIGN_IDENTITY] || '',

scripts/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export type ProjectConfiguration = {
139139
reactNativeHostPath: string;
140140
communityAutolinkingScriptPath?: string;
141141
singleApp?: string;
142+
useHermes: boolean | "from-source";
142143
useNewArch: boolean;
143144
useBridgeless: boolean;
144145
buildSettings: Record<string, string | string[]>;

test/ios/app.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ const PROJECT_FILES = {
268268
testsBuildSettings: {},
269269
uitestsBuildSettings: {},
270270
useBridgeless: true,
271+
useHermes: false,
271272
useNewArch: true,
272273
xcodeprojPath: "/~/node_modules/.generated/ios/ReactTestApp.xcodeproj",
273274
},
@@ -296,6 +297,7 @@ const PROJECT_FILES = {
296297
testsBuildSettings: {},
297298
uitestsBuildSettings: {},
298299
useBridgeless: true,
300+
useHermes: false,
299301
useNewArch: true,
300302
xcodeprojPath: "/~/node_modules/.generated/ios/ReactTestApp.xcodeproj",
301303
},
@@ -360,6 +362,7 @@ const PROJECT_FILES = {
360362
testsBuildSettings: {},
361363
uitestsBuildSettings: {},
362364
useBridgeless: true,
365+
useHermes: false,
363366
useNewArch: true,
364367
xcodeprojPath:
365368
"/~/node_modules/.generated/macos/ReactTestApp.xcodeproj",
@@ -425,6 +428,7 @@ const PROJECT_FILES = {
425428
testsBuildSettings: {},
426429
uitestsBuildSettings: {},
427430
useBridgeless: true,
431+
useHermes: false,
428432
useNewArch: true,
429433
xcodeprojPath:
430434
"/~/node_modules/.generated/visionos/ReactTestApp.xcodeproj",
@@ -486,6 +490,7 @@ const PROJECT_FILES = {
486490
testsBuildSettings: {},
487491
uitestsBuildSettings: {},
488492
useBridgeless: false,
493+
useHermes: false,
489494
useNewArch: false,
490495
xcodeprojPath: "/~/node_modules/.generated/ios/ReactTestApp.xcodeproj",
491496
},
@@ -544,6 +549,7 @@ const PROJECT_FILES = {
544549
testsBuildSettings: {},
545550
uitestsBuildSettings: {},
546551
useBridgeless: false,
552+
useHermes: false,
547553
useNewArch: false,
548554
xcodeprojPath:
549555
"/~/node_modules/.generated/macos/ReactTestApp.xcodeproj",
@@ -603,6 +609,7 @@ const PROJECT_FILES = {
603609
testsBuildSettings: {},
604610
uitestsBuildSettings: {},
605611
useBridgeless: false,
612+
useHermes: false,
606613
useNewArch: false,
607614
xcodeprojPath:
608615
"/~/node_modules/.generated/visionos/ReactTestApp.xcodeproj",

test/ios/features.test.ts

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { ok } from "node:assert/strict";
1+
import { equal, ok } from "node:assert/strict";
22
import { afterEach, before, describe, it } from "node:test";
3-
import { isBridgelessEnabled, isNewArchEnabled } from "../../ios/features.mjs";
3+
import {
4+
isBridgelessEnabled,
5+
isHermesEnabled,
6+
isNewArchEnabled,
7+
} from "../../ios/features.mjs";
48
import { v } from "../../scripts/helpers.js";
59

610
describe("isBridgelessEnabled()", () => {
@@ -19,34 +23,76 @@ describe("isBridgelessEnabled()", () => {
1923
});
2024

2125
it("returns true only when new architecture is enabled", () => {
22-
ok(!isBridgelessEnabled({}, 0));
23-
ok(!isBridgelessEnabled({}, firstAvailableVersion));
26+
ok(!isBridgelessEnabled(0, {}));
27+
ok(!isBridgelessEnabled(firstAvailableVersion, {}));
2428

2529
const options = { bridgelessEnabled: true, fabricEnabled: true };
2630

27-
ok(!isBridgelessEnabled(options, v(0, 72, 999)));
28-
ok(isBridgelessEnabled(options, firstAvailableVersion));
31+
ok(!isBridgelessEnabled(v(0, 72, 999), options));
32+
ok(isBridgelessEnabled(firstAvailableVersion, options));
2933
});
3034

3135
it("returns true by default starting with 0.74", () => {
3236
// Bridgeless mode is enabled by default starting with 0.74 unless opted-out of
33-
ok(isBridgelessEnabled({ fabricEnabled: true }, defaultVersion));
37+
ok(isBridgelessEnabled(defaultVersion, { fabricEnabled: true }));
3438
ok(
35-
!isBridgelessEnabled(
36-
{ bridgelessEnabled: false, fabricEnabled: true },
37-
defaultVersion
38-
)
39+
!isBridgelessEnabled(defaultVersion, {
40+
bridgelessEnabled: false,
41+
fabricEnabled: true,
42+
})
3943
);
4044
});
4145

4246
it("does not return true just because `RCT_NEW_ARCH_ENABLED` is set", () => {
4347
// `RCT_NEW_ARCH_ENABLED` does not enable bridgeless on older versions
4448
process.env["RCT_NEW_ARCH_ENABLED"] = "1";
4549

46-
ok(!isBridgelessEnabled({}, v(0, 72, 999)));
47-
ok(!isBridgelessEnabled({}, firstAvailableVersion));
48-
ok(isBridgelessEnabled({}, defaultVersion));
49-
ok(!isBridgelessEnabled({ bridgelessEnabled: false }, defaultVersion));
50+
ok(!isBridgelessEnabled(v(0, 72, 999), {}));
51+
ok(!isBridgelessEnabled(firstAvailableVersion, {}));
52+
ok(isBridgelessEnabled(defaultVersion, {}));
53+
ok(!isBridgelessEnabled(defaultVersion, { bridgelessEnabled: false }));
54+
});
55+
});
56+
57+
describe("isHermesEnabled()", () => {
58+
before(() => {
59+
delete process.env["USE_HERMES"];
60+
});
61+
62+
afterEach(() => {
63+
delete process.env["USE_HERMES"];
64+
});
65+
66+
for (const platform of ["ios", "macos", "visionos"] as const) {
67+
it(`[${platform}] is disabled by default`, () => {
68+
ok(!isHermesEnabled(platform, v(1, 0, 0), {}));
69+
});
70+
71+
it(`[${platform}] returns true when enabled`, () => {
72+
ok(isHermesEnabled(platform, v(1, 0, 0), { hermesEnabled: true }));
73+
});
74+
75+
it(`[${platform}] returns true if 'USE_HERMES=1'`, () => {
76+
process.env["USE_HERMES"] = "1";
77+
ok(isHermesEnabled(platform, v(1, 0, 0), {}));
78+
});
79+
80+
it(`[${platform}] returns false if 'USE_HERMES=0'`, () => {
81+
process.env["USE_HERMES"] = "0";
82+
ok(!isHermesEnabled(platform, v(1, 0, 0), { hermesEnabled: true }));
83+
});
84+
}
85+
86+
it("[visionos] builds from source when necessary", () => {
87+
const options = { hermesEnabled: true };
88+
89+
equal(isHermesEnabled("visionos", v(0, 75, 0), options), "from-source");
90+
equal(isHermesEnabled("visionos", v(0, 76, 0), options), true);
91+
92+
process.env["USE_HERMES"] = "1";
93+
94+
equal(isHermesEnabled("visionos", v(0, 75, 0), {}), "from-source");
95+
equal(isHermesEnabled("visionos", v(0, 76, 0), {}), true);
5096
});
5197
});
5298

@@ -63,27 +109,27 @@ describe("isNewArchEnabled()", () => {
63109
});
64110

65111
it("returns true if New Architecture is available and enabled", () => {
66-
ok(!isNewArchEnabled({}, 0));
67-
ok(!isNewArchEnabled({}, firstAvailableVersion));
112+
ok(!isNewArchEnabled(0, {}));
113+
ok(!isNewArchEnabled(firstAvailableVersion, {}));
68114

69115
// New architecture is first publicly available in 0.68, but we'll require 0.71
70-
ok(!isNewArchEnabled({ fabricEnabled: true }, v(0, 70, 999)));
71-
ok(isNewArchEnabled({ fabricEnabled: true }, firstAvailableVersion));
72-
ok(isNewArchEnabled({ newArchEnabled: true }, firstAvailableVersion));
116+
ok(!isNewArchEnabled(v(0, 70, 999), { fabricEnabled: true }));
117+
ok(isNewArchEnabled(firstAvailableVersion, { fabricEnabled: true }));
118+
ok(isNewArchEnabled(firstAvailableVersion, { newArchEnabled: true }));
73119
});
74120

75121
it("returns true if `RCT_NEW_ARCH_ENABLED=1`", () => {
76122
process.env["RCT_NEW_ARCH_ENABLED"] = "1";
77123

78-
ok(!isNewArchEnabled({}, v(0, 70, 999)));
79-
ok(isNewArchEnabled({}, firstAvailableVersion));
124+
ok(!isNewArchEnabled(v(0, 70, 999), {}));
125+
ok(isNewArchEnabled(firstAvailableVersion, {}));
80126
});
81127

82128
it("returns false if `RCT_NEW_ARCH_ENABLED=0`", () => {
83129
process.env["RCT_NEW_ARCH_ENABLED"] = "0";
84130

85-
ok(!isNewArchEnabled({}, v(0, 70, 999)));
86-
ok(!isNewArchEnabled({}, firstAvailableVersion));
87-
ok(!isNewArchEnabled({ fabric_enabled: true }, firstAvailableVersion));
131+
ok(!isNewArchEnabled(v(0, 70, 999), {}));
132+
ok(!isNewArchEnabled(firstAvailableVersion, {}));
133+
ok(!isNewArchEnabled(firstAvailableVersion, { fabric_enabled: true }));
88134
});
89135
});

test/ios/xcode.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ function makeProjectConfiguration(): ProjectConfiguration {
4343
reactNativePath: "",
4444
reactNativeVersion: 0,
4545
reactNativeHostPath: "",
46+
useHermes: false,
4647
useNewArch: false,
4748
useBridgeless: false,
4849
buildSettings: {},

0 commit comments

Comments
 (0)