mirror of https://github.com/status-im/metro.git
Updates from Mon Aug 31st.
This commit is contained in:
commit
0621018da6
|
@ -85,6 +85,10 @@ function connectToDebuggerProxy() {
|
|||
|
||||
ws.onmessage = function(message) {
|
||||
var object = JSON.parse(message.data);
|
||||
if (!object.method) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sendReply = function(result) {
|
||||
ws.send(JSON.stringify({replyID: object.id, result: result}));
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const Promise = require('promise');
|
||||
const ProgressBar = require('progress');
|
||||
const BundlesLayout = require('../BundlesLayout');
|
||||
const Cache = require('../Cache');
|
||||
const Transformer = require('../JSTransformer');
|
||||
const DependencyResolver = require('../DependencyResolver');
|
||||
|
@ -104,6 +105,13 @@ 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,
|
||||
|
@ -120,6 +128,10 @@ class Bundler {
|
|||
return this._cache.end();
|
||||
}
|
||||
|
||||
getLayout(main, isDev) {
|
||||
return this._bundlesLayout.generateLayout(main, isDev);
|
||||
}
|
||||
|
||||
bundle(main, runModule, sourceMapUrl, isDev, platform) {
|
||||
const bundle = new Bundle(sourceMapUrl);
|
||||
const findEventId = Activity.startEvent('find dependencies');
|
||||
|
|
|
@ -8,249 +8,320 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('../index');
|
||||
jest.dontMock('../index');
|
||||
jest.mock('fs');
|
||||
|
||||
const Promise = require('promise');
|
||||
|
||||
describe('BundlesLayout', () => {
|
||||
var BundlesLayout;
|
||||
var DependencyResolver;
|
||||
let BundlesLayout;
|
||||
let DependencyResolver;
|
||||
let loadCacheSync;
|
||||
|
||||
beforeEach(() => {
|
||||
BundlesLayout = require('../index');
|
||||
DependencyResolver = require('../../DependencyResolver');
|
||||
loadCacheSync = require('../../lib/loadCacheSync');
|
||||
});
|
||||
|
||||
describe('generate', () => {
|
||||
function newBundlesLayout() {
|
||||
return new BundlesLayout({
|
||||
dependencyResolver: new DependencyResolver(),
|
||||
});
|
||||
}
|
||||
function newBundlesLayout(options) {
|
||||
return new BundlesLayout(Object.assign({
|
||||
projectRoots: ['/root'],
|
||||
dependencyResolver: new DependencyResolver(),
|
||||
}, options));
|
||||
}
|
||||
|
||||
describe('layout', () => {
|
||||
function isPolyfill() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function dep(path) {
|
||||
return {
|
||||
path: path,
|
||||
isPolyfill: isPolyfill,
|
||||
};
|
||||
}
|
||||
describe('getLayout', () => {
|
||||
function dep(path) {
|
||||
return {
|
||||
path: path,
|
||||
isPolyfill: isPolyfill,
|
||||
};
|
||||
}
|
||||
|
||||
pit('should bundle sync dependencies', () => {
|
||||
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: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
pit('should bundle sync dependencies', () => {
|
||||
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: [],
|
||||
});
|
||||
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: [],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/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', () => {
|
||||
DependencyResolver.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;
|
||||
}
|
||||
});
|
||||
|
||||
pit('should separate async dependencies into different bundle', () => {
|
||||
DependencyResolver.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().generateLayout(['/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', () => {
|
||||
DependencyResolver.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().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
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.2',
|
||||
id:'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('separate async dependencies of async dependencies', () => {
|
||||
DependencyResolver.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', () => {
|
||||
DependencyResolver.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', () => {
|
||||
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({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 bundle sync dependencies of async ones on same bundle', () => {
|
||||
DependencyResolver.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().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
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: [['/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', '/root/b.js'],
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
},
|
||||
'/root/b.js': {
|
||||
id: 'bundle.2',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
pit('should load layouts', () => {
|
||||
const layout = newBundlesLayout({ resetCache: false });
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(
|
||||
bundles => expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
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: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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: [['/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;
|
||||
}
|
||||
});
|
||||
it('should load moduleToBundle map', () => {
|
||||
const layout = newBundlesLayout({ resetCache: false });
|
||||
|
||||
var layout = newBundlesLayout();
|
||||
return layout.generateLayout(['/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');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,7 +75,11 @@ describe('BundlesLayout', () => {
|
|||
assetRoots: ['/root'],
|
||||
});
|
||||
|
||||
return new BundlesLayout({dependencyResolver: resolver});
|
||||
return new BundlesLayout({
|
||||
dependencyResolver: resolver,
|
||||
resetCache: true,
|
||||
projectRoots: ['/root', '/' + __dirname.split('/')[1]],
|
||||
});
|
||||
}
|
||||
|
||||
function stripPolyfills(bundle) {
|
||||
|
@ -114,7 +118,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -140,7 +144,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -166,7 +170,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -201,7 +205,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -242,7 +246,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -282,7 +286,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -323,7 +327,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -370,7 +374,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -408,7 +412,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -446,7 +450,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -480,7 +484,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -512,7 +516,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -539,7 +543,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
@ -576,7 +580,7 @@ describe('BundlesLayout', () => {
|
|||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
|
|
|
@ -8,14 +8,33 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const Activity = require('../Activity');
|
||||
|
||||
const _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const getCacheFilePath = require('../lib/getCacheFilePath');
|
||||
const loadCacheSync = require('../lib/loadCacheSync');
|
||||
const version = require('../../../../package.json').version;
|
||||
const path = require('path');
|
||||
|
||||
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';
|
||||
|
@ -29,19 +48,37 @@ class BundlesLayout {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
generateLayout(entryPaths, isDev) {
|
||||
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: entryPaths, bundle: rootBundle}];
|
||||
var pending = [{paths: [entryPath], bundle: rootBundle}];
|
||||
|
||||
return promiseWhile(
|
||||
this._layouts[entryPath] = promiseWhile(
|
||||
() => pending.length > 0,
|
||||
() => rootBundle,
|
||||
() => {
|
||||
|
@ -62,6 +99,9 @@ class BundlesLayout {
|
|||
if (dependencies.length > 0) {
|
||||
bundle.modules = dependencies;
|
||||
}
|
||||
|
||||
// persist changes to layouts
|
||||
this._persistCacheEventually();
|
||||
},
|
||||
index => {
|
||||
const pendingSyncDep = pendingSyncDeps.shift();
|
||||
|
@ -90,11 +130,71 @@ class BundlesLayout {
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
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(
|
||||
'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.
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
jest
|
||||
.dontMock('underscore')
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../');
|
||||
.dontMock('../')
|
||||
.dontMock('../../lib/loadCacheSync')
|
||||
.dontMock('../../lib/getCacheFilePath');
|
||||
|
||||
jest
|
||||
.mock('os')
|
||||
|
|
|
@ -8,17 +8,17 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var crypto = require('crypto');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var fs = require('fs');
|
||||
var isAbsolutePath = require('absolute-path');
|
||||
var path = require('path');
|
||||
var Promise = require('promise');
|
||||
var tmpdir = require('os').tmpDir();
|
||||
var version = require('../../../../package.json').version;
|
||||
const Promise = require('promise');
|
||||
const _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const getCacheFilePath = require('../lib/getCacheFilePath');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const loadCacheSync = require('../lib/loadCacheSync');
|
||||
const path = require('path');
|
||||
const version = require('../../../../package.json').version;
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
const validateOpts = declareOpts({
|
||||
resetCache: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
|
@ -164,21 +164,7 @@ class Cache {
|
|||
|
||||
_loadCacheSync(cachePath) {
|
||||
var ret = Object.create(null);
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
var cacheOnDisk;
|
||||
try {
|
||||
cacheOnDisk = JSON.parse(fs.readFileSync(cachePath));
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.warn('Unable to parse cache file. Will clear and continue.');
|
||||
fs.unlinkSync(cachePath);
|
||||
return ret;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
var cacheOnDisk = loadCacheSync(cachePath);
|
||||
|
||||
// Filter outdated cache and convert to promises.
|
||||
Object.keys(cacheOnDisk).forEach(key => {
|
||||
|
@ -203,20 +189,13 @@ class Cache {
|
|||
}
|
||||
|
||||
_getCacheFilePath(options) {
|
||||
var hash = crypto.createHash('md5');
|
||||
hash.update(version);
|
||||
|
||||
var roots = options.projectRoots.join(',').split(path.sep).join('-');
|
||||
hash.update(roots);
|
||||
|
||||
var cacheVersion = options.cacheVersion || '0';
|
||||
hash.update(cacheVersion);
|
||||
|
||||
hash.update(options.transformModulePath);
|
||||
|
||||
var name = 'react-packager-cache-' + hash.digest('hex');
|
||||
|
||||
return path.join(tmpdir, name);
|
||||
return getCacheFilePath(
|
||||
'react-packager-cache-',
|
||||
version,
|
||||
options.projectRoots.join(',').split(path.sep).join('-'),
|
||||
options.cacheVersion || '0',
|
||||
options.transformModulePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,10 @@ const readFile = Promise.denodeify(fs.readFile);
|
|||
const MAX_CALLS_PER_WORKER = 600;
|
||||
|
||||
// Worker will timeout if one of the callers timeout.
|
||||
const DEFAULT_MAX_CALL_TIME = 60000;
|
||||
const DEFAULT_MAX_CALL_TIME = 120000;
|
||||
|
||||
// How may times can we tolerate failures from the worker.
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
|
@ -63,6 +66,7 @@ class Transformer {
|
|||
maxConcurrentCallsPerWorker: 1,
|
||||
maxCallsPerWorker: MAX_CALLS_PER_WORKER,
|
||||
maxCallTime: opts.transformTimeoutInterval,
|
||||
maxRetries: MAX_RETRIES,
|
||||
}, opts.transformModulePath);
|
||||
|
||||
this._transform = Promise.denodeify(this._workers);
|
||||
|
@ -118,6 +122,13 @@ class Transformer {
|
|||
);
|
||||
timeoutErr.type = 'TimeoutError';
|
||||
throw timeoutErr;
|
||||
} else if (err.type === 'ProcessTerminatedError') {
|
||||
const uncaughtError = new Error(
|
||||
'Uncaught error in the transformer worker: ' +
|
||||
this._opts.transformModulePath
|
||||
);
|
||||
uncaughtError.type = 'ProcessTerminatedError';
|
||||
throw uncaughtError;
|
||||
}
|
||||
|
||||
throw formatError(err, filePath);
|
||||
|
|
|
@ -13,6 +13,10 @@ const Promise = require('promise');
|
|||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactPackager:SocketClient');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
|
||||
const LOG_PATH = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
class SocketClient {
|
||||
static create(sockPath) {
|
||||
|
@ -81,7 +85,9 @@ class SocketClient {
|
|||
delete this._resolvers[message.id];
|
||||
|
||||
if (message.type === 'error') {
|
||||
resolver.reject(new Error(message.data));
|
||||
resolver.reject(new Error(
|
||||
message.data + '\n' + 'See logs ' + LOG_PATH
|
||||
));
|
||||
} else {
|
||||
resolver.resolve(message.data);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class SocketServer {
|
|||
options
|
||||
);
|
||||
resolve(this);
|
||||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
});
|
||||
});
|
||||
this._server.on('connection', (sock) => this._handleConnection(sock));
|
||||
|
@ -41,8 +42,6 @@ class SocketServer {
|
|||
this._packagerServer = new Server(options);
|
||||
this._jobs = 0;
|
||||
this._dieEventually();
|
||||
|
||||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
}
|
||||
|
||||
onReady() {
|
||||
|
@ -72,6 +71,11 @@ class SocketServer {
|
|||
debug('request error', error);
|
||||
this._jobs--;
|
||||
this._reply(sock, m.id, 'error', error.stack);
|
||||
|
||||
// Fatal error from JSTransformer transform workers.
|
||||
if (error.type === 'ProcessTerminatedError') {
|
||||
setImmediate(() => process.exit(1));
|
||||
}
|
||||
};
|
||||
|
||||
switch (m.type) {
|
||||
|
@ -138,12 +142,17 @@ class SocketServer {
|
|||
process.send({ type: 'createdServer' });
|
||||
},
|
||||
error => {
|
||||
debug('error creating server', error.code);
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
// Server already listening, this may happen if multiple
|
||||
// clients where started in quick succussion (buck).
|
||||
process.send({ type: 'createdServer' });
|
||||
|
||||
// Kill this server because some other server with the same
|
||||
// config and socket already started.
|
||||
debug('server already started');
|
||||
setImmediate(() => process.exit());
|
||||
} else {
|
||||
debug('error creating server', error.code);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,6 @@ describe('SocketClient', () => {
|
|||
data: 'some error'
|
||||
});
|
||||
|
||||
return promise.catch(m => expect(m.message).toBe('some error'));
|
||||
return promise.catch(m => expect(m.message).toContain('some error'));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,17 @@ describe('SocketInterface', () => {
|
|||
pit('creates socket path by hashing options', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => true);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let callback;
|
||||
|
||||
require('child_process').spawn.mockImpl(() => ({
|
||||
on: (event, cb) => callback = cb,
|
||||
send: (message) => {
|
||||
setImmediate(() => callback({ type: 'createdServer' }));
|
||||
},
|
||||
unref: () => undefined,
|
||||
disconnect: () => undefined,
|
||||
}));
|
||||
|
||||
// Check that given two equivelant server options, we end up with the same
|
||||
// socket path.
|
||||
|
@ -49,6 +60,7 @@ describe('SocketInterface', () => {
|
|||
pit('should fork a server', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => false);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let sockPath;
|
||||
let callback;
|
||||
|
||||
|
|
|
@ -13,12 +13,14 @@ const SocketClient = require('./SocketClient');
|
|||
const SocketServer = require('./SocketServer');
|
||||
const _ = require('underscore');
|
||||
const crypto = require('crypto');
|
||||
const debug = require('debug')('ReactPackager:SocketInterface');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const CREATE_SERVER_TIMEOUT = 30000;
|
||||
const CREATE_SERVER_TIMEOUT = 60000;
|
||||
|
||||
const SocketInterface = {
|
||||
getOrCreateSocketFor(options) {
|
||||
|
@ -38,66 +40,81 @@ const SocketInterface = {
|
|||
);
|
||||
|
||||
if (fs.existsSync(sockPath)) {
|
||||
resolve(SocketClient.create(sockPath));
|
||||
return;
|
||||
}
|
||||
|
||||
const logPath = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
const timeout = setTimeout(
|
||||
() => reject(
|
||||
new Error(
|
||||
'Took too long to start server. Server logs: \n' +
|
||||
fs.readFileSync(logPath, 'utf8')
|
||||
)
|
||||
),
|
||||
CREATE_SERVER_TIMEOUT,
|
||||
);
|
||||
|
||||
const log = fs.openSync(logPath, 'a');
|
||||
|
||||
// Enable server debugging by default since it's going to a log file.
|
||||
const env = _.clone(process.env);
|
||||
env.DEBUG = 'ReactPackager:SocketServer';
|
||||
|
||||
// We have to go through the main entry point to make sure
|
||||
// we go through the babel require hook.
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[path.join(__dirname, '..', '..', 'index.js')],
|
||||
{
|
||||
detached: true,
|
||||
env: env,
|
||||
stdio: ['ipc', log, log]
|
||||
}
|
||||
);
|
||||
|
||||
child.unref();
|
||||
|
||||
child.on('message', m => {
|
||||
if (m && m.type && m.type === 'createdServer') {
|
||||
clearTimeout(timeout);
|
||||
child.disconnect();
|
||||
var sock = net.connect(sockPath);
|
||||
sock.on('connect', () => {
|
||||
sock.end();
|
||||
resolve(SocketClient.create(sockPath));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (options.blacklistRE) {
|
||||
options.blacklistRE = { source: options.blacklistRE.source };
|
||||
});
|
||||
sock.on('error', (e) => {
|
||||
try {
|
||||
debug('deleting socket for not responding', sockPath);
|
||||
fs.unlinkSync(sockPath);
|
||||
} catch (err) {
|
||||
// Another client might have deleted it first.
|
||||
}
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
});
|
||||
} else {
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
}
|
||||
|
||||
child.send({
|
||||
type: 'createSocketServer',
|
||||
data: { sockPath, options }
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
listenOnServerMessages() {
|
||||
return SocketServer.listenOnServerIPCMessages();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function createServer(resolve, reject, options, sockPath) {
|
||||
const logPath = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
const timeout = setTimeout(
|
||||
() => reject(
|
||||
new Error(
|
||||
'Took too long to start server. Server logs: \n' +
|
||||
fs.readFileSync(logPath, 'utf8')
|
||||
)
|
||||
),
|
||||
CREATE_SERVER_TIMEOUT,
|
||||
);
|
||||
|
||||
const log = fs.openSync(logPath, 'a');
|
||||
|
||||
// Enable server debugging by default since it's going to a log file.
|
||||
const env = _.clone(process.env);
|
||||
env.DEBUG = 'ReactPackager:SocketServer';
|
||||
|
||||
// We have to go through the main entry point to make sure
|
||||
// we go through the babel require hook.
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[path.join(__dirname, '..', '..', 'index.js')],
|
||||
{
|
||||
detached: true,
|
||||
env: env,
|
||||
stdio: ['ipc', log, log]
|
||||
}
|
||||
);
|
||||
|
||||
child.unref();
|
||||
|
||||
child.on('message', m => {
|
||||
if (m && m.type && m.type === 'createdServer') {
|
||||
clearTimeout(timeout);
|
||||
child.disconnect();
|
||||
|
||||
resolve(SocketClient.create(sockPath));
|
||||
}
|
||||
});
|
||||
|
||||
if (options.blacklistRE) {
|
||||
options.blacklistRE = { source: options.blacklistRE.source };
|
||||
}
|
||||
|
||||
child.send({
|
||||
type: 'createSocketServer',
|
||||
data: { sockPath, options }
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SocketInterface;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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 crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpDir();
|
||||
|
||||
function getCacheFilePath(args) {
|
||||
args = Array.prototype.slice.call(args);
|
||||
const prefix = args.shift();
|
||||
|
||||
let hash = crypto.createHash('md5');
|
||||
args.forEach(arg => hash.update(arg));
|
||||
|
||||
return path.join(tmpdir, prefix + hash.digest('hex'));
|
||||
}
|
||||
|
||||
module.exports = getCacheFilePath;
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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 fs = require('fs');
|
||||
|
||||
function loadCacheSync(cachePath) {
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(cachePath));
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.warn('Unable to parse cache file. Will clear and continue.');
|
||||
fs.unlinkSync(cachePath);
|
||||
return Object.create(null);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = loadCacheSync;
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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('../')],
|
||||
blacklist: ['strict'],
|
||||
extra: { 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);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
var t = require('babel-core').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 systemImportTransform(babel) {
|
||||
return new babel.Transformer('system-import', {
|
||||
CallExpression: function(node, parent, scope, state) {
|
||||
if (!isAppropriateSystemImportCall(node, parent)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
var bundlesLayout = state.opts.extra.bundlesLayout;
|
||||
var bundleID = bundlesLayout.getBundleIDForModule(
|
||||
node.arguments[0].value
|
||||
);
|
||||
|
||||
var bundles = bundleID.split('.');
|
||||
bundles.splice(0, 1);
|
||||
bundles = bundles.map(function(id) {
|
||||
return t.literal('bundle.' + id);
|
||||
});
|
||||
|
||||
return t.callExpression(
|
||||
t.identifier('loadBundles'),
|
||||
[t.arrayExpression(bundles)]
|
||||
);
|
||||
},
|
||||
|
||||
metadata: {
|
||||
group: 'fb'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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 === 'Literal'
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue