Install node-haste2 and remove DependencyResolver

Summary: This installs the 2.0 version of node-haste, removes the DependencyResolver and fixes up all the tests.

Reviewed By: davidaurelio

Differential Revision: D2943416

fb-gh-sync-id: aa83d436a33f910d12ed4cc6e2ad8d5742c123a5
shipit-source-id: aa83d436a33f910d12ed4cc6e2ad8d5742c123a5
This commit is contained in:
Christoph Pojer 2016-02-18 18:02:21 -08:00 committed by facebook-github-bot-5
parent 7a87d38044
commit fa3a67e251
48 changed files with 206 additions and 8444 deletions

258
npm-shrinkwrap.json generated
View File

@ -122,16 +122,16 @@
"from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
},
"ast-types": {
"version": "0.8.12",
"from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
},
"babylon": {
"version": "5.8.29",
"from": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz"
},
"ast-types": {
"version": "0.8.12",
"from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
},
"globals": {
"version": "6.4.1",
"from": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz",
@ -248,15 +248,15 @@
"from": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz"
},
"regenerator": {
"version": "0.8.40",
"from": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
"resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz"
},
"source-map": {
"version": "0.5.3",
"from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
},
"regenerator": {
"version": "0.8.40",
"from": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
"resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz"
}
}
},
@ -1900,16 +1900,16 @@
"from": "abbrev@1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
},
"ansi": {
"version": "0.3.0",
"from": "ansi@~0.3.0",
"resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
},
"ansi-regex": {
"version": "2.0.0",
"from": "ansi-regex@^2.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
},
"ansi": {
"version": "0.3.0",
"from": "ansi@~0.3.0",
"resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
},
"ansi-styles": {
"version": "2.1.0",
"from": "ansi-styles@^2.1.0",
@ -1920,15 +1920,20 @@
"from": "are-we-there-yet@~1.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.4.tgz"
},
"asn1": {
"version": "0.1.11",
"from": "asn1@0.1.11",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
},
"assert-plus": {
"version": "0.1.5",
"from": "assert-plus@^0.1.5",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
},
"asn1": {
"version": "0.1.11",
"from": "asn1@0.1.11",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
"aws-sign2": {
"version": "0.6.0",
"from": "aws-sign2@~0.6.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
},
"async": {
"version": "1.5.0",
@ -1940,46 +1945,41 @@
"from": "balanced-match@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
},
"aws-sign2": {
"version": "0.6.0",
"from": "aws-sign2@~0.6.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
"block-stream": {
"version": "0.0.8",
"from": "block-stream@*",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
},
"boom": {
"version": "2.10.1",
"from": "boom@^2.8.x",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
},
"block-stream": {
"version": "0.0.8",
"from": "block-stream@*",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
},
"brace-expansion": {
"version": "1.1.1",
"from": "brace-expansion@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
},
"caseless": {
"version": "0.11.0",
"from": "caseless@~0.11.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
},
"chalk": {
"version": "1.1.1",
"from": "chalk@^1.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
},
"commander": {
"version": "2.9.0",
"from": "commander@^2.8.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
"caseless": {
"version": "0.11.0",
"from": "caseless@~0.11.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
},
"combined-stream": {
"version": "1.0.5",
"from": "combined-stream@~1.0.5",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
},
"commander": {
"version": "2.9.0",
"from": "commander@^2.8.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
},
"concat-map": {
"version": "0.0.1",
"from": "concat-map@0.0.1",
@ -2065,26 +2065,26 @@
"from": "graceful-fs@4.1",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
},
"graceful-readlink": {
"version": "1.0.1",
"from": "graceful-readlink@>= 1.0.0",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
},
"har-validator": {
"version": "2.0.2",
"from": "har-validator@~2.0.2",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.2.tgz"
},
"has-ansi": {
"version": "2.0.0",
"from": "has-ansi@^2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
"graceful-readlink": {
"version": "1.0.1",
"from": "graceful-readlink@>= 1.0.0",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
},
"has-unicode": {
"version": "1.0.1",
"from": "has-unicode@^1.0.0",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-1.0.1.tgz"
},
"has-ansi": {
"version": "2.0.0",
"from": "has-ansi@^2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
},
"hawk": {
"version": "3.1.0",
"from": "hawk@~3.1.0",
@ -2100,6 +2100,11 @@
"from": "http-signature@~0.11.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@*",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"ini": {
"version": "1.3.4",
"from": "ini@~1.3.0",
@ -2120,36 +2125,31 @@
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@*",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"jsonpointer": {
"version": "2.0.0",
"from": "jsonpointer@2.0.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
"isstream": {
"version": "0.1.2",
"from": "isstream@~0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
},
"json-stringify-safe": {
"version": "5.0.1",
"from": "json-stringify-safe@~5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"isstream": {
"version": "0.1.2",
"from": "isstream@~0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
},
"lodash._basetostring": {
"version": "3.0.1",
"from": "lodash._basetostring@^3.0.0",
"resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
"jsonpointer": {
"version": "2.0.0",
"from": "jsonpointer@2.0.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
},
"lodash._createpadding": {
"version": "3.6.1",
"from": "lodash._createpadding@^3.0.0",
"resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz"
},
"lodash._basetostring": {
"version": "3.0.1",
"from": "lodash._basetostring@^3.0.0",
"resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
},
"lodash.pad": {
"version": "3.1.1",
"from": "lodash.pad@^3.0.0",
@ -2270,16 +2270,16 @@
"from": "strip-json-comments@0.1.x",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
},
"supports-color": {
"version": "2.0.0",
"from": "supports-color@^2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
},
"tar": {
"version": "2.2.1",
"from": "tar@~2.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
},
"supports-color": {
"version": "2.0.0",
"from": "supports-color@^2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
},
"tough-cookie": {
"version": "2.2.0",
"from": "tough-cookie@~2.2.0",
@ -2305,6 +2305,28 @@
"from": "xtend@^4.0.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
},
"fstream-ignore": {
"version": "1.0.3",
"from": "fstream-ignore@~1.0.3",
"resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz",
"dependencies": {
"minimatch": {
"version": "3.0.0",
"from": "minimatch@>=3.0.0 <4.0.0"
}
}
},
"inflight": {
"version": "1.0.4",
"from": "inflight@>=1.0.4 <2.0.0",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
"dependencies": {
"once": {
"version": "1.3.2",
"from": "once@>=1.3.0 <2.0.0"
}
}
},
"rc": {
"version": "1.1.2",
"from": "rc@~1.1.0",
@ -2339,28 +2361,6 @@
}
}
},
"inflight": {
"version": "1.0.4",
"from": "inflight@>=1.0.4 <2.0.0",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
"dependencies": {
"once": {
"version": "1.3.2",
"from": "once@>=1.3.0 <2.0.0"
}
}
},
"fstream-ignore": {
"version": "1.0.3",
"from": "fstream-ignore@~1.0.3",
"resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz",
"dependencies": {
"minimatch": {
"version": "3.0.0",
"from": "minimatch@>=3.0.0 <4.0.0"
}
}
},
"bl": {
"version": "1.0.0",
"from": "bl@~1.0.0",
@ -3928,6 +3928,82 @@
}
}
},
"node-haste": {
"version": "2.0.0",
"from": "node-haste@*",
"dependencies": {
"graceful-fs": {
"version": "4.1.3",
"from": "graceful-fs@>=4.1.3 <5.0.0",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz"
},
"sane": {
"version": "1.3.1",
"from": "sane@>=1.3.1 <2.0.0",
"resolved": "https://registry.npmjs.org/sane/-/sane-1.3.1.tgz",
"dependencies": {
"exec-sh": {
"version": "0.2.0",
"from": "exec-sh@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
"dependencies": {
"merge": {
"version": "1.2.0",
"from": "merge@>=1.1.3 <2.0.0",
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz"
}
}
},
"fb-watchman": {
"version": "1.9.0",
"from": "fb-watchman@>=1.8.0 <2.0.0",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz"
},
"minimatch": {
"version": "0.2.14",
"from": "minimatch@>=0.2.14 <0.3.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
"dependencies": {
"lru-cache": {
"version": "2.7.3",
"from": "lru-cache@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
},
"sigmund": {
"version": "1.0.1",
"from": "sigmund@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
}
}
},
"walker": {
"version": "1.0.7",
"from": "walker@>=1.0.5 <1.1.0",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
"dependencies": {
"makeerror": {
"version": "1.0.11",
"from": "makeerror@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
"dependencies": {
"tmpl": {
"version": "1.0.4",
"from": "tmpl@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz"
}
}
}
}
},
"watch": {
"version": "0.10.0",
"from": "watch@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz"
}
}
}
}
},
"normalize-package-data": {
"version": "2.3.5",
"from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz",

View File

@ -148,6 +148,7 @@
"mkdirp": "^0.5.1",
"module-deps": "^3.9.1",
"node-fetch": "^1.3.3",
"node-haste": "^2.0.0",
"opn": "^3.0.2",
"optimist": "^0.6.1",
"progress": "^1.1.8",

View File

@ -1,8 +1,7 @@
'use strict';
jest
.dontMock('../../DependencyResolver/lib/getPlatformExtension')
.dontMock('../../DependencyResolver/lib/getAssetDataFromName')
.dontMock('node-haste/lib/lib/getPlatformExtension')
.dontMock('../');
jest
@ -16,6 +15,11 @@ var crypto = require('crypto');
var fs = require('fs');
describe('AssetServer', () => {
beforeEach(() => {
const NodeHaste = require('node-haste');
NodeHaste.getAssetDataFromName = require.requireActual('node-haste/lib/lib/getAssetDataFromName');
});
describe('assetServer.get', () => {
pit('should work for the simple case', () => {
const server = new AssetServer({

View File

@ -13,7 +13,7 @@ const Promise = require('promise');
const crypto = require('crypto');
const declareOpts = require('../lib/declareOpts');
const fs = require('fs');
const getAssetDataFromName = require('../DependencyResolver/lib/getAssetDataFromName');
const getAssetDataFromName = require('node-haste').getAssetDataFromName;
const path = require('path');
const stat = Promise.denodeify(fs.stat);

View File

@ -13,7 +13,7 @@ const fs = require('fs');
const path = require('path');
const Promise = require('promise');
const ProgressBar = require('progress');
const Cache = require('../DependencyResolver/Cache');
const Cache = require('node-haste').Cache;
const Transformer = require('../JSTransformer');
const Resolver = require('../Resolver');
const Bundle = require('./Bundle');

View File

@ -1,47 +0,0 @@
'use strict';
const Module = require('./Module');
const Promise = require('promise');
const getAssetDataFromName = require('./lib/getAssetDataFromName');
class AssetModule extends Module {
constructor(...args) {
super(...args);
const { resolution, name, type } = getAssetDataFromName(this.path);
this.resolution = resolution;
this._name = name;
this._type = type;
}
isHaste() {
return Promise.resolve(false);
}
getDependencies() {
return Promise.resolve([]);
}
read() {
return Promise.resolve({});
}
getName() {
return super.getName().then(
id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`)
);
}
hash() {
return `AssetModule : ${this.path}`;
}
isJSON() {
return false;
}
isAsset() {
return true;
}
}
module.exports = AssetModule;

View File

@ -1,45 +0,0 @@
'use strict';
const Module = require('./Module');
const Promise = require('promise');
const getAssetDataFromName = require('./lib/getAssetDataFromName');
class AssetModule_DEPRECATED extends Module {
constructor(...args) {
super(...args);
const {resolution, name} = getAssetDataFromName(this.path);
this.resolution = resolution;
this.name = name;
}
isHaste() {
return Promise.resolve(false);
}
getName() {
return Promise.resolve(`image!${this.name}`);
}
getDependencies() {
return Promise.resolve([]);
}
hash() {
return `AssetModule_DEPRECATED : ${this.path}`;
}
isJSON() {
return false;
}
isAsset_DEPRECATED() {
return true;
}
resolution() {
return getAssetDataFromName(this.path).resolution;
}
}
module.exports = AssetModule_DEPRECATED;

View File

@ -1,20 +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';
class Cache {
get(filepath, field, cb) {
return cb(filepath);
}
invalidate(filepath) { }
end() { }
}
module.exports = Cache;

View File

@ -1,335 +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('absolute-path')
.dontMock('../')
.dontMock('../lib/loadCacheSync')
.dontMock('../lib/getCacheFilePath');
jest
.mock('fs')
.setMock('os', {
tmpDir() { return 'tmpDir'; },
});
var Promise = require('promise');
var fs = require('graceful-fs');
var Cache = require('../');
describe('Cache', () => {
describe('getting/setting', () => {
pit('calls loader callback for uncached file', () => {
fs.stat.mockImpl((file, callback) => {
callback(null, {
mtime: {
getTime: () => {},
},
});
});
var cache = new Cache({
cacheKey: 'cache',
});
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', () => {
fs.stat.mockImpl((file, callback) => {
callback(null, {
mtime: {
getTime: () => {},
},
});
});
var cache = new Cache({
cacheKey: 'cache',
});
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', () => {
fs.stat.mockImpl((file, callback) =>
callback(null, {
mtime: {
getTime: () => {},
},
})
);
var cache = new Cache({
cacheKey: 'cache',
});
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', () => {
fs.stat.mockImpl((file, callback) => {
callback(null, {
mtime: {
getTime: () => {},
},
});
});
var cache = new Cache({
cacheKey: 'cache',
});
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;
fs.stat.mockImpl((file, callback) => {
callback(null, {
mtime: {
getTime: () => mtime++,
},
});
});
var cache = new Cache({
cacheKey: 'cache',
});
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,
},
},
};
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({
cacheKey: 'cache',
});
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', () => {
fs.stat.mockImpl((file, callback) =>
callback(null, {
mtime: {
getTime: () => {},
},
})
);
fileStats['/rootDir/foo'].mtime.getTime = () => 123;
var cache = new Cache({
cacheKey: 'cache',
});
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];
fs.stat.mockImpl((file, callback) =>
callback(null, {
mtime: {
getTime: () => mtimes[index++],
},
})
);
var cache = new Cache({
cacheKey: 'cache',
});
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 has some trouble with promises and timeouts within promises :(
jest.runAllTimers();
jest.runAllTimers();
expect(fs.writeFile).toBeCalled();
});
});
describe('check for cache presence', () => {
it('synchronously resolves cache presence', () => {
fs.stat.mockImpl((file, callback) =>
callback(null, {
mtime: {
getTime: () => {},
},
})
);
var cache = new Cache({
cacheKey: 'cache',
});
var loaderCb = jest.genMockFn().mockImpl(() =>
Promise.resolve('banana')
);
var file = '/rootDir/someFile';
return cache
.get(file, 'field', loaderCb)
.then(() => {
expect(cache.has(file)).toBe(true);
expect(cache.has(file, 'field')).toBe(true);
expect(cache.has(file, 'foo')).toBe(false);
});
});
});
describe('invalidate', () => {
it('invalidates the cache per file or per-field', () => {
fs.stat.mockImpl((file, callback) =>
callback(null, {
mtime: {
getTime: () => {},
},
})
);
var cache = new Cache({
cacheKey: 'cache',
});
var loaderCb = jest.genMockFn().mockImpl(() =>
Promise.resolve('banana')
);
var file = '/rootDir/someFile';
return cache.get(file, 'field', loaderCb).then(() => {
expect(cache.has(file)).toBe(true);
cache.invalidate(file, 'field');
expect(cache.has(file)).toBe(true);
expect(cache.has(file, 'field')).toBe(false);
cache.invalidate(file);
expect(cache.has(file)).toBe(false);
});
});
});
});

View File

@ -1,190 +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';
const Promise = require('promise');
const fs = require('graceful-fs');
const getCacheFilePath = require('./lib/getCacheFilePath');
const isAbsolutePath = require('absolute-path');
const loadCacheSync = require('./lib/loadCacheSync');
const tmpDir = require('os').tmpDir();
function getObjectValues(object) {
return Object.keys(object).map(key => object[key]);
}
function debounce(fn, delay) {
var timeout;
return () => {
clearTimeout(timeout);
timeout = setTimeout(fn, delay);
};
}
class Cache {
constructor({
resetCache,
cacheKey,
cacheDirectory = tmpDir,
}) {
this._cacheFilePath = getCacheFilePath(cacheDirectory, cacheKey);
if (!resetCache) {
this._data = this._loadCacheSync(this._cacheFilePath);
} else {
this._data = Object.create(null);
}
this._persistEventually = debounce(
this._persistCache.bind(this),
2000,
);
}
get(filepath, field, loaderCb) {
if (!isAbsolutePath(filepath)) {
throw new Error('Use absolute paths');
}
return this.has(filepath, field)
? this._data[filepath].data[field]
: this.set(filepath, field, loaderCb(filepath));
}
invalidate(filepath, field) {
if (this.has(filepath, field)) {
if (field == null) {
delete this._data[filepath];
} else {
delete this._data[filepath].data[field];
}
}
}
end() {
return this._persistCache();
}
has(filepath, field) {
return Object.prototype.hasOwnProperty.call(this._data, filepath) &&
(field == null || 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;
}
const data = this._data;
const cacheFilepath = this._cacheFilePath;
const allPromises = getObjectValues(data)
.map(record => {
const fieldNames = Object.keys(record.data);
const fieldValues = getObjectValues(record.data);
return Promise
.all(fieldValues)
.then(ref => {
const 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 => {
const json = Object.create(null);
Object.keys(data).forEach((key, i) => {
// make sure the key wasn't added nor removed after we started
// persisting the cache
const value = values[i];
if (!value) {
return;
}
json[key] = Object.create(null);
json[key].metadata = data[key].metadata;
json[key].data = value.data;
});
return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json));
})
.catch(e => console.error('Error while persisting cache:', e.message))
.then(() => {
this._persisting = null;
return true;
});
return this._persisting;
}
_loadCacheSync(cachePath) {
var ret = Object.create(null);
var cacheOnDisk = loadCacheSync(cachePath);
// 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;
}
}
module.exports = Cache;

View File

@ -1,20 +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';
const crypto = require('crypto');
const path = require('fast-path');
function getCacheFilePath(tmpdir, ...args) {
const hash = crypto.createHash('md5');
args.forEach(arg => hash.update(arg));
return path.join(tmpdir, hash.digest('hex'));
}
module.exports = getCacheFilePath;

View File

@ -1,34 +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';
const fs = require('graceful-fs');
function loadCacheSync(cachePath) {
if (!fs.existsSync(cachePath)) {
return Object.create(null);
}
try {
return JSON.parse(fs.readFileSync(cachePath));
} catch (e) {
if (e instanceof SyntaxError) {
console.warn('Unable to parse cache file. Will clear and continue.');
try {
fs.unlinkSync(cachePath);
} catch (err) {
// Someone else might've deleted it.
}
return Object.create(null);
}
throw e;
}
}
module.exports = loadCacheSync;

View File

@ -1,45 +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';
const path = require('fast-path');
class DependencyGraphHelpers {
constructor({ providesModuleNodeModules, assetExts }) {
this._providesModuleNodeModules = providesModuleNodeModules;
this._assetExts = assetExts;
}
isNodeModulesDir(file) {
const index = file.lastIndexOf('/node_modules/');
if (index === -1) {
return false;
}
const parts = file.substr(index + 14).split(path.sep);
const dirs = this._providesModuleNodeModules;
for (let i = 0; i < dirs.length; i++) {
if (parts.indexOf(dirs[i]) > -1) {
return false;
}
}
return true;
}
isAssetFile(file) {
return this._assetExts.indexOf(this.extname(file)) !== -1;
}
extname(name) {
return path.extname(name).substr(1);
}
}
module.exports = DependencyGraphHelpers;

View File

@ -1,119 +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';
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs');
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const path = require('fast-path');
const Promise = require('promise');
class DeprecatedAssetMap {
constructor({
fsCrawl,
roots,
assetExts,
fileWatcher,
ignoreFilePath,
helpers,
activity,
enabled,
}) {
if (roots == null || roots.length === 0 || !enabled) {
this._disabled = true;
return;
}
this._helpers = helpers;
this._map = Object.create(null);
this._assetExts = assetExts;
this._activity = activity;
if (!this._disabled) {
this._fastfs = new Fastfs(
'Assets',
roots,
fileWatcher,
{ ignore: ignoreFilePath, crawling: fsCrawl, activity }
);
this._fastfs.on('change', this._processFileChange.bind(this));
}
}
build() {
if (this._disabled) {
return Promise.resolve();
}
return this._fastfs.build().then(
() => {
const activity = this._activity;
let processAsset_DEPRECATEDActivity;
if (activity) {
processAsset_DEPRECATEDActivity = activity.startEvent(
'Building (deprecated) Asset Map',
);
}
this._fastfs.findFilesByExts(this._assetExts).forEach(
file => this._processAsset(file)
);
if (activity) {
activity.endEvent(processAsset_DEPRECATEDActivity);
}
}
);
}
resolve(fromModule, toModuleName) {
if (this._disabled) {
return null;
}
const assetMatch = toModuleName.match(/^image!(.+)/);
if (assetMatch && assetMatch[1]) {
if (!this._map[assetMatch[1]]) {
debug('WARINING: Cannot find asset:', assetMatch[1]);
return null;
}
return this._map[assetMatch[1]];
}
}
_processAsset(file) {
const ext = this._helpers.extname(file);
if (this._assetExts.indexOf(ext) !== -1) {
const name = assetName(file, ext);
if (this._map[name] != null) {
debug('Conflicting assets', name);
}
this._map[name] = new AssetModule_DEPRECATED({ file });
}
}
_processFileChange(type, filePath, root, fstat) {
const name = assetName(filePath);
if (type === 'change' || type === 'delete') {
delete this._map[name];
}
if (type === 'change' || type === 'add') {
this._processAsset(path.join(root, filePath));
}
}
}
function assetName(file, ext) {
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
}
module.exports = DeprecatedAssetMap;

View File

@ -1,142 +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';
const path = require('fast-path');
const getPlatformExtension = require('../lib/getPlatformExtension');
const Promise = require('promise');
const GENERIC_PLATFORM = 'generic';
const NATIVE_PLATFORM = 'native';
class HasteMap {
constructor({
extensions,
fastfs,
moduleCache,
preferNativePlatform,
helpers,
}) {
this._extensions = extensions;
this._fastfs = fastfs;
this._moduleCache = moduleCache;
this._preferNativePlatform = preferNativePlatform;
this._helpers = helpers;
}
build() {
this._map = Object.create(null);
const promises = [];
this._fastfs.getAllFiles().forEach(filePath => {
if (!this._helpers.isNodeModulesDir(filePath)) {
if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) {
promises.push(this._processHasteModule(filePath));
}
if (filePath.endsWith('/package.json')) {
promises.push(this._processHastePackage(filePath));
}
}
});
return Promise.all(promises).then(() => this._map);
}
processFileChange(type, absPath) {
return Promise.resolve().then(() => {
/*eslint no-labels: 0 */
if (type === 'delete' || type === 'change') {
loop: for (const name in this._map) {
const modulesMap = this._map[name];
for (const platform in modulesMap) {
const module = modulesMap[platform];
if (module.path === absPath) {
delete modulesMap[platform];
break loop;
}
}
}
if (type === 'delete') {
return null;
}
}
if (this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) {
if (path.basename(absPath) === 'package.json') {
return this._processHastePackage(absPath);
} else {
return this._processHasteModule(absPath);
}
}
});
}
getModule(name, platform = null) {
const modulesMap = this._map[name];
if (modulesMap == null) {
return null;
}
// If platform is 'ios', we prefer .ios.js to .native.js which we prefer to
// a plain .js file.
let module = undefined;
if (module == null && platform != null) {
module = modulesMap[platform];
}
if (module == null && this._preferNativePlatform) {
module = modulesMap[NATIVE_PLATFORM];
}
if (module == null) {
module = modulesMap[GENERIC_PLATFORM];
}
return module;
}
_processHasteModule(file) {
const module = this._moduleCache.getModule(file);
return module.isHaste().then(
isHaste => isHaste && module.getName()
.then(name => this._updateHasteMap(name, module))
);
}
_processHastePackage(file) {
file = path.resolve(file);
const p = this._moduleCache.getPackage(file);
return p.isHaste()
.then(isHaste => isHaste && p.getName()
.then(name => this._updateHasteMap(name, p)))
.catch(e => {
if (e instanceof SyntaxError) {
// Malformed package.json.
return;
}
throw e;
});
}
_updateHasteMap(name, mod) {
if (this._map[name] == null) {
this._map[name] = Object.create(null);
}
const moduleMap = this._map[name];
const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM;
const existingModule = moduleMap[modulePlatform];
if (existingModule && existingModule.path !== mod.path) {
throw new Error(
`Naming collision detected: ${mod.path} ` +
`collides with ${existingModule.path}`
);
}
moduleMap[modulePlatform] = mod;
}
}
module.exports = HasteMap;

View File

@ -1,446 +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';
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const util = require('util');
const path = require('fast-path');
const realPath = require('path');
const isAbsolutePath = require('absolute-path');
const getAssetDataFromName = require('../lib/getAssetDataFromName');
const Promise = require('promise');
class ResolutionRequest {
constructor({
platform,
preferNativePlatform,
entryPath,
hasteMap,
deprecatedAssetMap,
helpers,
moduleCache,
fastfs,
shouldThrowOnUnresolvedErrors,
}) {
this._platform = platform;
this._preferNativePlatform = preferNativePlatform;
this._entryPath = entryPath;
this._hasteMap = hasteMap;
this._deprecatedAssetMap = deprecatedAssetMap;
this._helpers = helpers;
this._moduleCache = moduleCache;
this._fastfs = fastfs;
this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors;
this._resetResolutionCache();
}
_tryResolve(action, secondaryAction) {
return action().catch((error) => {
if (error.type !== 'UnableToResolveError') {
throw error;
}
return secondaryAction();
});
}
resolveDependency(fromModule, toModuleName) {
const resHash = resolutionHash(fromModule.path, toModuleName);
if (this._immediateResolutionCache[resHash]) {
return Promise.resolve(this._immediateResolutionCache[resHash]);
}
const asset_DEPRECATED = this._deprecatedAssetMap.resolve(
fromModule,
toModuleName
);
if (asset_DEPRECATED) {
return Promise.resolve(asset_DEPRECATED);
}
const cacheResult = (result) => {
this._immediateResolutionCache[resHash] = result;
return result;
};
const forgive = (error) => {
if (
error.type !== 'UnableToResolveError' ||
this._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform)
) {
throw error;
}
debug(
'Unable to resolve module %s from %s',
toModuleName,
fromModule.path
);
return null;
};
if (!this._helpers.isNodeModulesDir(fromModule.path)
&& toModuleName[0] !== '.' &&
toModuleName[0] !== '/') {
return this._tryResolve(
() => this._resolveHasteDependency(fromModule, toModuleName),
() => this._resolveNodeDependency(fromModule, toModuleName)
).then(
cacheResult,
forgive,
);
}
return this._resolveNodeDependency(fromModule, toModuleName)
.then(
cacheResult,
forgive,
);
}
getOrderedDependencies(response, mocksPattern, recursive = true) {
return this._getAllMocks(mocksPattern).then(allMocks => {
const entry = this._moduleCache.getModule(this._entryPath);
const mocks = Object.create(null);
const visited = Object.create(null);
visited[entry.hash()] = true;
response.pushDependency(entry);
const collect = (mod) => {
return mod.getDependencies().then(
depNames => Promise.all(
depNames.map(name => this.resolveDependency(mod, name))
).then((dependencies) => [depNames, dependencies])
).then(([depNames, dependencies]) => {
if (allMocks) {
const list = [mod.getName()];
const pkg = mod.getPackage();
if (pkg) {
list.push(pkg.getName());
}
return Promise.all(list).then(names => {
names.forEach(name => {
if (allMocks[name] && !mocks[name]) {
const mockModule =
this._moduleCache.getModule(allMocks[name]);
depNames.push(name);
dependencies.push(mockModule);
mocks[name] = allMocks[name];
}
});
return [depNames, dependencies];
});
}
return Promise.resolve([depNames, dependencies]);
}).then(([depNames, dependencies]) => {
let p = Promise.resolve();
const filteredPairs = [];
dependencies.forEach((modDep, i) => {
const name = depNames[i];
if (modDep == null) {
// It is possible to require mocks that don't have a real
// module backing them. If a dependency cannot be found but there
// exists a mock with the desired ID, resolve it and add it as
// a dependency.
if (allMocks && allMocks[name] && !mocks[name]) {
const mockModule = this._moduleCache.getModule(allMocks[name]);
mocks[name] = allMocks[name];
return filteredPairs.push([name, mockModule]);
}
debug(
'WARNING: Cannot find required module `%s` from module `%s`',
name,
mod.path
);
return false;
}
return filteredPairs.push([name, modDep]);
});
response.setResolvedDependencyPairs(mod, filteredPairs);
filteredPairs.forEach(([depName, modDep]) => {
p = p.then(() => {
if (!visited[modDep.hash()]) {
visited[modDep.hash()] = true;
response.pushDependency(modDep);
if (recursive) {
return collect(modDep);
}
}
return null;
});
});
return p;
});
};
return collect(entry).then(() => response.setMocks(mocks));
});
}
_getAllMocks(pattern) {
// Take all mocks in all the roots into account. This is necessary
// because currently mocks are global: any module can be mocked by
// any mock in the system.
let mocks = null;
if (pattern) {
mocks = Object.create(null);
this._fastfs.matchFilesByPattern(pattern).forEach(file =>
mocks[path.basename(file, path.extname(file))] = file
);
}
return Promise.resolve(mocks);
}
_resolveHasteDependency(fromModule, toModuleName) {
toModuleName = normalizePath(toModuleName);
let p = fromModule.getPackage();
if (p) {
p = p.redirectRequire(toModuleName);
} else {
p = Promise.resolve(toModuleName);
}
return p.then((realModuleName) => {
let dep = this._hasteMap.getModule(realModuleName, this._platform);
if (dep && dep.type === 'Module') {
return dep;
}
let packageName = realModuleName;
while (packageName && packageName !== '.') {
dep = this._hasteMap.getModule(packageName, this._platform);
if (dep && dep.type === 'Package') {
break;
}
packageName = path.dirname(packageName);
}
if (dep && dep.type === 'Package') {
const potentialModulePath = path.join(
dep.root,
path.relative(packageName, realModuleName)
);
return this._tryResolve(
() => this._loadAsFile(
potentialModulePath,
fromModule,
toModuleName,
),
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName),
);
}
throw new UnableToResolveError(
fromModule,
toModuleName,
'Unable to resolve dependency',
);
});
}
_redirectRequire(fromModule, modulePath) {
return Promise.resolve(fromModule.getPackage()).then(p => {
if (p) {
return p.redirectRequire(modulePath);
}
return modulePath;
});
}
_resolveFileOrDir(fromModule, toModuleName) {
const potentialModulePath = isAbsolutePath(toModuleName) ?
toModuleName :
path.join(path.dirname(fromModule.path), toModuleName);
return this._redirectRequire(fromModule, potentialModulePath).then(
realModuleName => this._tryResolve(
() => this._loadAsFile(realModuleName, fromModule, toModuleName),
() => this._loadAsDir(realModuleName, fromModule, toModuleName)
)
);
}
_resolveNodeDependency(fromModule, toModuleName) {
if (toModuleName[0] === '.' || toModuleName[1] === '/') {
return this._resolveFileOrDir(fromModule, toModuleName);
} else {
return this._redirectRequire(fromModule, toModuleName).then(
realModuleName => {
if (realModuleName[0] === '.' || realModuleName[1] === '/') {
// derive absolute path /.../node_modules/fromModuleDir/realModuleName
const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13;
const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx));
const absPath = path.join(fromModuleDir, realModuleName);
return this._resolveFileOrDir(fromModule, absPath);
}
const searchQueue = [];
for (let currDir = path.dirname(fromModule.path);
currDir !== realPath.parse(fromModule.path).root;
currDir = path.dirname(currDir)) {
searchQueue.push(
path.join(currDir, 'node_modules', realModuleName)
);
}
let p = Promise.reject(new UnableToResolveError(
fromModule,
toModuleName,
'Node module not found',
));
searchQueue.forEach(potentialModulePath => {
p = this._tryResolve(
() => this._tryResolve(
() => p,
() => this._loadAsFile(potentialModulePath, fromModule, toModuleName),
),
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName)
);
});
return p;
});
}
}
_loadAsFile(potentialModulePath, fromModule, toModule) {
return Promise.resolve().then(() => {
if (this._helpers.isAssetFile(potentialModulePath)) {
const dirname = path.dirname(potentialModulePath);
if (!this._fastfs.dirExists(dirname)) {
throw new UnableToResolveError(
fromModule,
toModule,
`Directory ${dirname} doesn't exist`,
);
}
const {name, type} = getAssetDataFromName(potentialModulePath);
let pattern = '^' + name + '(@[\\d\\.]+x)?';
if (this._platform != null) {
pattern += '(\\.' + this._platform + ')?';
}
pattern += '\\.' + type;
// We arbitrarly grab the first one, because scale selection
// will happen somewhere
const [assetFile] = this._fastfs.matches(
dirname,
new RegExp(pattern)
);
if (assetFile) {
return this._moduleCache.getAssetModule(assetFile);
}
}
let file;
if (this._fastfs.fileExists(potentialModulePath)) {
file = potentialModulePath;
} else if (this._platform != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) {
file = potentialModulePath + '.' + this._platform + '.js';
} else if (this._preferNativePlatform &&
this._fastfs.fileExists(potentialModulePath + '.native.js')) {
file = potentialModulePath + '.native.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
file = potentialModulePath + '.json';
} else {
throw new UnableToResolveError(
fromModule,
toModule,
`File ${potentialModulePath} doesnt exist`,
);
}
return this._moduleCache.getModule(file);
});
}
_loadAsDir(potentialDirPath, fromModule, toModule) {
return Promise.resolve().then(() => {
if (!this._fastfs.dirExists(potentialDirPath)) {
throw new UnableToResolveError(
fromModule,
toModule,
`Unable to find this module in its module map or any of the node_modules directories under ${potentialDirPath} and its parent directories
This might be related to https://github.com/facebook/react-native/issues/4968
To resolve try the following:
1. Clear watchman watches: \`watchman watch-del-all\`.
2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`.
3. Reset packager cache: \`rm -fr $TMPDIR/react-*\` or \`npm start -- --reset-cache\`.`,
);
}
const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (this._fastfs.fileExists(packageJsonPath)) {
return this._moduleCache.getPackage(packageJsonPath)
.getMain().then(
(main) => this._tryResolve(
() => this._loadAsFile(main, fromModule, toModule),
() => this._loadAsDir(main, fromModule, toModule)
)
);
}
return this._loadAsFile(
path.join(potentialDirPath, 'index'),
fromModule,
toModule,
);
});
}
_resetResolutionCache() {
this._immediateResolutionCache = Object.create(null);
}
}
function resolutionHash(modulePath, depName) {
return `${path.resolve(modulePath)}:${depName}`;
}
function UnableToResolveError(fromModule, toModule, message) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.message = util.format(
'Unable to resolve module %s from %s: %s',
toModule,
fromModule.path,
message,
);
this.type = this.name = 'UnableToResolveError';
}
util.inherits(UnableToResolveError, Error);
function normalizePath(modulePath) {
if (path.sep === '/') {
modulePath = path.normalize(modulePath);
} else if (path.posix) {
modulePath = path.posix.normalize(modulePath);
}
return modulePath.replace(/\/$/, '');
}
module.exports = ResolutionRequest;

View File

@ -1,87 +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';
class ResolutionResponse {
constructor() {
this.dependencies = [];
this.mainModuleId = null;
this.mocks = null;
this.numPrependedDependencies = 0;
this._mappings = Object.create(null);
this._finalized = false;
}
copy(properties) {
const {
dependencies = this.dependencies,
mainModuleId = this.mainModuleId,
mocks = this.mocks,
} = properties;
return Object.assign(new this.constructor(), this, {
dependencies,
mainModuleId,
mocks,
});
}
_assertNotFinalized() {
if (this._finalized) {
throw new Error('Attempted to mutate finalized response.');
}
}
_assertFinalized() {
if (!this._finalized) {
throw new Error('Attempted to access unfinalized response.');
}
}
finalize() {
return this._mainModule.getName().then(id => {
this.mainModuleId = id;
this._finalized = true;
return this;
});
}
pushDependency(module) {
this._assertNotFinalized();
if (this.dependencies.length === 0) {
this._mainModule = module;
}
this.dependencies.push(module);
}
prependDependency(module) {
this._assertNotFinalized();
this.dependencies.unshift(module);
this.numPrependedDependencies += 1;
}
setResolvedDependencyPairs(module, pairs) {
this._assertNotFinalized();
const hash = module.hash();
if (this._mappings[hash] == null) {
this._mappings[hash] = pairs;
}
}
setMocks(mocks) {
this.mocks = mocks;
}
getResolvedDependencyPairs(module) {
this._assertFinalized();
return this._mappings[module.hash()];
}
}
module.exports = ResolutionResponse;

View File

@ -1,83 +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 docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
var ltrimRe = /^\s*/;
/**
* @param {String} contents
* @return {String}
*/
function extract(contents) {
var match = contents.match(docblockRe);
if (match) {
return match[0].replace(ltrimRe, '') || '';
}
return '';
}
var commentStartRe = /^\/\*\*/;
var commentEndRe = /\*\/$/;
var wsRe = /[\t ]+/g;
var stringStartRe = /(\r?\n|^) *\*/g;
var multilineRe =
/(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
/**
* @param {String} contents
* @return {Array}
*/
function parse(docblock) {
docblock = docblock
.replace(commentStartRe, '')
.replace(commentEndRe, '')
.replace(wsRe, ' ')
.replace(stringStartRe, '$1');
// Normalize multi-line directives
var prev = '';
while (prev !== docblock) {
prev = docblock;
docblock = docblock.replace(multilineRe, '\n$1 $2\n');
}
docblock = docblock.trim();
var result = [];
var match;
while ((match = propertyRe.exec(docblock))) {
result.push([match[1], match[2]]);
}
return result;
}
/**
* Same as parse but returns an object of prop: value instead of array of paris
* If a property appers more than once the last one will be returned
*
* @param {String} contents
* @return {Object}
*/
function parseAsObject(docblock) {
var pairs = parse(docblock);
var result = {};
for (var i = 0; i < pairs.length; i++) {
result[pairs[i][0]] = pairs[i][1];
}
return result;
}
exports.extract = extract;
exports.parse = parse;
exports.parseAsObject = parseAsObject;

View File

@ -1,287 +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';
const Fastfs = require('../fastfs');
const ModuleCache = require('../ModuleCache');
const Promise = require('promise');
const crawl = require('../crawlers');
const getPlatformExtension = require('../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path');
const path = require('fast-path');
const util = require('util');
const DependencyGraphHelpers = require('./DependencyGraphHelpers');
const ResolutionRequest = require('./ResolutionRequest');
const ResolutionResponse = require('./ResolutionResponse');
const HasteMap = require('./HasteMap');
const DeprecatedAssetMap = require('./DeprecatedAssetMap');
const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError';
const defaultActivity = {
startEvent: () => {},
endEvent: () => {},
};
class DependencyGraph {
constructor({
activity,
roots,
ignoreFilePath,
fileWatcher,
assetRoots_DEPRECATED,
assetExts,
providesModuleNodeModules,
platforms,
preferNativePlatform,
cache,
extensions,
mocksPattern,
extractRequires,
transformCode,
shouldThrowOnUnresolvedErrors = () => true,
enableAssetMap,
}) {
this._opts = {
activity: activity || defaultActivity,
roots,
ignoreFilePath: ignoreFilePath || (() => {}),
fileWatcher,
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
assetExts: assetExts || [],
providesModuleNodeModules,
platforms: platforms || [],
preferNativePlatform: preferNativePlatform || false,
extensions: extensions || ['js', 'json'],
mocksPattern,
extractRequires,
transformCode,
shouldThrowOnUnresolvedErrors,
enableAssetMap: enableAssetMap || true,
};
this._cache = cache;
this._helpers = new DependencyGraphHelpers(this._opts);
this.load();
}
load() {
if (this._loading) {
return this._loading;
}
const {activity} = this._opts;
const depGraphActivity = activity.startEvent('Building Dependency Graph');
const crawlActivity = activity.startEvent('Crawling File System');
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
this._crawling = crawl(allRoots, {
ignore: this._opts.ignoreFilePath,
exts: this._opts.extensions.concat(this._opts.assetExts),
fileWatcher: this._opts.fileWatcher,
});
this._crawling.then((files) => activity.endEvent(crawlActivity));
this._fastfs = new Fastfs(
'JavaScript',
this._opts.roots,
this._opts.fileWatcher,
{
ignore: this._opts.ignoreFilePath,
crawling: this._crawling,
activity: activity,
}
);
this._fastfs.on('change', this._processFileChange.bind(this));
this._moduleCache = new ModuleCache({
fastfs: this._fastfs,
cache: this._cache,
extractRequires: this._opts.extractRequires,
transformCode: this._opts.transformCode,
depGraphHelpers: this._helpers,
});
this._hasteMap = new HasteMap({
fastfs: this._fastfs,
extensions: this._opts.extensions,
moduleCache: this._moduleCache,
preferNativePlatform: this._opts.preferNativePlatform,
helpers: this._helpers,
});
this._deprecatedAssetMap = new DeprecatedAssetMap({
fsCrawl: this._crawling,
roots: this._opts.assetRoots_DEPRECATED,
helpers: this._helpers,
fileWatcher: this._opts.fileWatcher,
ignoreFilePath: this._opts.ignoreFilePath,
assetExts: this._opts.assetExts,
activity: this._opts.activity,
enabled: this._opts.enableAssetMap,
});
this._loading = Promise.all([
this._fastfs.build()
.then(() => {
const hasteActivity = activity.startEvent('Building Haste Map');
return this._hasteMap.build().then(map => {
activity.endEvent(hasteActivity);
return map;
});
}),
this._deprecatedAssetMap.build(),
]).then(
response => {
activity.endEvent(depGraphActivity);
return response[0]; // Return the haste map
},
err => {
const error = new Error(
`Failed to build DependencyGraph: ${err.message}`
);
error.type = ERROR_BUILDING_DEP_GRAPH;
error.stack = err.stack;
throw error;
}
);
return this._loading;
}
/**
* Returns a promise with the direct dependencies the module associated to
* the given entryPath has.
*/
getShallowDependencies(entryPath) {
return this._moduleCache.getModule(entryPath).getDependencies();
}
getFS() {
return this._fastfs;
}
/**
* Returns the module object for the given path.
*/
getModuleForPath(entryFile) {
return this._moduleCache.getModule(entryFile);
}
getAllModules() {
return this.load().then(() => this._moduleCache.getAllModules());
}
getDependencies(entryPath, platform, recursive = true) {
return this.load().then(() => {
platform = this._getRequestPlatform(entryPath, platform);
const absPath = this._getAbsolutePath(entryPath);
const req = new ResolutionRequest({
platform,
preferNativePlatform: this._opts.preferNativePlatform,
entryPath: absPath,
deprecatedAssetMap: this._deprecatedAssetMap,
hasteMap: this._hasteMap,
helpers: this._helpers,
moduleCache: this._moduleCache,
fastfs: this._fastfs,
shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors,
});
const response = new ResolutionResponse();
return req.getOrderedDependencies(
response,
this._opts.mocksPattern,
recursive,
).then(() => response);
});
}
matchFilesByPattern(pattern) {
return this.load().then(() => this._fastfs.matchFilesByPattern(pattern));
}
_getRequestPlatform(entryPath, platform) {
if (platform == null) {
platform = getPlatformExtension(entryPath);
if (platform == null || this._opts.platforms.indexOf(platform) === -1) {
platform = null;
}
} else if (this._opts.platforms.indexOf(platform) === -1) {
throw new Error('Unrecognized platform: ' + platform);
}
return platform;
}
_getAbsolutePath(filePath) {
if (isAbsolutePath(filePath)) {
return path.resolve(filePath);
}
for (let i = 0; i < this._opts.roots.length; i++) {
const root = this._opts.roots[i];
const potentialAbsPath = path.join(root, filePath);
if (this._fastfs.fileExists(potentialAbsPath)) {
return path.resolve(potentialAbsPath);
}
}
throw new NotFoundError(
'Cannot find entry file %s in any of the roots: %j',
filePath,
this._opts.roots
);
}
_processFileChange(type, filePath, root, fstat) {
const absPath = path.join(root, filePath);
if (fstat && fstat.isDirectory() ||
this._opts.ignoreFilePath(absPath) ||
this._helpers.isNodeModulesDir(absPath)) {
return;
}
// Ok, this is some tricky promise code. Our requirements are:
// * we need to report back failures
// * failures shouldn't block recovery
// * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild
// After we process a file change we record any errors which will also be
// reported via the next request. On the next file change, we'll see that
// we are in an error state and we should decide to do a full rebuild.
this._loading = this._loading.finally(() => {
if (this._hasteMapError) {
console.warn(
'Rebuilding haste map to recover from error:\n' +
this._hasteMapError.stack
);
this._hasteMapError = null;
// Rebuild the entire map if last change resulted in an error.
this._loading = this._hasteMap.build();
} else {
this._loading = this._hasteMap.processFileChange(type, absPath);
this._loading.catch((e) => this._hasteMapError = e);
}
return this._loading;
});
}
}
function NotFoundError() {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
var msg = util.format.apply(util, arguments);
this.message = msg;
this.type = this.name = 'NotFoundError';
this.status = 404;
}
util.inherits(NotFoundError, Error);
module.exports = DependencyGraph;

View File

@ -1,14 +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';
module.exports = {
WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'),
NodeWatcher: jest.genMockFromModule('sane/src/node_watcher'),
};

View File

@ -1,89 +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('util')
.dontMock('events')
.dontMock('../')
.setMock('child_process', {
exec: (cmd, cb) => cb(null, '/usr/bin/watchman'),
});
const sane = require('sane');
describe('FileWatcher', () => {
let WatchmanWatcher;
let NodeWatcher;
let FileWatcher;
let config;
beforeEach(() => {
WatchmanWatcher = sane.WatchmanWatcher;
WatchmanWatcher.prototype.once.mockImplementation(
(type, callback) => callback()
);
NodeWatcher = sane.NodeWatcher;
NodeWatcher.prototype.once.mockImplementation(
(type, callback) => callback()
);
FileWatcher = require('../');
config = [{
dir: 'rootDir',
globs: [
'**/*.js',
'**/*.json',
],
}];
});
pit('gets the watcher instance when ready', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: true});
return fileWatcher.getWatchers().then(watchers => {
watchers.forEach(watcher => {
expect(watcher instanceof WatchmanWatcher).toBe(true);
});
});
});
pit('gets the node watcher if watchman is disabled', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: false});
return fileWatcher.getWatchers().then(watchers => {
watchers.forEach(watcher => {
expect(watcher instanceof NodeWatcher).toBe(true);
});
});
});
pit('emits events', () => {
let cb;
WatchmanWatcher.prototype.on.mockImplementation((type, callback) => {
cb = callback;
});
const fileWatcher = new FileWatcher(config, {useWatchman: true});
const handler = jest.genMockFn();
fileWatcher.on('all', handler);
return fileWatcher.getWatchers().then(watchers => {
cb(1, 2, 3, 4);
jest.runAllTimers();
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
});
});
pit('ends the watcher', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: true});
WatchmanWatcher.prototype.close.mockImplementation(callback => callback());
return fileWatcher.end().then(() => {
expect(WatchmanWatcher.prototype.close).toBeCalled();
});
});
});

View File

@ -1,128 +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';
const EventEmitter = require('events').EventEmitter;
const sane = require('sane');
const Promise = require('promise');
const exec = require('child_process').exec;
const MAX_WAIT_TIME = 120000;
// TODO(amasad): can we use watchman version command instead?
const detectingWatcherClass = new Promise(function(resolve) {
exec('which watchman', function(err, out) {
if (err || out.length === 0) {
resolve(sane.NodeWatcher);
} else {
resolve(sane.WatchmanWatcher);
}
});
});
let inited = false;
class FileWatcher extends EventEmitter {
constructor(rootConfigs, options) {
if (inited) {
throw new Error('FileWatcher can only be instantiated once');
}
inited = true;
super();
this._useWatchman = options.useWatchman;
this._watcherByRoot = Object.create(null);
this._loading = Promise.all(
rootConfigs.map(rootConfig => this._createWatcher(rootConfig))
).then(watchers => {
watchers.forEach((watcher, i) => {
this._watcherByRoot[rootConfigs[i].dir] = watcher;
watcher.on(
'all',
// args = (type, filePath, root, stat)
(...args) => this.emit('all', ...args)
);
});
return watchers;
});
this._loading.done();
}
getWatchers() {
return this._loading;
}
getWatcherForRoot(root) {
return this._loading.then(() => this._watcherByRoot[root]);
}
isWatchman() {
return this._useWatchman ? detectingWatcherClass.then(
Watcher => Watcher === sane.WatchmanWatcher
) : Promise.resolve(false);
}
end() {
inited = false;
return this._loading.then(
(watchers) => watchers.map(
watcher => Promise.denodeify(watcher.close).call(watcher)
)
);
}
_createWatcher(rootConfig) {
return detectingWatcherClass.then(Watcher => {
if (!this._useWatchman) {
Watcher = sane.NodeWatcher;
}
const watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
});
return new Promise((resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error(timeoutMessage(Watcher))),
MAX_WAIT_TIME
);
watcher.once('ready', () => {
clearTimeout(rejectTimeout);
resolve(watcher);
});
});
});
}
static createDummyWatcher() {
return Object.assign(new EventEmitter(), {
isWatchman: () => Promise.resolve(false),
end: () => Promise.resolve(),
});
}
}
function timeoutMessage(Watcher) {
const lines = [
'Watcher took too long to load (' + Watcher.name + ')',
];
if (Watcher === sane.WatchmanWatcher) {
lines.push(
'Try running `watchman version` from your terminal',
'https://facebook.github.io/watchman/docs/troubleshooting.html',
);
}
return lines.join('\n');
}
module.exports = FileWatcher;

View File

@ -1,226 +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';
const crypto = require('crypto');
const docblock = require('./DependencyGraph/docblock');
const isAbsolutePath = require('absolute-path');
const jsonStableStringify = require('json-stable-stringify');
const path = require('fast-path');
const extractRequires = require('./lib/extractRequires');
class Module {
constructor({
file,
fastfs,
moduleCache,
cache,
extractor = extractRequires,
transformCode,
depGraphHelpers,
}) {
if (!isAbsolutePath(file)) {
throw new Error('Expected file to be absolute path but got ' + file);
}
this.path = file;
this.type = 'Module';
this._fastfs = fastfs;
this._moduleCache = moduleCache;
this._cache = cache;
this._extractor = extractor;
this._transformCode = transformCode;
this._depGraphHelpers = depGraphHelpers;
}
isHaste() {
return this._cache.get(
this.path,
'isHaste',
() => this._readDocBlock().then(({id}) => !!id)
);
}
getCode(transformOptions) {
return this.read(transformOptions).then(({code}) => code);
}
getMap(transformOptions) {
return this.read(transformOptions).then(({map}) => map);
}
getName() {
return this._cache.get(
this.path,
'name',
() => this._readDocBlock().then(({id}) => {
if (id) {
return id;
}
const p = this.getPackage();
if (!p) {
// Name is full path
return this.path;
}
return p.getName()
.then(name => {
if (!name) {
return this.path;
}
return path.join(name, path.relative(p.root, this.path)).replace(/\\/g, '/');
});
})
);
}
getPackage() {
return this._moduleCache.getPackageForModule(this);
}
getDependencies(transformOptions) {
return this.read(transformOptions).then(data => data.dependencies);
}
invalidate() {
this._cache.invalidate(this.path);
}
_parseDocBlock(docBlock) {
// Extract an id for the module if it's using @providesModule syntax
// and if it's NOT in node_modules (and not a whitelisted node_module).
// This handles the case where a project may have a dep that has @providesModule
// docblock comments, but doesn't want it to conflict with whitelisted @providesModule
// modules, such as react-haste, fbjs-haste, or react-native or with non-dependency,
// project-specific code that is using @providesModule.
const moduleDocBlock = docblock.parseAsObject(docBlock);
const provides = moduleDocBlock.providesModule || moduleDocBlock.provides;
const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path)
? /^\S+/.exec(provides)[0]
: undefined;
return {id, moduleDocBlock};
}
_readDocBlock(contentPromise) {
if (!this._docBlock) {
if (!contentPromise) {
contentPromise = this._fastfs.readWhile(this.path, whileInDocBlock);
}
this._docBlock = contentPromise
.then(docBlock => this._parseDocBlock(docBlock));
}
return this._docBlock;
}
read(transformOptions) {
return this._cache.get(
this.path,
cacheKey('moduleData', transformOptions),
() => {
const fileContentPromise = this._fastfs.readFile(this.path);
return Promise.all([
fileContentPromise,
this._readDocBlock(fileContentPromise),
]).then(([code, {id, moduleDocBlock}]) => {
// Ignore requires in JSON files or generated code. An example of this
// is prebuilt files like the SourceMap library.
if (this.isJSON() || 'extern' in moduleDocBlock) {
return {id, code, dependencies: []};
} else {
const transformCode = this._transformCode;
const codePromise = transformCode
? transformCode(this, code, transformOptions)
: Promise.resolve({code});
return codePromise.then(({code, dependencies, map}) => {
if (!dependencies) {
dependencies = this._extractor(code).deps.sync;
}
return {id, code, dependencies, map};
});
}
});
}
);
}
hash() {
return `Module : ${this.path}`;
}
isJSON() {
return path.extname(this.path) === '.json';
}
isAsset() {
return false;
}
isPolyfill() {
return false;
}
isAsset_DEPRECATED() {
return false;
}
toJSON() {
return {
hash: this.hash(),
isJSON: this.isJSON(),
isAsset: this.isAsset(),
isAsset_DEPRECATED: this.isAsset_DEPRECATED(),
type: this.type,
path: this.path,
};
}
}
function whileInDocBlock(chunk, i, result) {
// consume leading whitespace
if (!/\S/.test(result)) {
return true;
}
// check for start of doc block
if (!/^\s*\/(\*{2}|\*?$)/.test(result)) {
return false;
}
// check for end of doc block
return !/\*\//.test(result);
}
// use weak map to speed up hash creation of known objects
const knownHashes = new WeakMap();
function stableObjectHash(object) {
let digest = knownHashes.get(object);
if (!digest) {
digest = crypto.createHash('md5')
.update(jsonStableStringify(object))
.digest('base64');
knownHashes.set(object, digest);
}
return digest;
}
function cacheKey(field, transformOptions) {
return transformOptions !== undefined
? stableObjectHash(transformOptions) + '\0' + field
: field;
}
module.exports = Module;

View File

@ -1,104 +0,0 @@
'use strict';
const AssetModule = require('./AssetModule');
const Package = require('./Package');
const Module = require('./Module');
const path = require('fast-path');
class ModuleCache {
constructor({
fastfs,
cache,
extractRequires,
transformCode,
depGraphHelpers,
}) {
this._moduleCache = Object.create(null);
this._packageCache = Object.create(null);
this._fastfs = fastfs;
this._cache = cache;
this._extractRequires = extractRequires;
this._transformCode = transformCode;
this._depGraphHelpers = depGraphHelpers;
fastfs.on('change', this._processFileChange.bind(this));
}
getModule(filePath) {
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new Module({
file: filePath,
fastfs: this._fastfs,
moduleCache: this,
cache: this._cache,
extractor: this._extractRequires,
transformCode: this._transformCode,
depGraphHelpers: this._depGraphHelpers,
});
}
return this._moduleCache[filePath];
}
getAllModules() {
return this._moduleCache;
}
getAssetModule(filePath) {
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new AssetModule({
file: filePath,
fastfs: this._fastfs,
moduleCache: this,
cache: this._cache,
});
}
return this._moduleCache[filePath];
}
getPackage(filePath) {
if (!this._packageCache[filePath]) {
this._packageCache[filePath] = new Package({
file: filePath,
fastfs: this._fastfs,
cache: this._cache,
});
}
return this._packageCache[filePath];
}
getPackageForModule(module) {
// TODO(amasad): use ES6 Map.
if (module.__package) {
if (this._packageCache[module.__package]) {
return this._packageCache[module.__package];
} else {
delete module.__package;
}
}
const packagePath = this._fastfs.closest(module.path, 'package.json');
if (!packagePath) {
return null;
}
module.__package = packagePath;
return this.getPackage(packagePath);
}
_processFileChange(type, filePath, root) {
const absPath = path.join(root, filePath);
if (this._moduleCache[absPath]) {
this._moduleCache[absPath].invalidate();
delete this._moduleCache[absPath];
}
if (this._packageCache[absPath]) {
this._packageCache[absPath].invalidate();
delete this._packageCache[absPath];
}
}
}
module.exports = ModuleCache;

