Updates from Tue 11 Aug

This commit is contained in:
Christopher Chedeau 2015-08-11 08:42:07 -07:00
commit a457202f9b
21 changed files with 1129 additions and 543 deletions

View File

@ -17,9 +17,6 @@ var sharedBlacklist = [
'node_modules/react-tools/src/React.js',
'node_modules/react-tools/src/renderers/shared/event/EventPropagators.js',
'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js',
'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderSyntheticEvent.js',
'node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderTouchHistoryStore.js',
'node_modules/react-tools/src/renderers/shared/reconciler/ReactInstanceHandles.js',
'node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js',
];

View File

@ -27,6 +27,12 @@ window.onbeforeunload = function() {
}
};
window.addEventListener('load', function () {
if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
document.getElementById('devtools-banner').style.display = 'block';
}
});
// Alias native implementations needed by the debugger before platform-specific
// implementations are loaded into the global namespace
var debuggerSetTimeout = window.setTimeout;
@ -112,6 +118,13 @@ function loadScript(src, callback) {
})();
</script>
<style type="text/css">
body {
font-size: large;
margin: 0;
padding: 0;
font-family: Helvetica, Verdana, sans-serif;
font-weight: 200;
}
.shortcut {
font-family: monospace;
color: #eee;
@ -120,16 +133,52 @@ function loadScript(src, callback) {
border-radius: 4px;
letter-spacing: 3px;
}
body {
font-size: large;
#devtools-banner {
display: none;
background-color: #FDFDD5;
padding: 10px;
}
#devtools-banner h3 {
margin: 0;
font-weight: normal;
}
#devtools-banner a {
display: none;
padding: 10px 20px 10px 20px;
margin-bottom: 10px;
color: white;
text-decoration: none;
font-size: 11px;
text-shadow: 0 1px 1px rgba(0,0,0,0.1);
text-transform: uppercase;
font-weight: bold;
background-color: #4d7bd6;
border-radius: 2px;
border: 1px solid #2d53af;
display: inline-block;
}
.content {
padding: 10px;
}
</style>
</head>
<body>
<p>
React Native JS code runs inside this Chrome tab
</p>
<p>Press <span class="shortcut">⌘⌥J</span> to open Developer Tools. Enable <a href="http://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
<p>Status: <span id="status">Loading</span></p>
<div id="devtools-banner">
<h3>Install React DevTools</h3>
<p>
React Developer Tools is an extension that allows you to inspect the
React component hierarchies in the Chrome Developer Tools.
</p>
<a href="https://fb.me/react-devtools" target="_blank">
Install
</a>
</div>
<div class="content">
<p>
React Native JS code runs inside this Chrome tab
</p>
<p>Press <span class="shortcut">⌘⌥J</span> to open Developer Tools. Enable <a href="http://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
<p>Status: <span id="status">Loading</span></p>
</div>
</body>
</html>

17
launchAndroidPackager.command Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# 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.
# Set terminal title
echo -en "\033]0;React Packager\a"
clear
THIS_DIR=$(dirname "$0")
$THIS_DIR/packager.sh --platform android --port 8082
echo "Process terminated. Press <enter> to close the window"
read

View File

@ -10,15 +10,17 @@
"es6.constants",
"es6.classes",
"es6.destructuring",
"es6.parameters.rest",
"es6.parameters",
"es6.properties.computed",
"es6.properties.shorthand",
"es6.spread",
"es6.templateLiterals",
"es7.asyncFunctions",
"es7.trailingFunctionCommas",
"es7.objectRestSpread",
"flow",
"react"
"react",
"regenerator"
],
"sourceMaps": false
}

View File

@ -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;

View File

@ -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();
});
});
});

222
react-packager/src/Cache/index.js vendored Normal file
View File

@ -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;

View File

