From 9a3a082225277ff65857ac046fa09edb51702367 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Wed, 27 Apr 2016 09:56:48 -0700 Subject: [PATCH] Make polyfills and globals lazy Summary: This avoids requiring things that may never be used at all by the application such as WebSocket or Geolocation. It also stops us from asking for native modules before we actually start the application enabling us to potentially be more lazy in the future. Reviewed By: davidaurelio Differential Revision: D3212802 fb-gh-sync-id: 70cf0d1a85f39fedc47758e5eb5df789a511bc9b fbshipit-source-id: 70cf0d1a85f39fedc47758e5eb5df789a511bc9b --- .../BatchedBridgedModules/NativeModules.js | 89 ++++++++++--------- .../InitializeJavaScriptAppEngine.js | 65 +++++++++----- 2 files changed, 90 insertions(+), 64 deletions(-) diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index a8e6d0e70..3fea37e1a 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -13,6 +13,7 @@ const BatchedBridge = require('BatchedBridge'); const RemoteModules = BatchedBridge.RemoteModules; +const Platform = require('Platform'); function normalizePrefix(moduleName: string): string { return moduleName.replace(/^(RCT|RK)/, ''); @@ -65,51 +66,53 @@ Object.keys(RemoteModules).forEach((moduleName) => { * the call sites accessing NativeModules.UIManager directly have * been removed #9344445 */ -const UIManager = NativeModules.UIManager; -UIManager && Object.keys(UIManager).forEach(viewName => { - const viewConfig = UIManager[viewName]; - if (viewConfig.Manager) { - let constants; - /* $FlowFixMe - nice try. Flow doesn't like getters */ - Object.defineProperty(viewConfig, 'Constants', { - configurable: true, - enumerable: true, - get: () => { - if (constants) { +if (Platform.OS === 'ios') { + const UIManager = NativeModules.UIManager; + UIManager && Object.keys(UIManager).forEach(viewName => { + const viewConfig = UIManager[viewName]; + if (viewConfig.Manager) { + let constants; + /* $FlowFixMe - nice try. Flow doesn't like getters */ + Object.defineProperty(viewConfig, 'Constants', { + configurable: true, + enumerable: true, + get: () => { + if (constants) { + return constants; + } + constants = {}; + const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; + viewManager && Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value !== 'function') { + constants[key] = value; + } + }); return constants; - } - constants = {}; - const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; - viewManager && Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value !== 'function') { - constants[key] = value; + }, + }); + let commands; + /* $FlowFixMe - nice try. Flow doesn't like getters */ + Object.defineProperty(viewConfig, 'Commands', { + configurable: true, + enumerable: true, + get: () => { + if (commands) { + return commands; } - }); - return constants; - }, - }); - let commands; - /* $FlowFixMe - nice try. Flow doesn't like getters */ - Object.defineProperty(viewConfig, 'Commands', { - configurable: true, - enumerable: true, - get: () => { - if (commands) { + commands = {}; + const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; + viewManager && Object.keys(viewManager).forEach((key, index) => { + const value = viewManager[key]; + if (typeof value === 'function') { + commands[key] = index; + } + }); return commands; - } - commands = {}; - const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; - viewManager && Object.keys(viewManager).forEach((key, index) => { - const value = viewManager[key]; - if (typeof value === 'function') { - commands[key] = index; - } - }); - return commands; - }, - }); - } -}); + }, + }); + } + }); +} module.exports = NativeModules; diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index b6332da47..c0874b46b 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -70,6 +70,29 @@ function polyfillGlobal(name, newValue, scope = GLOBAL) { Object.defineProperty(scope, name, {...descriptor, value: newValue}); } +function polyfillLazyGlobal(name, valueFn, scope = GLOBAL) { + if (scope[name] !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(scope, name); + const backupName = `original${name[0].toUpperCase()}${name.substr(1)}`; + Object.defineProperty(scope, backupName, {...descriptor, value: scope[name]}); + } + + Object.defineProperty(scope, name, { + configurable: true, + enumerable: true, + get() { + return this[name] = valueFn(); + }, + set(value) { + Object.defineProperty(this, name, { + configurable: true, + enumerable: true, + value + }); + } + }); +} + /** * Polyfill a module if it is not already defined in `scope`. */ @@ -104,18 +127,17 @@ function setUpErrorHandler() { * unexplainably dropped timing signals. */ function setUpTimers() { - var JSTimers = require('JSTimers'); - GLOBAL.setTimeout = JSTimers.setTimeout; - GLOBAL.setInterval = JSTimers.setInterval; - GLOBAL.setImmediate = JSTimers.setImmediate; - GLOBAL.clearTimeout = JSTimers.clearTimeout; - GLOBAL.clearInterval = JSTimers.clearInterval; - GLOBAL.clearImmediate = JSTimers.clearImmediate; - GLOBAL.cancelAnimationFrame = JSTimers.clearInterval; - GLOBAL.requestAnimationFrame = function(cb) { - /*requestAnimationFrame() { [native code] };*/ // Trick scroller library - return JSTimers.requestAnimationFrame(cb); // into thinking it's native + const defineLazyTimer = (name) => { + polyfillLazyGlobal(name, () => require('JSTimers')[name]); }; + defineLazyTimer('setTimeout'); + defineLazyTimer('setInterval'); + defineLazyTimer('setImmediate'); + defineLazyTimer('clearTimeout'); + defineLazyTimer('clearInterval'); + defineLazyTimer('clearImmediate'); + defineLazyTimer('requestAnimationFrame'); + defineLazyTimer('cancelAnimationFrame'); } function setUpAlert() { @@ -131,20 +153,19 @@ function setUpAlert() { function setUpPromise() { // The native Promise implementation throws the following error: // ERROR: Event loop not supported. - GLOBAL.Promise = require('Promise'); + polyfillLazyGlobal('Promise', () => require('Promise')); } function setUpXHR() { // The native XMLHttpRequest in Chrome dev tools is CORS aware and won't // let you fetch anything from the internet - polyfillGlobal('XMLHttpRequest', require('XMLHttpRequest')); - polyfillGlobal('FormData', require('FormData')); + polyfillLazyGlobal('XMLHttpRequest', () => require('XMLHttpRequest')); + polyfillLazyGlobal('FormData', () => require('FormData')); - var fetchPolyfill = require('fetch'); - polyfillGlobal('fetch', fetchPolyfill.fetch); - polyfillGlobal('Headers', fetchPolyfill.Headers); - polyfillGlobal('Request', fetchPolyfill.Request); - polyfillGlobal('Response', fetchPolyfill.Response); + polyfillLazyGlobal('fetch', () => require('fetch').fetch); + polyfillLazyGlobal('Headers', () => require('fetch').Headers); + polyfillLazyGlobal('Request', () => require('fetch').Request); + polyfillLazyGlobal('Response', () => require('fetch').Response); } function setUpGeolocation() { @@ -153,10 +174,12 @@ function setUpGeolocation() { enumerable: true, configurable: true, }); - polyfillGlobal('geolocation', require('Geolocation'), GLOBAL.navigator); + polyfillLazyGlobal('geolocation', () => require('Geolocation'), GLOBAL.navigator); } function setUpMapAndSet() { + // We can't make these lazy as Map checks the global.Map to see if it's + // available but in our case it'll be a lazy getter. polyfillGlobal('Map', require('Map')); polyfillGlobal('Set', require('Set')); } @@ -166,7 +189,7 @@ function setUpProduct() { } function setUpWebSockets() { - polyfillGlobal('WebSocket', require('WebSocket')); + polyfillLazyGlobal('WebSocket', () => require('WebSocket')); } function setUpProfile() {