[react-packager] Fix bug on Bundles Layout algorithm

Summary:
The layout algorithm wasn't getting deep into the sync dependencies recursively to async dependencies.
This commit is contained in:
Martín Bigio 2015-08-19 12:27:27 -07:00
parent 90ca49cbd6
commit d8c8993758
4 changed files with 133 additions and 38 deletions

View File

@ -147,6 +147,37 @@ describe('BundlesLayout', () => {
); );
}); });
pit('separate cache in which bundle is each dependency', () => {
DependencyResolver.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().generateLayout(['/root/index.js']).then(
bundles => expect(bundles).toEqual([
[dep('/root/index.js'), dep('/root/a.js')],
[dep('/root/b.js')],
])
);
});
pit('separate cache in which bundle is each dependency', () => { pit('separate cache in which bundle is each dependency', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { DependencyResolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {

View File

@ -12,6 +12,7 @@ jest
.dontMock('absolute-path') .dontMock('absolute-path')
.dontMock('crypto') .dontMock('crypto')
.dontMock('underscore') .dontMock('underscore')
.dontMock('path')
.dontMock('../index') .dontMock('../index')
.dontMock('../../lib/getAssetDataFromName') .dontMock('../../lib/getAssetDataFromName')
.dontMock('../../DependencyResolver/crawlers') .dontMock('../../DependencyResolver/crawlers')
@ -29,6 +30,7 @@ jest
.dontMock('../../DependencyResolver/ModuleCache'); .dontMock('../../DependencyResolver/ModuleCache');
const Promise = require('promise'); const Promise = require('promise');
const path = require('path');
jest.mock('fs'); jest.mock('fs');
@ -39,6 +41,18 @@ describe('BundlesLayout', () => {
var fileWatcher; var fileWatcher;
var fs; var fs;
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',
];
const baseFs = getBaseFs();
beforeEach(() => { beforeEach(() => {
fs = require('fs'); fs = require('fs');
BundlesLayout = require('../index'); BundlesLayout = require('../index');
@ -54,7 +68,7 @@ describe('BundlesLayout', () => {
describe('generate', () => { describe('generate', () => {
function newBundlesLayout() { function newBundlesLayout() {
const resolver = new DependencyResolver({ const resolver = new DependencyResolver({
projectRoots: ['/root'], projectRoots: ['/root', '/' + __dirname.split('/')[1]],
fileWatcher: fileWatcher, fileWatcher: fileWatcher,
cache: new Cache(), cache: new Cache(),
assetExts: ['js', 'png'], assetExts: ['js', 'png'],
@ -75,8 +89,31 @@ describe('BundlesLayout', () => {
); );
} }
function setMockFilesystem(mockFs) {
fs.__setMockFilesystem(Object.assign(mockFs, baseFs));
}
pit('should bundle single-module app', () => {
setMockFilesystem({
'root': {
'index.js': `
/**
* @providesModule index
*/`,
}
});
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
modulePaths(bundles).then(paths =>
expect(paths).toEqual([
['/root/index.js'],
])
)
);
});
pit('should bundle dependant modules', () => { pit('should bundle dependant modules', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -84,7 +121,7 @@ describe('BundlesLayout', () => {
*/ */
require("a");`, require("a");`,
'a.js': ` 'a.js': `
/**, /**
* @providesModule a * @providesModule a
*/`, */`,
} }
@ -98,7 +135,7 @@ describe('BundlesLayout', () => {
}); });
pit('should split bundles for async dependencies', () => { pit('should split bundles for async dependencies', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -121,7 +158,7 @@ describe('BundlesLayout', () => {
}); });
pit('should split into multiple bundles separate async dependencies', () => { pit('should split into multiple bundles separate async dependencies', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -150,7 +187,7 @@ describe('BundlesLayout', () => {
}); });
pit('should put related async dependencies into the same bundle', () => { pit('should put related async dependencies into the same bundle', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -177,7 +214,7 @@ describe('BundlesLayout', () => {
}); });
pit('should fully traverse sync dependencies', () => { pit('should fully traverse sync dependencies', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -205,7 +242,7 @@ describe('BundlesLayout', () => {
}); });
pit('should include sync dependencies async dependencies might have', () => { pit('should include sync dependencies async dependencies might have', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -238,7 +275,7 @@ describe('BundlesLayout', () => {
}); });
pit('should allow duplicated dependencies across bundles', () => { pit('should allow duplicated dependencies across bundles', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -273,7 +310,7 @@ describe('BundlesLayout', () => {
}); });
pit('should put in separate bundles async dependencies of async dependencies', () => { pit('should put in separate bundles async dependencies of async dependencies', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -307,7 +344,7 @@ describe('BundlesLayout', () => {
}); });
pit('should dedup same async bundle duplicated dependencies', () => { pit('should dedup same async bundle duplicated dependencies', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -340,7 +377,7 @@ describe('BundlesLayout', () => {
}); });
pit('should put image dependencies into separate bundles', () => { pit('should put image dependencies into separate bundles', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -365,7 +402,7 @@ describe('BundlesLayout', () => {
}); });
pit('should put image dependencies across bundles', () => { pit('should put image dependencies across bundles', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -397,7 +434,7 @@ describe('BundlesLayout', () => {
}); });
pit('could async require asset', () => { pit('could async require asset', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -417,7 +454,7 @@ describe('BundlesLayout', () => {
}); });
pit('should include deprecated assets into separate bundles', () => { pit('should include deprecated assets into separate bundles', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -442,7 +479,7 @@ describe('BundlesLayout', () => {
}); });
pit('could async require deprecated asset', () => { pit('could async require deprecated asset', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -462,7 +499,7 @@ describe('BundlesLayout', () => {
}); });
pit('should put packages into bundles', () => { pit('should put packages into bundles', () => {
fs.__setMockFilesystem({ setMockFilesystem({
'root': { 'root': {
'index.js': ` 'index.js': `
/** /**
@ -491,4 +528,22 @@ describe('BundlesLayout', () => {
); );
}); });
}); });
function getBaseFs() {
const p = path.join(__dirname, '../../../DependencyResolver/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

@ -38,30 +38,40 @@ class BundlesLayout {
() => pending.length > 0, () => pending.length > 0,
() => bundles, () => bundles,
() => { () => {
const pendingPaths = pending.shift(); // pending sync dependencies we still need to explore for the current
return Promise // pending dependency
.all(pendingPaths.map(path => let pendingSyncDeps = pending.shift();
this._resolver.getDependencies(path, {dev: isDev})
)) // accum variable for sync dependencies of the current pending
.then(modulesDeps => { // dependency we're processing
let syncDependencies = Object.create(null); const syncDependencies = Object.create(null);
modulesDeps.forEach(moduleDeps => {
moduleDeps.dependencies.forEach(dep => { return promiseWhile(
syncDependencies[dep.path] = dep () => pendingSyncDeps.length > 0,
() => {
const dependencies = _.values(syncDependencies);
if (dependencies.length > 0) {
bundles.push(dependencies);
}
},
() => {
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] = dep;
this._moduleToBundle[dep.path] = bundles.length; this._moduleToBundle[dep.path] = bundles.length;
}); });
pending = pending.concat(moduleDeps.asyncDependencies); pending = pending.concat(deps.asyncDependencies);
});
syncDependencies = _.values(syncDependencies);
if (syncDependencies.length > 0) {
bundles.push(syncDependencies);
}
return Promise.resolve(bundles);
}); });
}, },
); );
},
);
} }
getBundleIDForModule(path) { getBundleIDForModule(path) {

View File

@ -155,7 +155,6 @@ class Fastfs extends EventEmitter {
this._getAndAssertRoot(file.path).addChild(file); this._getAndAssertRoot(file.path).addChild(file);
} }
_processFileChange(type, filePath, root, fstat) { _processFileChange(type, filePath, root, fstat) {
const absPath = path.join(root, filePath); const absPath = path.join(root, filePath);
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {