diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js index e8d4657b0..c4cb29e4c 100644 --- a/local-cli/server/util/attachHMRServer.js +++ b/local-cli/server/util/attachHMRServer.js @@ -136,7 +136,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { inverseDependenciesCache, }; - packagerServer.setHMRFileChangeListener((filename, stat) => { + packagerServer.setHMRFileChangeListener((type, filename) => { if (!client) { return; } @@ -151,14 +151,14 @@ function attachHMRServer({httpServer, path, packagerServer}) { } client.ws.send(JSON.stringify({type: 'update-start'})); - stat.then(() => { - return packagerServer.getShallowDependencies({ - entryFile: filename, - platform: client.platform, - dev: true, - hot: true, - }) - .then(deps => { + const promise = type === 'delete' + ? Promise.resolve() + : packagerServer.getShallowDependencies({ + entryFile: filename, + platform: client.platform, + dev: true, + hot: true, + }).then(deps => { if (!client) { return []; } @@ -300,11 +300,8 @@ function attachHMRServer({httpServer, path, packagerServer}) { print(createEntry('HMR Server sending update to client')); client.ws.send(update); }); - }, - () => { - // do nothing, file was removed - }, - ).then(() => { + + promise.then(() => { client.ws.send(JSON.stringify({type: 'update-done'})); }); }); diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 88ffb6912..eb5039a74 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -502,10 +502,6 @@ class Bundler { }); } - stat(filePath: string) { - return this._resolver.stat(filePath); - } - getModuleForPath(entryFile: string) { return this._resolver.getModuleForPath(entryFile); } diff --git a/packager/react-packager/src/ModuleGraph/node-haste/FastFS.js b/packager/react-packager/src/ModuleGraph/node-haste/HasteFS.js similarity index 96% rename from packager/react-packager/src/ModuleGraph/node-haste/FastFS.js rename to packager/react-packager/src/ModuleGraph/node-haste/HasteFS.js index a1db35fed..7d31413cb 100644 --- a/packager/react-packager/src/ModuleGraph/node-haste/FastFS.js +++ b/packager/react-packager/src/ModuleGraph/node-haste/HasteFS.js @@ -13,7 +13,7 @@ const {dirname, join, parse} = require('path'); -module.exports = class FastFS { +module.exports = class HasteFS { directories: Set; directoryEntries: Map>; files: Set; @@ -40,7 +40,7 @@ module.exports = class FastFS { return this.directories.has(path); } - fileExists(path: string) { + exists(path: string) { return this.files.has(path); } diff --git a/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js b/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js index 6c75fdc92..bde9d0df5 100644 --- a/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js +++ b/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js @@ -15,19 +15,19 @@ const Module = require('./Module'); const Package = require('./Package'); import type {PackageData, TransformedFile} from '../types.flow'; -import type {FastFS} from './node-haste.flow'; type GetFn = (path: string) => Promise; +type GetClosestPackageFn = (filePath: string) => ?string; module.exports = class ModuleCache { - fastfs: FastFS; + _getClosestPackage: GetClosestPackageFn; getPackageData: GetFn; getTransformedFile: GetFn; modules: Map; packages: Map; - constructor(fastfs: FastFS, getTransformedFile: GetFn) { - this.fastfs = fastfs; + constructor(getClosestPackage: GetClosestPackageFn, getTransformedFile: GetFn) { + this._getClosestPackage = getClosestPackage; this.getTransformedFile = getTransformedFile; this.getPackageData = path => getTransformedFile(path).then( f => f.package || Promise.reject(new Error(`"${path}" does not exist`)) @@ -59,7 +59,7 @@ module.exports = class ModuleCache { } getPackageOf(filePath: string) { - const candidate = this.fastfs.closest(filePath, 'package.json'); + const candidate = this._getClosestPackage(filePath); return candidate != null ? this.getPackage(candidate) : null; } }; diff --git a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js index 9f1cc1726..1209bd268 100644 --- a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js +++ b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js @@ -8,6 +8,7 @@ * * @flow */ +'use strict'; 'use strict'; @@ -57,7 +58,7 @@ export type FastFS = { type HasteMapOptions = {| allowRelativePaths: boolean, extensions: Extensions, - fastfs: FastFS, + files: Array, helpers: DependencyGraphHelpers, moduleCache: ModuleCache, platforms: Platforms, @@ -66,26 +67,6 @@ type HasteMapOptions = {| declare class HasteMap { // node-haste/DependencyGraph/HasteMap.js - constructor(options: HasteMapOptions): void, build(): Promise, + constructor(options: HasteMapOptions): void, } -export type HasteMapT = HasteMap; - -type ResolutionRequestOptions = {| - platform: Platform, - platforms: Platforms, - preferNativePlatform: true, - hasteMap: HasteMap, - helpers: DependencyGraphHelpers, - moduleCache: ModuleCache, - fastfs: FastFS, - shouldThrowOnUnresolvedErrors: () => true, - extraNodeModules: {[id: ModuleID]: Path}, -|}; - -declare class ResolutionRequest { - // node-haste/DependencyGraph/ResolutionRequest.js - constructor(options: ResolutionRequestOptions): void, - resolveDependency(from: Module, to: ModuleID): Promise, -} -export type ResolutionRequestT = ResolutionRequest; diff --git a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js index fb0997e66..426f1e246 100644 --- a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js +++ b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js @@ -13,9 +13,7 @@ import type { // eslint-disable-line sort-requires Extensions, - HasteMapT, Path, - ResolutionRequestT, } from './node-haste.flow'; import type { @@ -24,11 +22,12 @@ import type { } from '../types.flow'; const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers'); -const FastFS = require('./FastFS'); -const HasteMap: Class = require('../../node-haste/DependencyGraph/HasteMap'); +const HasteFS = require('./HasteFS'); +const HasteMap = require('../../node-haste/DependencyGraph/HasteMap'); const Module = require('./Module'); const ModuleCache = require('./ModuleCache'); -const ResolutionRequest: Class = require('../../node-haste/DependencyGraph/ResolutionRequest'); +const ResolutionRequest = require('../../node-haste/DependencyGraph/ResolutionRequest'); + const defaults = require('../../../../defaults'); type ResolveOptions = {| @@ -57,12 +56,15 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn { providesModuleNodeModules: defaults.providesModuleNodeModules, }); - const fastfs = new FastFS(files); - const moduleCache = new ModuleCache(fastfs, getTransformedFile); + const hasteFS = new HasteFS(files); + const moduleCache = new ModuleCache( + filePath => hasteFS.closest(filePath, 'package.json'), + getTransformedFile, + ); const hasteMap = new HasteMap({ allowRelativePaths: true, extensions: ['js', 'json'], - fastfs, + files, helpers, moduleCache, platforms, @@ -75,8 +77,10 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn { let resolutionRequest = resolutionRequests[platform]; if (!resolutionRequest) { resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({ + dirExists: filePath => hasteFS.dirExists(filePath), + entryPath: '', extraNodeModules, - fastfs, + hasteFS, hasteMap, helpers, moduleCache, diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js index 00019f5aa..f4cf6583a 100644 --- a/packager/react-packager/src/Resolver/index.js +++ b/packager/react-packager/src/Resolver/index.js @@ -123,10 +123,6 @@ class Resolver { return this._depGraph.getShallowDependencies(entryFile, transformOptions); } - stat(filePath) { - return this._depGraph.getFS().stat(filePath); - } - getModuleForPath(entryFile) { return this._depGraph.getModuleForPath(entryFile); } diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 2d52c5213..4bbe476e3 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -347,7 +347,7 @@ class Server { if (this._hmrFileChangeListener) { // Clear cached bundles in case user reloads this._clearBundles(); - this._hmrFileChangeListener(filePath, this._bundler.stat(filePath)); + this._hmrFileChangeListener(type, filePath); return; } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) { // node module resolution can be affected by added or removed files diff --git a/packager/react-packager/src/node-haste/AssetModule.js b/packager/react-packager/src/node-haste/AssetModule.js index c1b48cdea..97e0c840b 100644 --- a/packager/react-packager/src/node-haste/AssetModule.js +++ b/packager/react-packager/src/node-haste/AssetModule.js @@ -1,6 +1,15 @@ +/** + * 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 Module = require('./Module'); + const getAssetDataFromName = require('./lib/getAssetDataFromName'); class AssetModule extends Module { diff --git a/packager/react-packager/src/node-haste/DependencyGraph/HasteMap.js b/packager/react-packager/src/node-haste/DependencyGraph/HasteMap.js index a5d6a07fc..f59cbdd3f 100644 --- a/packager/react-packager/src/node-haste/DependencyGraph/HasteMap.js +++ b/packager/react-packager/src/node-haste/DependencyGraph/HasteMap.js @@ -21,7 +21,7 @@ const PACKAGE_JSON = path.sep + 'package.json'; class HasteMap extends EventEmitter { constructor({ extensions, - fastfs, + files, moduleCache, preferNativePlatform, helpers, @@ -29,11 +29,11 @@ class HasteMap extends EventEmitter { }) { super(); this._extensions = extensions; - this._fastfs = fastfs; - this._moduleCache = moduleCache; - this._preferNativePlatform = preferNativePlatform; + this._files = files; this._helpers = helpers; + this._moduleCache = moduleCache; this._platforms = platforms; + this._preferNativePlatform = preferNativePlatform; this._processHastePackage = throat(1, this._processHastePackage.bind(this)); this._processHasteModule = throat(1, this._processHasteModule.bind(this)); @@ -42,7 +42,7 @@ class HasteMap extends EventEmitter { build() { this._map = Object.create(null); const promises = []; - this._fastfs.getAllFiles().forEach(filePath => { + this._files.forEach(filePath => { if (!this._helpers.isNodeModulesDir(filePath)) { if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) { promises.push(this._processHasteModule(filePath)); diff --git a/packager/react-packager/src/node-haste/DependencyGraph/ResolutionRequest.js b/packager/react-packager/src/node-haste/DependencyGraph/ResolutionRequest.js index 73b0f0aa6..7da2d056e 100644 --- a/packager/react-packager/src/node-haste/DependencyGraph/ResolutionRequest.js +++ b/packager/react-packager/src/node-haste/DependencyGraph/ResolutionRequest.js @@ -1,15 +1,18 @@ - /** +/** * 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. + * + * @flow */ 'use strict'; const AsyncTaskGroup = require('../lib/AsyncTaskGroup'); const MapWithDefaults = require('../lib/MapWithDefaults'); + const debug = require('debug')('ReactNativePackager:DependencyGraph'); const util = require('util'); const path = require('path'); @@ -17,31 +20,70 @@ const realPath = require('path'); const isAbsolutePath = require('absolute-path'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); +import type {HasteFS} from '../types'; +import type DependencyGraphHelpers from './DependencyGraphHelpers'; +import type HasteMap from './HasteMap'; +import type Module from '../Module'; +import type ModuleCache from '../ModuleCache'; +import type ResolutionResponse from './ResolutionResponse'; + const emptyModule = require.resolve('./assets/empty-module.js'); +type DirExistsFn = (filePath: string) => boolean; + +type Options = { + dirExists: DirExistsFn, + entryPath: string, + extraNodeModules: Object, + hasteFS: HasteFS, + hasteMap: HasteMap, + helpers: DependencyGraphHelpers, + // TODO(cpojer): Remove 'any' type. This is used for ModuleGraph/node-haste + moduleCache: ModuleCache | any, + platform: string, + platforms: Set, + preferNativePlatform: boolean, + shouldThrowOnUnresolvedErrors: () => boolean, +}; + class ResolutionRequest { + _dirExists: DirExistsFn; + _entryPath: string; + _extraNodeModules: Object; + _hasteFS: HasteFS; + _hasteMap: HasteMap; + _helpers: DependencyGraphHelpers; + _immediateResolutionCache: {[key: string]: string}; + _moduleCache: ModuleCache; + _platform: string; + _platforms: Set; + _preferNativePlatform: boolean; + _shouldThrowOnUnresolvedErrors: () => boolean; + constructor({ - platform, - platforms, - preferNativePlatform, + dirExists, entryPath, + extraNodeModules, + hasteFS, hasteMap, helpers, moduleCache, - fastfs, + platform, + platforms, + preferNativePlatform, shouldThrowOnUnresolvedErrors, - extraNodeModules, - }) { - this._platform = platform; - this._platforms = platforms; - this._preferNativePlatform = preferNativePlatform; + }: Options) { + this._dirExists = dirExists; this._entryPath = entryPath; + this._extraNodeModules = extraNodeModules; + this._hasteFS = hasteFS; this._hasteMap = hasteMap; this._helpers = helpers; this._moduleCache = moduleCache; - this._fastfs = fastfs; + this._platform = platform; + this._platforms = platforms; + this._preferNativePlatform = preferNativePlatform; this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors; - this._extraNodeModules = extraNodeModules; this._resetResolutionCache(); } @@ -54,7 +96,8 @@ class ResolutionRequest { }); } - resolveDependency(fromModule, toModuleName) { + // TODO(cpojer): Remove 'any' type. This is used for ModuleGraph/node-haste + resolveDependency(fromModule: Module | any, toModuleName: string) { const resHash = resolutionHash(fromModule.path, toModuleName); if (this._immediateResolutionCache[resHash]) { @@ -102,151 +145,105 @@ class ResolutionRequest { getOrderedDependencies({ response, - mocksPattern, transformOptions, onProgress, recursive = true, + }: { + response: ResolutionResponse, + transformOptions: Object, + onProgress: () => void, + recursive: boolean, }) { - return this._getAllMocks(mocksPattern).then(allMocks => { - const entry = this._moduleCache.getModule(this._entryPath); - const mocks = Object.create(null); + const entry = this._moduleCache.getModule(this._entryPath); - response.pushDependency(entry); - let totalModules = 1; - let finishedModules = 0; + response.pushDependency(entry); + let totalModules = 1; + let finishedModules = 0; - const resolveDependencies = module => - module.getDependencies(transformOptions) - .then(dependencyNames => - Promise.all( - dependencyNames.map(name => this.resolveDependency(module, name)) - ).then(dependencies => [dependencyNames, dependencies]) + const resolveDependencies = module => + module.getDependencies(transformOptions) + .then(dependencyNames => + Promise.all( + dependencyNames.map(name => this.resolveDependency(module, name)) + ).then(dependencies => [dependencyNames, dependencies]) + ); + + const collectedDependencies = new MapWithDefaults(module => collect(module)); + const crawlDependencies = (mod, [depNames, dependencies]) => { + const filteredPairs = []; + + dependencies.forEach((modDep, i) => { + const name = depNames[i]; + if (modDep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`', + name, + mod.path ); - - const addMockDependencies = !allMocks - ? (module, result) => result - : (module, [dependencyNames, dependencies]) => { - const list = [module.getName()]; - const pkg = module.getPackage(); - if (pkg) { - list.push(pkg.getName()); - } - return Promise.all(list).then(names => { - names.forEach(name => { - if (allMocks[name] && !mocks[name]) { - const mockModule = this._moduleCache.getModule(allMocks[name]); - dependencyNames.push(name); - dependencies.push(mockModule); - mocks[name] = allMocks[name]; - } - }); - return [dependencyNames, dependencies]; - }); - }; - - const collectedDependencies = new MapWithDefaults(module => collect(module)); - const crawlDependencies = (mod, [depNames, dependencies]) => { - const filteredPairs = []; - - dependencies.forEach((modDep, i) => { - const name = depNames[i]; - if (modDep == null) { - // It is possible to require mocks that don't have a real - // module backing them. If a dependency cannot be found but there - // exists a mock with the desired ID, resolve it and add it as - // a dependency. - if (allMocks && allMocks[name] && !mocks[name]) { - const mockModule = this._moduleCache.getModule(allMocks[name]); - mocks[name] = allMocks[name]; - return filteredPairs.push([name, mockModule]); - } - - debug( - 'WARNING: Cannot find required module `%s` from module `%s`', - name, - mod.path - ); - return false; - } - return filteredPairs.push([name, modDep]); - }); - - response.setResolvedDependencyPairs(mod, filteredPairs); - - const dependencyModules = filteredPairs.map(([, m]) => m); - const newDependencies = - dependencyModules.filter(m => !collectedDependencies.has(m)); - - if (onProgress) { - finishedModules += 1; - totalModules += newDependencies.length; - onProgress(finishedModules, totalModules); + return false; } + return filteredPairs.push([name, modDep]); + }); - if (recursive) { - // doesn't block the return of this function invocation, but defers - // the resulution of collectionsInProgress.done.then(...) - dependencyModules - .forEach(dependency => collectedDependencies.get(dependency)); - } - return dependencyModules; - }; + response.setResolvedDependencyPairs(mod, filteredPairs); - const collectionsInProgress = new AsyncTaskGroup(); - function collect(module) { - collectionsInProgress.start(module); - const result = resolveDependencies(module) - .then(deps => addMockDependencies(module, deps)) - .then(deps => crawlDependencies(module, deps)); - const end = () => collectionsInProgress.end(module); - result.then(end, end); - return result; + const dependencyModules = filteredPairs.map(([, m]) => m); + const newDependencies = + dependencyModules.filter(m => !collectedDependencies.has(m)); + + if (onProgress) { + finishedModules += 1; + totalModules += newDependencies.length; + onProgress(finishedModules, totalModules); } - return Promise.all([ - // kicks off recursive dependency discovery, but doesn't block until it's done - collectedDependencies.get(entry), + if (recursive) { + // doesn't block the return of this function invocation, but defers + // the resulution of collectionsInProgress.done.then(...) + dependencyModules + .forEach(dependency => collectedDependencies.get(dependency)); + } + return dependencyModules; + }; - // resolves when there are no more modules resolving dependencies - collectionsInProgress.done, - ]).then(([rootDependencies]) => { - return Promise.all( - Array.from(collectedDependencies, resolveKeyWithPromise) - ).then(moduleToDependenciesPairs => - [rootDependencies, new MapWithDefaults(() => [], moduleToDependenciesPairs)] - ); - }).then(([rootDependencies, moduleDependencies]) => { - // serialize dependencies, and make sure that every single one is only - // included once - const seen = new Set([entry]); - function traverse(dependencies) { - dependencies.forEach(dependency => { - if (seen.has(dependency)) { return; } - - seen.add(dependency); - response.pushDependency(dependency); - traverse(moduleDependencies.get(dependency)); - }); - } - - traverse(rootDependencies); - response.setMocks(mocks); - }); - }); - } - - _getAllMocks(pattern) { - // Take all mocks in all the roots into account. This is necessary - // because currently mocks are global: any module can be mocked by - // any mock in the system. - let mocks = null; - if (pattern) { - mocks = Object.create(null); - this._fastfs.matchFilesByPattern(pattern).forEach(file => { - mocks[path.basename(file, path.extname(file))] = file; - }); + const collectionsInProgress = new AsyncTaskGroup(); + function collect(module) { + collectionsInProgress.start(module); + const result = resolveDependencies(module) + .then(deps => crawlDependencies(module, deps)); + const end = () => collectionsInProgress.end(module); + result.then(end, end); + return result; } - return Promise.resolve(mocks); + + return Promise.all([ + // kicks off recursive dependency discovery, but doesn't block until it's done + collectedDependencies.get(entry), + + // resolves when there are no more modules resolving dependencies + collectionsInProgress.done, + ]).then(([rootDependencies]) => { + return Promise.all( + Array.from(collectedDependencies, resolveKeyWithPromise) + ).then(moduleToDependenciesPairs => + [rootDependencies, new MapWithDefaults(() => [], moduleToDependenciesPairs)] + ); + }).then(([rootDependencies, moduleDependencies]) => { + // serialize dependencies, and make sure that every single one is only + // included once + const seen = new Set([entry]); + function traverse(dependencies) { + dependencies.forEach(dependency => { + if (seen.has(dependency)) { return; } + + seen.add(dependency); + response.pushDependency(dependency); + traverse(moduleDependencies.get(dependency)); + }); + } + + traverse(rootDependencies); + }); } _resolveHasteDependency(fromModule, toModuleName) { @@ -339,7 +336,10 @@ class ResolutionRequest { if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) { // derive absolute path /.../node_modules/fromModuleDir/realModuleName const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13; - const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx)); + const fromModuleDir = fromModule.path.slice( + 0, + fromModule.path.indexOf('/', fromModuleParentIdx), + ); const absPath = path.join(fromModuleDir, realModuleName); return this._resolveFileOrDir(fromModule, absPath); } @@ -349,7 +349,7 @@ class ResolutionRequest { currDir !== '.' && currDir !== realPath.parse(fromModule.path).root; currDir = path.dirname(currDir)) { const searchPath = path.join(currDir, 'node_modules'); - if (this._fastfs.dirExists(searchPath)) { + if (this._dirExists(searchPath)) { searchQueue.push( path.join(searchPath, realModuleName) ); @@ -380,11 +380,12 @@ class ResolutionRequest { if (error.type !== 'UnableToResolveError') { throw error; } + const hint = searchQueue.length ? ' or in these directories:' : ''; throw new UnableToResolveError( fromModule, toModuleName, - `Module does not exist in the module map ${searchQueue.length ? 'or in these directories:' : ''}\n` + - searchQueue.map(searchPath => ` ${path.dirname(searchPath)}\n`) + '\n' + + `Module does not exist in the module map${hint}\n` + + searchQueue.map(searchPath => ` ${path.dirname(searchPath)}\n`).join(', ') + '\n' + `This might be related to https://github.com/facebook/react-native/issues/4968\n` + `To resolve try the following:\n` + ` 1. Clear watchman watches: \`watchman watch-del-all\`.\n` + @@ -400,7 +401,7 @@ class ResolutionRequest { return Promise.resolve().then(() => { if (this._helpers.isAssetFile(potentialModulePath)) { const dirname = path.dirname(potentialModulePath); - if (!this._fastfs.dirExists(dirname)) { + if (!this._dirExists(dirname)) { throw new UnableToResolveError( fromModule, toModule, @@ -410,7 +411,7 @@ class ResolutionRequest { const {name, type} = getAssetDataFromName(potentialModulePath, this._platforms); - let pattern = '^' + name + '(@[\\d\\.]+x)?'; + let pattern = name + '(@[\\d\\.]+x)?'; if (this._platform != null) { pattern += '(\\.' + this._platform + ')?'; } @@ -418,28 +419,26 @@ class ResolutionRequest { // We arbitrarly grab the first one, because scale selection // will happen somewhere - const [assetFile] = this._fastfs.matches( - dirname, - new RegExp(pattern) + const [assetFile] = this._hasteFS.matchFiles( + new RegExp(dirname + '(\/|\\\\)' + pattern) ); - if (assetFile) { return this._moduleCache.getAssetModule(assetFile); } } let file; - if (this._fastfs.fileExists(potentialModulePath)) { + if (this._hasteFS.exists(potentialModulePath)) { file = potentialModulePath; } else if (this._platform != null && - this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) { + this._hasteFS.exists(potentialModulePath + '.' + this._platform + '.js')) { file = potentialModulePath + '.' + this._platform + '.js'; } else if (this._preferNativePlatform && - this._fastfs.fileExists(potentialModulePath + '.native.js')) { + this._hasteFS.exists(potentialModulePath + '.native.js')) { file = potentialModulePath + '.native.js'; - } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { + } else if (this._hasteFS.exists(potentialModulePath + '.js')) { file = potentialModulePath + '.js'; - } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { + } else if (this._hasteFS.exists(potentialModulePath + '.json')) { file = potentialModulePath + '.json'; } else { throw new UnableToResolveError( @@ -455,7 +454,7 @@ class ResolutionRequest { _loadAsDir(potentialDirPath, fromModule, toModule) { return Promise.resolve().then(() => { - if (!this._fastfs.dirExists(potentialDirPath)) { + if (!this._dirExists(potentialDirPath)) { throw new UnableToResolveError( fromModule, toModule, @@ -464,7 +463,7 @@ class ResolutionRequest { } const packageJsonPath = path.join(potentialDirPath, 'package.json'); - if (this._fastfs.fileExists(packageJsonPath)) { + if (this._hasteFS.exists(packageJsonPath)) { return this._moduleCache.getPackage(packageJsonPath) .getMain().then( (main) => this._tryResolve( @@ -493,21 +492,22 @@ function resolutionHash(modulePath, depName) { return `${path.resolve(modulePath)}:${depName}`; } +class UnableToResolveError extends Error { + type: string; + + constructor(fromModule, toModule, message) { + super(); + this.message = util.format( + 'Unable to resolve module %s from %s: %s', + toModule, + fromModule.path, + message, + ); + this.type = this.name = 'UnableToResolveError'; + } -function UnableToResolveError(fromModule, toModule, message) { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - this.message = util.format( - 'Unable to resolve module %s from %s: %s', - toModule, - fromModule.path, - message, - ); - this.type = this.name = 'UnableToResolveError'; } -util.inherits(UnableToResolveError, Error); - function normalizePath(modulePath) { if (path.sep === '/') { modulePath = path.normalize(modulePath); diff --git a/packager/react-packager/src/node-haste/Module.js b/packager/react-packager/src/node-haste/Module.js index 393de4e34..532d28adb 100644 --- a/packager/react-packager/src/node-haste/Module.js +++ b/packager/react-packager/src/node-haste/Module.js @@ -17,6 +17,7 @@ const TransformCache = require('../lib/TransformCache'); const chalk = require('chalk'); const crypto = require('crypto'); const docblock = require('./DependencyGraph/docblock'); +const fs = require('fs'); const invariant = require('invariant'); const isAbsolutePath = require('absolute-path'); const jsonStableStringify = require('json-stable-stringify'); @@ -28,7 +29,6 @@ import type {ReadTransformProps} from '../lib/TransformCache'; import type Cache from './Cache'; import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers'; import type ModuleCache from './ModuleCache'; -import type FastFs from './fastfs'; type ReadResult = { code?: string, @@ -50,7 +50,6 @@ export type Options = { export type ConstructorArgs = { file: string, - fastfs: FastFs, moduleCache: ModuleCache, cache: Cache, transformCode: ?TransformCode, @@ -64,7 +63,6 @@ class Module { path: string; type: string; - _fastfs: FastFs; _moduleCache: ModuleCache; _cache: Cache; _transformCode: ?TransformCode; @@ -80,7 +78,6 @@ class Module { constructor({ file, - fastfs, moduleCache, cache, transformCode, @@ -95,7 +92,6 @@ class Module { this.path = file; this.type = 'Module'; - this._fastfs = fastfs; this._moduleCache = moduleCache; this._cache = cache; this._transformCode = transformCode; @@ -190,7 +186,9 @@ class Module { _readSourceCode() { if (!this._readSourceCodePromise) { - this._readSourceCodePromise = this._fastfs.readFile(this.path); + this._readSourceCodePromise = new Promise( + resolve => resolve(fs.readFileSync(this.path, 'utf8')) + ); } return this._readSourceCodePromise; } diff --git a/packager/react-packager/src/node-haste/ModuleCache.js b/packager/react-packager/src/node-haste/ModuleCache.js index b82c2ba41..3ade7b7b3 100644 --- a/packager/react-packager/src/node-haste/ModuleCache.js +++ b/packager/react-packager/src/node-haste/ModuleCache.js @@ -22,58 +22,58 @@ import type { TransformCode, Options as ModuleOptions, } from './Module'; -import type FastFs from './fastfs'; + +type GetClosestPackageFn = (filePath: string) => ?string; class ModuleCache { - _moduleCache: {[filePath: string]: Module}; - _packageCache: {[filePath: string]: Package}; - _fastfs: FastFs; - _cache: Cache; - _transformCode: TransformCode; - _transformCacheKey: string; - _depGraphHelpers: DependencyGraphHelpers; - _platforms: mixed; _assetDependencies: mixed; + _cache: Cache; + _depGraphHelpers: DependencyGraphHelpers; + _getClosestPackage: GetClosestPackageFn; + _moduleCache: {[filePath: string]: Module}; _moduleOptions: ModuleOptions; + _packageCache: {[filePath: string]: Package}; _packageModuleMap: WeakMap; + _platforms: mixed; + _transformCacheKey: string; + _transformCode: TransformCode; constructor({ - fastfs, - cache, - extractRequires, - transformCode, - transformCacheKey, - depGraphHelpers, assetDependencies, + cache, + depGraphHelpers, + extractRequires, + getClosestPackage, moduleOptions, + transformCacheKey, + transformCode, }: { - fastfs: FastFs, - cache: Cache, - transformCode: TransformCode, - transformCacheKey: string, - depGraphHelpers: DependencyGraphHelpers, assetDependencies: mixed, + cache: Cache, + depGraphHelpers: DependencyGraphHelpers, + getClosestPackage: GetClosestPackageFn, moduleOptions: ModuleOptions, + transformCacheKey: string, + transformCode: TransformCode, }, platforms: mixed) { - this._moduleCache = Object.create(null); - this._packageCache = Object.create(null); - this._fastfs = fastfs; - this._cache = cache; - this._transformCode = transformCode; - this._transformCacheKey = transformCacheKey; - this._depGraphHelpers = depGraphHelpers; - this._platforms = platforms; this._assetDependencies = assetDependencies; + this._getClosestPackage = getClosestPackage; + this._cache = cache; + this._depGraphHelpers = depGraphHelpers; + this._moduleCache = Object.create(null); this._moduleOptions = moduleOptions; + this._packageCache = Object.create(null); this._packageModuleMap = new WeakMap(); + this._platforms = platforms; + this._transformCacheKey = transformCacheKey; + this._transformCode = transformCode; } getModule(filePath: string) { if (!this._moduleCache[filePath]) { this._moduleCache[filePath] = new Module({ file: filePath, - fastfs: this._fastfs, moduleCache: this, cache: this._cache, transformCode: this._transformCode, @@ -93,7 +93,6 @@ class ModuleCache { if (!this._moduleCache[filePath]) { this._moduleCache[filePath] = new AssetModule({ file: filePath, - fastfs: this._fastfs, moduleCache: this, cache: this._cache, dependencies: this._assetDependencies, @@ -106,7 +105,6 @@ class ModuleCache { if (!this._packageCache[filePath]) { this._packageCache[filePath] = new Package({ file: filePath, - fastfs: this._fastfs, cache: this._cache, }); } @@ -123,7 +121,7 @@ class ModuleCache { } } - const packagePath = this._fastfs.closest(module.path, 'package.json'); + const packagePath = this._getClosestPackage(module.path); if (!packagePath) { return null; } @@ -138,7 +136,6 @@ class ModuleCache { file, cache: this._cache, depGraphHelpers: this._depGraphHelpers, - fastfs: this._fastfs, moduleCache: this, transformCode: this._transformCode, transformCacheKey: this._transformCacheKey, diff --git a/packager/react-packager/src/node-haste/Package.js b/packager/react-packager/src/node-haste/Package.js index 1961f2737..118e822c6 100644 --- a/packager/react-packager/src/node-haste/Package.js +++ b/packager/react-packager/src/node-haste/Package.js @@ -11,17 +11,16 @@ 'use strict'; +const fs = require('fs'); const isAbsolutePath = require('absolute-path'); const path = require('path'); import type Cache from './Cache'; -import type FastFs from './fastfs'; class Package { path: string; root: string; - _fastfs: FastFs; type: string; _cache: Cache; @@ -32,14 +31,12 @@ class Package { main: ?string, }>; - constructor({ file, fastfs, cache }: { + constructor({file, cache}: { file: string, - fastfs: FastFs, cache: Cache, }) { this.path = path.resolve(file); this.root = path.dirname(this.path); - this._fastfs = fastfs; this.type = 'Package'; this._cache = cache; } @@ -132,8 +129,9 @@ class Package { read() { if (!this._reading) { - this._reading = this._fastfs.readFile(this.path) - .then(jsonStr => JSON.parse(jsonStr)); + this._reading = new Promise( + resolve => resolve(JSON.parse(fs.readFileSync(this.path, 'utf8'))) + ); } return this._reading; diff --git a/packager/react-packager/src/node-haste/__mocks__/graceful-fs.js b/packager/react-packager/src/node-haste/__mocks__/graceful-fs.js index 105d4cf06..03ce1aa7a 100644 --- a/packager/react-packager/src/node-haste/__mocks__/graceful-fs.js +++ b/packager/react-packager/src/node-haste/__mocks__/graceful-fs.js @@ -8,9 +8,11 @@ */ 'use strict'; -const fs = jest.genMockFromModule('fs'); const {dirname} = require.requireActual('path'); +const fs = jest.genMockFromModule('fs'); +const path = require('path'); const stream = require.requireActual('stream'); + const noop = () => {}; function asyncCallback(cb) { @@ -178,17 +180,17 @@ fs.lstatSync.mockImpl((filepath) => { }; }); -fs.open.mockImpl(function(path) { +fs.open.mockImpl(function(filepath) { const callback = arguments[arguments.length - 1] || noop; let data, error, fd; try { - data = getToNode(path); + data = getToNode(filepath); } catch (e) { error = e; } if (error || data == null) { - error = Error(`ENOENT: no such file or directory, open ${path}`); + error = Error(`ENOENT: no such file or directory, open ${filepath}`); } if (data != null) { /* global Buffer: true */ @@ -225,12 +227,12 @@ fs.close.mockImpl((fd, callback = noop) => { let filesystem; -fs.createReadStream.mockImpl(path => { - if (!path.startsWith('/')) { - throw Error('Cannot open file ' + path); +fs.createReadStream.mockImpl(filepath => { + if (!filepath.startsWith('/')) { + throw Error('Cannot open file ' + filepath); } - const parts = path.split('/').slice(1); + const parts = filepath.split('/').slice(1); let file = filesystem; for (const part of parts) { @@ -241,7 +243,7 @@ fs.createReadStream.mockImpl(path => { } if (typeof file !== 'string') { - throw Error('Cannot open file ' + path); + throw Error('Cannot open file ' + filepath); } return new stream.Readable({ @@ -284,6 +286,9 @@ function getToNode(filepath) { filepath = filepath.substring(2); } + if (filepath.endsWith(path.sep)) { + filepath = filepath.slice(0, -1); + } const parts = filepath.split(/[\/\\]/); if (parts[0] !== '') { throw new Error('Make sure all paths are absolute.'); diff --git a/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js b/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js index 0c90e4290..d871446da 100644 --- a/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js @@ -18,8 +18,6 @@ jest .mock('child_process', () => ({})) ; -const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/; - // This doesn't have state, and it's huge (Babel) so it's much faster to // require it only once. const extractDependencies = require('../../JSTransformer/worker/extract-dependencies'); @@ -4848,6 +4846,7 @@ describe('DependencyGraph', function() { ]); filesystem.root['foo.png'] = ''; + dgraph._hasteFS._files[root + '/foo.png'] = ['', 8648460, 1, []]; dgraph.processFileChange('add', root + '/foo.png', mockStat); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { @@ -5253,199 +5252,6 @@ describe('DependencyGraph', function() { }); }); - describe('Mocks', () => { - const realPlatform = process.platform; - let DependencyGraph; - beforeEach(function() { - process.platform = 'linux'; - DependencyGraph = require('../index'); - }); - - afterEach(function() { - process.platform = realPlatform; - }); - - it('resolves to null if mocksPattern is not specified', () => { - var root = '/root'; - setMockFileSystem({ - 'root': { - '__mocks__': { - 'A.js': '', - }, - 'index.js': '', - }, - }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - - return dgraph.getDependencies({entryPath: '/root/index.js'}) - .then(response => response.finalize()) - .then(response => { - expect(response.mocks).toEqual({}); - }); - }); - - it('retrieves a list of all required mocks', () => { - var root = '/root'; - setMockFileSystem({ - 'root': { - '__mocks__': { - 'A.js': '', - 'b.js': '', - }, - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - 'require("A");', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return dgraph.getDependencies({entryPath: '/root/b.js'}) - .then(response => response.finalize()) - .then(response => { - expect(response.mocks).toEqual({ - A: '/root/__mocks__/A.js', - b: '/root/__mocks__/b.js', - }); - }); - }); - - it('adds mocks as a dependency of their actual module', () => { - var root = '/root'; - setMockFileSystem({ - 'root': { - '__mocks__': { - 'A.js': [ - 'require("b");', - ].join('\n'), - 'b.js': '', - }, - 'A.js': [ - '/**', - ' * @providesModule A', - ' */', - 'require("foo");', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') - .then(deps => { - expect(deps).toEqual([ - { - path: '/root/A.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: 'A', - dependencies: ['foo', 'A'], - }, - { - path: '/root/foo.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: 'foo', - dependencies: [], - }, - { - path: '/root/__mocks__/A.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: '/root/__mocks__/A.js', - dependencies: ['b'], - }, - { - path: '/root/__mocks__/b.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: '/root/__mocks__/b.js', - dependencies: [], - }, - ]); - }); - }); - - it('resolves mocks that do not have a real module associated with them', () => { - var root = '/root'; - setMockFileSystem({ - 'root': { - '__mocks__': { - 'foo.js': [ - 'require("b");', - ].join('\n'), - 'b.js': '', - }, - 'A.js': [ - '/**', - ' * @providesModule A', - ' */', - 'require("foo");', - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - mocksPattern, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/A.js') - .then(deps => { - expect(deps).toEqual([ - { - path: '/root/A.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: 'A', - dependencies: ['foo'], - }, - { - path: '/root/__mocks__/foo.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: '/root/__mocks__/foo.js', - dependencies: ['b'], - }, - { - path: '/root/__mocks__/b.js', - isJSON: false, - isAsset: false, - isPolyfill: false, - id: '/root/__mocks__/b.js', - dependencies: [], - }, - ]); - }); - }); - }); - describe('Progress updates', () => { let dependencyGraph, onProgress; diff --git a/packager/react-packager/src/node-haste/__tests__/Module-test.js b/packager/react-packager/src/node-haste/__tests__/Module-test.js index 53afd8a56..37af632f3 100644 --- a/packager/react-packager/src/node-haste/__tests__/Module-test.js +++ b/packager/react-packager/src/node-haste/__tests__/Module-test.js @@ -12,7 +12,6 @@ jest .dontMock('absolute-path') .dontMock('json-stable-stringify') .dontMock('imurmurhash') - .dontMock('../fastfs') .dontMock('../lib/replacePatterns') .dontMock('../DependencyGraph/docblock') .dontMock('../Module'); @@ -20,7 +19,6 @@ jest jest .mock('fs'); -const Fastfs = require('../fastfs'); const Module = require('../Module'); const ModuleCache = require('../ModuleCache'); const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); @@ -49,7 +47,7 @@ function mockIndexFile(indexJs) { describe('Module', () => { const fileName = '/root/index.js'; - let cache, fastfs; + let cache; const createCache = () => ({ get: jest.genMockFn().mockImplementation( @@ -70,28 +68,18 @@ describe('Module', () => { }, ...options, cache, - fastfs, file: options && options.file || fileName, depGraphHelpers: new DependencyGraphHelpers(), - moduleCache: new ModuleCache({fastfs, cache}), + moduleCache: new ModuleCache({cache}), transformCacheKey, }); - const createFastFS = () => - new Fastfs( - 'test', - ['/root'], - ['/root/index.js', '/root/package.json'], - {ignore: []}, - ); - const createJSONModule = (options) => createModule({...options, file: '/root/package.json'}); beforeEach(function() { process.platform = 'linux'; cache = createCache(); - fastfs = createFastFS(); transformCacheKey = 'abcdef'; TransformCache.mock.reset(); }); @@ -425,7 +413,6 @@ describe('Module', () => { .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); cache = createCache(); - fastfs = createFastFS(); mockIndexFile('test'); module = createModule({transformCode}); return module.read() diff --git a/packager/react-packager/src/node-haste/__tests__/fastfs-data b/packager/react-packager/src/node-haste/__tests__/fastfs-data deleted file mode 100644 index fe2c63885..000000000 --- a/packager/react-packager/src/node-haste/__tests__/fastfs-data +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * An arbitrary module header - * @providesModule - */ - - const some: string = 'arbitrary code'; - const containing: string = 'ūñïčødę'; - -/** - * An arbitrary class that extends some thing - * It exposes a random number, which may be reset at the callers discretion - */ -class Arbitrary extends Something { - constructor() { - this.reset(); - } - - /** - * Returns the random number - * @returns number - */ - get random(): number { - return this._random; - } - - /** - * Re-creates the internal random number - * @returns void - */ - reset(): void { - this._random = Math.random(); - } -} diff --git a/packager/react-packager/src/node-haste/fastfs.js b/packager/react-packager/src/node-haste/fastfs.js deleted file mode 100644 index 58f381e2a..000000000 --- a/packager/react-packager/src/node-haste/fastfs.js +++ /dev/null @@ -1,320 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ - -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -const {EventEmitter} = require('events'); - -const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError'; - -const { - createActionStartEntry, - createActionEndEntry, - log, - print, -} = require('../Logger'); - -class Fastfs extends EventEmitter { - - _name: string; - _ignore: (filePath: string) => boolean; - _roots: Array; - _fastPaths: {[filePath: string]: File}; - - constructor( - name: string, - roots: Array, - files: Array, - {ignore}: { - ignore: (filePath: string) => boolean, - }, - ) { - super(); - this._name = name; - this._ignore = ignore; - this._roots = roots.map(root => { - // If the path ends in a separator ("/"), remove it to make string - // operations on paths safer. - if (root.endsWith(path.sep)) { - root = root.substr(0, root.length - 1); - } - - root = path.resolve(root); - - return new File(root, true); - }); - this._fastPaths = Object.create(null); - - const buildingInMemoryFSLogEntry = - print(log(createActionStartEntry('Building in-memory fs for ' + this._name))); - - files.forEach(filePath => { - const root = this._getRoot(filePath); - if (root) { - const newFile = new File(filePath, false); - const dirname = filePath.substr(0, filePath.lastIndexOf(path.sep)); - const parent = this._fastPaths[dirname]; - this._fastPaths[filePath] = newFile; - if (parent) { - parent.addChild(newFile, this._fastPaths); - } else { - root.addChild(newFile, this._fastPaths); - } - } - }); - - print(log(createActionEndEntry(buildingInMemoryFSLogEntry))); - } - - stat(filePath: string) { - return Promise.resolve().then(() => this._getFile(filePath).stat()); - } - - getAllFiles() { - return Object.keys(this._fastPaths) - .filter(filePath => !this._fastPaths[filePath].isDir); - } - - findFilesByExts( - exts: Array, - {ignore}: {ignore: (filePath: string) => boolean} = {}, - ) { - return this.getAllFiles() - .filter(filePath => ( - exts.indexOf(path.extname(filePath).substr(1)) !== -1 && - (!ignore || !ignore(filePath)) - )); - } - - matchFilesByPattern(pattern: RegExp) { - return this.getAllFiles().filter(file => file.match(pattern)); - } - - readFile(filePath: string) { - const file = this._getFile(filePath); - if (!file) { - throw new Error(`Unable to find file with path: ${filePath}`); - } - return file.read(); - } - - closest(filePath: string, name: string) { - for (let file = this._getFile(filePath).parent; - file; - file = file.parent) { - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - if (file.children[name]) { - return file.children[name].path; - } - } - return null; - } - - fileExists(filePath: string) { - 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: string) { - let file; - try { - file = this._getFile(filePath); - } catch (e) { - if (e.type === NOT_FOUND_IN_ROOTS) { - return false; - } - throw e; - } - - return file && file.isDir; - } - - matches(dir: string, pattern: RegExp) { - const dirFile = this._getFile(dir); - if (!dirFile.isDir) { - throw new Error(`Expected file ${dirFile.path} to be a directory`); - } - - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - return Object.keys(dirFile.children) - .filter(name => name.match(pattern)) - .map(name => path.join(dirFile.path, name)); - } - - _getRoot(filePath) { - for (let i = 0; i < this._roots.length; i++) { - const possibleRoot = this._roots[i]; - if (isDescendant(possibleRoot.path, filePath)) { - return possibleRoot; - } - } - return null; - } - - _getAndAssertRoot(filePath) { - const root = this._getRoot(filePath); - if (!root) { - const error = new Error(`File ${filePath} not found in any of the roots`); - /* $FlowFixMe: Monkey-patching Error. */ - error.type = NOT_FOUND_IN_ROOTS; - throw error; - } - return root; - } - - _getFile(filePath) { - filePath = path.resolve(filePath); - if (!this._fastPaths[filePath]) { - const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath); - if (file) { - this._fastPaths[filePath] = file; - } - } - - return this._fastPaths[filePath]; - } - - processFileChange(type: string, filePath: string) { - if (type === 'delete' || type === 'change') { - const file = this._getFile(filePath); - if (file) { - file.remove(); - } - } - - delete this._fastPaths[path.resolve(filePath)]; - - if (type !== 'delete') { - const file = new File(filePath, false); - const root = this._getRoot(filePath); - if (root) { - root.addChild(file, this._fastPaths); - } - } - } -} - -class File { - - path: string; - isDir: boolean; - children: ?{[filePath: string]: File}; - parent: ?File; - - _read: ?Promise; - _stat: ?Promise; - - constructor(filePath: string, isDir: boolean) { - this.path = filePath; - this.isDir = isDir; - this.children = this.isDir ? Object.create(null) : null; - } - - read() { - if (!this._read) { - this._read = new Promise((resolve, reject) => { - try { - resolve(fs.readFileSync(this.path, 'utf8')); - } catch (e) { - reject(e); - } - }); - } - return this._read; - } - - stat() { - if (!this._stat) { - this._stat = new Promise((resolve, reject) => { - try { - resolve(fs.statSync(this.path)); - } catch (e) { - reject(e); - } - }); - } - - return this._stat; - } - - addChild(file: File, fileMap: {[filePath: string]: File}) { - const parts = file.path.substr(this.path.length + 1).split(path.sep); - if (parts.length === 1) { - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - this.children[parts[0]] = file; - file.parent = this; - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - } else if (this.children[parts[0]]) { - this.children[parts[0]].addChild(file, fileMap); - } else { - const dir = new File(this.path + path.sep + parts[0], true); - dir.parent = this; - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - this.children[parts[0]] = dir; - fileMap[dir.path] = dir; - dir.addChild(file, fileMap); - } - } - - getFileFromPath(filePath) { - const parts = path.relative(this.path, filePath).split(path.sep); - - /*eslint consistent-this:0*/ - let file = this; - for (let i = 0; i < parts.length; i++) { - const fileName = parts[i]; - if (!fileName) { - continue; - } - - if (!file || !file.isDir) { - // File not found. - return null; - } - - /* $FlowFixMe: will crash if not `isDir`, see constructor */ - file = file.children[fileName]; - } - - return file; - } - - ext() { - return path.extname(this.path).substr(1); - } - - remove() { - if (!this.parent) { - throw new Error(`No parent to delete ${this.path} from`); - } - - /* $FlowFixMe: will crash if parent is not `isDir`, see constructor */ - delete this.parent.children[path.basename(this.path)]; - } -} - -function isDescendant(root, child) { - return root === child || child.startsWith(root + path.sep); -} - -module.exports = Fastfs; diff --git a/packager/react-packager/src/node-haste/index.js b/packager/react-packager/src/node-haste/index.js index c5cbf5f0a..8f4ccd125 100644 --- a/packager/react-packager/src/node-haste/index.js +++ b/packager/react-packager/src/node-haste/index.js @@ -13,7 +13,6 @@ const Cache = require('./Cache'); const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers'); -const Fastfs = require('./fastfs'); const HasteMap = require('./DependencyGraph/HasteMap'); const JestHasteMap = require('jest-haste-map'); const Module = require('./Module'); @@ -22,6 +21,7 @@ const Polyfill = require('./Polyfill'); const ResolutionRequest = require('./DependencyGraph/ResolutionRequest'); const ResolutionResponse = require('./DependencyGraph/ResolutionResponse'); +const fs = require('fs'); const getAssetDataFromName = require('./lib/getAssetDataFromName'); const getInverseDependencies = require('./lib/getInverseDependencies'); const getPlatformExtension = require('./lib/getPlatformExtension'); @@ -31,46 +31,46 @@ const path = require('path'); const replacePatterns = require('./lib/replacePatterns'); const util = require('util'); -import type { - TransformCode, - Options as ModuleOptions, -} from './Module'; - -const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError'; - const { - createActionStartEntry, createActionEndEntry, + createActionStartEntry, log, print, } = require('../Logger'); -class DependencyGraph { +import type { + Options as ModuleOptions, + TransformCode, +} from './Module'; +import type {HasteFS} from './types'; +const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError'; + +class DependencyGraph { _opts: {| - roots: Array, - ignoreFilePath: (filePath: string) => boolean, - watch: boolean, - forceNodeFilesystemAPI: boolean, assetExts: Array, - providesModuleNodeModules: Array, - platforms: Set, - preferNativePlatform: boolean, extensions: Array, - mocksPattern: mixed, - transformCode: TransformCode, - transformCacheKey: string, - shouldThrowOnUnresolvedErrors: () => boolean, - moduleOptions: ModuleOptions, - extraNodeModules: mixed, - useWatchman: boolean, + extraNodeModules: Object, + forceNodeFilesystemAPI: boolean, + ignoreFilePath: (filePath: string) => boolean, maxWorkers: number, + mocksPattern: mixed, + moduleOptions: ModuleOptions, + platforms: Set, + preferNativePlatform: boolean, + providesModuleNodeModules: Array, resetCache: boolean, + roots: Array, + shouldThrowOnUnresolvedErrors: () => boolean, + transformCacheKey: string, + transformCode: TransformCode, + useWatchman: boolean, + watch: boolean, |}; _assetDependencies: mixed; _cache: Cache; - _fastfs: Fastfs; _haste: JestHasteMap; + _hasteFS: HasteFS; _hasteMap: HasteMap; _hasteMapError: ?Error; _helpers: DependencyGraphHelpers; @@ -79,71 +79,70 @@ class DependencyGraph { _loading: Promise; constructor({ - roots, - ignoreFilePath, - watch, - forceNodeFilesystemAPI, + // additional arguments for jest-haste-map + assetDependencies, assetExts, - providesModuleNodeModules, - platforms, - preferNativePlatform, cache, extensions, - mocksPattern, - transformCode, - transformCacheKey, - shouldThrowOnUnresolvedErrors = () => true, - assetDependencies, - moduleOptions, extraNodeModules, - // additional arguments for jest-haste-map - useWatchman, + forceNodeFilesystemAPI, + ignoreFilePath, maxWorkers, + mocksPattern, + moduleOptions, + platforms, + preferNativePlatform, + providesModuleNodeModules, resetCache, + roots, + shouldThrowOnUnresolvedErrors = () => true, + transformCacheKey, + transformCode, + useWatchman, + watch, }: { - roots: Array, - ignoreFilePath: (filePath: string) => boolean, - watch: boolean, - forceNodeFilesystemAPI?: boolean, + assetDependencies: mixed, assetExts: Array, - providesModuleNodeModules: Array, - platforms: mixed, - preferNativePlatform: boolean, cache: Cache, extensions: Array, - mocksPattern: mixed, - transformCode: TransformCode, - transformCacheKey: string, - shouldThrowOnUnresolvedErrors: () => boolean, - assetDependencies: mixed, - moduleOptions: ?ModuleOptions, - extraNodeModules: mixed, - useWatchman: boolean, + extraNodeModules: Object, + forceNodeFilesystemAPI?: boolean, + ignoreFilePath: (filePath: string) => boolean, maxWorkers: number, + mocksPattern: mixed, + moduleOptions: ?ModuleOptions, + platforms: mixed, + preferNativePlatform: boolean, + providesModuleNodeModules: Array, resetCache: boolean, + roots: Array, + shouldThrowOnUnresolvedErrors: () => boolean, + transformCacheKey: string, + transformCode: TransformCode, + useWatchman: boolean, + watch: boolean, }) { this._opts = { - roots, - ignoreFilePath: ignoreFilePath || (() => {}), - watch: !!watch, - forceNodeFilesystemAPI: !!forceNodeFilesystemAPI, assetExts: assetExts || [], - providesModuleNodeModules, - platforms: new Set(platforms || []), - preferNativePlatform: preferNativePlatform || false, extensions: extensions || ['js', 'json'], + extraNodeModules, + forceNodeFilesystemAPI: !!forceNodeFilesystemAPI, + ignoreFilePath: ignoreFilePath || (() => {}), + maxWorkers, mocksPattern, - transformCode, - transformCacheKey, - shouldThrowOnUnresolvedErrors, moduleOptions: moduleOptions || { cacheTransformResults: true, }, - extraNodeModules, - // additional arguments for jest-haste-map & defaults - useWatchman: useWatchman !== false, - maxWorkers, + platforms: new Set(platforms || []), + preferNativePlatform: preferNativePlatform || false, + providesModuleNodeModules, resetCache, + roots, + shouldThrowOnUnresolvedErrors, + transformCacheKey, + transformCode, + useWatchman: useWatchman !== false, + watch: !!watch, }; this._cache = cache; @@ -174,33 +173,34 @@ class DependencyGraph { watch: this._opts.watch, }); - this._loading = this._haste.build().then(hasteMap => { - const initializingPackagerLogEntry = - print(log(createActionStartEntry('Initializing Packager'))); - - const hasteFSFiles = hasteMap.hasteFS.getAllFiles(); - - this._fastfs = new Fastfs( - 'JavaScript', - this._opts.roots, - hasteFSFiles, - { - ignore: this._opts.ignoreFilePath, - } - ); + const initializingPackagerLogEntry = + print(log(createActionStartEntry('Initializing Packager'))); + this._loading = this._haste.build().then(({hasteFS}) => { + this._hasteFS = hasteFS; + const hasteFSFiles = hasteFS.getAllFiles(); this._moduleCache = new ModuleCache({ - fastfs: this._fastfs, cache: this._cache, transformCode: this._opts.transformCode, transformCacheKey: this._opts.transformCacheKey, depGraphHelpers: this._helpers, assetDependencies: this._assetDependencies, moduleOptions: this._opts.moduleOptions, + getClosestPackage: filePath => { + let {dir, root} = path.parse(filePath); + do { + const candidate = path.join(dir, 'package.json'); + if (this._hasteFS.exists(candidate)) { + return candidate; + } + dir = path.dirname(dir); + } while (dir !== '.' && dir !== root); + return null; + } }, this._opts.platforms); this._hasteMap = new HasteMap({ - fastfs: this._fastfs, + files: hasteFSFiles, extensions: this._opts.extensions, moduleCache: this._moduleCache, preferNativePlatform: this._opts.preferNativePlatform, @@ -208,11 +208,12 @@ class DependencyGraph { platforms: this._opts.platforms, }); - this._haste.on('change', ({eventsQueue}) => + this._haste.on('change', ({eventsQueue, hasteFS: newHasteFS}) => { + this._hasteFS = newHasteFS; eventsQueue.forEach(({type, filePath, stat}) => this.processFileChange(type, filePath, stat) - ) - ); + ); + }); const buildingHasteMapLogEntry = print(log(createActionStartEntry('Building Haste Map'))); @@ -248,10 +249,6 @@ class DependencyGraph { .getDependencies(transformOptions); } - getFS() { - return this._fastfs; - } - getWatcher() { return this._haste; } @@ -283,24 +280,30 @@ class DependencyGraph { return this.load().then(() => { platform = this._getRequestPlatform(entryPath, platform); const absPath = this._getAbsolutePath(entryPath); + const dirExists = filePath => { + try { + return fs.lstatSync(filePath).isDirectory(); + } catch (e) {} + return false; + }; const req = new ResolutionRequest({ - platform, - platforms: this._opts.platforms, - preferNativePlatform: this._opts.preferNativePlatform, + dirExists, entryPath: absPath, + extraNodeModules: this._opts.extraNodeModules, + hasteFS: this._hasteFS, hasteMap: this._hasteMap, helpers: this._helpers, moduleCache: this._moduleCache, - fastfs: this._fastfs, + platform, + platforms: this._opts.platforms, + preferNativePlatform: this._opts.preferNativePlatform, shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors, - extraNodeModules: this._opts.extraNodeModules, }); const response = new ResolutionResponse({transformOptions}); return req.getOrderedDependencies({ response, - mocksPattern: this._opts.mocksPattern, transformOptions, onProgress, recursive, @@ -309,7 +312,7 @@ class DependencyGraph { } matchFilesByPattern(pattern: RegExp) { - return this.load().then(() => this._fastfs.matchFilesByPattern(pattern)); + return this.load().then(() => this._hasteFS.matchFiles(pattern)); } _getRequestPlatform(entryPath: string, platform: string) { @@ -329,7 +332,7 @@ class DependencyGraph { for (let i = 0; i < this._opts.roots.length; i++) { const root = this._opts.roots[i]; const potentialAbsPath = path.join(root, filePath); - if (this._fastfs.fileExists(potentialAbsPath)) { + if (this._hasteFS.exists(potentialAbsPath)) { return path.resolve(potentialAbsPath); } } @@ -342,7 +345,6 @@ class DependencyGraph { } processFileChange(type: string, filePath: string, stat: Object) { - this._fastfs.processFileChange(type, filePath, stat); this._moduleCache.processFileChange(type, filePath, stat); // This code reports failures but doesn't block recovery in the dev server @@ -379,7 +381,6 @@ class DependencyGraph { } static Cache; - static Fastfs; static Module; static Polyfill; static getAssetDataFromName; @@ -391,7 +392,6 @@ class DependencyGraph { Object.assign(DependencyGraph, { Cache, - Fastfs, Module, Polyfill, getAssetDataFromName, diff --git a/packager/react-packager/src/node-haste/types.js b/packager/react-packager/src/node-haste/types.js new file mode 100644 index 000000000..b7cc2001e --- /dev/null +++ b/packager/react-packager/src/node-haste/types.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2013-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'; + +// TODO(cpojer): Create a jest-types repo. +export type HasteFS = { + exists(filePath: string): boolean, + getAllFiles(): Array, + matchFiles(pattern: RegExp | string): Array, +};