diff --git a/package.json b/package.json index 9eef756352bf..23c700c2d51f 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,8 @@ "markdownlint-cli2": "^0.17.2", "markdownlint-rule-relative-links": "^3.0.0", "memfs": "^4.38.2", - "metro-babel-register": "^0.83.3", - "metro-transform-plugins": "^0.83.3", + "metro-babel-register": "^0.84.3", + "metro-transform-plugins": "^0.84.3", "micromatch": "^4.0.4", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", diff --git a/packages/community-cli-plugin/package.json b/packages/community-cli-plugin/package.json index 2d1d201f0621..b8923b4c058f 100644 --- a/packages/community-cli-plugin/package.json +++ b/packages/community-cli-plugin/package.json @@ -34,13 +34,13 @@ "@react-native/dev-middleware": "0.85.2", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.84.0", - "metro-config": "^0.84.0", - "metro-core": "^0.84.0", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", "semver": "^7.1.3" }, "devDependencies": { - "metro-resolver": "^0.84.0" + "metro-resolver": "^0.84.3" }, "peerDependencies": { "@react-native-community/cli": "*", diff --git a/packages/metro-config/package.json b/packages/metro-config/package.json index c8751df242ff..5239f3688420 100644 --- a/packages/metro-config/package.json +++ b/packages/metro-config/package.json @@ -37,7 +37,7 @@ "dependencies": { "@react-native/js-polyfills": "0.85.2", "@react-native/metro-babel-transformer": "0.85.2", - "metro-config": "^0.84.0", - "metro-runtime": "^0.84.0" + "metro-config": "^0.84.3", + "metro-runtime": "^0.84.3" } } diff --git a/packages/react-native/Libraries/Utilities/HMRClient.js b/packages/react-native/Libraries/Utilities/HMRClient.js index 02ff12e9750e..5911eaa6729e 100644 --- a/packages/react-native/Libraries/Utilities/HMRClient.js +++ b/packages/react-native/Libraries/Utilities/HMRClient.js @@ -212,6 +212,7 @@ Error: ${e.message}`; }); let pendingUpdatesCount = 0; + let lastMarkerChangeId: string | null = null; client.on('update-start', ({isInitialUpdate}) => { pendingUpdatesCount++; currentCompileErrorMessage = null; @@ -229,10 +230,15 @@ Error: ${e.message}`; } }); - client.on('update-done', () => { + client.on('update-done', body => { pendingUpdatesCount--; if (pendingUpdatesCount === 0) { DevLoadingView.hide(); + const changeId = body?.changeId; + if (changeId != null && changeId !== lastMarkerChangeId) { + lastMarkerChangeId = changeId; + emitFastRefreshCompleteEvents(); + } } }); @@ -379,4 +385,25 @@ function showCompileError() { throw error; } +function emitFastRefreshCompleteEvents() { + // Add marker entry in performance timeline + performance.mark('Fast Refresh - Update done', { + detail: { + devtools: { + dataType: 'marker', + color: 'primary', + tooltipText: 'Fast Refresh \u269b', + }, + }, + }); + + // Notify CDP clients via internal binding + if ( + // $FlowFixMe[prop-missing] - Injected by RuntimeTarget + typeof globalThis.__notifyFastRefreshComplete === 'function' + ) { + globalThis.__notifyFastRefreshComplete(); + } +} + export default HMRClient; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp index 24b89fa9336a..0f3120518ac9 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp @@ -8,6 +8,10 @@ #include "RuntimeAgent.h" #include "SessionState.h" +#include +#include + +#include #include namespace facebook::react::jsinspector_modern { @@ -119,6 +123,21 @@ void RuntimeAgent::notifyBindingCalled( "name", bindingName)("payload", payload))); } +void RuntimeAgent::notifyFastRefreshComplete() { + if (!sessionState_.isReactNativeApplicationDomainEnabled) { + return; + } + folly::dynamic params = folly::dynamic::object( + "timestamp", + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); + frontendChannel_( + cdp::jsonNotification( + "ReactNativeApplication.unstable_fastRefreshComplete", + std::move(params))); +} + RuntimeAgent::ExportedState RuntimeAgent::getExportedState() { return { .delegateState = delegate_ ? delegate_->getExportedState() : nullptr, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h index 74d5c935a571..d6a584ede942 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h @@ -72,6 +72,13 @@ class RuntimeAgent final { void notifyBindingCalled(const std::string &bindingName, const std::string &payload); + /** + * Called by RuntimeTarget when JS calls __notifyFastRefreshComplete(). + * Emits a ReactNativeApplication.unstable_fastRefreshComplete CDP + * notification if the ReactNativeApplication domain is enabled. + */ + void notifyFastRefreshComplete(); + struct ExportedState { std::unique_ptr delegateState; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index 317f1e1ae55b..946983d928de 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -47,6 +47,8 @@ void RuntimeTarget::installGlobals() { // NOTE: RuntimeTarget::installNetworkReporterAPI is in // RuntimeTargetNetwork.cpp installNetworkReporterAPI(); + + installFastRefreshHandler(); } std::shared_ptr RuntimeTarget::createAgent( @@ -128,6 +130,37 @@ void RuntimeTarget::installBindingHandler(const std::string& bindingName) { }); } +void RuntimeTarget::installFastRefreshHandler() { + jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) { + auto globalObj = runtime.global(); + try { + auto name = + jsi::PropNameID::forUtf8(runtime, "__notifyFastRefreshComplete"); + globalObj.setProperty( + runtime, + name, + jsi::Function::createFromHostFunction( + runtime, + name, + 0, + [selfExecutor]( + jsi::Runtime& /*rt*/, + const jsi::Value&, + const jsi::Value*, + size_t) -> jsi::Value { + selfExecutor([](auto& self) { + self.agents_.forEach( + [](auto& agent) { agent.notifyFastRefreshComplete(); }); + }); + + return jsi::Value::undefined(); + })); + } catch (jsi::JSError&) { + // Swallow JavaScript exceptions that occur while setting up the global. + } + }); +} + void RuntimeTarget::emitDebuggerSessionCreated() { jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) { try { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 14f289190543..75eb87410c33 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -295,6 +295,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis