From 86a099d00a9ac0693fc6ca6321dd94e4e8dec46e Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 11 Sep 2015 14:36:12 -0700 Subject: [PATCH] Refactor DependencyResolver into request/response Reviewed By: @martinbigio Differential Revision: D2425842 --- .../src/Bundler/__tests__/Bundler-test.js | 2 +- react-packager/src/Bundler/index.js | 23 +- .../BundlesLayoutIntegration-test.js | 21 +- .../DependencyGraph/DeprecatedAssetMap.js | 100 +++ .../DependencyGraph/HasteMap.js | 119 ++++ .../DependencyGraph/Helpers.js | 49 ++ .../DependencyGraph/ResolutionRequest.js | 347 ++++++++++ .../DependencyGraph/ResolutionResponse.js | 73 +++ .../__tests__/DependencyGraph-test.js | 76 ++- .../DependencyGraph/index.js | 593 ++---------------- .../__tests__/HasteDependencyResolver-test.js | 73 ++- .../src/DependencyResolver/fastfs.js | 34 +- .../src/DependencyResolver/index.js | 109 ++-- 13 files changed, 946 insertions(+), 673 deletions(-) create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/Helpers.js create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js create mode 100644 react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js diff --git a/react-packager/src/Bundler/__tests__/Bundler-test.js b/react-packager/src/Bundler/__tests__/Bundler-test.js index f70eb4b5..60e21c07 100644 --- a/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -125,7 +125,7 @@ describe('Bundler', function() { }); }); - wrapModule.mockImpl(function(module, code) { + wrapModule.mockImpl(function(response, module, code) { return Promise.resolve('lol ' + code + ' lol'); }); diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 5f2ead1c..6927e90e 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -137,7 +137,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'); @@ -147,14 +147,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, platform).then(transformed => { + response.dependencies.map( + module => this._transformModule( + bundle, + response, + module, + platform + ).then(transformed => { if (bar) { bar.tick(); } @@ -182,7 +187,7 @@ class Bundler { return this._resolver.getDependencies(main, { dev: isDev, platform }); } - _transformModule(bundle, module, platform = null) { + _transformModule(bundle, response, module, platform = null) { let transform; if (module.isAsset_DEPRECATED()) { @@ -199,7 +204,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, diff --git a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index a54ee216..7fea8f8f 100644 --- a/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -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'); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js new file mode 100644 index 00000000..d4900b80 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js new file mode 100644 index 00000000..576ea3e0 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js b/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js new file mode 100644 index 00000000..fda56007 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js new file mode 100644 index 00000000..4d0d0632 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js new file mode 100644 index 00000000..d29ff225 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -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; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 6dc2bc7e..c456ba29 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -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/getPlatformExtension') - .dontMock('../../../lib/getAssetDataFromName') - .dontMock('../../fastfs') - .dontMock('../../AssetModule_DEPRECATED') - .dontMock('../../AssetModule') - .dontMock('../../Module') - .dontMock('../../Package') - .dontMock('../../ModuleCache'); +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({ @@ -455,9 +443,7 @@ describe('DependencyGraph', function() { cache: cache, }); - dgraph.setup({ platform: 'ios' }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', 'ios').then(function(deps) { expect(deps) .toEqual([ { @@ -3699,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'] + ]); + }); + }); + }); }); diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 0820c14a..a03cd3db 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -9,18 +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: { @@ -69,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(); } @@ -104,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) ); @@ -118,536 +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 = getPontentialPlatformExt(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); - - let pattern = '^' + name + '(@[\\d\\.]+x)?'; - if (this._platformExt != null) { - pattern += '(\\.' + this._platformExt + ')?'; - } - 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._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 (getPontentialPlatformExt(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() { @@ -658,17 +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(/\/$/, ''); -} - util.inherits(NotFoundError, Error); module.exports = DependencyGraph; diff --git a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index be71b3a0..edb79cbb 100644 --- a/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -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 => { diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js index a4c04232..623df232 100644 --- a/react-packager/src/DependencyResolver/fastfs.js +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -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; } diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index cc7f9468..c42fd36a 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -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, + }) + ); + }); }); };