diff --git a/packager/react-packager/src/Cache/__mocks__/index.js b/packager/react-packager/src/Cache/__mocks__/index.js new file mode 100644 index 000000000..6f7632f66 --- /dev/null +++ b/packager/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/packager/react-packager/src/Cache/__tests__/Cache-test.js b/packager/react-packager/src/Cache/__tests__/Cache-test.js new file mode 100644 index 000000000..8172a2430 --- /dev/null +++ b/packager/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/packager/react-packager/src/Cache/index.js b/packager/react-packager/src/Cache/index.js new file mode 100644 index 000000000..2ed3575e4 --- /dev/null +++ b/packager/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/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index e2f0fd980..595b1f7cf 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/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/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 0a8c00765..e66191e44 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/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/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js index 3ae9354d3..3f1b13efa 100644 --- a/packager/react-packager/src/DependencyResolver/Module.js +++ b/packager/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/packager/react-packager/src/DependencyResolver/ModuleCache.js b/packager/react-packager/src/DependencyResolver/ModuleCache.js index 3fa02cc17..a7e293223 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleCache.js +++ b/packager/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/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js index f52ff42b8..5354e588c 100644 --- a/packager/react-packager/src/DependencyResolver/Package.js +++ b/packager/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/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index 0ddf5c3cc..eae2e3da0 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/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/packager/react-packager/src/JSTransformer/Cache.js b/packager/react-packager/src/JSTransformer/Cache.js deleted file mode 100644 index aee8d4f21..000000000 --- a/packager/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/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js deleted file mode 100644 index 3877b3dd5..000000000 --- a/packager/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/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js index 22ca53e63..fdcd17eba 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/packager/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/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 0cc7f2c6e..f78840163 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/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/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 3c6d1a2fc..a718bd264 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/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;