From 63942b4329080ff2f1079b3c027f928f3d68ad5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 12 Aug 2025 02:16:26 -0700 Subject: [PATCH 1/4] Refactor logic to find available port for Metro in Fantom Differential Revision: D79804005 --- .../runner/global-setup/globalSetup.js | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/private/react-native-fantom/runner/global-setup/globalSetup.js b/private/react-native-fantom/runner/global-setup/globalSetup.js index f87eec9848a0..af8871e74d97 100644 --- a/private/react-native-fantom/runner/global-setup/globalSetup.js +++ b/private/react-native-fantom/runner/global-setup/globalSetup.js @@ -11,6 +11,7 @@ import {isOSS, validateEnvironmentVariables} from '../EnvironmentOptions'; import build from './build'; import Metro from 'metro'; +import {Server} from 'net'; import path from 'path'; export default async function globalSetup( @@ -33,26 +34,38 @@ async function startMetroServer() { config: path.resolve(__dirname, '..', '..', 'config', 'metro.config.js'), }); + if (process.env.__FANTOM_METRO_PORT__ == null) { + const availablePort = await findAvailablePort(); + process.env.__FANTOM_METRO_PORT__ = String(availablePort); + } + // We need to reuse the same port across runs because can only set environment // variables for workers in the first one. // $FlowExpectedError[cannot-write] - metroConfig.server.port = - process.env.__FANTOM_METRO_PORT__ != null - ? Number(process.env.__FANTOM_METRO_PORT__) - : // Any available port - 0; + metroConfig.server.port = Number(process.env.__FANTOM_METRO_PORT__); const server = await Metro.runServer(metroConfig, { waitForBundler: true, watch: true, }); - if (process.env.__FANTOM_METRO_PORT__ == null) { - process.env.__FANTOM_METRO_PORT__ = String( - server.httpServer.address().port, - ); - } - // $FlowExpectedError[prop-missing] globalThis.__METRO_SERVER__ = server; } + +async function findAvailablePort(): Promise { + return new Promise((resolve, reject) => { + const server = new Server(); + server.listen(0, 'localhost', undefined, () => { + const port = server.address().port; + server.close(error => { + if (error != null) { + reject(error); + } else { + resolve(port); + } + }); + }); + server.on('error', reject); + }); +} From c2de971c2f14ec22c3dfc4da212e61b4828c8739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 12 Aug 2025 02:16:26 -0700 Subject: [PATCH 2/4] Rename global variable with Metro server for Fantom Differential Revision: D79804007 --- .../react-native-fantom/runner/global-setup/globalSetup.js | 2 +- .../runner/global-setup/globalTeardown.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/private/react-native-fantom/runner/global-setup/globalSetup.js b/private/react-native-fantom/runner/global-setup/globalSetup.js index af8871e74d97..94bf690a0335 100644 --- a/private/react-native-fantom/runner/global-setup/globalSetup.js +++ b/private/react-native-fantom/runner/global-setup/globalSetup.js @@ -50,7 +50,7 @@ async function startMetroServer() { }); // $FlowExpectedError[prop-missing] - globalThis.__METRO_SERVER__ = server; + globalThis.__FANTOM_METRO_SERVER__ = server; } async function findAvailablePort(): Promise { diff --git a/private/react-native-fantom/runner/global-setup/globalTeardown.js b/private/react-native-fantom/runner/global-setup/globalTeardown.js index 477bd3202389..1f6c4bd63e50 100644 --- a/private/react-native-fantom/runner/global-setup/globalTeardown.js +++ b/private/react-native-fantom/runner/global-setup/globalTeardown.js @@ -12,11 +12,12 @@ import type {RunServerResult} from 'metro'; type MetroServer = $NonMaybeType; -declare var __METRO_SERVER__: ?RunServerResult; +declare var __FANTOM_METRO_SERVER__: ?RunServerResult; function getMetroServer(): ?MetroServer { - return typeof __METRO_SERVER__ !== 'undefined' && __METRO_SERVER__ != null - ? __METRO_SERVER__.httpServer + return typeof __FANTOM_METRO_SERVER__ !== 'undefined' && + __FANTOM_METRO_SERVER__ != null + ? __FANTOM_METRO_SERVER__.httpServer : null; } From 1cbfc6f26f5da80fe7ce3e53d5a0bec056584cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 12 Aug 2025 02:16:26 -0700 Subject: [PATCH 3/4] Connect debugger before loading bundle Differential Revision: D79804004 --- .../ReactCxxPlatform/react/runtime/ReactHost.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index bcd8d15f8aed..b53b2b83bcfd 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp @@ -240,6 +240,10 @@ void ReactHost::createReactInstance() { auto jsInvoker = std::make_shared( reactInstance_->getRuntimeScheduler()); + if (inspector_ != nullptr) { + inspector_->connectDebugger(devServerHelper_->getInspectorUrl()); + } + auto liveReloadCallback = [this]() { reloadReactInstance(); }; reactInstance_->initializeRuntime( { @@ -451,9 +455,6 @@ bool ReactHost::loadScriptFromDevServer() { auto script = std::make_unique(response); reactInstance_->loadScript( std::move(script), devServerHelper_->getBundleUrl()); - if (inspector_ != nullptr) { - inspector_->connectDebugger(devServerHelper_->getInspectorUrl()); - } devServerHelper_->setupHMRClient(); return true; } catch (...) { From bcc68181b8f90abc195d708df25c3ff344cdeda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 12 Aug 2025 03:42:16 -0700 Subject: [PATCH 4/4] Split ReactInstanceConfig.enableDebugging into enableInspector and enableDevMode (#53201) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53201 Changelog: [internal] This splits the `ReactHost` option `enableDebugging` into more granular options: - `enableInspector` which enables the connection with the inspector/debugger. - `enableDevMode` which enables the use of bundles from Metro, reloads, etc. This allows us to enable the inspector in Fantom without consuming bundles from Metro. This should be backwards compatible with existing apps. In the future, we should be able to inject custom `DevSupportManager` instances into the `ReactHost` so we can customize all options with any level of granularity (the same way we do on Android, for example). Reviewed By: rshest Differential Revision: D79804006 --- .../react/runtime/ReactHost.cpp | 96 ++++++++++--------- .../react/runtime/ReactInstanceConfig.h | 10 +- 2 files changed, 55 insertions(+), 51 deletions(-) diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index b53b2b83bcfd..b35df76cf652 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp @@ -137,51 +137,52 @@ void ReactHost::createReactInstance() { WebSocketClientFactoryKey); // Create devServerHelper - if (reactInstanceConfig_.enableDebugging) { - if (!devServerHelper_) { - devServerHelper_ = std::make_shared( - reactInstanceConfig_.appId, - reactInstanceConfig_.deviceName, - reactInstanceConfig_.devServerHost, - reactInstanceConfig_.devServerPort, - httpClientFactory, - [this]( - const std::string& moduleName, - const std::string& methodName, - folly::dynamic&& args) { - reactInstance_->callFunctionOnModule( - moduleName, methodName, std::move(args)); - }); - } - if (!inspector_) { - inspector_ = std::make_shared( - reactInstanceConfig_.appId, - reactInstanceConfig_.deviceName, - webSocketClientFactory, - httpClientFactory); - inspector_->ensureHostTarget( - [this]() { reloadReactInstance(); }, - [weakDevUIDelegate = std::weak_ptr( - reactInstanceData_->devUIDelegate)]( - bool showDebuggerOverlay, - std::function&& resumeDebuggerFn) { - if (auto debugUIDelegate = weakDevUIDelegate.lock()) { - if (showDebuggerOverlay) { - debugUIDelegate->showDebuggerOverlay( - std::move(resumeDebuggerFn)); - } else { - debugUIDelegate->hideDebuggerOverlay(); - } + if (!devServerHelper_ && + (reactInstanceConfig_.enableInspector || + reactInstanceConfig_.enableDevMode)) { + devServerHelper_ = std::make_shared( + reactInstanceConfig_.appId, + reactInstanceConfig_.deviceName, + reactInstanceConfig_.devServerHost, + reactInstanceConfig_.devServerPort, + httpClientFactory, + [this]( + const std::string& moduleName, + const std::string& methodName, + folly::dynamic&& args) { + reactInstance_->callFunctionOnModule( + moduleName, methodName, std::move(args)); + }); + } + + if (!inspector_ && reactInstanceConfig_.enableInspector) { + inspector_ = std::make_shared( + reactInstanceConfig_.appId, + reactInstanceConfig_.deviceName, + webSocketClientFactory, + httpClientFactory); + inspector_->ensureHostTarget( + [this]() { reloadReactInstance(); }, + [weakDevUIDelegate = + std::weak_ptr(reactInstanceData_->devUIDelegate)]( + bool showDebuggerOverlay, + std::function&& resumeDebuggerFn) { + if (auto debugUIDelegate = weakDevUIDelegate.lock()) { + if (showDebuggerOverlay) { + debugUIDelegate->showDebuggerOverlay(std::move(resumeDebuggerFn)); + } else { + debugUIDelegate->hideDebuggerOverlay(); } - }); - } - if (!packagerConnection_) { - packagerConnection_ = std::make_unique( - webSocketClientFactory, - devServerHelper_->getPackagerConnectionUrl(), - [this]() { reloadReactInstance(); }, - []() {}); - } + } + }); + } + + if (!packagerConnection_ && reactInstanceConfig_.enableDevMode) { + packagerConnection_ = std::make_unique( + webSocketClientFactory, + devServerHelper_->getPackagerConnectionUrl(), + [this]() { reloadReactInstance(); }, + []() {}); } // Create the React Instance @@ -194,7 +195,7 @@ void ReactHost::createReactInstance() { reactInstanceData_->messageQueueThread, /* allocInOldGenBeforeTTI */ false); - if (reactInstanceConfig_.enableDebugging) { + if (reactInstanceConfig_.enableInspector) { react_native_assert( inspector_ != nullptr && "Inspector is not initialized"); } @@ -262,7 +263,8 @@ void ReactHost::createReactInstance() { reactInstanceData_->turboModuleManagerDelegates, jsInvoker = std::move(jsInvoker), logBoxSurfaceDelegate = reactInstanceData_->logBoxSurfaceDelegate, - devServerHelper = devServerHelper_, + devServerHelper = + reactInstanceConfig_.enableDevMode ? devServerHelper_ : nullptr, animatedNodesManagerProvider = reactInstanceData_->animatedNodesManagerProvider, onJsError = reactInstanceData_->onJsError, @@ -421,7 +423,7 @@ bool ReactHost::loadScript( const std::string& bundlePath, const std::string& sourcePath) noexcept { bool isLoaded = false; - if (devServerHelper_) { + if (reactInstanceConfig_.enableDevMode && devServerHelper_) { devServerHelper_->setSourcePath(sourcePath); isLoaded = loadScriptFromDevServer(); } diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactInstanceConfig.h b/packages/react-native/ReactCxxPlatform/react/runtime/ReactInstanceConfig.h index d90b42a35472..43a0accd6583 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactInstanceConfig.h +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactInstanceConfig.h @@ -13,13 +13,15 @@ namespace facebook::react { struct ReactInstanceConfig { + std::string appId; + std::string deviceName; #ifdef REACT_NATIVE_DEBUG - bool enableDebugging{true}; + bool enableDevMode{true}; + bool enableInspector{true}; #else - bool enableDebugging{false}; + bool enableDevMode{false}; + bool enableInspector{false}; #endif - std::string appId; - std::string deviceName; std::string devServerHost{"localhost"}; uint32_t devServerPort{8081}; };