From 49fde903b8956c7ba734b9d07587bcea58fb98d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 10 Aug 2015 16:00:16 -0700 Subject: [PATCH] [react-packager] Promote Cache to top level Summary: The cache is only used for JSTransformer at the moment. We're doing IO and some computation to get each module's name, whether is a haste or node module and it's dependencies. This work happens on startup so by caching this value we shouldbe able to reduce the start up time. Lets promote the Cache to the Packager level to be able to use it by any of the components of the packager. For now, on this diff we'll start using it to cache the mentioned fields. Also we had to introduce the concept of fields in the cache as manually merging the date we had for each path is not possible as we're using promisses all around. With the new API, each field is a promise. @amasad and I did some manual testing to measure the impact of this change and looks like it's saves 1 second when building the haste map (which represents 50% of the time). Overall this reduces 1 second of start up time which was currently about 8s on my mac book pro. --- react-packager/src/Cache/__mocks__/index.js | 20 ++ .../src/Cache/__tests__/Cache-test.js | 288 ++++++++++++++++++ react-packager/src/Cache/index.js | 222 ++++++++++++++ .../__tests__/DependencyGraph-test.js | 61 +++- .../DependencyGraph/index.js | 19 +- .../src/DependencyResolver/Module.js | 53 ++-- .../src/DependencyResolver/ModuleCache.js | 30 +- .../src/DependencyResolver/Package.js | 15 +- .../src/DependencyResolver/index.js | 7 +- react-packager/src/JSTransformer/Cache.js | 175 ----------- .../src/JSTransformer/__tests__/Cache-test.js | 236 -------------- .../__tests__/Transformer-test.js | 8 +- react-packager/src/JSTransformer/index.js | 35 +-- react-packager/src/Packager/index.js | 27 +- 14 files changed, 712 insertions(+), 484 deletions(-) create mode 100644 react-packager/src/Cache/__mocks__/index.js create mode 100644 react-packager/src/Cache/__tests__/Cache-test.js create mode 100644 react-packager/src/Cache/index.js delete mode 100644 react-packager/src/JSTransformer/Cache.js delete mode 100644 react-packager/src/JSTransformer/__tests__/Cache-test.js diff --git a/react-packager/src/Cache/__mocks__/index.js b/react-packager/src/Cache/__mocks__/index.js new file mode 100644 index 00000000..6f7632f6 --- /dev/null +++ b/react-packager/src/Cache/__mocks__/index.js @@ -0,0 +1,20 @@ +/** + * 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'; + +class Cache { + get(filepath, field, cb) { + return cb(filepath); + } + + invalidate(filepath) { } + end() { } +} + +module.exports = Cache; diff --git a/react-packager/src/Cache/__tests__/Cache-test.js b/react-packager/src/Cache/__tests__/Cache-test.js new file mode 100644 index 00000000..8172a243 --- /dev/null +++ b/react-packager/src/Cache/__tests__/Cache-test.js @@ -0,0 +1,288 @@ +/** + * 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('underscore') + .dontMock('absolute-path') + .dontMock('../'); + +jest + .mock('os') + .mock('fs'); + +var Promise = require('promise'); + +describe('JSTransformer Cache', () => { + var Cache; + + beforeEach(() => { + require('os').tmpDir.mockImpl(() => 'tmpDir'); + + Cache = require('../'); + }); + + describe('getting/setting', () => { + pit('calls loader callback for uncached file', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve()); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then($ => + expect(loaderCb).toBeCalledWith('/rootDir/someFile') + ); + }); + + pit('supports storing multiple fields', () => { + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var index = 0; + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve(index++) + ); + + return cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value => { + expect(value).toBe(0); + return cache + .get('/rootDir/someFile', 'field2', loaderCb) + .then(value2 => expect(value2).toBe(1)); + }); + }); + + pit('gets the value from the loader callback', () => { + require('fs').stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {} + } + }) + ); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => expect(value).toBe('lol')); + }); + + pit('caches the value after the first call', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(() => { + var shouldNotBeCalled = jest.genMockFn(); + return cache.get('/rootDir/someFile', 'field', shouldNotBeCalled) + .then(value => { + expect(shouldNotBeCalled).not.toBeCalled(); + expect(value).toBe('lol'); + }); + }); + }); + + pit('clears old field when getting new field and mtime changed', () => { + var mtime = 0; + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => mtime++ + } + }); + }); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('lol' + mtime) + ); + + return cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value => cache + .get('/rootDir/someFile', 'field2', loaderCb) + .then(value2 => cache + .get('/rootDir/someFile', 'field1', loaderCb) + .then(value3 => expect(value3).toBe('lol2')) + ) + ); + }); + }); + + describe('loading cache from disk', () => { + var fileStats; + + beforeEach(() => { + fileStats = { + '/rootDir/someFile': { + mtime: { + getTime: () => 22 + } + }, + '/rootDir/foo': { + mtime: { + getTime: () => 11 + } + } + }; + + var fs = require('fs'); + + fs.existsSync.mockImpl(() => true); + + fs.statSync.mockImpl(filePath => fileStats[filePath]); + + fs.readFileSync.mockImpl(() => JSON.stringify({ + '/rootDir/someFile': { + metadata: {mtime: 22}, + data: {field: 'oh hai'}, + }, + '/rootDir/foo': { + metadata: {mtime: 11}, + data: {field: 'lol wat'}, + } + })); + }); + + pit('should load cache from disk', () => { + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn(); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache + .get('/rootDir/foo', 'field', loaderCb) + .then(val => { + expect(loaderCb).not.toBeCalled(); + expect(val).toBe('lol wat'); + }); + }); + }); + + pit('should not load outdated cache', () => { + require('fs').stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => {} + } + }) + ); + + fileStats['/rootDir/foo'].mtime.getTime = () => 123; + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + var loaderCb = jest.genMockFn().mockImpl(() => + Promise.resolve('new value') + ); + + return cache + .get('/rootDir/someFile', 'field', loaderCb) + .then(value => { + expect(loaderCb).not.toBeCalled(); + expect(value).toBe('oh hai'); + + return cache + .get('/rootDir/foo', 'field', loaderCb) + .then(val => { + expect(loaderCb).toBeCalled(); + expect(val).toBe('new value'); + }); + }); + }); + }); + + describe('writing cache to disk', () => { + it('should write cache to disk', () => { + var index = 0; + var mtimes = [10, 20, 30]; + var debounceIndex = 0; + require('underscore').debounce = callback => { + return () => { + if (++debounceIndex === 3) { + callback(); + } + }; + }; + + var fs = require('fs'); + fs.stat.mockImpl((file, callback) => + callback(null, { + mtime: { + getTime: () => mtimes[index++] + } + }) + ); + + var cache = new Cache({ + projectRoots: ['/rootDir'], + transformModulePath: 'x.js', + }); + + cache.get('/rootDir/bar', 'field', () => + Promise.resolve('bar value') + ); + cache.get('/rootDir/foo', 'field', () => + Promise.resolve('foo value') + ); + cache.get('/rootDir/baz', 'field', () => + Promise.resolve('baz value') + ); + + jest.runAllTicks(); + expect(fs.writeFile).toBeCalled(); + }); + }); +}); diff --git a/react-packager/src/Cache/index.js b/react-packager/src/Cache/index.js new file mode 100644 index 00000000..2ed3575e --- /dev/null +++ b/react-packager/src/Cache/index.js @@ -0,0 +1,222 @@ +/** + * 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'; + +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; + +var validateOpts = declareOpts({ + resetCache: { + type: 'boolean', + default: false, + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + projectRoots: { + type: 'array', + required: true, + }, + transformModulePath: { + type:'string', + required: true, + }, +}); + +// TODO: move to Packager directory +class Cache { + constructor(options) { + var opts = validateOpts(options); + + this._cacheFilePath = this._getCacheFilePath(opts); + + var data; + if (!opts.resetCache) { + data = this._loadCacheSync(this._cacheFilePath); + } else { + data = Object.create(null); + } + this._data = data; + + this._persistEventually = _.debounce( + this._persistCache.bind(this), + 2000, + ); + } + + get(filepath, field, loaderCb) { + if (!isAbsolutePath(filepath)) { + throw new Error('Use absolute paths'); + } + + var recordP = this._has(filepath, field) + ? this._data[filepath].data[field] + : this._set(filepath, field, loaderCb(filepath)); + + return recordP.then(record => record); + } + + invalidate(filepath) { + if (this._has(filepath)) { + delete this._data[filepath]; + } + } + + end() { + return this._persistCache(); + } + + _has(filepath, field) { + return Object.prototype.hasOwnProperty.call(this._data, filepath) && + (!field || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field)); + } + + _set(filepath, field, loaderPromise) { + let record = this._data[filepath]; + if (!record) { + record = Object.create(null); + this._data[filepath] = record; + this._data[filepath].data = Object.create(null); + this._data[filepath].metadata = Object.create(null); + } + + record.data[field] = loaderPromise + .then(data => Promise.all([ + data, + Promise.denodeify(fs.stat)(filepath), + ])) + .then(([data, stat]) => { + this._persistEventually(); + + // Evict all existing field data from the cache if we're putting new + // more up to date data + var mtime = stat.mtime.getTime(); + if (record.metadata.mtime !== mtime) { + record.data = Object.create(null); + } + record.metadata.mtime = mtime; + + return data; + }); + + return record.data[field]; + } + + _persistCache() { + if (this._persisting != null) { + return this._persisting; + } + + var data = this._data; + var cacheFilepath = this._cacheFilePath; + + var allPromises = _.values(data) + .map(record => { + var fieldNames = Object.keys(record.data); + var fieldValues = _.values(record.data); + + return Promise + .all(fieldValues) + .then(ref => { + var ret = Object.create(null); + ret.metadata = record.metadata; + ret.data = Object.create(null); + fieldNames.forEach((field, index) => + ret.data[field] = ref[index] + ); + + return ret; + }); + } + ); + + this._persisting = Promise.all(allPromises) + .then(values => { + var json = Object.create(null); + Object.keys(data).forEach((key, i) => { + json[key] = Object.create(null); + json[key].metadata = data[key].metadata; + json[key].data = values[i].data; + }); + return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + }) + .then(() => { + this._persisting = null; + return true; + }); + + return this._persisting; + } + + _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; + } + + // Filter outdated cache and convert to promises. + Object.keys(cacheOnDisk).forEach(key => { + if (!fs.existsSync(key)) { + return; + } + var record = cacheOnDisk[key]; + var stat = fs.statSync(key); + if (stat.mtime.getTime() === record.metadata.mtime) { + ret[key] = Object.create(null); + ret[key].metadata = Object.create(null); + ret[key].data = Object.create(null); + ret[key].metadata.mtime = record.metadata.mtime; + + Object.keys(record.data).forEach(field => { + ret[key].data[field] = Promise.resolve(record.data[field]); + }); + } + }); + + return ret; + } + + _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); + } +} + +module.exports = Cache; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index e2f0fd98..595b1f7c 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -29,12 +29,15 @@ const Promise = require('promise'); jest.mock('fs'); describe('DependencyGraph', function() { + var cache; + var Cache; var DependencyGraph; var fileWatcher; var fs; beforeEach(function() { fs = require('fs'); + Cache = require('../../../Cache'); DependencyGraph = require('../index'); fileWatcher = { @@ -43,6 +46,8 @@ describe('DependencyGraph', function() { }, isWatchman: () => Promise.resolve(false) }; + + cache = new Cache({}); }); describe('getOrderedDependencies', function() { @@ -68,6 +73,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -125,6 +131,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -176,6 +183,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -235,6 +243,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -286,6 +295,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -342,6 +352,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -419,6 +430,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -480,6 +492,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -532,6 +545,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -584,6 +598,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -644,6 +659,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -700,6 +716,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -750,6 +767,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -799,6 +817,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -845,6 +864,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -895,6 +915,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -943,6 +964,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -996,6 +1018,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { expect(deps) @@ -1053,6 +1076,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1100,6 +1124,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1146,6 +1171,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1204,6 +1230,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1262,6 +1289,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1340,6 +1368,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1396,6 +1425,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1452,6 +1482,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1508,6 +1539,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1579,6 +1611,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1681,6 +1714,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1761,6 +1795,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -1842,6 +1877,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -1924,6 +1960,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2020,6 +2057,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2105,6 +2143,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2209,6 +2248,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2277,6 +2317,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) { expect(deps) @@ -2333,6 +2374,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2377,6 +2419,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -2437,6 +2480,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2492,6 +2536,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2540,6 +2585,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { expect(deps) @@ -2623,6 +2669,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2687,6 +2734,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = @@ -2751,6 +2799,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; @@ -2814,6 +2863,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -2895,6 +2945,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: [root], assetExts: ['png'], fileWatcher: fileWatcher, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { @@ -2966,6 +3017,7 @@ describe('DependencyGraph', function() { roots: [root], assetExts: ['png'], fileWatcher: fileWatcher, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { @@ -3052,7 +3104,8 @@ describe('DependencyGraph', function() { return true; } return false; - } + }, + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ @@ -3137,6 +3190,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { @@ -3207,6 +3261,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); @@ -3273,6 +3328,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3337,6 +3393,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ @@ -3399,6 +3456,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { expect(deps) @@ -3498,6 +3556,7 @@ describe('DependencyGraph', function() { roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], + cache: cache, }); return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 0a8c0076..e66191e4 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -59,7 +59,11 @@ const validateOpts = declareOpts({ platforms: { type: 'array', default: ['ios', 'android'], - } + }, + cache: { + type: 'object', + required: true, + }, }); class DependencyGraph { @@ -67,6 +71,7 @@ class DependencyGraph { this._opts = validateOpts(options); this._hasteMap = Object.create(null); this._immediateResolutionCache = Object.create(null); + this._cache = this._opts.cache; this.load(); } @@ -84,17 +89,21 @@ class DependencyGraph { }); this._crawling.then((files) => Activity.endEvent(crawlActivity)); - this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { + this._fastfs = new Fastfs(this._opts.roots, this._opts.fileWatcher, { ignore: this._opts.ignoreFilePath, crawling: this._crawling, }); this._fastfs.on('change', this._processFileChange.bind(this)); - this._moduleCache = new ModuleCache(this._fastfs); + this._moduleCache = new ModuleCache(this._fastfs, this._cache); this._loading = Promise.all([ - this._fastfs.build().then(() => this._buildHasteMap()), + this._fastfs.build() + .then(() => { + const hasteActivity = Activity.startEvent('haste map'); + this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); + }), this._buildAssetMap_DEPRECATED(), ]); @@ -141,7 +150,7 @@ class DependencyGraph { () => this._resolveNodeDependency(fromModule, toModuleName) ).then( cacheResult, - forgive + forgive, ); } diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 3ae9354d..3f1b13ef 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -8,7 +8,7 @@ const replacePatterns = require('./replacePatterns'); class Module { - constructor(file, fastfs, moduleCache) { + constructor(file, fastfs, moduleCache, cache) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -18,34 +18,41 @@ class Module { this._fastfs = fastfs; this._moduleCache = moduleCache; + this._cache = cache; } isHaste() { - return this._read().then(data => !!data.id); + return this._cache.get(this.path, 'haste', () => + this._read().then(data => !!data.id) + ); } getName() { - return this._read().then(data => { - if (data.id) { - return data.id; - } + return this._cache.get( + this.path, + 'name', + () => this._read().then(data => { + if (data.id) { + return data.id; + } - const p = this.getPackage(); + const p = this.getPackage(); - if (!p) { - // Name is full path - return this.path; - } + if (!p) { + // Name is full path + return this.path; + } - return p.getName() - .then(name => { - if (!name) { - return this.path; - } + return p.getName() + .then(name => { + if (!name) { + return this.path; + } - return path.join(name, path.relative(p.root, this.path)); - }); - }); + return path.join(name, path.relative(p.root, this.path)); + }); + }) + ); } getPackage() { @@ -53,7 +60,13 @@ class Module { } getDependencies() { - return this._read().then(data => data.dependencies); + return this._cache.get(this.path, 'dependencies', () => + this._read().then(data => data.dependencies) + ); + } + + invalidate() { + this._cache.invalidate(this.path); } _read() { diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index 3fa02cc1..a7e29322 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,17 +7,23 @@ const path = require('path'); class ModuleCache { - constructor(fastfs) { + constructor(fastfs, cache) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; + this._cache = cache; fastfs.on('change', this._processFileChange.bind(this)); } getModule(filePath) { filePath = path.resolve(filePath); if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new Module(filePath, this._fastfs, this); + this._moduleCache[filePath] = new Module( + filePath, + this._fastfs, + this, + this._cache, + ); } return this._moduleCache[filePath]; } @@ -28,7 +34,8 @@ class ModuleCache { this._moduleCache[filePath] = new AssetModule( filePath, this._fastfs, - this + this, + this._cache, ); } return this._moduleCache[filePath]; @@ -37,7 +44,11 @@ class ModuleCache { getPackage(filePath) { filePath = path.resolve(filePath); if (!this._packageCache[filePath]){ - this._packageCache[filePath] = new Package(filePath, this._fastfs); + this._packageCache[filePath] = new Package( + filePath, + this._fastfs, + this._cache, + ); } return this._packageCache[filePath]; } @@ -64,8 +75,15 @@ class ModuleCache { _processFileChange(type, filePath, root) { const absPath = path.join(root, filePath); - delete this._moduleCache[absPath]; - delete this._packageCache[absPath]; + + if (this._moduleCache[absPath]) { + this._moduleCache[absPath].invalidate(); + delete this._moduleCache[absPath]; + } + if (this._packageCache[absPath]) { + this._packageCache[absPath].invalidate(); + delete this._packageCache[absPath]; + } } } diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js index f52ff42b..5354e588 100644 --- a/react-packager/src/DependencyResolver/Package.js +++ b/react-packager/src/DependencyResolver/Package.js @@ -5,11 +5,12 @@ const path = require('path'); class Package { - constructor(file, fastfs) { + constructor(file, fastfs, cache) { this.path = path.resolve(file); this.root = path.dirname(this.path); this._fastfs = fastfs; this.type = 'Package'; + this._cache = cache; } getMain() { @@ -33,11 +34,19 @@ class Package { } isHaste() { - return this._read().then(json => !!json.name); + return this._cache.get(this.path, 'haste', () => + this._read().then(json => !!json.name) + ); } getName() { - return this._read().then(json => json.name); + return this._cache.get(this.path, 'name', () => + this._read().then(json => json.name) + ); + } + + invalidate() { + this._cache.invalidate(this.path); } redirectRequire(name) { diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index 0ddf5c3c..eae2e3da 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -45,7 +45,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + cache: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { @@ -60,6 +64,7 @@ function HasteDependencyResolver(options) { (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, fileWatcher: opts.fileWatcher, + cache: opts.cache, }); diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js deleted file mode 100644 index aee8d4f2..00000000 --- a/react-packager/src/JSTransformer/Cache.js +++ /dev/null @@ -1,175 +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'; - -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; - -var validateOpts = declareOpts({ - resetCache: { - type: 'boolean', - default: false, - }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - projectRoots: { - type: 'array', - required: true, - }, - transformModulePath: { - type:'string', - required: true, - }, -}); -module.exports = Cache; - -function Cache(options) { - var opts = validateOpts(options); - - this._cacheFilePath = cacheFilePath(opts); - - var data; - if (!opts.resetCache) { - data = loadCacheSync(this._cacheFilePath); - } else { - data = Object.create(null); - } - this._data = data; - - this._has = Object.prototype.hasOwnProperty.bind(data); - this._persistEventually = _.debounce( - this._persistCache.bind(this), - 2000 - ); -} - -Cache.prototype.get = function(filepath, loaderCb) { - if (!isAbsolutePath(filepath)) { - throw new Error('Use absolute paths'); - } - - var recordP = this._has(filepath) - ? this._data[filepath] - : this._set(filepath, loaderCb(filepath)); - - return recordP.then(function(record) { - return record.data; - }); -}; - -Cache.prototype._set = function(filepath, loaderPromise) { - this._data[filepath] = loaderPromise.then(function(data) { - return Promise.all([ - data, - Promise.denodeify(fs.stat)(filepath) - ]); - }).then(function(ref) { - var data = ref[0]; - var stat = ref[1]; - this._persistEventually(); - return { - data: data, - mtime: stat.mtime.getTime(), - }; - }.bind(this)); - - return this._data[filepath]; -}; - -Cache.prototype.invalidate = function(filepath){ - if (this._has(filepath)) { - delete this._data[filepath]; - } -}; - -Cache.prototype.end = function() { - return this._persistCache(); -}; - -Cache.prototype._persistCache = function() { - if (this._persisting != null) { - return this._persisting; - } - - var data = this._data; - var cacheFilepath = this._cacheFilePath; - - this._persisting = Promise.all(_.values(data)) - .then(function(values) { - var json = Object.create(null); - Object.keys(data).forEach(function(key, i) { - json[key] = values[i]; - }); - return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); - }) - .then(function() { - this._persisting = null; - return true; - }.bind(this)); - - return this._persisting; -}; - -function 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; - } - - // Filter outdated cache and convert to promises. - Object.keys(cacheOnDisk).forEach(function(key) { - if (!fs.existsSync(key)) { - return; - } - var value = cacheOnDisk[key]; - var stat = fs.statSync(key); - if (stat.mtime.getTime() === value.mtime) { - ret[key] = Promise.resolve(value); - } - }); - - return ret; -} - -function cacheFilePath(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); -} diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js deleted file mode 100644 index 3877b3dd..00000000 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ /dev/null @@ -1,236 +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('underscore') - .dontMock('absolute-path') - .dontMock('../Cache'); - -jest - .mock('os') - .mock('fs'); - -var Promise = require('promise'); - -describe('JSTransformer Cache', function() { - var Cache; - - beforeEach(function() { - require('os').tmpDir.mockImpl(function() { - return 'tmpDir'; - }); - - Cache = require('../Cache'); - }); - - describe('getting/setting', function() { - it('calls loader callback for uncached file', function() { - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve(); - }); - - cache.get('/rootDir/someFile', loaderCb); - expect(loaderCb).toBeCalledWith('/rootDir/someFile'); - }); - - pit('gets the value from the loader callback', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('lol'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(value).toBe('lol'); - }); - }); - - pit('caches the value after the first call', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('lol'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function() { - var shouldNotBeCalled = jest.genMockFn(); - return cache.get('/rootDir/someFile', shouldNotBeCalled) - .then(function(value) { - expect(shouldNotBeCalled).not.toBeCalled(); - expect(value).toBe('lol'); - }); - }); - }); - }); - - describe('loading cache from disk', function() { - var fileStats; - - beforeEach(function() { - fileStats = { - '/rootDir/someFile': { - mtime: { - getTime: function() { - return 22; - } - } - }, - '/rootDir/foo': { - mtime: { - getTime: function() { - return 11; - } - } - } - }; - - var fs = require('fs'); - - fs.existsSync.mockImpl(function() { - return true; - }); - - fs.statSync.mockImpl(function(filePath) { - return fileStats[filePath]; - }); - - fs.readFileSync.mockImpl(function() { - return JSON.stringify({ - '/rootDir/someFile': { - mtime: 22, - data: 'oh hai' - }, - '/rootDir/foo': { - mtime: 11, - data: 'lol wat' - } - }); - }); - }); - - pit('should load cache from disk', function() { - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn(); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache.get('/rootDir/foo', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('lol wat'); - }); - }); - }); - - pit('should not load outdated cache', function() { - require('fs').stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() {} - } - }); - }); - - fileStats['/rootDir/foo'].mtime.getTime = function() { - return 123; - }; - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - var loaderCb = jest.genMockFn().mockImpl(function() { - return Promise.resolve('new value'); - }); - - return cache.get('/rootDir/someFile', loaderCb).then(function(value) { - expect(loaderCb).not.toBeCalled(); - expect(value).toBe('oh hai'); - - return cache.get('/rootDir/foo', loaderCb).then(function(value) { - expect(loaderCb).toBeCalled(); - expect(value).toBe('new value'); - }); - }); - }); - }); - - describe('writing cache to disk', function() { - it('should write cache to disk', function() { - var index = 0; - var mtimes = [10, 20, 30]; - var debounceIndex = 0; - require('underscore').debounce = function(callback) { - return function () { - if (++debounceIndex === 3) { - callback(); - } - }; - }; - - var fs = require('fs'); - fs.stat.mockImpl(function(file, callback) { - callback(null, { - mtime: { - getTime: function() { - return mtimes[index++]; - } - } - }); - }); - - var cache = new Cache({ - projectRoots: ['/rootDir'], - transformModulePath: 'x.js', - }); - - cache.get('/rootDir/bar', function() { - return Promise.resolve('bar value'); - }); - cache.get('/rootDir/foo', function() { - return Promise.resolve('foo value'); - }); - cache.get('/rootDir/baz', function() { - return Promise.resolve('baz value'); - }); - - jest.runAllTicks(); - expect(fs.writeFile).toBeCalled(); - }); - }); -}); diff --git a/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 22ca53e6..fdcd17eb 100644 --- a/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -15,8 +15,11 @@ jest jest.mock('fs'); +var Cache = require('../../Cache'); + var OPTIONS = { - transformModulePath: '/foo/bar' + transformModulePath: '/foo/bar', + cache: new Cache({}), }; describe('Transformer', function() { @@ -28,9 +31,6 @@ describe('Transformer', function() { jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { return workers; })); - require('../Cache').prototype.get.mockImpl(function(filePath, callback) { - return callback(); - }); require('fs').readFile.mockImpl(function(file, callback) { callback(null, 'content'); }); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 0cc7f2c6..f7884016 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -10,7 +10,6 @@ var fs = require('fs'); var Promise = require('promise'); -var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); @@ -33,35 +32,20 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - resetCache: { - type: 'boolean', - default: false, - }, transformModulePath: { type:'string', required: false, }, - nonPersistent: { - type: 'boolean', - default: false, + cache: { + type: 'object', + required: true, }, }); function Transformer(options) { var opts = validateOpts(options); - this._cache = opts.nonPersistent - ? new DummyCache() - : new Cache({ - resetCache: options.resetCache, - cacheVersion: options.cacheVersion, - projectRoots: options.projectRoots, - transformModulePath: options.transformModulePath, - }); + this._cache = opts.cache; if (options.transformModulePath != null) { this._workers = workerFarm( @@ -75,7 +59,6 @@ function Transformer(options) { Transformer.prototype.kill = function() { this._workers && workerFarm.end(this._workers); - return this._cache.end(); }; Transformer.prototype.invalidateFile = function(filePath) { @@ -88,7 +71,8 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { } var transform = this._transform; - return this._cache.get(filePath, function() { + return this._cache.get(filePath, 'transformedSource', function() { + // TODO: use fastfs to avoid reading file from disk again return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); @@ -157,10 +141,3 @@ function formatBabelError(err, filename) { error.description = err.message; return error; } - -function DummyCache() {} -DummyCache.prototype.get = function(filePath, loaderCb) { - return loaderCb(); -}; -DummyCache.prototype.end = -DummyCache.prototype.invalidate = function(){}; diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 3c6d1a2f..a718bd26 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -12,6 +12,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); var Promise = require('promise'); +var Cache = require('../Cache'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var Package = require('./Package'); @@ -78,6 +79,15 @@ function Packager(options) { opts.projectRoots.forEach(verifyRootExists); + this._cache = opts.nonPersistent + ? new DummyCache() + : new Cache({ + resetCache: opts.resetCache, + cacheVersion: opts.cacheVersion, + projectRoots: opts.projectRoots, + transformModulePath: opts.transformModulePath, + }); + this._resolver = new DependencyResolver({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, @@ -87,15 +97,14 @@ function Packager(options) { assetRoots: opts.assetRoots, fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, + cache: this._cache, }); this._transformer = new Transformer({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, - cacheVersion: opts.cacheVersion, - resetCache: opts.resetCache, + cache: this._cache, transformModulePath: opts.transformModulePath, - nonPersistent: opts.nonPersistent, }); this._projectRoots = opts.projectRoots; @@ -103,7 +112,8 @@ function Packager(options) { } Packager.prototype.kill = function() { - return this._transformer.kill(); + this._transformer.kill(); + return this._cache.end(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { @@ -267,4 +277,13 @@ function verifyRootExists(root) { assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } +class DummyCache { + get(filepath, field, loaderCb) { + return loaderCb(); + } + + end(){} + invalidate(filepath){} +} + module.exports = Packager;