Skip to content

Announce the JavaScript about to run with its URL and dev server revision - #59985

Open
RobertHerhold wants to merge 1 commit into
react:mainfrom
RobertHerhold:export-D131359967
Open

RobertHerhold wants to merge 1 commit into
react:mainfrom
RobertHerhold:export-D131359967

Conversation

@RobertHerhold

Copy link
Copy Markdown

Summary:
Observers cannot tell which JavaScript runs in bridgeless mode, or which dev server revision it was built from. On iOS, RCTJavaScriptDidLoadNotification arrives on the main queue only after a bundle finishes evaluating and names it only through the bridge's current bundle URL, so a bundle that throws goes unseen, a reload in between is attributed to the new URL, and segments are never announced. On Android, the performance markers name a bundle only by its file's basename and a segment only by its ID. The dev server labels every bundle response with the revision of the module graph it was built from (X-Metro-Delta-ID) and every hot update with a revisionId, but React Native drops both.

iOS:

  • RCTInstance posts RCTJavaScriptWillStartExecutingNotification synchronously right before a bundle runs (on the JS thread) and before a segment is registered (on the registering thread). userInfo carries the bridge, the bundle or segment file url, and the bundle's revisionId when the dev server sent one. RCTJavaScriptDidLoadNotification now carries the url too. RCTConstants.h states when each notification is posted, on which thread and with which keys, and that will-execute observers must not use UIKit or wait on the main thread. The macOS RCTInstance (runtime/platform/macos) is unchanged.
  • RCTInstance calls RCTMarkJavaScriptStartedExecuting() right before each of those posts, and RCTHasJavaScriptStartedExecuting() reads it, so an observer that registers after JavaScript started can tell that it missed some instead of assuming none ran.
  • RCTJavaScriptLoader keeps the X-Metro-Delta-ID header of a bundle it downloads as RCTSource.revisionId (nil for bundles read from disk or served without the header).

Android:

  • ReactJavaScriptLoads (UnstableReactNativeAPI) calls its listeners right before ReactInstance runs a bundle and before it registers a segment file, on the thread that loads it, with the script's source URL, the file read (null for an APK asset) and the bundle's dev server revision. ReactJavaScriptLoads.hasStartedExecuting() is the late-listener check. A listener that throws is logged and does not stop the load or the other listeners.
  • BundleDownloader.BundleInfo.revisionId keeps the X-Metro-Delta-ID of a downloaded bundle, DevSupportManagerBase.downloadedJSBundleRevisionId remembers it for the bundle last downloaded, and ReactHostImpl passes it to JSBundleLoader.createCachedBundleFromNetworkLoader, whose loader exposes it as JSBundleLoader.revisionId.

Both platforms:

  • HMRClient calls global.__HMR_UPDATE_REPORTER__?.onUpdate({revisionId}) for every update the server sends, after the client has applied it, or stashed it while Fast Refresh is off. An update that throws while it runs is not reported.

Nothing changes when no one observes the notification, adds a listener or installs the reporter.

Changelog:
[iOS][Added] - Post RCTJavaScriptWillStartExecutingNotification with the bundle or segment url and the dev server revisionId before JavaScript runs in bridgeless mode, add both to RCTJavaScriptDidLoadNotification, and expose a dev server bundle's revision as RCTSource.revisionId
[iOS][Added] - Add RCTHasJavaScriptStartedExecuting(), which tells an observer that registers late whether JavaScript already started executing
[Android][Added] - Add ReactJavaScriptLoads, which announces each bundle and segment the bridgeless runtime is about to run with its source URL, file and dev server revision, and expose a downloaded bundle's dev server revision as JSBundleLoader.revisionId
[General][Added] - Report each hot update's revision to an optional global.__HMR_UPDATE_REPORTER__

Differential Revision: D131359967

…sion

Summary:
Observers cannot tell which JavaScript runs in bridgeless mode, or which dev server revision it was built from. On iOS, `RCTJavaScriptDidLoadNotification` arrives on the main queue only after a bundle finishes evaluating and names it only through the bridge's current bundle URL, so a bundle that throws goes unseen, a reload in between is attributed to the new URL, and segments are never announced. On Android, the performance markers name a bundle only by its file's basename and a segment only by its ID. The dev server labels every bundle response with the revision of the module graph it was built from (`X-Metro-Delta-ID`) and every hot update with a `revisionId`, but React Native drops both.

iOS:
- `RCTInstance` posts `RCTJavaScriptWillStartExecutingNotification` synchronously right before a bundle runs (on the JS thread) and before a segment is registered (on the registering thread). `userInfo` carries the `bridge`, the bundle or segment file `url`, and the bundle's `revisionId` when the dev server sent one. `RCTJavaScriptDidLoadNotification` now carries the `url` too. `RCTConstants.h` states when each notification is posted, on which thread and with which keys, and that will-execute observers must not use UIKit or wait on the main thread. The macOS `RCTInstance` (`runtime/platform/macos`) is unchanged.
- `RCTInstance` calls `RCTMarkJavaScriptStartedExecuting()` right before each of those posts, and `RCTHasJavaScriptStartedExecuting()` reads it, so an observer that registers after JavaScript started can tell that it missed some instead of assuming none ran.
- `RCTJavaScriptLoader` keeps the `X-Metro-Delta-ID` header of a bundle it downloads as `RCTSource.revisionId` (nil for bundles read from disk or served without the header).

Android:
- `ReactJavaScriptLoads` (`UnstableReactNativeAPI`) calls its listeners right before `ReactInstance` runs a bundle and before it registers a segment file, on the thread that loads it, with the script's source URL, the file read (null for an APK asset) and the bundle's dev server revision. `ReactJavaScriptLoads.hasStartedExecuting()` is the late-listener check. A listener that throws is logged and does not stop the load or the other listeners.
- `BundleDownloader.BundleInfo.revisionId` keeps the `X-Metro-Delta-ID` of a downloaded bundle, `DevSupportManagerBase.downloadedJSBundleRevisionId` remembers it for the bundle last downloaded, and `ReactHostImpl` passes it to `JSBundleLoader.createCachedBundleFromNetworkLoader`, whose loader exposes it as `JSBundleLoader.revisionId`.

Both platforms:
- `HMRClient` calls `global.__HMR_UPDATE_REPORTER__?.onUpdate({revisionId})` for every update the server sends, after the client has applied it, or stashed it while Fast Refresh is off. An update that throws while it runs is not reported.

Nothing changes when no one observes the notification, adds a listener or installs the reporter.

Changelog:
[iOS][Added] - Post `RCTJavaScriptWillStartExecutingNotification` with the bundle or segment `url` and the dev server `revisionId` before JavaScript runs in bridgeless mode, add both to `RCTJavaScriptDidLoadNotification`, and expose a dev server bundle's revision as `RCTSource.revisionId`
[iOS][Added] - Add `RCTHasJavaScriptStartedExecuting()`, which tells an observer that registers late whether JavaScript already started executing
[Android][Added] - Add `ReactJavaScriptLoads`, which announces each bundle and segment the bridgeless runtime is about to run with its source URL, file and dev server revision, and expose a downloaded bundle's dev server revision as `JSBundleLoader.revisionId`
[General][Added] - Report each hot update's revision to an optional `global.__HMR_UPDATE_REPORTER__`

Differential Revision: D131359967
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 12, 2026
@meta-codesync

meta-codesync Bot commented Nov 12, 2026

Copy link
Copy Markdown

@RobertHerhold has exported this pull request. If you are a Meta employee, you can view the originating Diff in D131359967.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant