diff --git a/src/HasteModuleLoader/HasteModuleLoader.js b/src/HasteModuleLoader/HasteModuleLoader.js index d28f01f88088..8f2dcc8d51b4 100644 --- a/src/HasteModuleLoader/HasteModuleLoader.js +++ b/src/HasteModuleLoader/HasteModuleLoader.js @@ -6,13 +6,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -/* eslint-disable fb-www/require-args */ +/* eslint-disable fb-www/require-args, fb-www/object-create-only-one-param */ 'use strict'; const fs = require('graceful-fs'); const moduleMocker = require('../lib/moduleMocker'); -const DependencyGraph = require('node-haste/lib/DependencyGraph'); -const extractRequires = require('node-haste/lib/lib/extractRequires'); +const HasteResolver = require('../resolvers/HasteResolver'); const path = require('path'); const resolve = require('resolve'); const transform = require('../lib/transform'); @@ -21,8 +20,6 @@ const NODE_PATH = (process.env.NODE_PATH ? process.env.NODE_PATH.split(path.delimiter) : null); const IS_PATH_BASED_MODULE_NAME = /^(?:\.\.?\/|\/)/; const VENDOR_PATH = path.resolve(__dirname, '../../vendor'); -const MOCKS_PATTERN = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; -const REQUIRE_EXTENSIONS_PATTERN = /(\brequire\s*?\.\s*?(?:requireActual|requireMock)\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; const mockParentModule = { id: 'mockParent', @@ -47,7 +44,6 @@ let _configUnmockListRegExpCache = null; class Loader { constructor(config, environment) { - /* eslint-disable fb-www/object-create-only-one-param */ this._config = config; this._coverageCollectors = {}; this._currentlyExecutingModulePath = ''; @@ -60,41 +56,17 @@ class Loader { this._configShouldMockModuleNames = {}; this._extensions = config.moduleFileExtensions.map(ext => '.' + ext); - const ignoreFilePattern = new RegExp( - [config.cacheDirectory].concat(config.modulePathIgnorePatterns).join('|') + this._resolver = HasteResolver.get( + [config.rootDir], + { + extensions: this._extensions.concat(this._config.testFileExtensions), + ignoreFilePattern: [config.cacheDirectory] + .concat(config.modulePathIgnorePatterns).join('|'), + } ); - this._depGraph = new DependencyGraph({ - roots: [config.rootDir], - ignoreFilePath: path => path.match(ignoreFilePattern), - cache: { - get: (a, b, cb) => Promise.resolve(cb()), - invalidate: () => {}, - }, - fileWatcher: { - on: function() { - return this; - }, - isWatchman: () => Promise.resolve(false), - }, - extensions: this._extensions.concat(this._config.testFileExtensions), - mocksPattern: MOCKS_PATTERN, - extractRequires: code => { - const data = extractRequires(code); - data.code = data.code.replace( - REQUIRE_EXTENSIONS_PATTERN, - (match, pre, quot, dep, post) => { - data.deps.sync.push(dep); - return match; - } - ); - return data; - }, - }); this._resolvedModules = Object.create(null); this._resources = Object.create(null); - this._resolveDependencyPromises = Object.create(null); this._mocks = Object.create(null); - /* eslint-enable fb-www/object-create-only-one-param */ if (config.collectCoverage) { this._CoverageCollector = require(config.coverageCollector); @@ -131,7 +103,7 @@ class Loader { } getAllTestPaths() { - return this._depGraph.matchFilesByPattern(this._config.testDirectoryName); + return this._resolver.matchFilesByPattern(this._config.testDirectoryName); } /** @@ -302,28 +274,11 @@ class Loader { } resolveDependencies(path) { - if (this._resolveDependencyPromises[path]) { - return this._resolveDependencyPromises[path]; - } - - return this._resolveDependencyPromises[path] = this._depGraph.load() - .then(() => this._depGraph.getDependencies(path)) + return this._resolver.getDependencies(path) .then(response => { - return response.finalize().then(() => { - this._mocks = response.mocks; - return Promise.all(response.dependencies.map(module => { - if (!this._resolvedModules[module.path]) { - this._resolvedModules[module.path] = {}; - } - response.getResolvedDependencyPairs(module).forEach((pair) => - this._resolvedModules[module.path][pair[0]] = pair[1] - ); - - return module.getName().then( - name => this._resources[name] = module - ); - })); - }); + this._mocks = response.mocks; + this._resolvedModules = response.resolvedModules; + this._resources = response.resources; }) .then(() => this); } @@ -482,8 +437,7 @@ class Loader { resolveDirectDependencies(path) { // TODO this should only resolve direct dependencies - return this._depGraph.load() - .then(() => this._depGraph.getDependencies(path)) + return this._resolver.getDependencies(path) .then(response => response.dependencies.map(dep => dep.path)); } diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js index f569b5fa707b..5bec01c76a22 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js @@ -41,35 +41,28 @@ describe('nodeHasteModuleLoader', function() { 'does not cause side effects in the rest of the module system when ' + 'generating a mock', function() { - return buildLoader() - .then( - loader => Promise.all([ - loader.resolveDependencies('./root.js'), - loader.resolveDependencies('./RegularModule.js'), - ]).then(() => loader) - ) - .then(loader => { - const rootPath = path.join(rootDir, 'root.js'); - const testRequire = loader.requireModule.bind( - loader, - rootPath - ); + return buildLoader().then(loader => { + const rootPath = path.join(rootDir, 'root.js'); + const testRequire = loader.requireModule.bind( + loader, + rootPath + ); - const regularModule = testRequire('RegularModule'); - const origModuleStateValue = regularModule.getModuleStateValue(); + const regularModule = testRequire('RegularModule'); + const origModuleStateValue = regularModule.getModuleStateValue(); - expect(origModuleStateValue).toBe('default'); + expect(origModuleStateValue).toBe('default'); - // Generate a mock for a module with side effects - const mock = regularModule.jest.genMockFromModule('ModuleWithSideEffects'); + // Generate a mock for a module with side effects + const mock = regularModule.jest.genMockFromModule('ModuleWithSideEffects'); - // Make sure we get a mock. - expect(mock.fn()).toBe(undefined); + // Make sure we get a mock. + expect(mock.fn()).toBe(undefined); - expect(regularModule.getModuleStateValue()).toBe( - origModuleStateValue - ); - }); + expect(regularModule.getModuleStateValue()).toBe( + origModuleStateValue + ); + }); } ); }); diff --git a/src/resolvers/HasteResolver.js b/src/resolvers/HasteResolver.js new file mode 100644 index 000000000000..a37d83ffd338 --- /dev/null +++ b/src/resolvers/HasteResolver.js @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2014, 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. + */ + +/* eslint-disable fb-www/object-create-only-one-param */ + +'use strict'; + +const DependencyGraph = require('node-haste/lib/DependencyGraph'); +const extractRequires = require('node-haste/lib/lib/extractRequires'); + +const MOCKS_PATTERN = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; +const REQUIRE_EXTENSIONS_PATTERN = /(\brequire\s*?\.\s*?(?:requireActual|requireMock)\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; + +const resolvers = Object.create(null); + +class HasteResolver { + + constructor(roots, options) { + const ignoreFilePattern = new RegExp(options.ignoreFilePattern); + this._resolvePromises = Object.create(null); + this._depGraph = new DependencyGraph({ + roots, + ignoreFilePath: path => path.match(ignoreFilePattern), + cache: { + get: (a, b, cb) => Promise.resolve(cb()), + invalidate: () => {}, + }, + fileWatcher: { + on: function() { + return this; + }, + isWatchman: () => Promise.resolve(false), + }, + extensions: options.extensions, + mocksPattern: MOCKS_PATTERN, + extractRequires: code => { + const data = extractRequires(code); + data.code = data.code.replace( + REQUIRE_EXTENSIONS_PATTERN, + (match, pre, quot, dep, post) => { + data.deps.sync.push(dep); + return match; + } + ); + return data; + }, + }); + + // warm-up + this._depGraph.load(); + } + + matchFilesByPattern(pattern) { + return this._depGraph.matchFilesByPattern(pattern); + } + + getDependencies(path) { + if (this._resolvePromises[path]) { + return this._resolvePromises[path]; + } + + return this._resolvePromises[path] = this._depGraph.load().then( + () => this._depGraph.getDependencies(path).then(response => + response.finalize().then(() => { + var deps = { + mocks: response.mocks, + resolvedModules: Object.create(null), + resources: Object.create(null), + }; + return Promise.all( + response.dependencies.map(module => { + if (!deps.resolvedModules[module.path]) { + deps.resolvedModules[module.path] = {}; + } + response.getResolvedDependencyPairs(module).forEach((pair) => + deps.resolvedModules[module.path][pair[0]] = pair[1] + ); + return module.getName().then( + name => deps.resources[name] = module + ); + }) + ).then(() => deps); + }) + )); + } + + static get(roots, options) { + const key = + roots.sort().join(':') + + '$' + options.extensions.sort().join(':') + + '$' + options.ignoreFilePattern; + if (!resolvers[key]) { + resolvers[key] = new HasteResolver(roots, options); + } + + return resolvers[key]; + } + +} + +module.exports = HasteResolver;