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
This commit is contained in:
Alexander Blom 2016-04-27 09:56:48 -07:00 committed by Facebook Github Bot 9
parent 9547a98a68
commit 9a3a082225
2 changed files with 90 additions and 64 deletions

View File

@ -13,6 +13,7 @@
const BatchedBridge = require('BatchedBridge'); const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules; const RemoteModules = BatchedBridge.RemoteModules;
const Platform = require('Platform');
function normalizePrefix(moduleName: string): string { function normalizePrefix(moduleName: string): string {
return moduleName.replace(/^(RCT|RK)/, ''); return moduleName.replace(/^(RCT|RK)/, '');
@ -65,51 +66,53 @@ Object.keys(RemoteModules).forEach((moduleName) => {
* the call sites accessing NativeModules.UIManager directly have * the call sites accessing NativeModules.UIManager directly have
* been removed #9344445 * been removed #9344445
*/ */
const UIManager = NativeModules.UIManager; if (Platform.OS === 'ios') {
UIManager && Object.keys(UIManager).forEach(viewName => { const UIManager = NativeModules.UIManager;
const viewConfig = UIManager[viewName]; UIManager && Object.keys(UIManager).forEach(viewName => {
if (viewConfig.Manager) { const viewConfig = UIManager[viewName];
let constants; if (viewConfig.Manager) {
/* $FlowFixMe - nice try. Flow doesn't like getters */ let constants;
Object.defineProperty(viewConfig, 'Constants', { /* $FlowFixMe - nice try. Flow doesn't like getters */
configurable: true, Object.defineProperty(viewConfig, 'Constants', {
enumerable: true, configurable: true,
get: () => { enumerable: true,
if (constants) { 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; return constants;
} },
constants = {}; });
const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; let commands;
viewManager && Object.keys(viewManager).forEach(key => { /* $FlowFixMe - nice try. Flow doesn't like getters */
const value = viewManager[key]; Object.defineProperty(viewConfig, 'Commands', {
if (typeof value !== 'function') { configurable: true,
constants[key] = value; enumerable: true,
get: () => {
if (commands) {
return commands;
} }
}); commands = {};
return constants; const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
}, viewManager && Object.keys(viewManager).forEach((key, index) => {
}); const value = viewManager[key];
let commands; if (typeof value === 'function') {
/* $FlowFixMe - nice try. Flow doesn't like getters */ commands[key] = index;
Object.defineProperty(viewConfig, 'Commands', { }
configurable: true, });
enumerable: true,
get: () => {
if (commands) {
return commands; 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; module.exports = NativeModules;

View File

@ -70,6 +70,29 @@ function polyfillGlobal(name, newValue, scope = GLOBAL) {
Object.defineProperty(scope, name, {...descriptor, value: newValue}); 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`. * Polyfill a module if it is not already defined in `scope`.
*/ */
@ -104,18 +127,17 @@ function setUpErrorHandler() {
* unexplainably dropped timing signals. * unexplainably dropped timing signals.
*/ */
function setUpTimers() { function setUpTimers() {
var JSTimers = require('JSTimers'); const defineLazyTimer = (name) => {
GLOBAL.setTimeout = JSTimers.setTimeout; polyfillLazyGlobal(name, () => require('JSTimers')[name]);
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
}; };
defineLazyTimer('setTimeout');
defineLazyTimer('setInterval');
defineLazyTimer('setImmediate');
defineLazyTimer('clearTimeout');
defineLazyTimer('clearInterval');
defineLazyTimer('clearImmediate');
defineLazyTimer('requestAnimationFrame');
defineLazyTimer('cancelAnimationFrame');
} }
function setUpAlert() { function setUpAlert() {
@ -131,20 +153,19 @@ function setUpAlert() {
function setUpPromise() { function setUpPromise() {
// The native Promise implementation throws the following error: // The native Promise implementation throws the following error:
// ERROR: Event loop not supported. // ERROR: Event loop not supported.
GLOBAL.Promise = require('Promise'); polyfillLazyGlobal('Promise', () => require('Promise'));
} }
function setUpXHR() { function setUpXHR() {
// The native XMLHttpRequest in Chrome dev tools is CORS aware and won't // The native XMLHttpRequest in Chrome dev tools is CORS aware and won't
// let you fetch anything from the internet // let you fetch anything from the internet
polyfillGlobal('XMLHttpRequest', require('XMLHttpRequest')); polyfillLazyGlobal('XMLHttpRequest', () => require('XMLHttpRequest'));
polyfillGlobal('FormData', require('FormData')); polyfillLazyGlobal('FormData', () => require('FormData'));
var fetchPolyfill = require('fetch'); polyfillLazyGlobal('fetch', () => require('fetch').fetch);
polyfillGlobal('fetch', fetchPolyfill.fetch); polyfillLazyGlobal('Headers', () => require('fetch').Headers);
polyfillGlobal('Headers', fetchPolyfill.Headers); polyfillLazyGlobal('Request', () => require('fetch').Request);
polyfillGlobal('Request', fetchPolyfill.Request); polyfillLazyGlobal('Response', () => require('fetch').Response);
polyfillGlobal('Response', fetchPolyfill.Response);
} }
function setUpGeolocation() { function setUpGeolocation() {
@ -153,10 +174,12 @@ function setUpGeolocation() {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
}); });
polyfillGlobal('geolocation', require('Geolocation'), GLOBAL.navigator); polyfillLazyGlobal('geolocation', () => require('Geolocation'), GLOBAL.navigator);
} }
function setUpMapAndSet() { 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('Map', require('Map'));
polyfillGlobal('Set', require('Set')); polyfillGlobal('Set', require('Set'));
} }
@ -166,7 +189,7 @@ function setUpProduct() {
} }
function setUpWebSockets() { function setUpWebSockets() {
polyfillGlobal('WebSocket', require('WebSocket')); polyfillLazyGlobal('WebSocket', () => require('WebSocket'));
} }
function setUpProfile() { function setUpProfile() {