Files
react-native/packager/react-packager/src/JSTransformer/Cache.js
2015-06-22 08:44:03 -08:00

176 lines
4.0 KiB
JavaScript

/**
* 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);
}