Skip to content

Commit

Permalink
Reverted commit D3703896
Browse files Browse the repository at this point in the history
Summary:
This is a very hacky solution to make reloads from packager faster for simple file changes.

Simple means that no dependencies have changed, otherwise packager will abort the attempt to update and fall back to the usual rebuilding method.

In principle, this change avoids re-walking and analyzing the whole dependency tree, and just updates modules in existing bundles.

Reviewed By: bestander

Differential Revision: D3703896

fbshipit-source-id: abc2a41144536baf969d346522a17044c1c9558b
  • Loading branch information
jingc authored and Facebook Github Bot 2 committed Aug 12, 2016
1 parent d16c676 commit 5b040a5
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 192 deletions.
2 changes: 1 addition & 1 deletion packager/react-packager/src/Bundler/Bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class Bundle extends BundleBase {
this._addRequireCall(super.getMainModuleId());
}

super.finalize(options);
super.finalize();
}

_addRequireCall(moduleId) {
Expand Down
10 changes: 2 additions & 8 deletions packager/react-packager/src/Bundler/BundleBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,8 @@ class BundleBase {
}

finalize(options) {
if (!options.allowUpdates) {
Object.freeze(this._modules);
Object.freeze(this._assets);
}
Object.freeze(this._modules);
Object.freeze(this._assets);

this._finalized = true;
}
Expand All @@ -78,10 +76,6 @@ class BundleBase {
return this._source;
}

invalidateSource() {
this._source = null;
}

assertFinalized(message) {
if (!this._finalized) {
throw new Error(message || 'Bundle needs to be finalized before getting any source');
Expand Down
11 changes: 4 additions & 7 deletions packager/react-packager/src/Bundler/__tests__/Bundler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ describe('Bundler', function() {
dependencies: modules,
transformOptions,
getModuleId: () => 123,
getResolvedDependencyPairs: () => [],
})
);

Expand All @@ -142,7 +141,7 @@ describe('Bundler', function() {
});
});

