Make "Enable Hot Reloading" Instant

Summary:
As we saw in D15947985, and later traced down to D5623623, the `hot` option isn't used by Metro anymore. The relevant transforms _always_ run in DEV regardless of the option.

Given that, it doesn't make sense that enabling or disabling Hot Reloading forces a full refresh. This significantly raises the usage barrier because **currently, you might have to wait ~20 seconds (on a large app) to just start using Hot Reloading when you're already in the middle of some screen.** So you just end up not using it.

This diff changes enabling/disabling Hot Reloading to be _instant_.

Here's how it works:

1. Now we always send the necessary info to the client via the new `HMRClient.setup()` function. It creates a Metro HMR client instance, but only actually sets up the socket if Hot Reloading is on.

2. The "Enable Hot Reloading" menu no longer forces a reload. Instead, it calls `HMRClient.enable()` which lazily sets up a socket (at most once).

3. The "Disable Hot Reloading" menu also doesn't trigger a refresh now. Instead, it calls `HMRClient.disable()`. We don't actually tear down the socket here because it's a pain to deal with race conditions and such. Instead, we keep the connection — but we _ignore the updates_ that come in while we're disabled.

4. As a result, it is possible to enable and disable it many times during a single session. (Updates while disabled would be ignored — which has a risk of making your running app inconsistent — but I'd argue it's expected and is worth it. You can always save a particular file to force it to update once the mode is on.)

5. In order to support "ignoring" updates, Metro's `HMRClient` (not to be confused with RN's module) now supports a `shouldApplyUpdates` field. The RN module uses it to disable handling updates when the mode is off.

6. In case there is an error that makes hot reloading unavailable (such as the server disconnecting), we surface the error only if the mode is on. If the mode is off, we stash the error message in the `_hmrUnavailableReason` variable, and display it next time you try to enable Hot Reloading.

Reviewed By: rickhanlonii

Differential Revision: D15958160

fbshipit-source-id: 8256fc4d5c2c3f653a78edf13b8515a5671953e4
This commit is contained in:
Dan Abramov
2019-06-24 09:48:56 -07:00
committed by Facebook Github Bot
parent 055b28b4f4
commit 1f04ff580d
5 changed files with 172 additions and 37 deletions
@@ -488,15 +488,23 @@ public class DevSupportManagerImpl implements
new DevOptionHandler() {
@Override
public void onOptionSelected() {
if (!mDevSettings.isHotModuleReplacementEnabled() && !mDevSettings.isJSDevModeEnabled()) {
Toast.makeText(
mApplicationContext,
mApplicationContext.getString(R.string.catalyst_hot_reloading_auto_enable),
Toast.LENGTH_LONG).show();
mDevSettings.setJSDevModeEnabled(true);
}
mDevSettings.setHotModuleReplacementEnabled(!mDevSettings.isHotModuleReplacementEnabled());
handleReloadJS();
boolean nextEnabled = !mDevSettings.isHotModuleReplacementEnabled();
mDevSettings.setHotModuleReplacementEnabled(nextEnabled);
if (mCurrentContext != null) {
if (nextEnabled) {
mCurrentContext.getJSModule(HMRClient.class).enable();
} else {
mCurrentContext.getJSModule(HMRClient.class).disable();
}
}
if (nextEnabled && !mDevSettings.isJSDevModeEnabled()) {
Toast.makeText(
mApplicationContext,
mApplicationContext.getString(R.string.catalyst_hot_reloading_auto_enable),
Toast.LENGTH_LONG).show();
mDevSettings.setJSDevModeEnabled(true);
handleReloadJS();
}
}
});
options.put(
@@ -692,13 +700,15 @@ public class DevSupportManagerImpl implements
mDebugOverlayController = new DebugOverlayController(reactContext);
}
if (mDevSettings.isHotModuleReplacementEnabled() && mCurrentContext != null) {
if (mCurrentContext != null) {
try {
URL sourceUrl = new URL(getSourceUrl());
String path = sourceUrl.getPath().substring(1); // strip initial slash in path
String host = sourceUrl.getHost();
int port = sourceUrl.getPort();
mCurrentContext.getJSModule(HMRClient.class).enable("android", path, host, port);
mCurrentContext
.getJSModule(HMRClient.class)
.setup("android", path, host, port, mDevSettings.isHotModuleReplacementEnabled());
} catch (MalformedURLException e) {
showNewJavaError(e.getMessage(), e);
}
@@ -25,6 +25,18 @@ public interface HMRClient extends JavaScriptModule {
* @param bundleEntry The path to the bundle entry file (e.g. index.ios.bundle).
* @param host The host that the HMRClient should communicate with.
* @param port The port that the HMRClient should communicate with on the host.
* @param isEnabled Whether HMR is enabled initially.
*/
void enable(String platform, String bundleEntry, String host, int port);
void setup(String platform, String bundleEntry, String host, int port, boolean isEnabled);
/**
* Sets up a connection to the packager when called the first time.
* Ensures code updates received from the packager are applied.
*/
void enable();
/**
* Turns off the HMR client so it doesn't process updates from the packager.
*/
void disable();
}