View File

@ -1,100 +0,0 @@
'use strict';
const isAbsolutePath = require('absolute-path');
const path = require('fast-path');
class Package {
constructor({ file, fastfs, cache }) {
this.path = file;
this.root = path.dirname(this.path);
this._fastfs = fastfs;
this.type = 'Package';
this._cache = cache;
}
getMain() {
return this.read().then(json => {
var replacements = getReplacements(json);
if (typeof replacements === 'string') {
return path.join(this.root, replacements);
}
let main = json.main || 'index';
if (replacements && typeof replacements === 'object') {
main = replacements[main] ||
replacements[main + '.js'] ||
replacements[main + '.json'] ||
replacements[main.replace(/(\.js|\.json)$/, '')] ||
main;
}
return path.join(this.root, main);
});
}
isHaste() {
return this._cache.get(this.path, 'package-haste', () =>
this.read().then(json => !!json.name)
);
}
getName() {
return this._cache.get(this.path, 'package-name', () =>
this.read().then(json => json.name)
);
}
invalidate() {
this._cache.invalidate(this.path);
}
redirectRequire(name) {
return this.read().then(json => {
var replacements = getReplacements(json);
if (!replacements || typeof replacements !== 'object') {
return name;
}
if (name[0] !== '/') {
return replacements[name] || name;
}
if (!isAbsolutePath(name)) {
throw new Error(`Expected ${name} to be absolute path`);
}
const relPath = './' + path.relative(this.root, name);
const redirect = replacements[relPath] ||
replacements[relPath + '.js'] ||
replacements[relPath + '.json'];
if (redirect) {
return path.join(
this.root,
redirect
);
}
return name;
});
}
read() {
if (!this._reading) {
this._reading = this._fastfs.readFile(this.path)
.then(jsonStr => JSON.parse(jsonStr));
}
return this._reading;
}
}
function getReplacements(pkg) {
return pkg['react-native'] == null
? pkg.browser
: pkg['react-native'];
}
module.exports = Package;