it('create a bundle', function() {
pit('create a bundle', function() {
assetServer.getAssetData.mockImpl(() => {
return {
scales: [1,2,3],
Expand Down Expand Up @@ -171,11 +170,9 @@ describe('Bundler', function() {
expect(ithAddedModule(3)).toEqual('/root/img/new_image.png');
expect(ithAddedModule(4)).toEqual('/root/file.json');

expect(bundle.finalize.mock.calls[0]).toEqual([{
runMainModule: true,
runBeforeMainModule: [],
allowUpdates: false,
}]);
expect(bundle.finalize.mock.calls[0]).toEqual([
{runMainModule: true, runBeforeMainModule: []}
]);

expect(bundle.addAsset.mock.calls[0]).toEqual([{
__packager_asset: true,
Expand Down
17 changes: 2 additions & 15 deletions packager/react-packager/src/Bundler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ const validateOpts = declareOpts({
type: 'boolean',
default: false,
},
allowBundleUpdates: {
type: 'boolean',
default: false,
},
});

const assetPropertyBlacklist = new Set([
Expand Down Expand Up @@ -284,7 +280,6 @@ class Bundler {
bundle.finalize({
runMainModule,
runBeforeMainModule: runBeforeMainModuleIds,
allowUpdates: this._opts.allowBundleUpdates,
});
return bundle;
});
Expand Down Expand Up @@ -407,7 +402,6 @@ class Bundler {
entryFilePath,
transformOptions: response.transformOptions,
getModuleId: response.getModuleId,
dependencyPairs: response.getResolvedDependencyPairs(module),
}).then(transformed => {
modulesByName[transformed.name] = module;
onModuleTransformed({
Expand Down Expand Up @@ -539,14 +533,7 @@ class Bundler {
);
}

_toModuleTransport({
module,
bundle,
entryFilePath,
transformOptions,
getModuleId,
dependencyPairs,
}) {
_toModuleTransport({module, bundle, entryFilePath, transformOptions, getModuleId}) {
let moduleTransport;
const moduleId = getModuleId(module);

Expand Down Expand Up @@ -579,7 +566,7 @@ class Bundler {
id: moduleId,
code,
map,
meta: {dependencies, dependencyOffsets, preloaded, dependencyPairs},
meta: {dependencies, dependencyOffsets, preloaded},
sourceCode: source,
sourcePath: module.path
});
Expand Down
171 changes: 16 additions & 155 deletions packager/react-packager/src/Server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,11 @@ const mime = require('mime-types');
const path = require('path');
const url = require('url');

const debug = require('debug')('ReactNativePackager:Server');

function debounceAndBatch(fn, delay) {
let timeout, args = [];
return (value) => {
args.push(value);
function debounce(fn, delay) {
var timeout;
return () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
const a = args;
args = [];
fn(a);
}, delay);
timeout = setTimeout(fn, delay);
};
}

Expand Down Expand Up @@ -146,10 +139,7 @@ const bundleOpts = declareOpts({
isolateModuleIDs: {
type: 'boolean',
default: false
},
resolutionResponse: {
type: 'object',
},
}
});

const dependencyOpts = declareOpts({
Expand All @@ -173,14 +163,8 @@ const dependencyOpts = declareOpts({
type: 'boolean',
default: false,
},
minify: {
type: 'boolean',
default: undefined,
},
});

const bundleDeps = new WeakMap();

class Server {
constructor(options) {
const opts = validateOpts(options);
Expand Down Expand Up @@ -225,27 +209,12 @@ class Server {
const bundlerOpts = Object.create(opts);
bundlerOpts.fileWatcher = this._fileWatcher;
bundlerOpts.assetServer = this._assetServer;
bundlerOpts.allowBundleUpdates = !options.nonPersistent;
this._bundler = new Bundler(bundlerOpts);

this._fileWatcher.on('all', this._onFileChange.bind(this));

this._debouncedFileChangeHandler = debounceAndBatch(filePaths => {
// only clear bundles for non-JS changes
if (filePaths.every(RegExp.prototype.test, /\.js(?:on)?$/i)) {
for (const key in this._bundles) {
this._bundles[key].then(bundle => {
const deps = bundleDeps.get(bundle);
filePaths.forEach(filePath => {
if (deps.files.has(filePath)) {
deps.outdated.add(filePath);
}
});
});
}
} else {
this._clearBundles();
}
this._debouncedFileChangeHandler = debounce(filePath => {
this._clearBundles();
this._informChangeWatchers();
}, 50);
}
Expand Down Expand Up @@ -274,25 +243,7 @@ class Server {
}

const opts = bundleOpts(options);
const building = this._bundler.bundle(opts);
building.then(bundle => {
const modules = bundle.getModules().filter(m => !m.virtual);
bundleDeps.set(bundle, {
files: new Map(
modules
.map(({sourcePath, meta = {dependencies: []}}) =>
[sourcePath, meta.dependencies])
),
idToIndex: new Map(modules.map(({id}, i) => [id, i])),
dependencyPairs: new Map(
modules
.filter(({meta}) => meta && meta.dependencyPairs)
.map(m => [m.sourcePath, m.meta.dependencyPairs])
),
outdated: new Set(),
});
});
return building;
return this._bundler.bundle(opts);
});
}

Expand Down Expand Up @@ -477,91 +428,6 @@ class Server {
).done(() => Activity.endEvent(assetEvent));
}

_useCachedOrUpdateOrCreateBundle(options) {
const optionsJson = JSON.stringify(options);
const bundleFromScratch = () => {
const building = this.buildBundle(options);
this._bundles[optionsJson] = building;
return building;
};

if (optionsJson in this._bundles) {
return this._bundles[optionsJson].then(bundle => {
const deps = bundleDeps.get(bundle);
const {dependencyPairs, files, idToIndex, outdated} = deps;
if (outdated.size) {
debug('Attempt to update existing bundle');
const changedModules =
Array.from(outdated, this.getModuleForPath, this);
deps.outdated = new Set();

const opts = bundleOpts(options);
const {platform, dev, minify, hot} = opts;

// Need to create a resolution response to pass to the bundler
// to process requires after transform. By providing a
// specific response we can compute a non recursive one which
// is the least we need and improve performance.
const bundlePromise = this._bundles[optionsJson] =
this.getDependencies({
platform, dev, hot, minify,
entryFile: options.entryFile,
recursive: false,
}).then(response => {
debug('Update bundle: rebuild shallow bundle');

changedModules.forEach(m => {
response.setResolvedDependencyPairs(
m,
dependencyPairs.get(m.path),
{ignoreFinalized: true},
);
});

return this.buildBundle({
...options,
resolutionResponse: response.copy({
dependencies: changedModules,
})
}).then(updateBundle => {
const oldModules = bundle.getModules();
const newModules = updateBundle.getModules();
for (let i = 0, n = newModules.length; i < n; i++) {
const moduleTransport = newModules[i];
const {meta, sourcePath} = moduleTransport;
if (outdated.has(sourcePath)) {
if (!contentsEqual(meta.dependencies, new Set(files.get(sourcePath)))) {
// bail out if any dependencies changed
return Promise.reject(Error(
`Dependencies of ${sourcePath} changed from [${
files.get(sourcePath).join(', ')
}] to [${meta.dependencies.join(', ')}]`
));
}

oldModules[idToIndex.get(moduleTransport.id)] = moduleTransport;
}
}

bundle.invalidateSource();
debug('Successfully updated existing bundle');
return bundle;
});
}).catch(e => {
debug('Failed to update existing bundle, rebuilding...', e.stack || e.message);
return bundleFromScratch();
});
return bundlePromise;
} else {
debug('Using cached bundle');
return bundle;
}
});
}

return bundleFromScratch();
}

processRequest(req, res, next) {
const urlObj = url.parse(req.url, true);
const pathname = urlObj.pathname;
Expand Down Expand Up @@ -592,29 +458,26 @@ class Server {

const startReqEventId = Activity.startEvent('request:' + req.url);
const options = this._getOptionsFromUrl(req.url);
debug('Getting bundle for request');
const building = this._useCachedOrUpdateOrCreateBundle(options);
const optionsJson = JSON.stringify(options);
const building = this._bundles[optionsJson] || this.buildBundle(options);

this._bundles[optionsJson] = building;
building.then(
p => {
if (requestType === 'bundle') {
debug('Generating source code');
const bundleSource = p.getSource({
inlineSourceMap: options.inlineSourceMap,
minify: options.minify,
dev: options.dev,
});
debug('Writing response headers');
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('ETag', p.getEtag());
if (req.headers['if-none-match'] === res.getHeader('ETag')){
debug('Responding with 304');
res.statusCode = 304;
res.end();
} else {
debug('Writing request body');
res.end(bundleSource);
}
debug('Finished response');
Activity.endEvent(startReqEventId);
} else if (requestType === 'map') {
let sourceMap = p.getSourceMap({
Expand All @@ -636,7 +499,7 @@ class Server {
Activity.endEvent(startReqEventId);
}
},
error => this._handleError(res, JSON.stringify(options), error)
this._handleError.bind(this, res, optionsJson)
).done();
}

Expand Down Expand Up @@ -701,7 +564,9 @@ class Server {

_sourceMapForURL(reqUrl) {
const options = this._getOptionsFromUrl(reqUrl);
const building = this._useCachedOrUpdateOrCreateBundle(options);
const optionsJson = JSON.stringify(options);
const building = this._bundles[optionsJson] || this.buildBundle(options);
this._bundles[optionsJson] = building;
return building.then(p => {
const sourceMap = p.getSourceMap({
minify: options.minify,
Expand Down Expand Up @@ -794,8 +659,4 @@ class Server {
}
}

function contentsEqual(array, set) {
return array.length === set.size && array.every(set.has, set);
}

module.exports = Server;
Loading

0 comments on commit 5b040a5

Please sign in to comment.