Skip to content

Commit 7846672

Browse files
motiz88huntie
andauthored
[0.85] Add Fast Refresh performance marker and unstable_fastRefreshComplete CDP event (#56639)
* Add performance marker for Fast Refresh update (#56176) Summary: Pull Request resolved: #56176 NOTE: Resubmission of D84624705. Adds a prominent vertical "Fast Refresh ⚛︎" marker entry in the performance timeline when a Fast Refresh update is complete. Changelog: [Internal] Reviewed By: GijsWeterings Differential Revision: D97486551 fbshipit-source-id: 4c70b674370bab06f02e8d356bbd2979c9686a44 * Add unstable_fastRefreshComplete CDP event (#56273) Summary: Pull Request resolved: #56273 Adds a new, experimental `ReactNativeApplication.unstable_fastRefreshComplete` CDP event, emitted to subscribed active CDP sessions when a Fast Refresh update completes. **Notes** - As with D97486551, we reuse the `changeId` block in `HMRClient.js`, ensuring duplicate updates for the same change are not reported. Changelog: [Internal] Reviewed By: GijsWeterings, hoxyq Differential Revision: D98493216 fbshipit-source-id: b0b81a210fb84873e9358aa5484038062f110103 * Bump Metro to ^0.84.3 Metro 0.84.3 adds `changeId` to HMR `update-done` messages, which is required by the `unstable_fastRefreshComplete` CDP event and the Fast Refresh performance marker to deduplicate events across multiple updates for the same file change. --------- Co-authored-by: Alex Hunt <huntie@meta.com>
1 parent 691b232 commit 7846672

10 files changed

Lines changed: 225 additions & 144 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@
9898
"markdownlint-cli2": "^0.17.2",
9999
"markdownlint-rule-relative-links": "^3.0.0",
100100
"memfs": "^4.38.2",
101-
"metro-babel-register": "^0.83.3",
102-
"metro-transform-plugins": "^0.83.3",
101+
"metro-babel-register": "^0.84.3",
102+
"metro-transform-plugins": "^0.84.3",
103103
"micromatch": "^4.0.4",
104104
"node-fetch": "^2.2.0",
105105
"nullthrows": "^1.1.1",

packages/community-cli-plugin/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434
"@react-native/dev-middleware": "0.85.2",
3535
"debug": "^4.4.0",
3636
"invariant": "^2.2.4",
37-
"metro": "^0.84.0",
38-
"metro-config": "^0.84.0",
39-
"metro-core": "^0.84.0",
37+
"metro": "^0.84.3",
38+
"metro-config": "^0.84.3",
39+
"metro-core": "^0.84.3",
4040
"semver": "^7.1.3"
4141
},
4242
"devDependencies": {
43-
"metro-resolver": "^0.84.0"
43+
"metro-resolver": "^0.84.3"
4444
},
4545
"peerDependencies": {
4646
"@react-native-community/cli": "*",

packages/metro-config/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"dependencies": {
3838
"@react-native/js-polyfills": "0.85.2",
3939
"@react-native/metro-babel-transformer": "0.85.2",
40-
"metro-config": "^0.84.0",
41-
"metro-runtime": "^0.84.0"
40+
"metro-config": "^0.84.3",
41+
"metro-runtime": "^0.84.3"
4242
}
4343
}

packages/react-native/Libraries/Utilities/HMRClient.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ Error: ${e.message}`;
212212
});
213213

214214
let pendingUpdatesCount = 0;
215+
let lastMarkerChangeId: string | null = null;
215216
client.on('update-start', ({isInitialUpdate}) => {
216217
pendingUpdatesCount++;
217218
currentCompileErrorMessage = null;
@@ -229,10 +230,15 @@ Error: ${e.message}`;
229230
}
230231
});
231232

232-
client.on('update-done', () => {
233+
client.on('update-done', body => {
233234
pendingUpdatesCount--;
234235
if (pendingUpdatesCount === 0) {
235236
DevLoadingView.hide();
237+
const changeId = body?.changeId;
238+
if (changeId != null && changeId !== lastMarkerChangeId) {
239+
lastMarkerChangeId = changeId;
240+
emitFastRefreshCompleteEvents();
241+
}
236242
}
237243
});
238244

@@ -379,4 +385,25 @@ function showCompileError() {
379385
throw error;
380386
}
381387

388+
function emitFastRefreshCompleteEvents() {
389+
// Add marker entry in performance timeline
390+
performance.mark('Fast Refresh - Update done', {
391+
detail: {
392+
devtools: {
393+
dataType: 'marker',
394+
color: 'primary',
395+
tooltipText: 'Fast Refresh \u269b',
396+
},
397+
},
398+
});
399+
400+
// Notify CDP clients via internal binding
401+
if (
402+
// $FlowFixMe[prop-missing] - Injected by RuntimeTarget
403+
typeof globalThis.__notifyFastRefreshComplete === 'function'
404+
) {
405+
globalThis.__notifyFastRefreshComplete();
406+
}
407+
}
408+
382409
export default HMRClient;

packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
#include "RuntimeAgent.h"
99
#include "SessionState.h"
1010

11+
#include <folly/dynamic.h>
12+
#include <jsinspector-modern/cdp/CdpJson.h>
13+
14+
#include <chrono>
1115
#include <utility>
1216

1317
namespace facebook::react::jsinspector_modern {
@@ -119,6 +123,21 @@ void RuntimeAgent::notifyBindingCalled(
119123
"name", bindingName)("payload", payload)));
120124
}
121125

126+
void RuntimeAgent::notifyFastRefreshComplete() {
127+
if (!sessionState_.isReactNativeApplicationDomainEnabled) {
128+
return;
129+
}
130+
folly::dynamic params = folly::dynamic::object(
131+
"timestamp",
132+
std::chrono::duration_cast<std::chrono::milliseconds>(
133+
std::chrono::system_clock::now().time_since_epoch())
134+
.count());
135+
frontendChannel_(
136+
cdp::jsonNotification(
137+
"ReactNativeApplication.unstable_fastRefreshComplete",
138+
std::move(params)));
139+
}
140+
122141
RuntimeAgent::ExportedState RuntimeAgent::getExportedState() {
123142
return {
124143
.delegateState = delegate_ ? delegate_->getExportedState() : nullptr,

packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ class RuntimeAgent final {
7272

7373
void notifyBindingCalled(const std::string &bindingName, const std::string &payload);
7474

75+
/**
76+
* Called by RuntimeTarget when JS calls __notifyFastRefreshComplete().
77+
* Emits a ReactNativeApplication.unstable_fastRefreshComplete CDP
78+
* notification if the ReactNativeApplication domain is enabled.
79+
*/
80+
void notifyFastRefreshComplete();
81+
7582
struct ExportedState {
7683
std::unique_ptr<RuntimeAgentDelegate::ExportedState> delegateState;
7784
};

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ void RuntimeTarget::installGlobals() {
4747
// NOTE: RuntimeTarget::installNetworkReporterAPI is in
4848
// RuntimeTargetNetwork.cpp
4949
installNetworkReporterAPI();
50+
51+
installFastRefreshHandler();
5052
}
5153

5254
std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
@@ -128,6 +130,37 @@ void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
128130
});
129131
}
130132

133+
void RuntimeTarget::installFastRefreshHandler() {
134+
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
135+
auto globalObj = runtime.global();
136+
try {
137+
auto name =
138+
jsi::PropNameID::forUtf8(runtime, "__notifyFastRefreshComplete");
139+
globalObj.setProperty(
140+
runtime,
141+
name,
142+
jsi::Function::createFromHostFunction(
143+
runtime,
144+
name,
145+
0,
146+
[selfExecutor](
147+
jsi::Runtime& /*rt*/,
148+
const jsi::Value&,
149+
const jsi::Value*,
150+
size_t) -> jsi::Value {
151+
selfExecutor([](auto& self) {
152+
self.agents_.forEach(
153+
[](auto& agent) { agent.notifyFastRefreshComplete(); });
154+
});
155+
156+
return jsi::Value::undefined();
157+
}));
158+
} catch (jsi::JSError&) {
159+
// Swallow JavaScript exceptions that occur while setting up the global.
160+
}
161+
});
162+
}
163+
131164
void RuntimeTarget::emitDebuggerSessionCreated() {
132165
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
133166
try {

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
295295
*/
296296
void installGlobals();
297297

298+
/**
299+
* Installs __notifyFastRefreshComplete on the runtime's global object.
300+
* When called from JS, dispatches to all connected RuntimeAgents.
301+
*/
302+
void installFastRefreshHandler();
303+
298304
/**
299305
* Install the console API handler.
300306
*/

packages/react-native/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@
179179
"hermes-compiler": "250829098.0.10",
180180
"invariant": "^2.2.4",
181181
"memoize-one": "^5.0.0",
182-
"metro-runtime": "^0.84.0",
183-
"metro-source-map": "^0.84.0",
182+
"metro-runtime": "^0.84.3",
183+
"metro-source-map": "^0.84.3",
184184
"nullthrows": "^1.1.1",
185185
"pretty-format": "^29.7.0",
186186
"promise": "^8.3.0",

0 commit comments

Comments
 (0)