View File

@ -1,38 +0,0 @@
'use strict';
const Promise = require('promise');
const Module = require('./Module');
class Polyfill extends Module {
constructor({ path, id, dependencies }) {
super({ file: path });
this._id = id;
this._dependencies = dependencies;
}
isHaste() {
return Promise.resolve(false);
}
getName() {
return Promise.resolve(this._id);
}
getPackage() {
return null;
}
getDependencies() {
return Promise.resolve(this._dependencies);
}
isJSON() {
return false;
}
isPolyfill() {
return true;
}
}
module.exports = Polyfill;

View File

@ -1,335 +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('absolute-path')
.dontMock('json-stable-stringify')
.dontMock('../fastfs')
.dontMock('../lib/extractRequires')
.dontMock('../lib/replacePatterns')
.dontMock('../DependencyGraph/docblock')
.dontMock('../Module');
jest
.mock('fs');
const Fastfs = require('../fastfs');
const Module = require('../Module');
const ModuleCache = require('../ModuleCache');
const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
const Promise = require('promise');
const fs = require('graceful-fs');
function mockIndexFile(indexJs) {
fs.__setMockFilesystem({'root': {'index.js': indexJs}});
}
describe('Module', () => {
const fileWatcher = {
on: () => this,
isWatchman: () => Promise.resolve(false),
};
const fileName = '/root/index.js';
let cache, fastfs;
const createCache = () => ({
get: jest.genMockFn().mockImplementation(
(filepath, field, cb) => cb(filepath)
),
invalidate: jest.genMockFn(),
end: jest.genMockFn(),
});
const createModule = (options) =>
new Module({
cache,
fastfs,
file: fileName,
depGraphHelpers: new DependencyGraphHelpers(),
moduleCache: new ModuleCache({fastfs, cache}),
...options,
});
beforeEach(function(done) {
cache = createCache();
fastfs = new Fastfs(
'test',
['/root'],
fileWatcher,
{crawling: Promise.resolve([fileName]), ignore: []},
);
fastfs.build().then(done);
});
describe('Module ID', () => {
const moduleId = 'arbitraryModule';
const source =
`/**
* @providesModule ${moduleId}
*/
`;
let module;
beforeEach(() => {
module = createModule();
});
describe('@providesModule annotations', () => {
beforeEach(() => {
mockIndexFile(source);
});
pit('extracts the module name from the header', () =>
module.getName().then(name => expect(name).toEqual(moduleId))
);
pit('identifies the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
);
pit('does not transform the file in order to access the name', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled());
});
pit('does not transform the file in order to access the haste status', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
.then(() => expect(transformCode).not.toBeCalled());
});
});
describe('@provides annotations', () => {
beforeEach(() => {
mockIndexFile(source.replace(/@providesModule/, '@provides'));
});
pit('extracts the module name from the header if it has a @provides annotation', () =>
module.getName().then(name => expect(name).toEqual(moduleId))
);
pit('identifies the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
);
pit('does not transform the file in order to access the name', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled());
});
pit('does not transform the file in order to access the haste status', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
.then(() => expect(transformCode).not.toBeCalled());
});
});
describe('no annotation', () => {
beforeEach(() => {
mockIndexFile('arbitrary(code);');
});
pit('uses the file name as module name', () =>
module.getName().then(name => expect(name).toEqual(fileName))
);
pit('does not identify the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(false))
);
pit('does not transform the file in order to access the name', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled());
});
pit('does not transform the file in order to access the haste status', () => {
const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
.then(() => expect(transformCode).not.toBeCalled());
});
});
});
describe('Code', () => {
const fileContents = 'arbitrary(code)';
beforeEach(function() {
mockIndexFile(fileContents);
});
pit('exposes file contents as `code` property on the data exposed by `read()`', () =>
createModule().read().then(({code}) =>
expect(code).toBe(fileContents))
);
pit('exposes file contents via the `getCode()` method', () =>
createModule().getCode().then(code =>
expect(code).toBe(fileContents))
);
});
describe('Extrators', () => {
pit('uses custom require extractors if specified', () => {
mockIndexFile('');
const module = createModule({
extractor: code => ({deps: {sync: ['foo', 'bar']}}),
});
return module.getDependencies().then(actual =>
expect(actual).toEqual(['foo', 'bar']));
});
});
describe('Custom Code Transform', () => {
let transformCode;
const fileContents = 'arbitrary(code);';
const exampleCode = `
${'require'}('a');
${'System.import'}('b');
${'require'}('c');`;
beforeEach(function() {
transformCode = jest.genMockFn();
mockIndexFile(fileContents);
transformCode.mockReturnValue(Promise.resolve({code: ''}));
});
pit('passes the module and file contents to the transform function when reading', () => {
const module = createModule({transformCode});
return module.read()
.then(() => {
expect(transformCode).toBeCalledWith(module, fileContents, undefined);
});
});
pit('passes any additional options to the transform function when reading', () => {
const module = createModule({transformCode});
const transformOptions = {arbitrary: Object()};
return module.read(transformOptions)
.then(() =>
expect(transformCode.mock.calls[0][2]).toBe(transformOptions)
);
});
pit('uses the code that `transformCode` resolves to to extract dependencies', () => {
transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
const module = createModule({transformCode});
return module.getDependencies().then(dependencies => {
expect(dependencies).toEqual(['a', 'c']);
});
});
pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
const mockedDependencies = ['foo', 'bar'];
transformCode.mockReturnValue(Promise.resolve({
code: exampleCode,
dependencies: mockedDependencies,
}));
const module = createModule({transformCode});
return module.getDependencies().then(dependencies => {
expect(dependencies).toEqual(mockedDependencies);
});
});
pit('exposes the transformed code rather than the raw file contents', () => {
transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
const module = createModule({transformCode});
return Promise.all([module.read(), module.getCode()])
.then(([data, code]) => {
expect(data.code).toBe(exampleCode);
expect(code).toBe(exampleCode);
});
});
pit('exposes a source map returned by the transform', () => {
const map = {version: 3};
transformCode.mockReturnValue(Promise.resolve({map, code: exampleCode}));
const module = createModule({transformCode});
return Promise.all([module.read(), module.getMap()])
.then(([data, sourceMap]) => {
expect(data.map).toBe(map);
expect(sourceMap).toBe(map);
});
});
describe('Caching based on options', () => {
let module;
beforeEach(function() {
module = createModule({transformCode});
});
const callsEqual = ([path1, key1], [path2, key2]) => {
expect(path1).toEqual(path2);
expect(key1).toEqual(key2);
};
it('gets dependencies from the cache with the same cache key for the same transform options', () => {
const options = {some: 'options'};
module.getDependencies(options); // first call
module.getDependencies(options); // second call
const {calls} = cache.get.mock;
callsEqual(calls[0], calls[1]);
});
it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => {
module.getDependencies({a: 'b', c: 'd'}); // first call
module.getDependencies({c: 'd', a: 'b'}); // second call
const {calls} = cache.get.mock;
callsEqual(calls[0], calls[1]);
});
it('gets dependencies from the cache with different cache keys for different transform options', () => {
module.getDependencies({some: 'options'});
module.getDependencies({other: 'arbitrary options'});
const {calls} = cache.get.mock;
expect(calls[0][1]).not.toEqual(calls[1][1]);
});
it('gets code from the cache with the same cache key for the same transform options', () => {
const options = {some: 'options'};
module.getCode(options); // first call
module.getCode(options); // second call
const {calls} = cache.get.mock;
callsEqual(calls[0], calls[1]);
});
it('gets code from the cache with the same cache key for the equivalent transform options', () => {
module.getCode({a: 'b', c: 'd'}); // first call
module.getCode({c: 'd', a: 'b'}); // second call
const {calls} = cache.get.mock;
callsEqual(calls[0], calls[1]);
});
it('gets code from the cache with different cache keys for different transform options', () => {
module.getCode({some: 'options'});
module.getCode({other: 'arbitrary options'});
const {calls} = cache.get.mock;
expect(calls[0][1]).not.toEqual(calls[1][1]);
});
});
});
});

View File

@ -1,39 +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.
* An arbitrary module header
* @providesModule
*/
const some: string = 'arbitrary code';
const containing: string = 'ūñïčødę';
/**
* An arbitrary class that extends some thing
* It exposes a random number, which may be reset at the callers discretion
*/
class Arbitrary extends Something {
constructor() {
this.reset();
}
/**
* Returns the random number
* @returns number
*/
get random(): number {
return this._random;
}
/**
* Re-creates the internal random number
* @returns void
*/
reset(): void {
this._random = Math.random();
}
}

View File

