Introduce `loadBundles`

Summary:
public

Introduce a new Polyfill module to load bundles. This polyfill contains the function to which System.import gets transformed into. It works similarly to require in the way it's registered. It keeps track of the bundles that have ever been requested using promises, either fulfilled or not. The use of promises makes the implementation quite easy as we don't need to differenciate whether the request has been started or not.

There're a couple of follow up steps that still need to get done:

- Included this polyfill on the ones we automatically include on the bundle.
- Modify the transform to include the modules the user is actually requesting and pipe that through loadBundles so that the promise, once resolved, has the ordered list of modules the user requested.
- Implement the actual native code that loads a bundle. This shouldn't be that taught  as native will be able to assume it will never receive the same request twice.

Reviewed By: davidaurelio

Differential Revision: D2727241

fb-gh-sync-id: 317d80754783caf43f10c71a34a4558a4d298d45
This commit is contained in:
Martín Bigio 2015-12-08 12:23:41 -08:00 committed by facebook-github-bot-8
parent b025a8d946
commit 1e8e4dc223
2 changed files with 114 additions and 0 deletions

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.dontMock('../loadBundles');
jest.mock('NativeModules');
let loadBundles;
let loadBundlesCalls;
describe('loadBundles', () => {
beforeEach(() => {
loadBundles = jest.genMockFunction();
loadBundlesCalls = loadBundles.mock.calls;
require('NativeModules').RCTBundlesLoader = {loadBundles};
require('../loadBundles');
});
it('should set `global.__loadBundles` function when polyfill is initialized', () => {
expect(typeof global.__loadBundles).toBe('function');
});
it('should return a promise', () => {
loadBundles.mockImpl((bundles, callback) => callback());
expect(global.__loadBundles(['bundle.0']) instanceof Promise).toBeTruthy();
});
pit('shouldn\'t request already loaded bundles', () => {
loadBundles.mockImpl((bundles, callback) => callback());
return global
.__loadBundles(['bundle.0'])
.then(() => global.__loadBundles(['bundle.0']))
.then(() => expect(loadBundlesCalls.length).toBe(1));
});
pit('shouldn\'n request inflight bundles', () => {
loadBundles.mockImpl((bundles, callback) => {
if (bundles.length === 1 && bundles[0] === 'bundle.0') {
setTimeout(callback, 1000);
} else if (bundles.length === 1 && bundles[0] === 'bundle.1') {
setTimeout(callback, 500);
}
});
const promises = Promise.all([
global.__loadBundles(['bundle.0']),
global.__loadBundles(['bundle.0', 'bundle.1']),
]).then(() => {
expect(loadBundlesCalls.length).toBe(2);
expect(loadBundlesCalls[0][0][0]).toBe('bundle.0');
expect(loadBundlesCalls[1][0][0]).toBe('bundle.1');
});
jest.runAllTimers();
return promises;
});
});

View File

@ -0,0 +1,50 @@
/* eslint global-strict:0 */
(global => {
let loadBundlesOnNative = (bundles) =>
new Promise((_, resolve) =>
require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve));
let requestedBundles = Object.create(null);
/**
* Returns a promise that is fulfilled once all the indicated bundles are
* loaded into memory and injected into the JS engine.
* This invokation might need to go through the bridge
* and run native code to load some, if not all, the requested bundles.
* If all the bundles have already been loaded, the promise is resolved
* immediately. Otherwise, we'll determine which bundles still need to get
* loaded considering both, the ones already loaded, and the ones being
* currently asynchronously loaded by other invocations to `__loadBundles`,
* and return a promise that will get fulfilled once all these are finally
* loaded.
*
* Note this function should only be invoked by generated code.
*/
global.__loadBundles = function(bundles) {
// split bundles by whether they've already been requested or not
const bundlesToRequest = bundles.filter(b => !requestedBundles[b]);
const bundlesAlreadyRequested = bundles.filter(b => !!requestedBundles[b]);
// keep a reference to the promise loading each bundle
if (bundlesToRequest.length > 0) {
const nativePromise = loadBundlesOnNative(bundlesToRequest);
bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise);
}
return Promise.all(bundles.map(bundle => requestedBundles[bundle]));
};
})(global);
// turns a callback async based function into a promised based one
function promisify(fn) {
return function() {
var self = this;
var args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
args.push(resolve);
fn.apply(self, args);
});
};
}