Compare commits

...
Author SHA1 Message Date
James Ide e1032e1d09 [0.11.0] 2015-09-14 18:23:33 -07:00
Amjad MasadandKrzysztof Magiera 0c2f3fe145 Refactor DependencyResolver into request/response
Reviewed By: @martinbigio

Differential Revision: D2425842

Conflicts:
	packager/react-packager/src/Bundler/index.js
	packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
2015-09-11 18:11:58 -07:00
Amjad MasadandKrzysztof Magiera 329f059ba9 [react-packager] Support platform extensions in image requires
Summary:
We don't currently support platform extensions in asset modules.
This adds supports for it:

```
require('./a.png');
```

Will require 'a.ios.png' if it exists and 'a.png' if it doesn't.
2015-09-11 18:01:53 -07:00
James Ide 2d8ec5eca7 [npm] Update to stacktrace-parser@0.1.3 and ws@0.8.0 to satisfy io.js 3 / Node 4
Summary:
The previous version of stacktrace-parser 0.1.2 had an line in its package.json file that required Node <= 2.x. That line was removed, so it no longer warns on io.js 3.x or Node 4.x.

Similarly, ws 0.8.0 was published with support for the new V8, so it compiles with io.js 3.x and therefore should work with Node 4.x.

Updated the Travis file as well to run on io.js 3.x.

Fixes #2258, #2455