@ -1,87 +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.autoMockOff()
.dontMock('graceful-fs');
const Fastfs = require('../fastfs');
const {EventEmitter} = require('events');
const fs = require('fs');
const path = require('path');
const fileName = path.resolve(__dirname, 'fastfs-data');
const contents = fs.readFileSync(fileName, 'utf-8');
describe('fastfs:', function() {
let fastfs;
const crawling = Promise.resolve([fileName]);
const roots = [__dirname];
const watcher = new EventEmitter();
beforeEach(function(done) {
fastfs = new Fastfs('arbitrary', roots, watcher, {crawling});
fastfs.build().then(done);
});
describe('partial reading', () => {
// these are integrated tests that read real files from disk
pit('reads a file while a predicate returns true', function() {
return fastfs.readWhile(fileName, () => true).then(readContent =>
expect(readContent).toEqual(contents)
);
});
pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => {
const predicate = jest.genMockFn().mockReturnValue(true);
return fastfs.readWhile(fileName, predicate).then(() => {
let aggregated = '';
const {calls} = predicate.mock;
expect(calls).not.toEqual([]);
calls.forEach((call, i) => {
const [chunk] = call;
aggregated += chunk;
expect(chunk).not.toBe('');
expect(call).toEqual([chunk, i, aggregated]);
});
expect(aggregated).toEqual(contents);
});
});
pit('stops reading when the predicate returns false', () => {
const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0);
return fastfs.readWhile(fileName, predicate).then((readContent) => {
const {calls} = predicate.mock;
expect(calls.length).toBe(1);
expect(readContent).toBe(calls[0][2]);
});
});
pit('after reading the whole file with `readWhile`, `read()` still works', () => {
// this test allows to reuse the results of `readWhile` for `readFile`
return fastfs.readWhile(fileName, () => true).then(() => {
fastfs.readFile(fileName).then(readContent =>
expect(readContent).toEqual(contents)
);
});
});
pit('after reading parts of the file with `readWhile`, `read()` still works', () => {
return fastfs.readWhile(fileName, () => false).then(() => {
fastfs.readFile(fileName).then(readContent =>
expect(readContent).toEqual(contents)
);
});
});
});
});

View File

@ -1,26 +0,0 @@
'use strict';
const nodeCrawl = require('./node');
const watchmanCrawl = require('./watchman');
function crawl(roots, options) {
const {fileWatcher} = options;
return fileWatcher.isWatchman().then(isWatchman => {
if (!isWatchman) {
return false;
}
// Make sure we're dealing with a version of watchman
// that's using `watch-project`
// TODO(amasad): properly expose (and document) used sane internals.
return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root);
}).then(isWatchman => {
if (isWatchman) {
return watchmanCrawl(roots, options);
}
return nodeCrawl(roots, options);
});
}
module.exports = crawl;

View File

@ -1,61 +0,0 @@
'use strict';
const Promise = require('promise');
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const fs = require('graceful-fs');
const path = require('fast-path');
const readDir = Promise.denodeify(fs.readdir);
const stat = Promise.denodeify(fs.stat);
function nodeRecReadDir(roots, {ignore, exts}) {
const queue = roots.slice();
const retFiles = [];
const extPattern = new RegExp(
'\.(' + exts.join('|') + ')$'
);
function search() {
const currDir = queue.shift();
if (!currDir) {
return Promise.resolve();
}
return readDir(currDir)
.then(files => files.map(f => path.join(currDir, f)))
.then(files => Promise.all(
files.map(f => stat(f).catch(handleBrokenLink))
).then(stats => [
// Remove broken links.
files.filter((file, i) => !!stats[i]),
stats.filter(Boolean),
]))
.then(([files, stats]) => {
files.forEach((filePath, i) => {
if (ignore(filePath)) {
return;
}
if (stats[i].isDirectory()) {
queue.push(filePath);
return;
}
if (filePath.match(extPattern)) {
retFiles.push(filePath);
}
});
return search();
});
}
return search().then(() => retFiles);
}
function handleBrokenLink(e) {
debug('WARNING: error stating, possibly broken symlink', e.message);
return Promise.resolve();
}
module.exports = nodeRecReadDir;

View File

@ -1,66 +0,0 @@
'use strict';
const Promise = require('promise');
const path = require('fast-path');
function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
const files = [];
return Promise.all(
roots.map(
root => fileWatcher.getWatcherForRoot(root)
)
).then(
watchers => {
// All watchman roots for all watches we have.
const watchmanRoots = watchers.map(
watcher => watcher.watchProjectInfo.root
);
// Actual unique watchers (because we use watch-project we may end up with
// duplicate "real" watches, and that's by design).
// TODO(amasad): push this functionality into the `FileWatcher`.
const uniqueWatchers = watchers.filter(
(watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i
);
return Promise.all(
uniqueWatchers.map(watcher => {
const watchedRoot = watcher.watchProjectInfo.root;
// Build up an expression to filter the output by the relevant roots.
const dirExpr = ['anyof'];
for (let i = 0; i < roots.length; i++) {
const root = roots[i];
if (isDescendant(watchedRoot, root)) {
dirExpr.push(['dirname', path.relative(watchedRoot, root)]);
}
}
const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client));
return cmd(['query', watchedRoot, {
suffix: exts,
expression: ['allof', ['type', 'f'], 'exists', dirExpr],
fields: ['name'],
}]).then(resp => {
if ('warning' in resp) {
console.warn('watchman warning: ', resp.warning);
}
resp.files.forEach(filePath => {
filePath = watchedRoot + path.sep + filePath;
if (!ignore(filePath)) {
files.push(filePath);
}
return false;
});
});
})
);
}).then(() => files);
}
function isDescendant(root, child) {
return child.startsWith(root);
}
module.exports = watchmanRecReadDir;

View File

@ -1,360 +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';
const Promise = require('promise');
const {EventEmitter} = require('events');
const fs = require('graceful-fs');
const path = require('fast-path');
// workaround for https://github.com/isaacs/node-graceful-fs/issues/56
// fs.close is patched, whereas graceful-fs.close is not.
const fsClose = require('fs').close;
const readFile = Promise.denodeify(fs.readFile);
const stat = Promise.denodeify(fs.stat);
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
class Fastfs extends EventEmitter {
constructor(name, roots, fileWatcher, {ignore, crawling, activity}) {
super();
this._name = name;
this._fileWatcher = fileWatcher;
this._ignore = ignore;
this._roots = roots.map(root => {
// If the path ends in a separator ("/"), remove it to make string
// operations on paths safer.
if (root.endsWith(path.sep)) {
root = root.substr(0, root.length - 1);
}
return new File(root, true);
});
this._fastPaths = Object.create(null);
this._crawling = crawling;
this._activity = activity;
}
build() {
return this._crawling.then(files => {
let fastfsActivity;
const activity = this._activity;
if (activity) {
fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name);
}
files.forEach(filePath => {
const root = this._getRoot(filePath);
if (root) {
const newFile = new File(filePath, false);
const dirname = filePath.substr(0, filePath.lastIndexOf(path.sep));
const parent = this._fastPaths[dirname];
this._fastPaths[filePath] = newFile;
if (parent) {
parent.addChild(newFile, this._fastPaths);
} else {
root.addChild(newFile, this._fastPaths);
}
}
});
if (activity) {
activity.endEvent(fastfsActivity);
}
this._fileWatcher.on('all', this._processFileChange.bind(this));
});
}
stat(filePath) {
return Promise.resolve().then(() => this._getFile(filePath).stat());
}
getAllFiles() {
return Object.keys(this._fastPaths)
.filter(filePath => !this._fastPaths[filePath].isDir);
}
findFilesByExts(exts, { ignore } = {}) {
return this.getAllFiles()
.filter(filePath => (
exts.indexOf(path.extname(filePath).substr(1)) !== -1 &&
(!ignore || !ignore(filePath))
));
}
matchFilesByPattern(pattern) {
return this.getAllFiles().filter(file => file.match(pattern));
}
readFile(filePath) {
const file = this._getFile(filePath);
if (!file) {
throw new Error(`Unable to find file with path: ${filePath}`);
}
return file.read();
}
readWhile(filePath, predicate) {
const file = this._getFile(filePath);
if (!file) {
throw new Error(`Unable to find file with path: ${filePath}`);
}
return file.readWhile(predicate);
}
closest(filePath, name) {
for (let file = this._getFile(filePath).parent;
file;
file = file.parent) {
if (file.children[name]) {
return file.children[name].path;
}
}
return null;
}
fileExists(filePath) {
let file;
try {
file = this._getFile(filePath);
} catch (e) {
if (e.type === NOT_FOUND_IN_ROOTS) {
return false;
}
throw e;
}
return file && !file.isDir;
}
dirExists(filePath) {
let file;
try {
file = this._getFile(filePath);
} catch (e) {
if (e.type === NOT_FOUND_IN_ROOTS) {
return false;
}
throw e;
}
return file && file.isDir;
}
matches(dir, pattern) {
const dirFile = this._getFile(dir);
if (!dirFile.isDir) {
throw new Error(`Expected file ${dirFile.path} to be a directory`);
}
return Object.keys(dirFile.children)
.filter(name => name.match(pattern))
.map(name => path.join(dirFile.path, name));
}
_getRoot(filePath) {
for (let i = 0; i < this._roots.length; i++) {
const possibleRoot = this._roots[i];
if (isDescendant(possibleRoot.path, filePath)) {
return possibleRoot;
}
}
return null;
}
_getAndAssertRoot(filePath) {
const root = this._getRoot(filePath);
if (!root) {
const error = new Error(`File ${filePath} not found in any of the roots`);
error.type = NOT_FOUND_IN_ROOTS;
throw error;
}
return root;
}
_getFile(filePath) {
filePath = path.normalize(filePath);
if (!this._fastPaths[filePath]) {
const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
if (file) {
this._fastPaths[filePath] = file;
}
}
return this._fastPaths[filePath];
}
_processFileChange(type, filePath, rootPath, fstat) {
const absPath = path.join(rootPath, filePath);
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
return;
}
// Make sure this event belongs to one of our roots.
const root = this._getRoot(absPath);
if (!root) {
return;
}
if (type === 'delete' || type === 'change') {
const file = this._getFile(absPath);
if (file) {
file.remove();
}
}
delete this._fastPaths[path.normalize(absPath)];
if (type !== 'delete') {
const file = new File(absPath, false);
root.addChild(file, this._fastPaths);
}
this.emit('change', type, filePath, rootPath, fstat);
}
}
class File {
constructor(filePath, isDir) {
this.path = filePath;
this.isDir = isDir;
this.children = this.isDir ? Object.create(null) : null;
}
read() {
if (!this._read) {
this._read = readFile(this.path, 'utf8');
}
return this._read;
}
readWhile(predicate) {
return readWhile(this.path, predicate).then(({result, completed}) => {
if (completed && !this._read) {
this._read = Promise.resolve(result);
}
return result;
});
}
stat() {
if (!this._stat) {
this._stat = stat(this.path);
}
return this._stat;
}
addChild(file, fileMap) {
const parts = file.path.substr(this.path.length + 1).split(path.sep);
if (parts.length === 1) {
this.children[parts[0]] = file;
file.parent = this;
} else if (this.children[parts[0]]) {
this.children[parts[0]].addChild(file, fileMap);
} else {
const dir = new File(this.path + path.sep + parts[0], true);
dir.parent = this;
this.children[parts[0]] = dir;
fileMap[dir.path] = dir;
dir.addChild(file, fileMap);
}
}
getFileFromPath(filePath) {
const parts = path.relative(this.path, filePath).split(path.sep);
/*eslint consistent-this:0*/
let file = this;
for (let i = 0; i < parts.length; i++) {
const fileName = parts[i];
if (!fileName) {
continue;
}
if (!file || !file.isDir) {
// File not found.
return null;
}
file = file.children[fileName];
}
return file;
}
ext() {
return path.extname(this.path).substr(1);
}
remove() {
if (!this.parent) {
throw new Error(`No parent to delete ${this.path} from`);
}
delete this.parent.children[path.basename(this.path)];
}
}
function readWhile(filePath, predicate) {
return new Promise((resolve, reject) => {
fs.open(filePath, 'r', (openError, fd) => {
if (openError) {
reject(openError);
return;
}
read(
fd,
/*global Buffer: true*/
new Buffer(512),
makeReadCallback(fd, predicate, (readError, result, completed) => {
if (readError) {
reject(readError);
} else {
resolve({result, completed});
}
})
);
});
});
}
function read(fd, buffer, callback) {
fs.read(fd, buffer, 0, buffer.length, -1, callback);
}
function close(fd, error, result, complete, callback) {
fsClose(fd, closeError => callback(error || closeError, result, complete));
}
function makeReadCallback(fd, predicate, callback) {
let result = '';
let index = 0;
return function readCallback(error, bytesRead, buffer) {
if (error) {
close(fd, error, undefined, false, callback);
return;
}
const completed = bytesRead === 0;
const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead);
result += chunk;
if (completed || !predicate(chunk, index++, result)) {
close(fd, null, result, completed, callback);
} else {
read(fd, buffer, readCallback);
}
};
}
function isDescendant(root, child) {
return child.startsWith(root);
}
module.exports = Fastfs;

