From 36efbc341d8bda87a083251409456643a28cdd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 1 Feb 2016 12:41:04 -0800 Subject: [PATCH] Hot Loading Indicators Summary: public Introduce a header bar similar to the one shown when loading the bundle to indicate that the packager server is processing an HMR update. Hook into HMR events to show this bar when appropriate. Reviewed By: javache Differential Revision: D2873521 fb-gh-sync-id: a77cbb2368b75b045aa8c6ababce2f731baf514b --- Libraries/Utilities/HMRClient.js | 74 +++++++++++++++--------- React/Modules/RCTDevLoadingView.m | 42 ++++++++------ local-cli/server/util/attachHMRServer.js | 5 +- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js index 79be2be45..ed55ee6b9 100644 --- a/Libraries/Utilities/HMRClient.js +++ b/Libraries/Utilities/HMRClient.js @@ -11,6 +11,7 @@ 'use strict'; const invariant = require('invariant'); +const processColor = require('processColor'); /** * HMR Client that receives from the server HMR updates and propagates them @@ -49,39 +50,60 @@ Error: ${e.message}` ); }; activeWS.onmessage = ({data}) => { + const DevLoadingView = require('NativeModules').DevLoadingView; data = JSON.parse(data); - if (data.type === 'update') { - const modules = data.body.modules; - const sourceMappingURLs = data.body.sourceMappingURLs; - const sourceURLs = data.body.sourceURLs; - const RCTRedBox = require('NativeModules').RedBox; - RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss(); + switch(data.type) { + case 'update-start': { + DevLoadingView.showMessage( + 'Hot Loading...', + processColor('#000000'), + processColor('#aaaaaa'), + ); + break; + } + case 'update': { + const modules = data.body.modules; + const sourceMappingURLs = data.body.sourceMappingURLs; + const sourceURLs = data.body.sourceURLs; - modules.forEach((code, i) => { - code = code + '\n\n' + sourceMappingURLs[i]; + const RCTRedBox = require('NativeModules').RedBox; + RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss(); - require('SourceMapsCache').fetch({ - text: code, - url: sourceURLs[i], - sourceMappingURL: sourceMappingURLs[i], + modules.forEach((code, i) => { + code = code + '\n\n' + sourceMappingURLs[i]; + + require('SourceMapsCache').fetch({ + text: code, + url: sourceURLs[i], + sourceMappingURL: sourceMappingURLs[i], + }); + + // on JSC we need to inject from native for sourcemaps to work + // (Safari doesn't support `sourceMappingURL` nor any variant when + // evaluating code) but on Chrome we can simply use eval + const injectFunction = typeof __injectHMRUpdate === 'function' + ? __injectHMRUpdate + : eval; + + injectFunction(code, sourceURLs[i]); }); - // on JSC we need to inject from native for sourcemaps to work - // (Safari doesn't support `sourceMappingURL` nor any variant when - // evaluating code) but on Chrome we can simply use eval - const injectFunction = typeof __injectHMRUpdate === 'function' - ? __injectHMRUpdate - : eval; - - injectFunction(code, sourceURLs[i]); - }) - return; + DevLoadingView.hide(); + break; + } + case 'update-done': { + DevLoadingView.hide(); + break; + } + case 'error': { + DevLoadingView.hide(); + throw new Error(data.body.type + ' ' + data.body.description); + } + default: { + throw new Error(`Unexpected message: ${data}`); + } } - - // TODO: add support for opening filename by clicking on the stacktrace - const error = data.body; - throw new Error(error.type + ' ' + error.description); }; }, }; diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m index b652f7ce9..5c799791c 100644 --- a/React/Modules/RCTDevLoadingView.m +++ b/React/Modules/RCTDevLoadingView.m @@ -55,14 +55,13 @@ RCT_EXPORT_MODULE() [self showWithURL:bridge.bundleURL]; } -- (void)showWithURL:(NSURL *)URL +RCT_EXPORT_METHOD(showMessage:(NSString *)message color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor) { if (!isEnabled) { return; } dispatch_async(dispatch_get_main_queue(), ^{ - _showDate = [NSDate date]; if (!_window && !RCTRunningInTestEnvironment()) { CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; @@ -77,34 +76,23 @@ RCT_EXPORT_MODULE() [_window makeKeyAndVisible]; } - NSString *source; - if (URL.fileURL) { - _window.backgroundColor = [UIColor blackColor]; - _label.textColor = [UIColor grayColor]; - source = @"pre-bundled file"; - } else { - _window.backgroundColor = [UIColor colorWithHue:1./3 saturation:1 brightness:.35 alpha:1]; - _label.textColor = [UIColor whiteColor]; - source = [NSString stringWithFormat:@"%@:%@", URL.host, URL.port]; - } - - _label.text = [NSString stringWithFormat:@"Loading from %@...", source]; + _label.text = message; + _label.textColor = color; + _window.backgroundColor = backgroundColor; _window.hidden = NO; }); } -- (void)hide +RCT_EXPORT_METHOD(hide) { if (!isEnabled) { return; } dispatch_async(dispatch_get_main_queue(), ^{ - const NSTimeInterval MIN_PRESENTED_TIME = 0.6; NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:_showDate]; NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime); - CGRect windowFrame = _window.frame; [UIView animateWithDuration:0.25 delay:delay @@ -119,6 +107,26 @@ RCT_EXPORT_MODULE() }); } +- (void)showWithURL:(NSURL *)URL +{ + UIColor *color; + UIColor *backgroundColor; + NSString *source; + if (URL.fileURL) { + color = [UIColor grayColor]; + backgroundColor = [UIColor blackColor]; + source = @"pre-bundled file"; + } else { + color = [UIColor whiteColor]; + backgroundColor = [UIColor colorWithHue:1./3 saturation:1 brightness:.35 alpha:1]; + source = [NSString stringWithFormat:@"%@:%@", URL.host, URL.port]; + } + + [self showMessage:[NSString stringWithFormat:@"Loading from %@...", source] + color:color + backgroundColor:backgroundColor]; +} + @end #else diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js index 9eb62b551..3fc85e98c 100644 --- a/local-cli/server/util/attachHMRServer.js +++ b/local-cli/server/util/attachHMRServer.js @@ -112,6 +112,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { return; } + client.ws.send(JSON.stringify({type: 'update-start'})); stat.then(() => { return packagerServer.getShallowDependencies(filename) .then(deps => { @@ -240,7 +241,9 @@ function attachHMRServer({httpServer, path, packagerServer}) { () => { // do nothing, file was removed }, - ); + ).finally(() => { + client.ws.send(JSON.stringify({type: 'update-done'})); + }); }); client.ws.on('error', e => {