Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ namespace facebook::react {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char HttpClientFactoryKey[] = "HttpClientFactory";

// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char DevToolsHttpClientFactoryKey[] = "DevToolsHttpClientFactory";

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ struct IHttpClient {

extern const char HttpClientFactoryKey[];

extern const char DevToolsHttpClientFactoryKey[];

using HttpClientFactory = std::function<std::unique_ptr<IHttpClient>()>;

HttpClientFactory getHttpClientFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ namespace facebook::react {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char WebSocketClientFactoryKey[] = "WebSocketClientFactory";

// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char DevToolsWebSocketClientFactoryKey[] =
"DevToolsWebSocketClientFactory";

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class IWebSocketClient {

extern const char WebSocketClientFactoryKey[];

extern const char DevToolsWebSocketClientFactoryKey[];

using WebSocketClientFactory =
std::function<std::unique_ptr<IWebSocketClient>()>;

Expand Down
113 changes: 63 additions & 50 deletions packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,52 +136,63 @@ void ReactHost::createReactInstance() {
reactInstanceData_->contextContainer->at<WebSocketClientFactory>(
WebSocketClientFactoryKey);

auto devToolsHttpClientFactory =
reactInstanceData_->contextContainer
->find<HttpClientFactory>(DevToolsHttpClientFactoryKey)
.value_or(httpClientFactory);

auto devToolsWebSocketClientFactory =
reactInstanceData_->contextContainer
->find<WebSocketClientFactory>(DevToolsWebSocketClientFactoryKey)
.value_or(webSocketClientFactory);

// Create devServerHelper
if (reactInstanceConfig_.enableDebugging) {
if (!devServerHelper_) {
devServerHelper_ = std::make_shared<DevServerHelper>(
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<Inspector>(
reactInstanceConfig_.appId,
reactInstanceConfig_.deviceName,
webSocketClientFactory,
httpClientFactory);
inspector_->ensureHostTarget(
[this]() { reloadReactInstance(); },
[weakDevUIDelegate = std::weak_ptr<IDevUIDelegate>(
reactInstanceData_->devUIDelegate)](
bool showDebuggerOverlay,
std::function<void()>&& 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<DevServerHelper>(
reactInstanceConfig_.appId,
reactInstanceConfig_.deviceName,
reactInstanceConfig_.devServerHost,
reactInstanceConfig_.devServerPort,
devToolsHttpClientFactory,
[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<Inspector>(
reactInstanceConfig_.appId,
reactInstanceConfig_.deviceName,
devToolsWebSocketClientFactory,
devToolsHttpClientFactory);
inspector_->ensureHostTarget(
[this]() { reloadReactInstance(); },
[weakDevUIDelegate =
std::weak_ptr<IDevUIDelegate>(reactInstanceData_->devUIDelegate)](
bool showDebuggerOverlay,
std::function<void()>&& resumeDebuggerFn) {
if (auto debugUIDelegate = weakDevUIDelegate.lock()) {
if (showDebuggerOverlay) {
debugUIDelegate->showDebuggerOverlay(std::move(resumeDebuggerFn));
} else {
debugUIDelegate->hideDebuggerOverlay();
}
});
}
if (!packagerConnection_) {
packagerConnection_ = std::make_unique<PackagerConnection>(
webSocketClientFactory,
devServerHelper_->getPackagerConnectionUrl(),
[this]() { reloadReactInstance(); },
[]() {});
}
}
});
}

if (!packagerConnection_ && reactInstanceConfig_.enableDevMode) {
packagerConnection_ = std::make_unique<PackagerConnection>(
devToolsWebSocketClientFactory,
devServerHelper_->getPackagerConnectionUrl(),
[this]() { reloadReactInstance(); },
[]() {});
}

// Create the React Instance
Expand All @@ -194,7 +205,7 @@ void ReactHost::createReactInstance() {
reactInstanceData_->messageQueueThread,
/* allocInOldGenBeforeTTI */ false);

if (reactInstanceConfig_.enableDebugging) {
if (reactInstanceConfig_.enableInspector) {
react_native_assert(
inspector_ != nullptr && "Inspector is not initialized");
}
Expand Down Expand Up @@ -240,6 +251,10 @@ void ReactHost::createReactInstance() {
auto jsInvoker = std::make_shared<RuntimeSchedulerCallInvoker>(
reactInstance_->getRuntimeScheduler());

if (inspector_ != nullptr) {
inspector_->connectDebugger(devServerHelper_->getInspectorUrl());
}

auto liveReloadCallback = [this]() { reloadReactInstance(); };
reactInstance_->initializeRuntime(
{
Expand All @@ -258,7 +273,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,
Expand Down Expand Up @@ -417,7 +433,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();
}
Expand Down Expand Up @@ -451,9 +467,6 @@ bool ReactHost::loadScriptFromDevServer() {
auto script = std::make_unique<JSBigStdString>(response);
reactInstance_->loadScript(
std::move(script), devServerHelper_->getBundleUrl());
if (inspector_ != nullptr) {
inspector_->connectDebugger(devServerHelper_->getInspectorUrl());
}
devServerHelper_->setupHMRClient();
return true;
} catch (...) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
*/

/**
* This transform injects a single `debugger;` statement at the top of every
* Fantom test, so we can automatically stop on them when debugging.
*/
module.exports = function ({types: t}) {
return {
name: 'inject-debugger-statements-in-tests',
visitor: {
Program(path, state) {
const filename = state.filename || '';
if (
(filename.endsWith('-itest.js') ||
filename.endsWith('-itest.fb.js')) &&
!filename.includes('/.out/')
) {
// Check if the first statement is already a debugger statement
const first = path.node.body[0];
if (!first || first.type !== 'DebuggerStatement') {
path.unshiftContainer('body', t.debuggerStatement());
}
}
},
},
};
};
49 changes: 49 additions & 0 deletions private/react-native-fantom/config/metro-babel-transformer.flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import type {
BabelTransformer,
BabelTransformerArgs,
} from 'metro-babel-transformer';

import MetroBabelTransformer from '@react-native/metro-babel-transformer';
import crypto from 'crypto';
import fs from 'fs';

const transform: BabelTransformer['transform'] = (
args: BabelTransformerArgs,
) => {
const processedArgs = {
...args,
plugins: [
...(args.plugins ?? []),
// $FlowExpectedError[untyped-import]
require('./babel-plugins/inject-debugger-statements-in-tests'),
],
};
return MetroBabelTransformer.transform(processedArgs);
};

module.exports = {
...MetroBabelTransformer,
transform,
getCacheKey(): string {
const key = crypto.createHash('md5');
const cacheKeyParts = [
MetroBabelTransformer.getCacheKey?.() ?? '',
fs.readFileSync(__filename),
fs.readFileSync(
require.resolve('./babel-plugins/inject-debugger-statements-in-tests'),
),
];
cacheKeyParts.forEach(part => key.update(part));
return key.digest('hex');
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
'use strict';

require('../../../scripts/shared/babelRegister').registerForMonorepo();
module.exports = require('@react-native/metro-babel-transformer');
module.exports = require('./metro-babel-transformer.flow');
37 changes: 25 additions & 12 deletions private/react-native-fantom/runner/global-setup/globalSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
globalThis.__FANTOM_METRO_SERVER__ = server;
}

async function findAvailablePort(): Promise<number> {
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);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import type {RunServerResult} from 'metro';

type MetroServer = $NonMaybeType<RunServerResult?.['httpServer']>;

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;
}

Expand Down
Loading