Make metro always use the delta bundler

Reviewed By: jeanlauliac

Differential Revision: D6208727

fbshipit-source-id: 08589cc92de340d896833cfee2ed14b48b1e6f38
This commit is contained in:
Rafael Oleza 2017-11-01 19:51:35 -07:00 committed by Facebook Github Bot
parent cdfe5b0f20
commit cecb9bae99
3 changed files with 44 additions and 138 deletions

View File

@ -27,8 +27,16 @@ jest
.mock('../../lib/GlobalTransformCache') .mock('../../lib/GlobalTransformCache')
.mock('../../DeltaBundler/Serializers'); .mock('../../DeltaBundler/Serializers');
const DeltaBundler = require('../../DeltaBundler');
describe('processRequest', () => { describe('processRequest', () => {
let Bundler, Server, AssetServer, symbolicate, Serializers; let Bundler;
let Server;
let AssetServer;
let symbolicate;
let Serializers;
const lastModified = new Date();
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.resetModules(); jest.resetModules();
@ -53,7 +61,7 @@ describe('processRequest', () => {
const makeRequest = (reqHandler, requrl, reqOptions) => const makeRequest = (reqHandler, requrl, reqOptions) =>
new Promise(resolve => new Promise(resolve =>
reqHandler( reqHandler(
{url: requrl, headers: {}, ...reqOptions}, {url: requrl, headers: {host: 'localhost:8081'}, ...reqOptions},
{ {
statusCode: 200, statusCode: 200,
headers: {}, headers: {},
@ -79,6 +87,18 @@ describe('processRequest', () => {
let requestHandler; let requestHandler;
beforeEach(() => { beforeEach(() => {
Serializers.fullBundle.mockReturnValue(
Promise.resolve({
bundle: 'this is the source',
numModifiedFiles: 38,
lastModified,
}),
);
Serializers.fullSourceMap.mockReturnValue(
Promise.resolve('this is the source map'),
);
Bundler.prototype.bundle = jest.fn(() => Bundler.prototype.bundle = jest.fn(() =>
Promise.resolve({ Promise.resolve({
getModules: () => [], getModules: () => [],
@ -118,12 +138,12 @@ describe('processRequest', () => {
).then(response => expect(response.body).toEqual('this is the source')); ).then(response => expect(response.body).toEqual('this is the source'));
}); });
it('returns ETag header on request of *.bundle', () => { it('returns Last-Modified header on request of *.bundle', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.bundle?runModule=true', 'mybundle.bundle?runModule=true',
).then(response => { ).then(response => {
expect(response.getHeader('ETag')).toBeDefined(); expect(response.getHeader('Last-Modified')).toBeDefined();
}); });
}); });
@ -132,7 +152,7 @@ describe('processRequest', () => {
requestHandler, requestHandler,
'mybundle.bundle?runModule=true', 'mybundle.bundle?runModule=true',
).then(response => { ).then(response => {
expect(response.getHeader('X-Metro-Files-Changed-Count')).toBeDefined(); expect(response.getHeader('X-Metro-Files-Changed-Count')).toEqual('38');
}); });
}); });
@ -141,15 +161,15 @@ describe('processRequest', () => {
requestHandler, requestHandler,
'mybundle.bundle?runModule=true', 'mybundle.bundle?runModule=true',
).then(response => { ).then(response => {
expect(response.getHeader('Content-Length')).toBe( expect(response.getHeader('Content-Length')).toEqual(
Buffer.byteLength(response.body), '' + Buffer.byteLength(response.body),
); );
}); });
}); });
it('returns 304 on request of *.bundle when if-none-match equals the ETag', () => { it('returns 304 on request of *.bundle when if-modified-since equals Last-Modified', () => {
return makeRequest(requestHandler, 'mybundle.bundle?runModule=true', { return makeRequest(requestHandler, 'mybundle.bundle?runModule=true', {
headers: {'if-none-match': 'this is an etag'}, headers: {'if-modified-since': lastModified.toUTCString()},
}).then(response => { }).then(response => {
expect(response.statusCode).toEqual(304); expect(response.statusCode).toEqual(304);
}); });
@ -168,8 +188,9 @@ describe('processRequest', () => {
'index.ios.includeRequire.bundle', 'index.ios.includeRequire.bundle',
).then(response => { ).then(response => {
expect(response.body).toEqual('this is the source'); expect(response.body).toEqual('this is the source');
expect(Bundler.prototype.bundle).toBeCalledWith({ expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), {
assetPlugins: [], assetPlugins: [],
deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.ios.js', entryFile: 'index.ios.js',
entryModuleOnly: false, entryModuleOnly: false,
@ -184,7 +205,7 @@ describe('processRequest', () => {
resolutionResponse: null, resolutionResponse: null,
runBeforeMainModule: ['InitializeCore'], runBeforeMainModule: ['InitializeCore'],
runModule: true, runModule: true,
sourceMapUrl: 'index.ios.includeRequire.map', sourceMapUrl: 'http://localhost:8081/index.ios.includeRequire.map',
unbundle: false, unbundle: false,
}); });
}); });
@ -196,8 +217,9 @@ describe('processRequest', () => {
'index.bundle?platform=ios', 'index.bundle?platform=ios',
).then(function(response) { ).then(function(response) {
expect(response.body).toEqual('this is the source'); expect(response.body).toEqual('this is the source');
expect(Bundler.prototype.bundle).toBeCalledWith({ expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), {
assetPlugins: [], assetPlugins: [],
deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.js', entryFile: 'index.js',
entryModuleOnly: false, entryModuleOnly: false,
@ -212,7 +234,7 @@ describe('processRequest', () => {
resolutionResponse: null, resolutionResponse: null,
runBeforeMainModule: ['InitializeCore'], runBeforeMainModule: ['InitializeCore'],
runModule: true, runModule: true,
sourceMapUrl: 'index.map?platform=ios', sourceMapUrl: 'http://localhost:8081/index.map?platform=ios',
unbundle: false, unbundle: false,
}); });
}); });
@ -224,8 +246,9 @@ describe('processRequest', () => {
'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2', 'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2',
).then(function(response) { ).then(function(response) {
expect(response.body).toEqual('this is the source'); expect(response.body).toEqual('this is the source');
expect(Bundler.prototype.bundle).toBeCalledWith({ expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), {
assetPlugins: ['assetPlugin1', 'assetPlugin2'], assetPlugins: ['assetPlugin1', 'assetPlugin2'],
deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.js', entryFile: 'index.js',
entryModuleOnly: false, entryModuleOnly: false,
@ -241,130 +264,13 @@ describe('processRequest', () => {
runBeforeMainModule: ['InitializeCore'], runBeforeMainModule: ['InitializeCore'],
runModule: true, runModule: true,
sourceMapUrl: sourceMapUrl:
'index.map?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2', 'http://localhost:8081/index.map?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2',
unbundle: false, unbundle: false,
}); });
}); });
}); });
describe('file changes', () => {
it('does not rebuild the bundles that contain a file when that file is changed', () => {
const bundleFunc = jest.fn();
bundleFunc
.mockReturnValueOnce(
Promise.resolve({
getModules: () => [],
getSource: () => 'this is the first source',
getSourceMap: () => {},
getSourceMapString: () => 'this is the source map',
getEtag: () => () => 'this is an etag',
}),
)
.mockReturnValue(
Promise.resolve({
getModules: () => [],
getSource: () => 'this is the rebuilt source',
getSourceMap: () => {},
getSourceMapString: () => 'this is the source map',
getEtag: () => () => 'this is an etag',
}),
);
Bundler.prototype.bundle = bundleFunc;
server = new Server(options);
requestHandler = server.processRequest.bind(server);
makeRequest(
requestHandler,
'mybundle.bundle?runModule=true',
).done(response => {
expect(response.body).toEqual('this is the first source');
expect(bundleFunc.mock.calls.length).toBe(1);
});
jest.runAllTicks();
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
jest.runAllTicks();
expect(bundleFunc.mock.calls.length).toBe(1);
makeRequest(
requestHandler,
'mybundle.bundle?runModule=true',
).done(response =>
expect(response.body).toEqual('this is the rebuilt source'),
);
jest.runAllTicks();
});
it(
'does not rebuild the bundles that contain a file ' +
'when that file is changed, even when hot loading is enabled',
() => {
const bundleFunc = jest.fn();
bundleFunc
.mockReturnValueOnce(
Promise.resolve({
getModules: () => [],
getSource: () => 'this is the first source',
getSourceMap: () => {},
getSourceMapString: () => 'this is the source map',
getEtag: () => () => 'this is an etag',
}),
)
.mockReturnValue(
Promise.resolve({
getModules: () => [],
getSource: () => 'this is the rebuilt source',
getSourceMap: () => {},
getSourceMapString: () => 'this is the source map',
getEtag: () => () => 'this is an etag',
}),
);
Bundler.prototype.bundle = bundleFunc;
server = new Server(options);
server.setHMRFileChangeListener(() => {});
requestHandler = server.processRequest.bind(server);
makeRequest(
requestHandler,
'mybundle.bundle?runModule=true',
).done(response => {
expect(response.body).toEqual('this is the first source');
expect(bundleFunc.mock.calls.length).toBe(1);
});
jest.runAllTicks();
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
jest.runAllTicks();
expect(bundleFunc.mock.calls.length).toBe(1);
server.setHMRFileChangeListener(null);
makeRequest(
requestHandler,
'mybundle.bundle?runModule=true',
).done(response => {
expect(response.body).toEqual('this is the rebuilt source');
expect(bundleFunc.mock.calls.length).toBe(2);
});
jest.runAllTicks();
},
);
});
describe('Generate delta bundle endpoint', () => { describe('Generate delta bundle endpoint', () => {
Serializers;
it('should generate a new delta correctly', () => { it('should generate a new delta correctly', () => {
Serializers.deltaBundle.mockImplementation(async (_, options) => { Serializers.deltaBundle.mockImplementation(async (_, options) => {
expect(options.deltaBundleId).toBe(undefined); expect(options.deltaBundleId).toBe(undefined);

View File

@ -97,7 +97,7 @@ export type Options = {|
+sourceExts: ?Array<string>, +sourceExts: ?Array<string>,
+transformCache: TransformCache, +transformCache: TransformCache,
transformModulePath?: string, transformModulePath?: string,
useDeltaBundler: boolean, useDeltaBundler?: boolean, // TODO: remove this, since it's no longer used
watch?: boolean, watch?: boolean,
workerPath: ?string, workerPath: ?string,
|}; |};
@ -143,6 +143,8 @@ const FILES_CHANGED_COUNT_REBUILD = -1;
const bundleDeps = new WeakMap(); const bundleDeps = new WeakMap();
const NODE_MODULES = `${path.sep}node_modules${path.sep}`; const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
const USE_DELTA_BUNDLER = true;
class Server { class Server {
_opts: { _opts: {
assetExts: Array<string>, assetExts: Array<string>,
@ -226,7 +228,6 @@ class Server {
transformCache: options.transformCache, transformCache: options.transformCache,
transformModulePath: transformModulePath:
options.transformModulePath || defaults.transformModulePath, options.transformModulePath || defaults.transformModulePath,
useDeltaBundler: options.useDeltaBundler,
watch: options.watch || false, watch: options.watch || false,
workerPath: options.workerPath, workerPath: options.workerPath,
}; };
@ -448,7 +449,7 @@ class Server {
+minify: boolean, +minify: boolean,
+generateSourceMaps: boolean, +generateSourceMaps: boolean,
}): Promise<Array<string>> { }): Promise<Array<string>> {
if (this._opts.useDeltaBundler) { if (USE_DELTA_BUNDLER) {
const bundleOptions = { const bundleOptions = {
...Server.DEFAULT_BUNDLE_OPTIONS, ...Server.DEFAULT_BUNDLE_OPTIONS,
...options, ...options,
@ -855,7 +856,7 @@ class Server {
return; return;
} }
if (this._opts.useDeltaBundler) { if (USE_DELTA_BUNDLER) {
if (requestType === 'bundle') { if (requestType === 'bundle') {
await this._processBundleUsingDeltaBundler(req, res); await this._processBundleUsingDeltaBundler(req, res);
return; return;
@ -1214,7 +1215,7 @@ class Server {
async _sourceMapForURL(reqUrl: string): Promise<SourceMap> { async _sourceMapForURL(reqUrl: string): Promise<SourceMap> {
const options: DeltaBundlerOptions = this._getOptionsFromUrl(reqUrl); const options: DeltaBundlerOptions = this._getOptionsFromUrl(reqUrl);
if (this._opts.useDeltaBundler) { if (USE_DELTA_BUNDLER) {
return await Serializers.fullSourceMapObject(this._deltaBundler, { return await Serializers.fullSourceMapObject(this._deltaBundler, {
...options, ...options,
deltaBundleId: this.optionsHash(options), deltaBundleId: this.optionsHash(options),

View File

@ -198,7 +198,6 @@ function toServerOptions(options: Options): ServerOptions {
sourceExts: options.sourceExts, sourceExts: options.sourceExts,
transformCache: options.transformCache || TransformCaching.useTempDir(), transformCache: options.transformCache || TransformCaching.useTempDir(),
transformModulePath: options.transformModulePath, transformModulePath: options.transformModulePath,
useDeltaBundler: options.useDeltaBundler,
watch: watch:
typeof options.watch === 'boolean' typeof options.watch === 'boolean'
? options.watch ? options.watch