/ [node-haste] Remove support for asynchronous dependencies (`System.import`)

Summary:
public
Remove the unused feature for async dependencies / bundle layouts. We can bring it back later, if needed.

Reviewed By: cpojer

Differential Revision: D2916543

fb-gh-sync-id: 3a3890f10d7d275a4cb9371a6e9cace601a82b2c
shipit-source-id: 3a3890f10d7d275a4cb9371a6e9cace601a82b2c
This commit is contained in:
David Aurelio 2016-02-09 17:15:22 -08:00 committed by facebook-github-bot-4
parent 03a85ea8c2
commit 54e6576199
19 changed files with 10 additions and 1551 deletions

View File

@ -13,7 +13,6 @@ const fs = require('fs');
const path = require('path');
const Promise = require('promise');
const ProgressBar = require('progress');
const BundlesLayout = require('../BundlesLayout');
const Cache = require('../DependencyResolver/Cache');
const Transformer = require('../JSTransformer');
const Resolver = require('../Resolver');
@ -126,13 +125,6 @@ class Bundler {
cache: this._cache,
});
this._bundlesLayout = new BundlesLayout({
dependencyResolver: this._resolver,
resetCache: opts.resetCache,
cacheVersion: opts.cacheVersion,
projectRoots: opts.projectRoots,
});
this._transformer = new Transformer({
projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
@ -156,10 +148,6 @@ class Bundler {
return this._cache.end();
}
getLayout(main, isDev) {
return this._bundlesLayout.generateLayout(main, isDev);
}
bundle(options) {
const {dev, isUnbundle, platform} = options;
const moduleSystemDeps =

View File

@ -1,320 +0,0 @@
/**
* 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('../index')
.mock('fs');
var Promise = require('promise');
var BundlesLayout = require('../index');
var Resolver = require('../../Resolver');
var loadCacheSync = require('../../DependencyResolver/Cache/lib/loadCacheSync');
describe('BundlesLayout', () => {
function newBundlesLayout(options) {
return new BundlesLayout(Object.assign({
projectRoots: ['/root'],
dependencyResolver: new Resolver(),
}, options));
}
describe('layout', () => {
function isPolyfill() {
return false;
}
describe('getLayout', () => {
function dep(path) {
return {
path: path,
isPolyfill: isPolyfill,
};
}
pit('should bundle sync dependencies', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
asyncDependencies: [],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
return newBundlesLayout({resetCache: true})
.getLayout('/root/index.js')
.then(bundles =>
expect(bundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js', '/root/a.js'],
children: [],
})
);
});
pit('should separate async dependencies into different bundle', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js')],
asyncDependencies: [['/root/a.js']],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
return newBundlesLayout({resetCache: true})
.getLayout('/root/index.js')
.then(bundles =>
expect(bundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id:'bundle.0.1',
modules: ['/root/a.js'],
children: [],
}],
})
);
});
pit('separate async dependencies of async dependencies', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js')],
asyncDependencies: [['/root/a.js']],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js')],
asyncDependencies: [['/root/b.js']],
});
case '/root/b.js':
return Promise.resolve({
dependencies: [dep('/root/b.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
return newBundlesLayout({resetCache: true})
.getLayout('/root/index.js')
.then(bundles =>
expect(bundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [{
id: 'bundle.0.1.2',
modules: ['/root/b.js'],
children: [],
}],
}],
})
);
});
pit('separate bundle sync dependencies of async ones on same bundle', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js')],
asyncDependencies: [['/root/a.js']],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js'), dep('/root/b.js')],
asyncDependencies: [],
});
case '/root/b.js':
return Promise.resolve({
dependencies: [dep('/root/b.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
return newBundlesLayout({resetCache: true})
.getLayout('/root/index.js')
.then(bundles =>
expect(bundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/b.js'],
children: [],
}],
})
);
});
pit('separate cache in which bundle is each dependency', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
asyncDependencies: [],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js')],
asyncDependencies: [['/root/b.js']],
});
case '/root/b.js':
return Promise.resolve({
dependencies: [dep('/root/b.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
return newBundlesLayout({resetCache: true})
.getLayout('/root/index.js')
.then(bundles =>
expect(bundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js', '/root/a.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/b.js'],
children: [],
}],
})
);
});
pit('separate cache in which bundle is each dependency', () => {
Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) {
case '/root/index.js':
return Promise.resolve({
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
asyncDependencies: [['/root/b.js'], ['/root/c.js']],
});
case '/root/a.js':
return Promise.resolve({
dependencies: [dep('/root/a.js')],
asyncDependencies: [],
});
case '/root/b.js':
return Promise.resolve({
dependencies: [dep('/root/b.js')],
asyncDependencies: [['/root/d.js']],
});
case '/root/c.js':
return Promise.resolve({
dependencies: [dep('/root/c.js')],
asyncDependencies: [],
});
case '/root/d.js':
return Promise.resolve({
dependencies: [dep('/root/d.js')],
asyncDependencies: [],
});
default:
throw 'Undefined path: ' + path;
}
});
var layout = newBundlesLayout({resetCache: true});
return layout.getLayout('/root/index.js').then(() => {
expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0');
expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1');
expect(layout.getBundleIDForModule('/root/c.js')).toBe('bundle.0.2');
expect(layout.getBundleIDForModule('/root/d.js')).toBe('bundle.0.1.3');
});
});
});
});
describe('cache', () => {
beforeEach(() => {
loadCacheSync.mockReturnValue({
'/root/index.js': {
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [],
}],
},
'/root/b.js': {
id: 'bundle.2',
modules: ['/root/b.js'],
children: [],
},
});
});
pit('should load layouts', () => {
const layout = newBundlesLayout({ resetCache: false });
return Promise
.all([
layout.getLayout('/root/index.js'),
layout.getLayout('/root/b.js'),
])
.then(([layoutIndex, layoutB]) => {
expect(layoutIndex).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [],
}],
});
expect(layoutB).toEqual({
id: 'bundle.2',
modules: ['/root/b.js'],
children: [],
});
});
});
it('should load moduleToBundle map', () => {
const layout = newBundlesLayout({ resetCache: false });
expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0.1');
expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.2');
});
});
});

View File

@ -1,599 +0,0 @@
/**
* 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
.autoMockOff()
.mock('../../DependencyResolver/Cache')
.mock('../../Activity');
const Promise = require('promise');
const path = require('path');
jest.mock('fs');
var BundlesLayout = require('../index');
var Cache = require('../../DependencyResolver/Cache');
var Resolver = require('../../Resolver');
var fs = require('fs');
describe('BundlesLayout', () => {
var fileWatcher;
const polyfills = [
'polyfills/prelude_dev.js',
'polyfills/prelude.js',
'polyfills/require.js',
'polyfills/polyfills.js',
'polyfills/console.js',
'polyfills/error-guard.js',
'polyfills/String.prototype.es6.js',
'polyfills/Array.prototype.es6.js',
'polyfills/Array.es6.js',
'polyfills/Object.es7.js',
'polyfills/babelHelpers.js',
];
const baseFs = getBaseFs();
beforeEach(() => {
fileWatcher = {
on: () => this,
isWatchman: () => Promise.resolve(false)
};
});
describe('generate', () => {
function newBundlesLayout() {
const resolver = new Resolver({
projectRoots: ['/root', '/' + __dirname.split('/')[1]],
fileWatcher: fileWatcher,
cache: new Cache(),
assetExts: ['js', 'png'],
assetRoots: ['/root'],
});
return new BundlesLayout({
dependencyResolver: resolver,
resetCache: true,
projectRoots: ['/root', '/' + __dirname.split('/')[1]],
});
}
function stripPolyfills(bundle) {
return Promise
.all(bundle.children.map(childModule => stripPolyfills(childModule)))
.then(children => {
const modules = bundle.modules
.filter(moduleName => { // filter polyfills
for (let p of polyfills) {
if (moduleName.indexOf(p) !== -1) {
return false;
}
}
return true;
});
return {
id: bundle.id,
modules: modules,
children: children,
};
});
}
function setMockFilesystem(mockFs) {
fs.__setMockFilesystem(Object.assign(mockFs, baseFs));
}
pit('should bundle single-module app', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [],
})
)
);
});
pit('should bundle dependant modules', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
require("xa");`,
'a.js': `
/**
* @providesModule xa
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js', '/root/a.js'],
children: [],
})
)
);
});
pit('should split bundles for async dependencies', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");`,
'a.js': `
/**,
* @providesModule xa
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [],
}],
})
)
);
});
pit('should split into multiple bundles separate async dependencies', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");
${'System.import'}("xb");`,
'a.js': `
/**,
* @providesModule xa
*/`,
'b.js': `
/**
* @providesModule xb
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [
{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [],
}, {
id: 'bundle.0.2',
modules: ['/root/b.js'],
children: [],
},
],
})
)
);
});
pit('should fully traverse sync dependencies', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
require("xa");
${'System.import'}("xb");`,
'a.js': `
/**,
* @providesModule xa
*/`,
'b.js': `
/**
* @providesModule xb
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js', '/root/a.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/b.js'],
children: [],
}],
})
)
);
});
pit('should include sync dependencies async dependencies might have', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");`,
'a.js': `
/**,
* @providesModule xa
*/,
require("xb");`,
'b.js': `
/**
* @providesModule xb
*/
require("xc");`,
'c.js': `
/**
* @providesModule xc
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/b.js', '/root/c.js'],
children: [],
}],
})
)
);
});
pit('should allow duplicated dependencies across bundles', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");
${'System.import'}("xb");`,
'a.js': `
/**,
* @providesModule xa
*/,
require("xc");`,
'b.js': `
/**
* @providesModule xb
*/
require("xc");`,
'c.js': `
/**
* @providesModule xc
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [
{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/c.js'],
children: [],
},
{
id: 'bundle.0.2',
modules: ['/root/b.js', '/root/c.js'],
children: [],
},
],
})
)
);
});
pit('should put in separate bundles async dependencies of async dependencies', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");`,
'a.js': `
/**,
* @providesModule xa
*/,
${'System.import'}("xb");`,
'b.js': `
/**
* @providesModule xb
*/
require("xc");`,
'c.js': `
/**
* @providesModule xc
*/`,
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [
{
id: 'bundle.0.1',
modules: ['/root/a.js'],
children: [{
id: 'bundle.0.1.2',
modules: ['/root/b.js', '/root/c.js'],
children: [],
}],
},
],
})
)
);
});
pit('should put image dependencies into separate bundles', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");`,
'a.js':`
/**,
* @providesModule xa
*/,
require("./img.png");`,
'img.png': '',
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/img.png'],
children: [],
}],
})
)
);
});
pit('should put image dependencies across bundles', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");
${'System.import'}("xb");`,
'a.js':`
/**,
* @providesModule xa
*/,
require("./img.png");`,
'b.js':`
/**,
* @providesModule xb
*/,
require("./img.png");`,
'img.png': '',
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [
{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/img.png'],
children: [],
},
{
id: 'bundle.0.2',
modules: ['/root/b.js', '/root/img.png'],
children: [],
},
],
})
)
);
});
pit('could async require asset', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("./img.png");`,
'img.png': '',
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/img.png'],
children: [],
}],
})
)
);
});
pit('should include deprecated assets into separate bundles', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("xa");`,
'a.js':`
/**,
* @providesModule xa
*/,
require("image!img");`,
'img.png': '',
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/a.js', '/root/img.png'],
children: [],
}],
})
)
);
});
pit('could async require deprecated asset', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("image!img");`,
'img.png': '',
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/img.png'],
children: [],
}],
})
)
);
});
pit('should put packages into bundles', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule xindex
*/
${'System.import'}("aPackage");`,
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: './main.js',
browser: {
'./main.js': './client.js',
},
}),
'main.js': 'some other code',
'client.js': 'some code',
},
}
});
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
stripPolyfills(bundles).then(resolvedBundles =>
expect(resolvedBundles).toEqual({
id: 'bundle.0',
modules: ['/root/index.js'],
children: [{
id: 'bundle.0.1',
modules: ['/root/aPackage/client.js'],
children: [],
}],
})
)
);
});
});
function getBaseFs() {
const p = path.join(__dirname, '../../../Resolver/polyfills').substring(1);
const root = {};
let currentPath = root;
p.split('/').forEach(part => {
const child = {};
currentPath[part] = child;
currentPath = child;
});
polyfills.forEach(polyfill =>
currentPath[polyfill.split('/')[1]] = ''
);
return root;
}
});

View File

@ -1,219 +0,0 @@
/**
* 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';
const Activity = require('../Activity');
const _ = require('underscore');
const declareOpts = require('../lib/declareOpts');
const fs = require('fs');
const getCacheFilePath = require('../DependencyResolver/Cache/lib/getCacheFilePath');
const loadCacheSync = require('../DependencyResolver/Cache/lib/loadCacheSync');
const version = require('../../../../package.json').version;
const path = require('path');
const tmpdir = require('os').tmpDir();
const validateOpts = declareOpts({
dependencyResolver: {
type: 'object',
required: true,
},
resetCache: {
type: 'boolean',
default: false,
},
cacheVersion: {
type: 'string',
default: '1.0',
},
projectRoots: {
type: 'array',
required: true,
},
});
const BUNDLE_PREFIX = 'bundle';
/**
* Class that takes care of separating the graph of dependencies into
* separate bundles
*/
class BundlesLayout {
constructor(options) {
const opts = validateOpts(options);
this._resolver = opts.dependencyResolver;
// Cache in which bundle is each module.
this._moduleToBundle = Object.create(null);
// Cache the bundles layouts for each entry point. This entries
// are not evicted unless the user explicitly specifies so as
// computing them is pretty expensive
this._layouts = Object.create(null);
// TODO: watch for file creations and removals to update this caches
this._cacheFilePath = this._getCacheFilePath(opts);
if (!opts.resetCache) {
this._loadCacheSync(this._cacheFilePath);
} else {
this._persistCacheEventually();
}
}
getLayout(entryPath, isDev) {
if (this._layouts[entryPath]) {
return this._layouts[entryPath];
}
var currentBundleID = 0;
const rootBundle = {
id: BUNDLE_PREFIX + '.' + currentBundleID++,
modules: [],
children: [],
};
var pending = [{paths: [entryPath], bundle: rootBundle}];
this._layouts[entryPath] = promiseWhile(
() => pending.length > 0,
() => rootBundle,
() => {
const {paths, bundle} = pending.shift();
// pending sync dependencies we still need to explore for the current
// pending dependency
const pendingSyncDeps = paths;
// accum variable for sync dependencies of the current pending
// dependency we're processing
const syncDependencies = Object.create(null);
return promiseWhile(
() => pendingSyncDeps.length > 0,
() => {
const dependencies = Object.keys(syncDependencies);
if (dependencies.length > 0) {
bundle.modules = dependencies;
}
// persist changes to layouts
this._persistCacheEventually();
},
index => {
const pendingSyncDep = pendingSyncDeps.shift();
return this._resolver
.getDependencies(pendingSyncDep, {dev: isDev})
.then(deps => {
deps.dependencies.forEach(dep => {
if (dep.path !== pendingSyncDep && !dep.isPolyfill()) {
pendingSyncDeps.push(dep.path);
}
syncDependencies[dep.path] = true;
this._moduleToBundle[dep.path] = bundle.id;
});
deps.asyncDependencies.forEach(asyncDeps => {
const childBundle = {
id: bundle.id + '.' + currentBundleID++,
modules: [],
children: [],
};
bundle.children.push(childBundle);
pending.push({paths: asyncDeps, bundle: childBundle});
});
});
},
);
},
);
return this._layouts[entryPath];
}
getBundleIDForModule(path) {
return this._moduleToBundle[path];
}
_loadCacheSync(cachePath) {
const loadCacheId = Activity.startEvent('Loading bundles layout');
const cacheOnDisk = loadCacheSync(cachePath);
// TODO: create single-module bundles for unexistent modules
// TODO: remove modules that no longer exist
Object.keys(cacheOnDisk).forEach(entryPath => {
this._layouts[entryPath] = Promise.resolve(cacheOnDisk[entryPath]);
this._fillModuleToBundleMap(cacheOnDisk[entryPath]);
});
Activity.endEvent(loadCacheId);
}
_fillModuleToBundleMap(bundle) {
bundle.modules.forEach(module => this._moduleToBundle[module] = bundle.id);
bundle.children.forEach(child => this._fillModuleToBundleMap(child));
}
_persistCacheEventually() {
_.debounce(
this._persistCache.bind(this),
2000,
);
}
_persistCache() {
if (this._persisting !== null) {
return this._persisting;
}
this._persisting = Promise
.all(_.values(this._layouts))
.then(bundlesLayout => {
var json = Object.create(null);
Object.keys(this._layouts).forEach((p, i) =>
json[p] = bundlesLayout[i]
);
return Promise.denodeify(fs.writeFile)(
this._cacheFilepath,
JSON.stringify(json),
);
})
.then(() => this._persisting = null);
return this._persisting;
}
_getCacheFilePath(options) {
return getCacheFilePath(
tmpdir,
'react-packager-bundles-cache-',
version,
options.projectRoots.join(',').split(path.sep).join('-'),
options.cacheVersion || '0',
);
}
}
// Runs the body Promise meanwhile the condition callback is satisfied.
// Once it's not satisfied anymore, it returns what the results callback
// indicates
function promiseWhile(condition, result, body) {
return _promiseWhile(condition, result, body, 0);
}
function _promiseWhile(condition, result, body, index) {
if (!condition()) {
return Promise.resolve(result());
}
return body(index).then(() =>
_promiseWhile(condition, result, body, index + 1)
);
}
module.exports = BundlesLayout;

View File

@ -21,10 +21,6 @@ class AssetModule extends Module {
return Promise.resolve([]);
}
getAsyncDependencies() {
return Promise.resolve([]);
}
read() {
return Promise.resolve({});
}

View File

@ -24,10 +24,6 @@ class AssetModule_DEPRECATED extends Module {
return Promise.resolve([]);
}
getAsyncDependencies() {
return Promise.resolve([]);
}
hash() {
return `AssetModule_DEPRECATED : ${this.path}`;
}

View File

@ -187,23 +187,6 @@ class ResolutionRequest {
});
}
getAsyncDependencies(response) {
return Promise.resolve().then(() => {
const mod = this._moduleCache.getModule(this._entryPath);
return mod.getAsyncDependencies().then(bundles =>
Promise
.all(bundles.map(bundle =>
Promise.all(bundle.map(
dep => this.resolveDependency(mod, dep)
))
))
.then(bs => bs.map(bundle => bundle.map(dep => dep.path)))
);
}).then(asyncDependencies => asyncDependencies.forEach(
(dependency) => response.pushAsyncDependency(dependency)
));
}
_getAllMocks(pattern) {
// Take all mocks in all the roots into account. This is necessary
// because currently mocks are global: any module can be mocked by

View File

@ -11,7 +11,6 @@
class ResolutionResponse {
constructor() {
this.dependencies = [];
this.asyncDependencies = [];
this.mainModuleId = null;
this.mocks = null;
this.numPrependedDependencies = 0;
@ -22,13 +21,11 @@ class ResolutionResponse {
copy(properties) {
const {
dependencies = this.dependencies,
asyncDependencies = this.asyncDependencies,
mainModuleId = this.mainModuleId,
mocks = this.mocks,
} = properties;
return Object.assign(new this.constructor(), this, {
dependencies,
asyncDependencies,
mainModuleId,
mocks,
});
@ -69,11 +66,6 @@ class ResolutionResponse {
this.numPrependedDependencies += 1;
}
pushAsyncDependency(dependency) {
this._assertNotFinalized();
this.asyncDependencies.push(dependency);
}
setResolvedDependencyPairs(module, pairs) {
this._assertNotFinalized();
const hash = module.hash();

View File

@ -4068,40 +4068,6 @@ describe('DependencyGraph', function() {
});
});
describe('getAsyncDependencies', () => {
pit('should get dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'System.' + 'import("a")',
].join('\n'),
'a.js': [
'/**',
' * @providesModule a',
' */',
].join('\n'),
},
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
});
return dgraph.getDependencies('/root/index.js')
.then(response => response.finalize())
.then(({ asyncDependencies }) => {
expect(asyncDependencies).toEqual([
['/root/a.js'],
]);
});
});
});
describe('Extensions', () => {
pit('supports custom file extensions', () => {
var root = '/root';

View File

@ -185,14 +185,11 @@ class DependencyGraph {
const response = new ResolutionResponse();
return Promise.all([
req.getOrderedDependencies(
return req.getOrderedDependencies(
response,
this._opts.mocksPattern,
recursive,
),
req.getAsyncDependencies(response),
]).then(() => response);
).then(() => response);
});
}

View File

@ -91,14 +91,6 @@ class Module {
);
}
getAsyncDependencies() {
return this._cache.get(
this.path,
'asyncDependencies',
() => this.read().then(data => data.asyncDependencies)
);
}
invalidate() {
this._cache.invalidate(this.path);
}
@ -146,7 +138,6 @@ class Module {
return {
id,
dependencies: [],
asyncDependencies: [],
code: content,
};
} else {
@ -155,13 +146,12 @@ class Module {
? transformCode(this, content)
: Promise.resolve({code: content});
return codePromise.then(({code, dependencies, asyncDependencies}) => {
return codePromise.then(({code, dependencies}) => {
const {deps} = this._extractor(code);
return {
id,
code,
dependencies: dependencies || deps.sync,
asyncDependencies: asyncDependencies || deps.async,
};
});
}

View File

@ -167,45 +167,6 @@ describe('Module', () => {
});
});
describe('Async Dependencies', () => {
function expectAsyncDependenciesToEqual(expected) {
const module = createModule();
return module.getAsyncDependencies().then(actual =>
expect(actual).toEqual(expected)
);
}
pit('should recognize single dependency', () => {
mockIndexFile('System.' + 'import("dep1")');
return expectAsyncDependenciesToEqual([['dep1']]);
});
pit('should parse single quoted dependencies', () => {
mockIndexFile('System.' + 'import(\'dep1\')');
return expectAsyncDependenciesToEqual([['dep1']]);
});
pit('should parse multiple async dependencies on the same module', () => {
mockIndexFile([
'System.' + 'import("dep1")',
'System.' + 'import("dep2")',
].join('\n'));
return expectAsyncDependenciesToEqual([
['dep1'],
['dep2'],
]);
});
pit('parse fine new lines', () => {
mockIndexFile('System.' + 'import(\n"dep1"\n)');
return expectAsyncDependenciesToEqual([['dep1']]);
});
});
describe('Code', () => {
const fileContents = 'arbitrary(code)';
beforeEach(function() {
@ -247,7 +208,7 @@ describe('Module', () => {
const fileContents = 'arbitrary(code);';
const exampleCode = `
require('a');
System.import('b');
arbitrary.code('b');
require('c');`;
beforeEach(function() {
@ -268,12 +229,8 @@ describe('Module', () => {
transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
const module = createModule({transformCode});
return Promise.all([
module.getDependencies(),
module.getAsyncDependencies(),
]).then(([dependencies, asyncDependencies]) => {
return module.getDependencies().then(dependencies => {
expect(dependencies).toEqual(['a', 'c']);
expect(asyncDependencies).toEqual([['b']]);
});
});
@ -285,29 +242,8 @@ describe('Module', () => {
}));
const module = createModule({transformCode});
return Promise.all([
module.getDependencies(),
module.getAsyncDependencies(),
]).then(([dependencies, asyncDependencies]) => {
return module.getDependencies().then(dependencies => {
expect(dependencies).toEqual(mockedDependencies);
expect(asyncDependencies).toEqual([['b']]);
});
});
pit('uses async dependencies that `transformCode` resolves to, instead of extracting them', () => {
const mockedAsyncDependencies = [['foo', 'bar'], ['baz']];
transformCode.mockReturnValue(Promise.resolve({
code: exampleCode,
asyncDependencies: mockedAsyncDependencies,
}));
const module = createModule({transformCode});
return Promise.all([
module.getDependencies(),
module.getAsyncDependencies(),
]).then(([dependencies, asyncDependencies]) => {
expect(dependencies).toEqual(['a', 'c']);
expect(asyncDependencies).toEqual(mockedAsyncDependencies);
});
});

View File

@ -18,7 +18,6 @@ const lineCommentRe = /\/\/.+(\n|$)/g;
function extractRequires(code) {
var deps = {
sync: [],
async: [],
};
code = code
@ -41,15 +40,6 @@ function extractRequires(code) {
deps.sync.push(dep);
return match;
})
// Parse async dependencies this module has. As opposed to what happens
// with sync dependencies, when the module is required, it's async
// dependencies won't be loaded into memory. This is deferred till the
// code path gets to the import statement:
// System.import('dep1')
.replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => {
deps.async.push([dep]);
return match;
});
return {code, deps};
}

View File

@ -12,4 +12,3 @@
exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;

View File

@ -37,10 +37,9 @@ describe('Resolver', function() {
});
class ResolutionResponseMock {
constructor({dependencies, mainModuleId, asyncDependencies}) {
constructor({dependencies, mainModuleId}) {
this.dependencies = dependencies;
this.mainModuleId = mainModuleId;
this.asyncDependencies = asyncDependencies;
}
prependDependency(dependency) {
@ -80,7 +79,6 @@ describe('Resolver', function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
@ -180,7 +178,6 @@ describe('Resolver', function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
@ -208,7 +205,6 @@ describe('Resolver', function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
@ -637,7 +633,6 @@ describe('Resolver', function() {
const resolutionResponse = new ResolutionResponseMock({
dependencies: [module],
mainModuleId: 'test module',
asyncDependencies: [],
});
resolutionResponse.getResolvedDependencyPairs = (module) => {

View File

@ -1,63 +0,0 @@
/**
* 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

@ -1,34 +0,0 @@
/* eslint strict:0 */
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]);
// 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]));
};

View File

@ -1,71 +0,0 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @emails oncall+jsinfra
*/
'use strict';
jest.autoMockOff();
jest.mock('../../../BundlesLayout');
const babel = require('babel-core');
const BundlesLayout = require('../../../BundlesLayout');
const testData = {
isolated: {
input: 'System.' + 'import("moduleA");',
output: 'loadBundles(["bundle.0"]);'
},
single: {
input: 'System.' + 'import("moduleA").then(function (bundleA) {});',
output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});'
},
multiple: {
input: [
'Promise.all([',
'System.' + 'import("moduleA"), System.' + 'import("moduleB"),',
']).then(function (bundlesA, bundlesB) {});',
].join('\n'),
output: [
'Promise.all([',
'loadBundles(["bundle.0"]), loadBundles(["bundle.1"])',
']).then(function (bundlesA, bundlesB) {});',
].join(''),
},
};
describe('System.import', () => {
let layout = new BundlesLayout();
BundlesLayout.prototype.getBundleIDForModule.mockImpl(module => {
switch (module) {
case 'moduleA': return 'bundle.0';
case 'moduleB': return 'bundle.1';
}
});
function transform(source) {
return babel.transform(source, {
plugins: [
[require('../'), { bundlesLayout: layout }]
],
}).code;
}
function test(data) {
// transform and remove new lines
expect(transform(data.input).replace(/(\r\n|\n|\r)/gm,'')).toEqual(data.output);
}
it('should transform isolated `System.import`', () => {
test(testData.isolated);
});
it('should transform single `System.import`', () => {
test(testData.single);
});
it('should transform multiple `System.import`s', () => {
test(testData.multiple);
});
});

View File

@ -1,63 +0,0 @@
/**
* 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.
*
*/
/*jslint node: true */
'use strict';
const t = require('babel-types');
/**
* Transforms asynchronous module importing into a function call
* that includes which bundle needs to be loaded
*
* Transforms:
*
* System.import('moduleA')
*
* to:
*
* loadBundles('bundleA')
*/
module.exports = function() {
return {
visitor: {
CallExpression: function (path, state) {
if (!isAppropriateSystemImportCall(path.node)) {
return;
}
var bundlesLayout = state.opts.bundlesLayout;
var bundleID = bundlesLayout.getBundleIDForModule(
path.node.arguments[0].value
);
var bundles = bundleID.split('.');
bundles.splice(0, 1);
bundles = bundles.map(function(id) {
return t.stringLiteral('bundle.' + id);
});
path.replaceWith(t.callExpression(
t.identifier('loadBundles'),
[t.arrayExpression(bundles)]
));
},
},
};
};
function isAppropriateSystemImportCall(node) {
return (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'System' &&
node.callee.property.name === 'import' &&
node.arguments.length === 1 &&
node.arguments[0].type === 'StringLiteral'
);
}