View File

@ -1,119 +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('../getPlatformExtension')
.dontMock('../getAssetDataFromName');
var getAssetDataFromName = require('../getAssetDataFromName');
describe('getAssetDataFromName', () => {
it('should get data from name', () => {
expect(getAssetDataFromName('a/b/c.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
});
describe('resolution extraction', () => {
it('should extract resolution simple case', () => {
var data = getAssetDataFromName('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
type: 'png',
name: 'test',
platform: null,
});
});
it('should default resolution to 1', () => {
var data = getAssetDataFromName('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
type: 'png',
name: 'test',
platform: null,
});
});
it('should support float', () => {
var data = getAssetDataFromName('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
type: 'png',
name: 'test',
platform: null,
});
});
});
});

View File

@ -1,25 +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('../getPlatformExtension');
var getPlatformExtension = require('../getPlatformExtension');
describe('getPlatformExtension', function() {
it('should get platform ext', function() {
expect(getPlatformExtension('a.ios.js')).toBe('ios');
expect(getPlatformExtension('a.android.js')).toBe('android');
expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null);
expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null);
});
});

View File

@ -1,47 +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';
const replacePatterns = require('./replacePatterns');
/**
* Extract all required modules from a `code` string.
*/
const blockCommentRe = /\/\*(.|\n)*?\*\//g;
const lineCommentRe = /\/\/.+(\n|$)/g;
function extractRequires(code) {
var deps = {
sync: [],
};
code = code
.replace(blockCommentRe, '')
.replace(lineCommentRe, '')
// Parse the sync dependencies this module has. When the module is
// required, all it's sync dependencies will be loaded into memory.
// Sync dependencies can be defined either using `require` or the ES6
// `import` or `export` syntaxes:
// var dep1 = require('dep1');
.replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
return match;
})
.replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
return match;
})
.replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
return match;
});
return {code, deps};
}
module.exports = extractRequires;

View File

@ -1,55 +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';
const path = require('fast-path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) {
const ext = path.extname(filename);
const platformExt = getPlatformExtension(filename);
let pattern = '@([\\d\\.]+)x';
if (platformExt != null) {
pattern += '(\\.' + platformExt + ')?';
}
pattern += '\\' + ext + '$';
const re = new RegExp(pattern);
const match = filename.match(re);
let resolution;
if (!(match && match[1])) {
resolution = 1;
} else {
resolution = parseFloat(match[1], 10);
if (isNaN(resolution)) {
resolution = 1;
}
}
let assetName;
if (match) {
assetName = filename.replace(re, ext);
} else if (platformExt != null) {
assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext);
} else {
assetName = filename;
}
return {
resolution: resolution,
assetName: assetName,
type: ext.slice(1),
name: path.basename(assetName, ext),
platform: platformExt,
};
}
module.exports = getAssetDataFromName;

View File

@ -1,28 +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';
const SUPPORTED_PLATFORM_EXTS = {
android: true,
ios: true,
web: true,
};
// Extract platform extension: index.ios.js -> ios
function getPlatformExtension(file) {
const last = file.lastIndexOf('.');
const secondToLast = file.lastIndexOf('.', last - 1);
if (secondToLast === -1) {
return null;
}
const platform = file.substring(secondToLast + 1, last);
return SUPPORTED_PLATFORM_EXTS[platform] ? platform : null;
}
module.exports = getPlatformExtension;

View File

@ -1,14 +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';
exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;

View File

@ -15,16 +15,20 @@ jest
jest.mock('fs');
jest.setMock('temp', {path: () => '/arbitrary/path'});
var Cache = require('../../DependencyResolver/Cache');
var Transformer = require('../');
var fs = require('fs');
var Cache;
var options;
describe('Transformer', function() {
var workers;
beforeEach(function() {
Cache = require('node-haste').Cache;
Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c());
workers = jest.genMockFn();
jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() {
return workers;

View File

@ -9,29 +9,29 @@
'use strict';
jest.dontMock('../')
.dontMock('underscore')
.dontMock('../../DependencyResolver/lib/extractRequires')
.dontMock('../../DependencyResolver/lib/replacePatterns');
.dontMock('underscore');
jest.mock('path');
var Promise = require('promise');
var Resolver = require('../');
var Module = require('../../DependencyResolver/Module');
var Polyfill = require('../../DependencyResolver/Polyfill');
var DependencyGraph = require('../../DependencyResolver/DependencyGraph');
const Promise = require('promise');
const Resolver = require('../');
const DependencyGraph = require('node-haste');
var path = require('path');
var _ = require('underscore');
const path = require('path');
const _ = require('underscore');
let Module;
let Polyfill;
describe('Resolver', function() {
beforeEach(function() {
Polyfill.mockClear();
Module = require('node-haste').Module;
Polyfill = require('node-haste').Polyfill;
DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns');
// For the polyfillDeps
path.join.mockImpl(function(a, b) {
return b;
});
path.join = jest.genMockFn().mockImpl((a, b) => b);
DependencyGraph.prototype.load.mockImpl(() => Promise.resolve());
});

View File

@ -11,9 +11,9 @@
const path = require('path');
const Activity = require('../Activity');
const DependencyGraph = require('../DependencyResolver/DependencyGraph');
const replacePatterns = require('../DependencyResolver/lib/replacePatterns');
const Polyfill = require('../DependencyResolver/Polyfill');
const DependencyGraph = require('node-haste');
const replacePatterns = require('node-haste').replacePatterns;
const Polyfill = require('node-haste').Polyfill;
const declareOpts = require('../lib/declareOpts');
const Promise = require('promise');

View File

@ -21,11 +21,12 @@ jest.setMock('worker-farm', function() { return () => {}; })
const Promise = require('promise');
var Bundler = require('../../Bundler');
var FileWatcher = require('../../DependencyResolver/FileWatcher');
var Server = require('../');
var Server = require('../../Server');
var AssetServer = require('../../AssetServer');
var FileWatcher;
describe('processRequest', () => {
var server;
@ -58,6 +59,7 @@ describe('processRequest', () => {
var triggerFileChange;
beforeEach(() => {
FileWatcher = require('node-haste').FileWatcher;
Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() =>
Promise.resolve({
getSource: () => 'this is the source',

View File

@ -10,8 +10,8 @@
const Activity = require('../Activity');
const AssetServer = require('../AssetServer');
const FileWatcher = require('../DependencyResolver/FileWatcher');
const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension');
const FileWatcher = require('node-haste').FileWatcher;
const getPlatformExtension = require('node-haste').getPlatformExtension;
const Bundler = require('../Bundler');
const Promise = require('promise');

View File

@ -8,8 +8,7 @@
*/
'use strict';
jest.setMock('worker-farm', function() { return () => {}; })
.setMock('uglify-js')
jest.setMock('uglify-js')
.mock('child_process')
.dontMock('underscore')
.dontMock('../');

View File

@ -8,8 +8,7 @@
*/
'use strict';
jest.setMock('worker-farm', function() { return () => {}; })
.setMock('uglify-js')
jest.setMock('uglify-js')
.mock('net')
.mock('fs')
.dontMock('../SocketServer');

View File

@ -8,181 +8,4 @@
*/
'use strict';
var fs = jest.genMockFromModule('fs');
function asyncCallback(callback) {
return function() {
setImmediate(() => callback.apply(this, arguments));
};
}
fs.realpath.mockImpl(function(filepath, callback) {
callback = asyncCallback(callback);
var node;
try {
node = getToNode(filepath);
} catch (e) {
return callback(e);
}
if (node && typeof node === 'object' && node.SYMLINK != null) {
return callback(null, node.SYMLINK);
}
callback(null, filepath);
});
fs.readdir.mockImpl(function(filepath, callback) {
callback = asyncCallback(callback);
var node;
try {
node = getToNode(filepath);
if (node && typeof node === 'object' && node.SYMLINK != null) {
node = getToNode(node.SYMLINK);
}
} catch (e) {
return callback(e);
}
if (!(node && typeof node === 'object' && node.SYMLINK == null)) {
return callback(new Error(filepath + ' is not a directory.'));
}
callback(null, Object.keys(node));
});
fs.readFile.mockImpl(function(filepath, encoding, callback) {
callback = asyncCallback(callback);
if (arguments.length === 2) {
callback = encoding;
encoding = null;
}
try {
var node = getToNode(filepath);
// dir check
if (node && typeof node === 'object' && node.SYMLINK == null) {
callback(new Error('Error readFile a dir: ' + filepath));
}
return callback(null, node);
} catch (e) {
return callback(e);
}
});
fs.stat.mockImpl(function(filepath, callback) {
callback = asyncCallback(callback);
var node;
try {
node = getToNode(filepath);
} catch (e) {
callback(e);
return;
}
var mtime = {
getTime: function() {
return Math.ceil(Math.random() * 10000000);
},
};
if (node.SYMLINK) {
fs.stat(node.SYMLINK, callback);
return;
}
if (node && typeof node === 'object') {
callback(null, {
isDirectory: function() {
return true;
},
isSymbolicLink: function() {
return false;
},
mtime: mtime,
});
} else {
callback(null, {
isDirectory: function() {
return false;
},
isSymbolicLink: function() {
return false;
},
mtime: mtime,
});
}
});
const noop = () => {};
fs.open.mockImpl(function(path) {
const callback = arguments[arguments.length - 1] || noop;
let data, error, fd;
try {
data = getToNode(path);
} catch (e) {
error = e;
}
if (error || data == null) {
error = Error(`ENOENT: no such file or directory, open ${path}`);
}
if (data != null) {
/* global Buffer: true */
fd = {
buffer: new Buffer(data, 'utf8'),
position: 0,
};
}
callback(error, fd);
});
fs.read.mockImpl((fd, buffer, writeOffset, length, position, callback = noop) => {
let bytesWritten;
try {
if (position == null || position < 0) {
({position} = fd);
}
bytesWritten =
fd.buffer.copy(buffer, writeOffset, position, position + length);
fd.position = position + bytesWritten;
} catch (e) {
callback(Error('invalid argument'));
return;
}
callback(null, bytesWritten, buffer);
});
fs.close.mockImpl((fd, callback = noop) => {
try {
fd.buffer = fs.position = undefined;
} catch (e) {
callback(Error('invalid argument'));
return;
}
callback(null);
});
var filesystem;
fs.__setMockFilesystem = function(object) {
filesystem = object;
return filesystem;
};
function getToNode(filepath) {
var parts = filepath.split('/');
if (parts[0] !== '') {
throw new Error('Make sure all paths are absolute.');
}
var node = filesystem;
parts.slice(1).forEach(function(part) {
if (node && node.SYMLINK) {
node = getToNode(node.SYMLINK);
}
node = node[part];
});
return node;
}
module.exports = fs;
module.exports = require.requireActual('node-haste/mocks/graceful-fs');