Closes https://github.com/facebook/react-native/pull/2398
Github Author: James Ide <ide@jameside.com>
2015-09-08 13:30:57 -07:00
Martin Konicek 6edb671e2d 0.11.0-rc 2015-08-26 17:28:18 +01:00
24 changed files with 1349 additions and 750 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "React"
s.version = "0.8.0"
s.version = "0.11.0-rc"
s.summary = "Build high quality mobile apps using React."
s.description = <<-DESC
React Native apps are built using the React JS
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "0.8.0",
"version": "0.11.0",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
@@ -69,12 +69,12 @@
"sane": "^1.1.2",
"semver": "^4.3.6",
"source-map": "0.1.31",
"stacktrace-parser": "frantic/stacktrace-parser#493c5e5638",
"stacktrace-parser": "0.1.3",
"uglify-js": "2.4.16",
"underscore": "1.7.0",
"wordwrap": "^1.0.0",
"worker-farm": "^1.3.1",
"ws": "^0.7.2",
"ws": "0.8.0",
"yargs": "1.3.2",
"yeoman-environment": "^1.2.7",
"yeoman-generator": "^0.20.2"
@@ -1,6 +1,7 @@
'use strict';
jest
.dontMock('../../lib/getPlatformExtension')
.dontMock('../../lib/getAssetDataFromName')
.dontMock('../');
@@ -47,6 +48,43 @@ describe('AssetServer', () => {
);
});
pit('should work for the simple case with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.ios.png': 'b ios image',
'b.android.png': 'b android image',
'c.png': 'c general image',
'c.android.png': 'c android image',
}
}
});
return Promise.all([
server.get('imgs/b.png', 'ios').then(
data => expect(data).toBe('b ios image')
),
server.get('imgs/b.png', 'android').then(
data => expect(data).toBe('b android image')
),
server.get('imgs/c.png', 'android').then(
data => expect(data).toBe('c android image')
),
server.get('imgs/c.png', 'ios').then(
data => expect(data).toBe('c general image')
),
server.get('imgs/c.png').then(
data => expect(data).toBe('c general image')
),
]);
});
pit('should work for the simple case with jpg', () => {
const server = new AssetServer({
projectRoots: ['/root'],
@@ -95,6 +133,37 @@ describe('AssetServer', () => {
);
});
pit('should pick the bigger one with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
'b@1x.ios.png': 'b1 ios image',
'b@2x.ios.png': 'b2 ios image',
'b@4x.ios.png': 'b4 ios image',
'b@4.5x.ios.png': 'b4.5 ios image',
}
}
});
return Promise.all([
server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
),
server.get('imgs/b@3x.png', 'ios').then(data =>
expect(data).toBe('b4 ios image')
),
]);
});
pit('should support multiple project roots', () => {
const server = new AssetServer({
projectRoots: ['/root', '/root2'],
+29 -10
View File
@@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat);
const readDir = Promise.denodeify(fs.readdir);
const readFile = Promise.denodeify(fs.readFile);
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
@@ -39,9 +38,9 @@ class AssetServer {
this._assetExts = opts.assetExts;
}
get(assetPath) {
get(assetPath, platform = null) {
const assetData = getAssetDataFromName(assetPath);
return this._getAssetRecord(assetPath).then(record => {
return this._getAssetRecord(assetPath, platform).then(record => {
for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) {
return readFile(record.files[i]);
@@ -85,9 +84,10 @@ class AssetServer {
* 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots
* 3. We then build a map of all assets and their scales in this directory
* 4. Then pick the closest resolution (rounding up) to the requested one
* 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one
*/
_getAssetRecord(assetPath) {
_getAssetRecord(assetPath, platform = null) {
const filename = path.basename(assetPath);
return (
@@ -104,11 +104,20 @@ class AssetServer {
const files = res[1];
const assetData = getAssetDataFromName(filename);
const map = this._buildAssetMap(dir, files);
const record = map[assetData.assetName];
const map = this._buildAssetMap(dir, files, platform);
let record;
if (platform != null){
record = map[getAssetKey(assetData.assetName, platform)] ||
map[assetData.assetName];
} else {
record = map[assetData.assetName];
}
if (!record) {
throw new Error('Asset not found');
throw new Error(
`Asset not found: ${assetPath} for platform: ${platform}`
);
}
return record;
@@ -141,9 +150,10 @@ class AssetServer {
const map = Object.create(null);
assets.forEach(function(asset, i) {
const file = files[i];
let record = map[asset.assetName];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey];
if (!record) {
record = map[asset.assetName] = {
record = map[assetKey] = {
scales: [],
files: [],
};
@@ -151,6 +161,7 @@ class AssetServer {
let insertIndex;
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
break;
@@ -164,4 +175,12 @@ class AssetServer {
}
}
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
module.exports = AssetServer;
@@ -125,7 +125,7 @@ describe('Bundler', function() {
});
});
wrapModule.mockImpl(function(module, code) {
wrapModule.mockImpl(function(response, module, code) {
return Promise.resolve('lol ' + code + ' lol');
});
+16 -7
View File
@@ -125,7 +125,7 @@ class Bundler {
const findEventId = Activity.startEvent('find dependencies');
let transformEventId;
return this.getDependencies(main, isDev, platform).then((result) => {
return this.getDependencies(main, isDev, platform).then((response) => {
Activity.endEvent(findEventId);
transformEventId = Activity.startEvent('transform');
@@ -135,14 +135,19 @@ class Bundler {
complete: '=',
incomplete: ' ',
width: 40,
total: result.dependencies.length,
total: response.dependencies.length,
});
}
bundle.setMainModuleId(result.mainModuleId);
bundle.setMainModuleId(response.mainModuleId);
return Promise.all(
result.dependencies.map(
module => this._transformModule(bundle, module).then(transformed => {
response.dependencies.map(
module => this._transformModule(
bundle,
response,
module,
platform
).then(transformed => {
if (bar) {
bar.tick();
}
@@ -170,7 +175,7 @@ class Bundler {
return this._resolver.getDependencies(main, { dev: isDev, platform });
}
_transformModule(bundle, module) {
_transformModule(bundle, response, module, platform = null) {
let transform;
if (module.isAsset_DEPRECATED()) {
@@ -187,7 +192,11 @@ class Bundler {
const resolver = this._resolver;
return transform.then(
transformed => resolver.wrapModule(module, transformed.code).then(
transformed => resolver.wrapModule(
response,
module,
transformed.code
).then(
code => new ModuleTransport({
code: code,
map: transformed.map,
@@ -9,25 +9,8 @@
'use strict';
jest
.dontMock('absolute-path')
.dontMock('crypto')
.dontMock('underscore')
.dontMock('path')
.dontMock('../index')
.dontMock('../../lib/getAssetDataFromName')
.dontMock('../../DependencyResolver/crawlers')
.dontMock('../../DependencyResolver/crawlers/node')
.dontMock('../../DependencyResolver/DependencyGraph/docblock')
.dontMock('../../DependencyResolver/fastfs')
.dontMock('../../DependencyResolver/replacePatterns')
.dontMock('../../DependencyResolver')
.dontMock('../../DependencyResolver/DependencyGraph')
.dontMock('../../DependencyResolver/AssetModule_DEPRECATED')
.dontMock('../../DependencyResolver/AssetModule')
.dontMock('../../DependencyResolver/Module')
.dontMock('../../DependencyResolver/Package')
.dontMock('../../DependencyResolver/Polyfill')
.dontMock('../../DependencyResolver/ModuleCache');
.autoMockOff()
.mock('../../Cache');
const Promise = require('promise');
const path = require('path');
@@ -0,0 +1,100 @@
/**
* 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 Activity = require('../../Activity');
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs');
const debug = require('debug')('ReactPackager:DependencyGraph');
const path = require('path');
class DeprecatedAssetMap {
constructor({ fsCrawl, roots, assetExts, fileWatcher, ignoreFilePath, helpers }) {
if (roots == null || roots.length === 0) {
this._disabled = true;
return;
}
this._helpers = helpers;
this._map = Object.create(null);
this._assetExts = assetExts;
this._fastfs = new Fastfs(
'Assets',
roots,
fileWatcher,
{ ignore: ignoreFilePath, crawling: fsCrawl }
);
this._fastfs.on('change', this._processFileChange.bind(this));
}
build() {
if (this._disabled) {
return Promise.resolve();
}
return this._fastfs.build().then(
() => {
const processAsset_DEPRECATEDActivity = Activity.startEvent(
'Building (deprecated) Asset Map',
);
this._fastfs.findFilesByExts(this._assetExts).forEach(
file => this._processAsset(file)
);
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) {
let ext = this._helpers.extname(file);
if (this._assetExts.indexOf(ext) !== -1) {
let name = assetName(file, ext);
if (this._map[name] != null) {
debug('Conflcting 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;
@@ -0,0 +1,119 @@
/**
* 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('path');
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
class HasteMap {
constructor({ fastfs, moduleCache, helpers }) {
this._fastfs = fastfs;
this._moduleCache = moduleCache;
this._helpers = helpers;
this._map = Object.create(null);
}
build() {
let promises = this._fastfs.findFilesByExt('js', {
ignore: (file) => this._helpers.isNodeModulesDir(file)
}).map(file => this._processHasteModule(file));
promises = promises.concat(
this._fastfs.findFilesByName('package.json', {
ignore: (file) => this._helpers.isNodeModulesDir(file)
}).map(file => this._processHastePackage(file))
);
return Promise.all(promises);
}
processFileChange(type, absPath) {
return Promise.resolve().then(() => {
/*eslint no-labels: 0 */
if (type === 'delete' || type === 'change') {
loop: for (let name in this._map) {
let modules = this._map[name];
for (var i = 0; i < modules.length; i++) {
if (modules[i].path === absPath) {
modules.splice(i, 1);
break loop;
}
}
}
if (type === 'delete') {
return;
}
}
if (this._helpers.extname(absPath) === 'js' ||
this._helpers.extname(absPath) === 'json') {
if (path.basename(absPath) === 'package.json') {
return this._processHastePackage(absPath);
} else {
return this._processHasteModule(absPath);
}
}
});
}
getModule(name, platform = null) {
if (this._map[name]) {
const modules = this._map[name];
if (platform != null) {
for (let i = 0; i < modules.length; i++) {
if (getPontentialPlatformExt(modules[i].path) === platform) {
return modules[i];
}
}
}
return modules[0];
}
return null;
}
_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, this._fastfs);
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] = [];
}
if (mod.type === 'Module') {
// Modules takes precendence over packages.
this._map[name].unshift(mod);
} else {
this._map[name].push(mod);
}
}
}
module.exports = HasteMap;
@@ -0,0 +1,49 @@
/**
* 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('path');
class Helpers {
constructor({ providesModuleNodeModules, assetExts }) {
this._providesModuleNodeModules = providesModuleNodeModules;
this._assetExts = assetExts;
}
isNodeModulesDir(file) {
let parts = path.normalize(file).split(path.sep);
const indexOfNodeModules = parts.lastIndexOf('node_modules');
if (indexOfNodeModules === -1) {
return false;
}
parts = parts.slice(indexOfNodeModules + 1);
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).replace(/^\./, '');
}
}
module.exports = Helpers;
@@ -0,0 +1,347 @@
/**
* 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')('ReactPackager:DependencyGraph');
const util = require('util');
const path = require('path');
const isAbsolutePath = require('absolute-path');
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
class ResolutionRequest {
constructor({
platform,
entryPath,
hasteMap,
deprecatedAssetMap,
helpers,
moduleCache,
fastfs,
}) {
this._platform = platform;
this._entryPath = entryPath;
this._hasteMap = hasteMap;
this._deprecatedAssetMap = deprecatedAssetMap;
this._helpers = helpers;
this._moduleCache = moduleCache;
this._fastfs = fastfs;
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') {
throw error;
}
console.warn(
'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) {
return Promise.resolve().then(() => {
const entry = this._moduleCache.getModule(this._entryPath);
const visited = Object.create(null);
visited[entry.hash()] = true;
const collect = (mod) => {
response.pushDependency(mod);
return mod.getDependencies().then(
depNames => Promise.all(
depNames.map(name => this.resolveDependency(mod, name))
).then((dependencies) => [depNames, dependencies])
).then(([depNames, dependencies]) => {
let p = Promise.resolve();
const filteredPairs = [];
dependencies.forEach((modDep, i) => {
if (modDep == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`',
depNames[i],
mod.path
);
return false;
}
return filteredPairs.push([depNames[i], modDep]);
});
response.setResolvedDependencyPairs(mod, filteredPairs);
filteredPairs.forEach(([depName, modDep]) => {
p = p.then(() => {
if (!visited[modDep.hash()]) {
visited[modDep.hash()] = true;
return collect(modDep);
}
return null;
});
});
return p;
});
};
return collect(entry);
});
}
getAsyncDependencies(response) {
return Promise.resolve().then(() => {
const mod = this._moduleCache.getModule(this._entryPath);
return mod.getAsyncDependencies().then(bundles =>
Promise
.all(bundles.map(bundle =>
Promise.all(bundle.map(
dep => this.resolveDependency(mod, dep)
))
))
.then(bs => bs.map(bundle => bundle.map(dep => dep.path)))
);
}).then(asyncDependencies => asyncDependencies.forEach(
(dependency) => response.pushAsyncDependency(dependency)
));
}
_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),
() => this._loadAsDir(potentialModulePath),
);
}
throw new UnableToResolveError('Unable to resolve dependency');
});
}
_redirectRequire(fromModule, modulePath) {
return Promise.resolve(fromModule.getPackage()).then(p => {
if (p) {
return p.redirectRequire(modulePath);
}
return modulePath;
});
}
_resolveNodeDependency(fromModule, toModuleName) {
if (toModuleName[0] === '.' || toModuleName[1] === '/') {
const potentialModulePath = isAbsolutePath(toModuleName) ?
toModuleName :
path.join(path.dirname(fromModule.path), toModuleName);
return this._redirectRequire(fromModule, potentialModulePath).then(
realModuleName => this._tryResolve(
() => this._loadAsFile(realModuleName),
() => this._loadAsDir(realModuleName)
)
);
} else {
return this._redirectRequire(fromModule, toModuleName).then(
realModuleName => {
const searchQueue = [];
for (let currDir = path.dirname(fromModule.path);
currDir !== '/';
currDir = path.dirname(currDir)) {
searchQueue.push(
path.join(currDir, 'node_modules', realModuleName)
);
}
let p = Promise.reject(new UnableToResolveError('Node module not found'));
searchQueue.forEach(potentialModulePath => {
p = this._tryResolve(
() => this._tryResolve(
() => p,
() => this._loadAsFile(potentialModulePath),
),
() => this._loadAsDir(potentialModulePath)
);
});
return p;
});
}
}
_loadAsFile(potentialModulePath) {
return Promise.resolve().then(() => {
if (this._helpers.isAssetFile(potentialModulePath)) {
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(
path.dirname(potentialModulePath),
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._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
file = potentialModulePath + '.json';
} else {
throw new UnableToResolveError(`File ${potentialModulePath} doesnt exist`);
}
return this._moduleCache.getModule(file);
});
}
_loadAsDir(potentialDirPath) {
return Promise.resolve().then(() => {
if (!this._fastfs.dirExists(potentialDirPath)) {
throw new UnableToResolveError(`Invalid directory ${potentialDirPath}`);
}
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),
() => this._loadAsDir(main)
)
);
}
return this._loadAsFile(path.join(potentialDirPath, 'index'));
});
}
_resetResolutionCache() {
this._immediateResolutionCache = Object.create(null);
}
}
function resolutionHash(modulePath, depName) {
return `${path.resolve(modulePath)}:${depName}`;
}
function UnableToResolveError() {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
var msg = util.format.apply(util, arguments);
this.message = msg;
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;
@@ -0,0 +1,73 @@
/**
* 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.asyncDependencies = [];
this.mainModuleId = null;
this._mappings = Object.create(null);
this._finalized = false;
}
_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);
}
pushAsyncDependency(dependency){
this._assertNotFinalized();
this.asyncDependencies.push(dependency);
}
setResolvedDependencyPairs(module, pairs) {
this._assertNotFinalized();
const hash = module.hash();
if (this._mappings[hash] == null) {
this._mappings[hash] = pairs;
}
}
getResolvedDependencyPairs(module) {
this._assertFinalized();
return this._mappings[module.hash()];
}
}
module.exports = ResolutionResponse;
@@ -8,26 +8,13 @@
*/
'use strict';
jest
.dontMock('../index')
.dontMock('crypto')
.dontMock('absolute-path')
.dontMock('../docblock')
.dontMock('../../crawlers')
.dontMock('../../crawlers/node')
.dontMock('../../replacePatterns')
.dontMock('../../../lib/getAssetDataFromName')
.dontMock('../../fastfs')
.dontMock('../../AssetModule_DEPRECATED')
.dontMock('../../AssetModule')
.dontMock('../../Module')
.dontMock('../../Package')
.dontMock('../../ModuleCache')
.setMock('chalk', { dim: function(s) { return s; } });
jest.autoMockOff();
const Promise = require('promise');
jest.mock('fs');
jest
.mock('fs')
.mock('../../../Cache');
describe('DependencyGraph', function() {
var cache;
@@ -36,12 +23,13 @@ describe('DependencyGraph', function() {
var fileWatcher;
var fs;
function getOrderedDependenciesAsJSON(dgraph, entry) {
return dgraph.getOrderedDependencies(entry).then(
deps => Promise.all(deps.map(dep => Promise.all([
function getOrderedDependenciesAsJSON(dgraph, entry, platform) {
return dgraph.getDependencies(entry, platform)
.then(response => response.finalize())
.then(({ dependencies }) => Promise.all(dependencies.map(dep => Promise.all([
dep.getName(),
dep.getDependencies(),
]).then(([name, dependencies]) => ({
]).then(([name, moduleDependencies]) => ({
path: dep.path,
isJSON: dep.isJSON(),
isAsset: dep.isAsset(),
@@ -49,7 +37,7 @@ describe('DependencyGraph', function() {
isPolyfill: dep.isPolyfill(),
resolution: dep.resolution,
id: name,
dependencies
dependencies: moduleDependencies,
})))
));
}
@@ -66,10 +54,10 @@ describe('DependencyGraph', function() {
isWatchman: () => Promise.resolve(false)
};
cache = new Cache({});
cache = new Cache();
});
describe('getOrderedDependencies', function() {
describe('get sync dependencies', function() {
pit('should get dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
@@ -424,6 +412,88 @@ describe('DependencyGraph', function() {
});
});
pit('should respect platform extension in assets', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("./imgs/a.png");',
'require("./imgs/b.png");',
'require("./imgs/c.png");',
].join('\n'),
'imgs': {
'a@1.5x.ios.png': '',
'b@.7x.ios.png': '',
'c.ios.png': '',
'c@2x.ios.png': '',
},
'package.json': JSON.stringify({
name: 'rootPackage'
}),
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', 'ios').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: [
'./imgs/a.png',
'./imgs/b.png',
'./imgs/c.png',
],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'rootPackage/imgs/a.png',
path: '/root/imgs/a@1.5x.ios.png',
resolution: 1.5,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/b.png',
path: '/root/imgs/b@.7x.ios.png',
resolution: 0.7,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/c.png',
path: '/root/imgs/c.ios.png',
resolution: 1,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
]);
});
});
pit('Deprecated and relative assets can live together', function() {
var root = '/root';
fs.__setMockFilesystem({
@@ -3615,4 +3685,40 @@ describe('DependencyGraph', function() {
});
});
});
describe('getAsyncDependencies', () => {
pit('should get dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'System.import("a")'
].join('\n'),
'a.js': [
'/**',
' * @providesModule a',
' */',
].join('\n'),
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
return dgraph.getDependencies('/root/index.js')
.then(response => response.finalize())
.then(({ asyncDependencies }) => {
expect(asyncDependencies).toEqual([
['/root/a.js']
]);
});
});
});
});
@@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
@@ -9,17 +9,20 @@
'use strict';
const Activity = require('../../Activity');
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs');
const ModuleCache = require('../ModuleCache');
const Promise = require('promise');
const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts');
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const util = require('util');
const Helpers = require('./Helpers');
const ResolutionRequest = require('./ResolutionRequest');
const ResolutionResponse = require('./ResolutionResponse');
const HasteMap = require('./HasteMap');
const DeprecatedAssetMap = require('./DeprecatedAssetMap');
const validateOpts = declareOpts({
roots: {
@@ -68,9 +71,8 @@ const validateOpts = declareOpts({
class DependencyGraph {
constructor(options) {
this._opts = validateOpts(options);
this._hasteMap = Object.create(null);
this._resetResolutionCache();
this._cache = this._opts.cache;
this._helpers = new Helpers(this._opts);
this.load();
}
@@ -103,13 +105,29 @@ class DependencyGraph {
this._moduleCache = new ModuleCache(this._fastfs, this._cache);
this._hasteMap = new HasteMap({
fastfs: this._fastfs,
moduleCache: this._moduleCache,
assetExts: this._opts.exts,
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,
});
this._loading = Promise.all([
this._fastfs.build()
.then(() => {
const hasteActivity = Activity.startEvent('Building Haste Map');
return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity));
return this._hasteMap.build().then(() => Activity.endEvent(hasteActivity));
}),
this._buildAssetMap_DEPRECATED(),
this._deprecatedAssetMap.build(),
]).then(() =>
Activity.endEvent(depGraphActivity)
);
@@ -117,530 +135,73 @@ class DependencyGraph {
return this._loading;
}
setup({ platform }) {
if (platform && this._opts.platforms.indexOf(platform) === -1) {
getDependencies(entryPath, platform) {
return this.load().then(() => {
platform = this._getRequestPlatform(entryPath, platform);
const absPath = this._getAbsolutePath(entryPath);
const req = new ResolutionRequest({
platform,
entryPath: absPath,
deprecatedAssetMap: this._deprecatedAssetMap,
hasteMap: this._hasteMap,
helpers: this._helpers,
moduleCache: this._moduleCache,
fastfs: this._fastfs,
});
const response = new ResolutionResponse();
return Promise.all([
req.getOrderedDependencies(response),
req.getAsyncDependencies(response),
]).then(() => response);
});
}
_getRequestPlatform(entryPath, platform) {
if (platform == null) {
platform = getPontentialPlatformExt(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);
}
// TODO(amasad): This is a potential race condition. Mutliple requests could
// interfere with each other. This needs a refactor to fix -- which will
// follow this diff.
if (this._platformExt !== platform) {
this._resetResolutionCache();
}
this._platformExt = platform;
}
resolveDependency(fromModule, toModuleName) {
const resHash = resolutionHash(fromModule.path, toModuleName);
if (this._immediateResolutionCache[resHash]) {
return Promise.resolve(this._immediateResolutionCache[resHash]);
}
const asset_DEPRECATED = this._resolveAsset_DEPRECATED(
fromModule,
toModuleName
);
if (asset_DEPRECATED) {
return Promise.resolve(asset_DEPRECATED);
}
const cacheResult = (result) => {
this._immediateResolutionCache[resHash] = result;
return result;
};
const forgive = () => {
console.warn(
'Unable to resolve module %s from %s',
toModuleName,
fromModule.path
);
return null;
};
if (!this._isNodeModulesDir(fromModule.path)
&& toModuleName[0] !== '.' &&
toModuleName[0] !== '/') {
return this._resolveHasteDependency(fromModule, toModuleName).catch(
() => this._resolveNodeDependency(fromModule, toModuleName)
).then(
cacheResult,
forgive,
);
}
return this._resolveNodeDependency(fromModule, toModuleName)
.then(
cacheResult,
forgive
);
}
getOrderedDependencies(entryPath) {
return this.load().then(() => {
const entry = this._getModuleForEntryPath(entryPath);
const deps = [];
const visited = Object.create(null);
visited[entry.hash()] = true;
const collect = (mod) => {
deps.push(mod);
return mod.getDependencies().then(
depNames => Promise.all(
depNames.map(name => this.resolveDependency(mod, name))
).then((dependencies) => [depNames, dependencies])
).then(([depNames, dependencies]) => {
let p = Promise.resolve();
dependencies.forEach((modDep, i) => {
if (modDep == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`',
depNames[i],
mod.path
);
return;
}
p = p.then(() => {
if (!visited[modDep.hash()]) {
visited[modDep.hash()] = true;
return collect(modDep);
}
return null;
});
});
return p;
});
};
return collect(entry)
.then(() => deps);
});
}
getAsyncDependencies(entryPath) {
return this.load().then(() => {
const mod = this._getModuleForEntryPath(entryPath);
return mod.getAsyncDependencies().then(bundles =>
Promise
.all(bundles.map(bundle =>
Promise.all(bundle.map(
dep => this.resolveDependency(mod, dep)
))
))
.then(bs => bs.map(bundle => bundle.map(dep => dep.path)))
);
});
return platform;
}
_getAbsolutePath(filePath) {
if (isAbsolutePath(filePath)) {
return filePath;
return path.resolve(filePath);
}
for (let i = 0; i < this._opts.roots.length; i++) {
const root = this._opts.roots[i];
const absPath = path.join(root, filePath);
if (this._fastfs.fileExists(absPath)) {
return absPath;
const potentialAbsPath = path.join(root, filePath);
if (this._fastfs.fileExists(potentialAbsPath)) {
return path.resolve(potentialAbsPath);
}
}
return null;
}
_getModuleForEntryPath(entryPath) {
const absPath = this._getAbsolutePath(entryPath);
if (absPath == null) {
throw new NotFoundError(
'Could not find source file at %s',
entryPath
);
}
const absolutePath = path.resolve(absPath);
if (absolutePath == null) {
throw new NotFoundError(
'Cannot find entry file %s in any of the roots: %j',
entryPath,
this._opts.roots
);
}
// `platformExt` could be set in the `setup` method.
if (!this._platformExt) {
const platformExt = getPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt;
} else {
this._platformExt = null;
}
}
return this._moduleCache.getModule(absolutePath);
}
_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._getHasteModule(realModuleName);
if (dep && dep.type === 'Module') {
return dep;
}
let packageName = realModuleName;
while (packageName && packageName !== '.') {
dep = this._getHasteModule(packageName);
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._loadAsFile(potentialModulePath)
.catch(() => this._loadAsDir(potentialModulePath));
}
throw new Error('Unable to resolve dependency');
});
}
_redirectRequire(fromModule, modulePath) {
return Promise.resolve(fromModule.getPackage()).then(p => {
if (p) {
return p.redirectRequire(modulePath);
}
return modulePath;
});
}
_resolveNodeDependency(fromModule, toModuleName) {
if (toModuleName[0] === '.' || toModuleName[1] === '/') {
const potentialModulePath = isAbsolutePath(toModuleName) ?
toModuleName :
path.join(path.dirname(fromModule.path), toModuleName);
return this._redirectRequire(fromModule, potentialModulePath).then(
realModuleName => this._loadAsFile(realModuleName)
.catch(() => this._loadAsDir(realModuleName))
);
} else {
return this._redirectRequire(fromModule, toModuleName).then(
realModuleName => {
const searchQueue = [];
for (let currDir = path.dirname(fromModule.path);
currDir !== '/';
currDir = path.dirname(currDir)) {
searchQueue.push(
path.join(currDir, 'node_modules', realModuleName)
);
}
let p = Promise.reject(new Error('Node module not found'));
searchQueue.forEach(potentialModulePath => {
p = p.catch(
() => this._loadAsFile(potentialModulePath)
).catch(
() => this._loadAsDir(potentialModulePath)
);
});
return p;
});
}
}
_resolveAsset_DEPRECATED(fromModule, toModuleName) {
if (this._assetMap_DEPRECATED != null) {
const assetMatch = toModuleName.match(/^image!(.+)/);
// Process DEPRECATED global asset requires.
if (assetMatch && assetMatch[1]) {
if (!this._assetMap_DEPRECATED[assetMatch[1]]) {
debug('WARINING: Cannot find asset:', assetMatch[1]);
return null;
}
return this._assetMap_DEPRECATED[assetMatch[1]];
}
}
return null;
}
_isAssetFile(file) {
return this._opts.assetExts.indexOf(extname(file)) !== -1;
}
_loadAsFile(potentialModulePath) {
return Promise.resolve().then(() => {
if (this._isAssetFile(potentialModulePath)) {
const {name, type} = getAssetDataFromName(potentialModulePath);
const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type);
// We arbitrarly grab the first one, because scale selection
// will happen somewhere
const [assetFile] = this._fastfs.matches(
path.dirname(potentialModulePath),
pattern
);
if (assetFile) {
return this._moduleCache.getAssetModule(assetFile);
}
}
let file;
if (this._fastfs.fileExists(potentialModulePath)) {
file = potentialModulePath;
} else if (this._platformExt != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platformExt + '.js')) {
file = potentialModulePath + '.' + this._platformExt + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
file = potentialModulePath + '.json';
} else {
throw new Error(`File ${potentialModulePath} doesnt exist`);
}
return this._moduleCache.getModule(file);
});
}
_loadAsDir(potentialDirPath) {
return Promise.resolve().then(() => {
if (!this._fastfs.dirExists(potentialDirPath)) {
throw new Error(`Invalid directory ${potentialDirPath}`);
}
const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (this._fastfs.fileExists(packageJsonPath)) {
return this._moduleCache.getPackage(packageJsonPath)
.getMain().then(
(main) => this._loadAsFile(main).catch(
() => this._loadAsDir(main)
)
);
}
return this._loadAsFile(path.join(potentialDirPath, 'index'));
});
}
_buildHasteMap() {
let promises = this._fastfs.findFilesByExt('js', {
ignore: (file) => this._isNodeModulesDir(file)
}).map(file => this._processHasteModule(file));
promises = promises.concat(
this._fastfs.findFilesByName('package.json', {
ignore: (file) => this._isNodeModulesDir(file)
}).map(file => this._processHastePackage(file))
throw new NotFoundError(
'Cannot find entry file %s in any of the roots: %j',
filePath,
this._opts.roots
);
return Promise.all(promises);
}
_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, this._fastfs);
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._hasteMap[name] == null) {
this._hasteMap[name] = [];
}
if (mod.type === 'Module') {
// Modules takes precendence over packages.
this._hasteMap[name].unshift(mod);
} else {
this._hasteMap[name].push(mod);
}
}
_getHasteModule(name) {
if (this._hasteMap[name]) {
const modules = this._hasteMap[name];
if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) {
if (getPlatformExt(modules[i].path) === this._platformExt) {
return modules[i];
}
}
}
return modules[0];
}
return null;
}
_isNodeModulesDir(file) {
let parts = path.normalize(file).split(path.sep);
const indexOfNodeModules = parts.lastIndexOf('node_modules');
if (indexOfNodeModules === -1) {
return false;
}
parts = parts.slice(indexOfNodeModules + 1);
const dirs = this._opts.providesModuleNodeModules;
for (let i = 0; i < dirs.length; i++) {
if (parts.indexOf(dirs[i]) > -1) {
return false;
}
}
return true;
}
_processAsset_DEPRECATED(file) {
let ext = extname(file);
if (this._opts.assetExts.indexOf(ext) !== -1) {
let name = assetName(file, ext);
if (this._assetMap_DEPRECATED[name] != null) {
debug('Conflcting assets', name);
}
this._assetMap_DEPRECATED[name] = new AssetModule_DEPRECATED(file);
}
}
_buildAssetMap_DEPRECATED() {
if (this._opts.assetRoots_DEPRECATED == null ||
this._opts.assetRoots_DEPRECATED.length === 0) {
return Promise.resolve();
}
this._assetMap_DEPRECATED = Object.create(null);
const fastfs = new Fastfs(
'Assets',
this._opts.assetRoots_DEPRECATED,
this._opts.fileWatcher,
{ ignore: this._opts.ignoreFilePath, crawling: this._crawling }
);
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));
return fastfs.build().then(
() => {
const processAsset_DEPRECATEDActivity = Activity.startEvent(
'Building (deprecated) Asset Map',
);
const assets = fastfs.findFilesByExts(this._opts.assetExts).map(
file => this._processAsset_DEPRECATED(file)
);
Activity.endEvent(processAsset_DEPRECATEDActivity);
return assets;
}
);
}
_processAssetChange_DEPRECATED(type, filePath, root, fstat) {
const name = assetName(filePath);
if (type === 'change' || type === 'delete') {
delete this._assetMap_DEPRECATED[name];
}
if (type === 'change' || type === 'add') {
this._loading = this._loading.then(
() => this._processAsset_DEPRECATED(path.join(root, filePath))
);
}
}
_processFileChange(type, filePath, root, fstat) {
// It's really hard to invalidate the right module resolution cache
// so we just blow it up with every file change.
this._resetResolutionCache();
const absPath = path.join(root, filePath);
if ((fstat && fstat.isDirectory()) ||
if (fstat && fstat.isDirectory() ||
this._opts.ignoreFilePath(absPath) ||
this._isNodeModulesDir(absPath)) {
this._helpers.isNodeModulesDir(absPath)) {
return;
}
/*eslint no-labels: 0 */
if (type === 'delete' || type === 'change') {
loop: for (let name in this._hasteMap) {
let modules = this._hasteMap[name];
for (var i = 0; i < modules.length; i++) {
if (modules[i].path === absPath) {
modules.splice(i, 1);
break loop;
}
}
}
if (type === 'delete') {
return;
}
}
if (extname(absPath) === 'js' || extname(absPath) === 'json') {
this._loading = this._loading.then(() => {
if (path.basename(filePath) === 'package.json') {
return this._processHastePackage(absPath);
} else {
return this._processHasteModule(absPath);
}
});
}
this._loading = this._loading.then(
() => this._hasteMap.processFileChange(type, absPath)
);
}
_resetResolutionCache() {
this._immediateResolutionCache = Object.create(null);
}
}
function assetName(file, ext) {
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
}
function extname(name) {
return path.extname(name).replace(/^\./, '');
}
function resolutionHash(modulePath, depName) {
return `${path.resolve(modulePath)}:${depName}`;
}
function NotFoundError() {
@@ -651,26 +212,6 @@ function NotFoundError() {
this.type = this.name = 'NotFoundError';
this.status = 404;
}
function normalizePath(modulePath) {
if (path.sep === '/') {
modulePath = path.normalize(modulePath);
} else if (path.posix) {
modulePath = path.posix.normalize(modulePath);
}
return modulePath.replace(/\/$/, '');
}
// Extract platform extension: index.ios.js -> ios
function getPlatformExt(file) {
const parts = path.basename(file).split('.');
if (parts.length < 3) {
return null;
}
return parts[parts.length - 2];
}
util.inherits(NotFoundError, Error);
module.exports = DependencyGraph;
@@ -34,6 +34,22 @@ describe('HasteDependencyResolver', function() {
HasteDependencyResolver = require('../');
});
class ResolutionResponseMock {
constructor({dependencies, mainModuleId, asyncDependencies}) {
this.dependencies = dependencies;
this.mainModuleId = mainModuleId;
this.asyncDependencies = asyncDependencies;
}
prependDependency(dependency) {
this.dependencies.unshift(dependency);
}
finalize() {
return Promise.resolve(this);
}
}
function createModule(id, dependencies) {
var module = new Module();
module.getName.mockImpl(() => Promise.resolve(id));
@@ -52,11 +68,12 @@ describe('HasteDependencyResolver', function() {
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return Promise.resolve(deps);
});
depGraph.load.mockImpl(function() {
return Promise.resolve();
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
return depResolver.getDependencies('/root/index.js', { dev: false })
@@ -133,19 +150,19 @@ describe('HasteDependencyResolver', function() {
projectRoot: '/root',
});
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return Promise.resolve(deps);
});
depGraph.load.mockImpl(function() {
return Promise.resolve();
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
return depResolver.getDependencies('/root/index.js', { dev: true })
.then(function(result) {
expect(result.mainModuleId).toEqual('index');
expect(depGraph.getOrderedDependencies).toBeCalledWith('/root/index.js');
expect(depGraph.getDependencies).toBeCalledWith('/root/index.js', undefined);
expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
expect(result.dependencies[result.dependencies.length - 1])
.toBe(module);
@@ -161,13 +178,13 @@ describe('HasteDependencyResolver', function() {
polyfillModuleNames: ['some module'],
});
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return Promise.resolve(deps);
});
depGraph.load.mockImpl(function() {
return Promise.resolve();
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
asyncDependencies: [],
}));
});
return depResolver.getDependencies('/root/index.js', { dev: false })
@@ -343,17 +360,23 @@ describe('HasteDependencyResolver', function() {
].join('\n');
/*eslint-disable */
depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) {
if (toModuleName === 'x') {
return Promise.resolve(createModule('changed'));
} else if (toModuleName === 'y') {
return Promise.resolve(createModule('Y'));
}
const module = createModule('test module', ['x', 'y']);
return Promise.resolve(null);
const resolutionResponse = new ResolutionResponseMock({
dependencies: [module],
mainModuleId: 'test module',
asyncDependencies: [],
});
resolutionResponse.getResolvedDependencyPairs = (module) => {
return [
['x', createModule('changed')],
['y', createModule('Y')],
];
}
return depResolver.wrapModule(
resolutionResponse,
createModule('test module', ['x', 'y']),
code
).then(processedCode => {
+30 -4
View File
@@ -13,6 +13,8 @@ const stat = Promise.denodeify(fs.stat);
const hasOwn = Object.prototype.hasOwnProperty;
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
class Fastfs extends EventEmitter {
constructor(name, roots, fileWatcher, {ignore, crawling}) {
super();
@@ -90,7 +92,11 @@ class Fastfs extends EventEmitter {
}
readFile(filePath) {
return this._getFile(filePath).read();
const file = this._getFile(filePath);
if (!file) {
throw new Error(`Unable to find file with path: ${file}`);
}
return file.read();
}
closest(filePath, name) {
@@ -105,12 +111,30 @@ class Fastfs extends EventEmitter {
}
fileExists(filePath) {
const file = this._getFile(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) {
const file = this._getFile(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;
}
@@ -138,7 +162,9 @@ class Fastfs extends EventEmitter {
_getAndAssertRoot(filePath) {
const root = this._getRoot(filePath);
if (!root) {
throw new Error(`File ${filePath} not found in any of the roots`);
const error = new Error(`File ${filePath} not found in any of the roots`);
error.type = NOT_FOUND_IN_ROOTS;
throw error;
}
return root;
}
+44 -65
View File
@@ -82,36 +82,18 @@ var getDependenciesValidateOpts = declareOpts({
HasteDependencyResolver.prototype.getDependencies = function(main, options) {
var opts = getDependenciesValidateOpts(options);
var depGraph = this._depGraph;
var self = this;
return this._depGraph.getDependencies(main, opts.platform).then(
resolutionResponse => {
this._getPolyfillDependencies(opts.dev).reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
depGraph.setup({ platform: opts.platform });
return Promise.all([
depGraph.getOrderedDependencies(main),
depGraph.getAsyncDependencies(main),
]).then(
([dependencies, asyncDependencies]) => dependencies[0].getName().then(
mainModuleId => {
self._prependPolyfillDependencies(
dependencies,
opts.dev,
);
return {
mainModuleId,
dependencies,
asyncDependencies,
};
}
)
return resolutionResponse.finalize();
}
);
};
HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
dependencies,
isDev
) {
HasteDependencyResolver.prototype._getPolyfillDependencies = function(isDev) {
var polyfillModuleNames = [
isDev
? path.join(__dirname, 'polyfills/prelude_dev.js')
@@ -124,7 +106,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
].concat(this._polyfillModuleNames);
var polyfillModules = polyfillModuleNames.map(
return polyfillModuleNames.map(
(polyfillModuleName, idx) => new Polyfill({
path: polyfillModuleName,
id: polyfillModuleName,
@@ -132,50 +114,47 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
isPolyfill: true,
})
);
dependencies.unshift.apply(dependencies, polyfillModules);
};
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
if (module.isPolyfill()) {
return Promise.resolve(code);
}
HasteDependencyResolver.prototype.wrapModule = function(resolutionResponse, module, code) {
return Promise.resolve().then(() => {
if (module.isPolyfill()) {
return Promise.resolve(code);
}
const resolvedDeps = Object.create(null);
const resolvedDepsArr = [];
const resolvedDeps = Object.create(null);
const resolvedDepsArr = [];
return module.getDependencies().then(
dependencies => Promise.all(dependencies.map(
depName => this._depGraph.resolveDependency(module, depName)
.then(depModule => {
if (depModule) {
return depModule.getName().then(name => {
resolvedDeps[depName] = name;
resolvedDepsArr.push(name);
});
}
})
return Promise.all(
resolutionResponse.getResolvedDependencyPairs(module).map(
([depName, depModule]) => {
if (depModule) {
return depModule.getName().then(name => {
resolvedDeps[depName] = name;
resolvedDepsArr.push(name);
});
}
}
)
)
).then(() => {
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
const depId = resolvedDeps[depName];
if (depId) {
return pre + quot + depId + post;
} else {
return codeMatch;
}
};
).then(() => {
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
const depId = resolvedDeps[depName];
if (depId) {
return pre + quot + depId + post;
} else {
return codeMatch;
}
};
return module.getName().then(
name => defineModuleCode({
code: code
.replace(replacePatterns.IMPORT_RE, relativizeCode)
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
deps: JSON.stringify(resolvedDepsArr),
moduleName: name,
})
);
return module.getName().then(
name => defineModuleCode({
code: code.replace(replacePatterns.IMPORT_RE, relativizeCode)
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
deps: JSON.stringify(resolvedDepsArr),
moduleName: name,
})
);
});
});
};
@@ -245,8 +245,16 @@ describe('processRequest', () => {
expect(res.end).toBeCalledWith('i am image');
});
it('should return 404', () => {
it('should parse the platform option', () => {
const req = {url: '/assets/imgs/a.png?platform=ios'};
const res = {end: jest.genMockFn()};
AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image'));
server.processRequest(req, res);
jest.runAllTimers();
expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios');
expect(res.end).toBeCalledWith('i am image');
});
});
+3 -2
View File
@@ -278,7 +278,8 @@ class Server {
_processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
this._assetServer.get(assetPath[1])
const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`);
this._assetServer.get(assetPath[1], urlObj.query.platform)
.then(
data => res.end(data),
error => {
@@ -286,7 +287,7 @@ class Server {
res.writeHead('404');
res.end('Asset not found');
}
).done();
).done(() => Activity.endEvent(assetEvent));
}
_processProfile(req, res) {
@@ -1,52 +0,0 @@
'use strict';
jest.autoMockOff();
var getAssetDataFromName = require('../getAssetDataFromName');
describe('getAssetDataFromName', function() {
it('should extract resolution simple case', function() {
var data = getAssetDataFromName('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
type: 'png',
name: 'test',
});
});
it('should default resolution to 1', function() {
var data = getAssetDataFromName('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
type: 'png',
name: 'test',
});
});
it('should support float', function() {
var data = getAssetDataFromName('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
type: 'png',
name: 'test',
});
data = getAssetDataFromName('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
type: 'png',
name: 'test',
});
data = getAssetDataFromName('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
type: 'png',
name: 'test',
});
});
});
@@ -0,0 +1,123 @@
/**
* 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');
describe('getAssetDataFromName', () => {
let getAssetDataFromName;
beforeEach(() => {
getAssetDataFromName = require('../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,
});
});
});
});
@@ -0,0 +1,24 @@
/**
* 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');
describe('getPlatformExtension', function() {
it('should get platform ext', function() {
var getPlatformExtension = require('../getPlatformExtension');
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);
});
});
+31 -7
View File
@@ -1,14 +1,29 @@
/**
* 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 path = require('path');
const path = require('path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) {
var ext = path.extname(filename);
const ext = path.extname(filename);
const platformExt = getPlatformExtension(filename);
var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$');
let pattern = '@([\\d\\.]+)x';
if (platformExt != null) {
pattern += '(\\.' + platformExt + ')?';
}
pattern += '\\' + ext + '$';
const re = new RegExp(pattern);
var match = filename.match(re);
var resolution;
const match = filename.match(re);
let resolution;
if (!(match && match[1])) {
resolution = 1;
@@ -19,12 +34,21 @@ function getAssetDataFromName(filename) {
}
}
var assetName = match ? filename.replace(re, ext) : filename;
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)
name: path.basename(assetName, ext),
platform: platformExt,
};
}
+28
View File
@@ -0,0 +1,28 @@
/**
* 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('path');
const SUPPORTED_PLATFORM_EXTS = ['android', 'ios'];
const re = new RegExp(
'[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$'
);
// Extract platform extension: index.ios.js -> ios
function getPlatformExtension(file) {
const match = file.match(re);
if (match && match[1]) {
return match[1];
}
return null;
}
module.exports = getPlatformExtension;