@ -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)
@ -1809,6 +1844,78 @@ describe('DependencyGraph', function() {
});
});
pit('platform should work with node_modules', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': [
'/**',
' * @providesModule index',
' */',
'require("foo");',
'require("bar");',
].join('\n'),
'node_modules': {
'foo': {
'package.json': JSON.stringify({
name: 'foo',
}),
'index.ios.js': '',
},
'bar': {
'package.json': JSON.stringify({
name: 'bar',
main: 'main'
}),
'main.ios.js': '',
},
},
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['foo', 'bar'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'foo/index.ios.js',
path: '/root/node_modules/foo/index.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'bar/main.ios.js',
path: '/root/node_modules/bar/main.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
pit('nested node_modules with specific paths', function() {
var root = '/root';
fs.__setMockFilesystem({
@ -1853,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)
@ -1949,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)
@ -2034,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)
@ -2138,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)
@ -2206,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)
@ -2262,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)
@ -2306,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)
@ -2333,6 +2447,172 @@ describe('DependencyGraph', function() {
]);
});
});
pit('should work with multiple platforms (haste)', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('a');
`,
'a.ios.js': `
/**
* @providesModule a
*/
`,
'a.android.js': `
/**
* @providesModule a
*/
`,
'a.js': `
/**
* @providesModule a
*/
`,
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['a'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'a',
path: '/root/a.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
pit('should pick the generic file', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('a');
`,
'a.android.js': `
/**
* @providesModule a
*/
`,
'a.js': `
/**
* @providesModule a
*/
`,
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['a'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'a',
path: '/root/a.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
pit('should work with multiple platforms (node)', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('./a');
`,
'a.ios.js': '',
'a.android.js': '',
'a.js': '',
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['./a'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: '/root/a.ios.js',
path: '/root/a.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
});
describe('file watch updating', function() {
@ -2389,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'] =
@ -2453,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'] =
@ -2517,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;
@ -2580,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'] = [
@ -2661,6 +2945,7 @@ describe('DependencyGraph', function() {
assetRoots_DEPRECATED: [root],
assetExts: ['png'],
fileWatcher: fileWatcher,
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
@ -2732,6 +3017,7 @@ describe('DependencyGraph', function() {
roots: [root],
assetExts: ['png'],
fileWatcher: fileWatcher,
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
@ -2818,7 +3104,8 @@ describe('DependencyGraph', function() {
return true;
}
return false;
}
},
cache: cache,
});
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
filesystem.root['bar.js'] = [
@ -2903,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', {
@ -2973,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');
@ -3039,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({
@ -3103,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({
@ -3165,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)
@ -3264,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({

View File

@ -56,6 +56,14 @@ const validateOpts = declareOpts({
'parse',
],
},
platforms: {
type: 'array',
default: ['ios', 'android'],
},
cache: {
type: 'object',
required: true,
},
});
class DependencyGraph {
@ -63,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();
}
@ -80,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('Building Haste Map');
this._buildHasteMap().then(() => Activity.endEvent(hasteActivity));
}),
this._buildAssetMap_DEPRECATED(),
]);
@ -137,7 +150,7 @@ class DependencyGraph {
() => this._resolveNodeDependency(fromModule, toModuleName)
).then(
cacheResult,
forgive
forgive,
);
}
@ -169,6 +182,13 @@ class DependencyGraph {
);
}
const platformExt = getPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt;
} else {
this._platformExt = null;
}
const entry = this._moduleCache.getModule(absolutePath);
const deps = [];
const visited = Object.create(null);
@ -237,16 +257,14 @@ class DependencyGraph {
}
return p.then((realModuleName) => {
let dep = this._hasteMap[realModuleName];
let dep = this._getHasteModule(realModuleName);
if (dep && dep.type === 'Module') {
return dep;
}
let packageName = realModuleName;
while (packageName && packageName !== '.') {
dep = this._hasteMap[packageName];
dep = this._getHasteModule(packageName);
if (dep && dep.type === 'Package') {
break;
}
@ -349,6 +367,9 @@ class DependencyGraph {
let file;
if (this._fastfs.fileExists(potentialModulePath)) {
file = potentialModulePath;
} else if (this._platformExt != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platformExt + '.js')) {
file = potentialModulePath + '.' + this._platformExt + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
@ -419,15 +440,32 @@ class DependencyGraph {
}
_updateHasteMap(name, mod) {
if (this._hasteMap[name]) {
debug('WARNING: conflicting haste modules: ' + name);
if (mod.type === 'Package' &&
this._hasteMap[name].type === 'Module') {
// Modules takes precendence over packages.
return;
}
if (this._hasteMap[name] == null) {
this._hasteMap[name] = [];
}
this._hasteMap[name] = mod;
if (mod.type === 'Module') {
// Modules takes precendence over packages.
this._hasteMap[name].unshift(mod);
} else {
this._hasteMap[name].push(mod);
}
}
_getHasteModule(name) {
if (this._hasteMap[name]) {
const modules = this._hasteMap[name];
if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) {
if (getPlatformExt(modules[i].path) === this._platformExt) {
return modules[i];
}
}
}
return modules[0];
}
return null;
}
_isNodeModulesDir(file) {
@ -480,9 +518,18 @@ class DependencyGraph {
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));
return fastfs.build().then(
() => fastfs.findFilesByExts(this._opts.assetExts).map(
file => this._processAsset_DEPRECATED(file)
)
() => {
const processAsset_DEPRECATEDActivity = Activity.startEvent(
'Building (deprecated) Asset Map',
);
const assets = fastfs.findFilesByExts(this._opts.assetExts).map(
file => this._processAsset_DEPRECATED(file)
);
Activity.endEvent(processAsset_DEPRECATEDActivity);
return assets;
}
);
}
@ -511,12 +558,17 @@ class DependencyGraph {
return;
}
/*eslint no-labels: 0 */
if (type === 'delete' || type === 'change') {
_.each(this._hasteMap, (mod, name) => {
if (mod.path === absPath) {
delete this._hasteMap[name];
loop: for (let name in this._hasteMap) {
let modules = this._hasteMap[name];
for (var i = 0; i < modules.length; i++) {
if (modules[i].path === absPath) {
modules.splice(i, 1);
break loop;
}
}
});
}
if (type === 'delete') {
return;
@ -566,6 +618,15 @@ function normalizePath(modulePath) {
return modulePath.replace(/\/$/, '');
}
// Extract platform extension: index.ios.js -> ios
function getPlatformExt(file) {
const parts = path.basename(file).split('.');
if (parts.length < 3) {
return null;
}
return parts[parts.length - 2];
}
util.inherits(NotFoundError, Error);
module.exports = DependencyGraph;

View File

@ -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() {

View File

@ -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];
}
}
}

View File

@ -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) {

View File

@ -1,5 +1,6 @@
'use strict';
const Activity = require('../Activity');
const Promise = require('promise');
const {EventEmitter} = require('events');
@ -28,6 +29,7 @@ class Fastfs extends EventEmitter {
);
return this._crawling.then(files => {
const fastfsActivity = Activity.startEvent('Building in-memory fs');
files.forEach(filePath => {
if (filePath.match(rootsPattern)) {
const newFile = new File(filePath, { isDir: false });
@ -44,6 +46,7 @@ class Fastfs extends EventEmitter {
}
}
});
Activity.endEvent(fastfsActivity);
this._fileWatcher.on('all', this._processFileChange.bind(this));
});
}

View File

@ -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,
});

View File

@ -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);
}

View File

@ -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();
});
});
});

View File

@ -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');
});

View File

@ -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();
@ -121,7 +105,9 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
});
};
function TransformError() {}
function TransformError() {
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
}
util.inherits(TransformError, SyntaxError);
function formatError(err, filename, source) {
@ -155,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(){};

View File

@ -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;

View File

@ -372,12 +372,36 @@ Server.prototype.processRequest = function(req, res, next) {
Activity.endEvent(startReqEventId);
}
},
function(error) {
handleError(res, error);
}
this._handleError.bind(this, res, optionsJson)
).done();
};
Server.prototype._handleError = function(res, packageID, error) {
res.writeHead(error.status || 500, {
'Content-Type': 'application/json; charset=UTF-8',
});
if (error.type === 'TransformError' || error.type === 'NotFoundError') {
error.errors = [{
description: error.description,
filename: error.filename,
lineNumber: error.lineNumber,
}];
res.end(JSON.stringify(error));
if (error.type === 'NotFoundError') {
delete this._packages[packageID];
}
} else {
console.error(error.stack || error);
res.end(JSON.stringify({
type: 'InternalError',
message: 'react-packager has encountered an internal error, ' +
'please check your terminal error output for more details',
}));
}
};
function getOptionsFromUrl(reqUrl) {
// `true` to parse the query param as an object.
var urlObj = url.parse(reqUrl, true);
@ -417,26 +441,3 @@ function getBoolOptionFromQuery(query, opt, defaultVal) {
return query[opt] === 'true' || query[opt] === '1';
}
function handleError(res, error) {
res.writeHead(error.status || 500, {
'Content-Type': 'application/json; charset=UTF-8',
});
if (error.type === 'TransformError' || error.type === 'NotFoundError') {
error.errors = [{
description: error.description,
filename: error.filename,
lineNumber: error.lineNumber,
}];
console.error(error);
res.end(JSON.stringify(error));
} else {
console.error(error.stack || error);
res.end(JSON.stringify({
type: 'InternalError',
message: 'react-packager has encountered an internal error, ' +
'please check your terminal error output for more details',
}));
}
}

View File

@ -23,16 +23,17 @@ function transform(srcTxt, filename, options) {
'es6.blockScoping',
'es6.classes',
'es6.destructuring',
'es6.parameters.rest',
'es6.parameters',
'es6.properties.computed',
'es6.properties.shorthand',
'es6.spread',
'es6.templateLiterals',
'es7.asyncFunctions',
'es7.trailingFunctionCommas',
'es7.objectRestSpread',
'flow',
'react',
'react.displayName',
'regenerator',
],
sourceFileName: filename,
sourceMaps: false,