Kill fastfs

Summary:
This kills fastfs in favor of Jest's hasteFS. It gets rid of a ton of code, including the mocking code in ResolutionRequest which we don't need any more. Next step after this is to rewrite HasteMap, ModuleCache, Module/Package. We are getting closer to a nicer and faster world! :)

Here is what I did:
* Use Jest's HasteFS instead of fastfs. A fresh instance is received every time something changes on the FS.
* HasteFS is not shared with everything any more. Only one reference is kept in DependencyGraph and there are a few smaller functions that are passed around (getClosestPackage and dirExists). Note: `dirExists` now does fs access instead of an offline check. This sucks but stat calls aren't slow and aren't going to be a bottleneck in ResolutionRequest, I promise! When it is time to tackle a ResolutionRequest rewrite with jest-resolve, this will go away. "It gets worse before it gets better" :) The ModuleGraph equivalent does *not* do fs access and retains the previous way of doing things because we shouldn't do online fs access there.
* Add flow annotations to ResolutionRequest. This required a few tiny hacks for now because of ModuleGraph's duck typing. I'll get rid of this soon.
* Updated ModuleGraph to work with the new code, also created a mock HasteFS instance there.
* I fixed a few tiny mock issues for `fs` to make the tests work; I had to add one tiny little internal update to `dgraph._hasteFS._files` because the file watching in the tests isn't real. It is instrumented through some function calls, therefore the hasteFS instance doesn't get automatically updated. One way to solve this is to add `JestHasteMap.emit('change', …)` for testing but I didn't want to cut a Jest release just for that. #movefast

(Note: I will likely land this in 1.5 weeks from now after my vacation and I have yet to fully test all the product flows. Please give me feedback so I can make sure this is solid!)

Reviewed By: davidaurelio

Differential Revision: D4204082

fbshipit-source-id: d6dc9fcb77ac224df4554a59f0fce241c01b0512
This commit is contained in:
Christoph Pojer 2016-11-30 04:15:32 -08:00 committed by Facebook Github Bot
parent 2151936b80
commit 771e60235a
20 changed files with 387 additions and 952 deletions

View File

@ -502,10 +502,6 @@ class Bundler {
}); });
} }
stat(filePath: string) {
return this._resolver.stat(filePath);
}
getModuleForPath(entryFile: string) { getModuleForPath(entryFile: string) {
return this._resolver.getModuleForPath(entryFile); return this._resolver.getModuleForPath(entryFile);
} }

View File

@ -13,7 +13,7 @@
const {dirname, join, parse} = require('path'); const {dirname, join, parse} = require('path');
module.exports = class FastFS { module.exports = class HasteFS {
directories: Set<string>; directories: Set<string>;
directoryEntries: Map<string, Array<string>>; directoryEntries: Map<string, Array<string>>;
files: Set<string>; files: Set<string>;
@ -40,7 +40,7 @@ module.exports = class FastFS {
return this.directories.has(path); return this.directories.has(path);
} }
fileExists(path: string) { exists(path: string) {
return this.files.has(path); return this.files.has(path);
} }

View File

@ -15,19 +15,19 @@ const Module = require('./Module');
const Package = require('./Package'); const Package = require('./Package');
import type {PackageData, TransformedFile} from '../types.flow'; import type {PackageData, TransformedFile} from '../types.flow';
import type {FastFS} from './node-haste.flow';
type GetFn<T> = (path: string) => Promise<T>; type GetFn<T> = (path: string) => Promise<T>;
type GetClosestPackageFn = (filePath: string) => ?string;
module.exports = class ModuleCache { module.exports = class ModuleCache {
fastfs: FastFS; _getClosestPackage: GetClosestPackageFn;
getPackageData: GetFn<PackageData>; getPackageData: GetFn<PackageData>;
getTransformedFile: GetFn<TransformedFile>; getTransformedFile: GetFn<TransformedFile>;
modules: Map<string, Module>; modules: Map<string, Module>;
packages: Map<string, Package>; packages: Map<string, Package>;
constructor(fastfs: FastFS, getTransformedFile: GetFn<TransformedFile>) { constructor(getClosestPackage: GetClosestPackageFn, getTransformedFile: GetFn<TransformedFile>) {
this.fastfs = fastfs; this._getClosestPackage = getClosestPackage;
this.getTransformedFile = getTransformedFile; this.getTransformedFile = getTransformedFile;
this.getPackageData = path => getTransformedFile(path).then( this.getPackageData = path => getTransformedFile(path).then(
f => f.package || Promise.reject(new Error(`"${path}" does not exist`)) f => f.package || Promise.reject(new Error(`"${path}" does not exist`))
@ -59,7 +59,7 @@ module.exports = class ModuleCache {
} }
getPackageOf(filePath: string) { getPackageOf(filePath: string) {
const candidate = this.fastfs.closest(filePath, 'package.json'); const candidate = this._getClosestPackage(filePath);
return candidate != null ? this.getPackage(candidate) : null; return candidate != null ? this.getPackage(candidate) : null;
} }
}; };

View File

@ -8,6 +8,7 @@
* *
* @flow * @flow
*/ */
'use strict';
'use strict'; 'use strict';
@ -57,7 +58,7 @@ export type FastFS = {
type HasteMapOptions = {| type HasteMapOptions = {|
allowRelativePaths: boolean, allowRelativePaths: boolean,
extensions: Extensions, extensions: Extensions,
fastfs: FastFS, files: Array<string>,
helpers: DependencyGraphHelpers, helpers: DependencyGraphHelpers,
moduleCache: ModuleCache, moduleCache: ModuleCache,
platforms: Platforms, platforms: Platforms,
@ -66,26 +67,6 @@ type HasteMapOptions = {|
declare class HasteMap { declare class HasteMap {
// node-haste/DependencyGraph/HasteMap.js // node-haste/DependencyGraph/HasteMap.js
constructor(options: HasteMapOptions): void,
build(): Promise<Object>, build(): Promise<Object>,
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<Module>,
}
export type ResolutionRequestT = ResolutionRequest;

View File

@ -13,9 +13,7 @@
import type { // eslint-disable-line sort-requires import type { // eslint-disable-line sort-requires
Extensions, Extensions,
HasteMapT,
Path, Path,
ResolutionRequestT,
} from './node-haste.flow'; } from './node-haste.flow';
import type { import type {
@ -24,11 +22,12 @@ import type {
} from '../types.flow'; } from '../types.flow';
const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers'); const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
const FastFS = require('./FastFS'); const HasteFS = require('./HasteFS');
const HasteMap: Class<HasteMapT> = require('../../node-haste/DependencyGraph/HasteMap'); const HasteMap = require('../../node-haste/DependencyGraph/HasteMap');
const Module = require('./Module'); const Module = require('./Module');
const ModuleCache = require('./ModuleCache'); const ModuleCache = require('./ModuleCache');
const ResolutionRequest: Class<ResolutionRequestT> = require('../../node-haste/DependencyGraph/ResolutionRequest'); const ResolutionRequest = require('../../node-haste/DependencyGraph/ResolutionRequest');
const defaults = require('../../../../defaults'); const defaults = require('../../../../defaults');
type ResolveOptions = {| type ResolveOptions = {|
@ -57,12 +56,15 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
providesModuleNodeModules: defaults.providesModuleNodeModules, providesModuleNodeModules: defaults.providesModuleNodeModules,
}); });
const fastfs = new FastFS(files); const hasteFS = new HasteFS(files);
const moduleCache = new ModuleCache(fastfs, getTransformedFile); const moduleCache = new ModuleCache(
filePath => hasteFS.closest(filePath, 'package.json'),
getTransformedFile,
);
const hasteMap = new HasteMap({ const hasteMap = new HasteMap({
allowRelativePaths: true, allowRelativePaths: true,
extensions: ['js', 'json'], extensions: ['js', 'json'],
fastfs, files,
helpers, helpers,
moduleCache, moduleCache,
platforms, platforms,
@ -75,8 +77,10 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
let resolutionRequest = resolutionRequests[platform]; let resolutionRequest = resolutionRequests[platform];
if (!resolutionRequest) { if (!resolutionRequest) {
resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({ resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({
dirExists: filePath => hasteFS.dirExists(filePath),
entryPath: '',
extraNodeModules, extraNodeModules,
fastfs, hasteFS,
hasteMap, hasteMap,
helpers, helpers,
moduleCache, moduleCache,

View File

@ -123,10 +123,6 @@ class Resolver {
return this._depGraph.getShallowDependencies(entryFile, transformOptions); return this._depGraph.getShallowDependencies(entryFile, transformOptions);
} }
stat(filePath) {
return this._depGraph.getFS().stat(filePath);
}
getModuleForPath(entryFile) { getModuleForPath(entryFile) {
return this._depGraph.getModuleForPath(entryFile); return this._depGraph.getModuleForPath(entryFile);
} }

View File

@ -347,7 +347,7 @@ class Server {
if (this._hmrFileChangeListener) { if (this._hmrFileChangeListener) {
// Clear cached bundles in case user reloads // Clear cached bundles in case user reloads
this._clearBundles(); this._clearBundles();
this._hmrFileChangeListener(filePath, this._bundler.stat(filePath)); this._hmrFileChangeListener(type, filePath);
return; return;
} else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) { } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) {
// node module resolution can be affected by added or removed files // node module resolution can be affected by added or removed files

View File

@ -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'; 'use strict';
const Module = require('./Module'); const Module = require('./Module');
const getAssetDataFromName = require('./lib/getAssetDataFromName'); const getAssetDataFromName = require('./lib/getAssetDataFromName');
class AssetModule extends Module { class AssetModule extends Module {

View File

@ -21,7 +21,7 @@ const PACKAGE_JSON = path.sep + 'package.json';
class HasteMap extends EventEmitter { class HasteMap extends EventEmitter {
constructor({ constructor({
extensions, extensions,
fastfs, files,
moduleCache, moduleCache,
preferNativePlatform, preferNativePlatform,
helpers, helpers,
@ -29,11 +29,11 @@ class HasteMap extends EventEmitter {
}) { }) {
super(); super();
this._extensions = extensions; this._extensions = extensions;
this._fastfs = fastfs; this._files = files;
this._moduleCache = moduleCache;
this._preferNativePlatform = preferNativePlatform;
this._helpers = helpers; this._helpers = helpers;
this._moduleCache = moduleCache;
this._platforms = platforms; this._platforms = platforms;
this._preferNativePlatform = preferNativePlatform;
this._processHastePackage = throat(1, this._processHastePackage.bind(this)); this._processHastePackage = throat(1, this._processHastePackage.bind(this));
this._processHasteModule = throat(1, this._processHasteModule.bind(this)); this._processHasteModule = throat(1, this._processHasteModule.bind(this));
@ -42,7 +42,7 @@ class HasteMap extends EventEmitter {
build() { build() {
this._map = Object.create(null); this._map = Object.create(null);
const promises = []; const promises = [];
this._fastfs.getAllFiles().forEach(filePath => { this._files.forEach(filePath => {
if (!this._helpers.isNodeModulesDir(filePath)) { if (!this._helpers.isNodeModulesDir(filePath)) {
if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) { if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) {
promises.push(this._processHasteModule(filePath)); promises.push(this._processHasteModule(filePath));

View File

@ -1,15 +1,18 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved. * All rights reserved.
* *
* This source code is licensed under the BSD-style license found in the * 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 * 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. * of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/ */
'use strict'; 'use strict';
const AsyncTaskGroup = require('../lib/AsyncTaskGroup'); const AsyncTaskGroup = require('../lib/AsyncTaskGroup');
const MapWithDefaults = require('../lib/MapWithDefaults'); const MapWithDefaults = require('../lib/MapWithDefaults');
const debug = require('debug')('ReactNativePackager:DependencyGraph'); const debug = require('debug')('ReactNativePackager:DependencyGraph');
const util = require('util'); const util = require('util');
const path = require('path'); const path = require('path');
@ -17,31 +20,70 @@ const realPath = require('path');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const getAssetDataFromName = require('../lib/getAssetDataFromName'); 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'); 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<string>,
preferNativePlatform: boolean,
shouldThrowOnUnresolvedErrors: () => boolean,
};
class ResolutionRequest { class ResolutionRequest {
_dirExists: DirExistsFn;
_entryPath: string;
_extraNodeModules: Object;
_hasteFS: HasteFS;
_hasteMap: HasteMap;
_helpers: DependencyGraphHelpers;
_immediateResolutionCache: {[key: string]: string};
_moduleCache: ModuleCache;
_platform: string;
_platforms: Set<string>;
_preferNativePlatform: boolean;
_shouldThrowOnUnresolvedErrors: () => boolean;
constructor({ constructor({
platform, dirExists,
platforms,
preferNativePlatform,
entryPath, entryPath,
extraNodeModules,
hasteFS,
hasteMap, hasteMap,
helpers, helpers,
moduleCache, moduleCache,
fastfs, platform,
platforms,
preferNativePlatform,
shouldThrowOnUnresolvedErrors, shouldThrowOnUnresolvedErrors,
extraNodeModules, }: Options) {
}) { this._dirExists = dirExists;
this._platform = platform;
this._platforms = platforms;
this._preferNativePlatform = preferNativePlatform;
this._entryPath = entryPath; this._entryPath = entryPath;
this._extraNodeModules = extraNodeModules;
this._hasteFS = hasteFS;
this._hasteMap = hasteMap; this._hasteMap = hasteMap;
this._helpers = helpers; this._helpers = helpers;
this._moduleCache = moduleCache; this._moduleCache = moduleCache;
this._fastfs = fastfs; this._platform = platform;
this._platforms = platforms;
this._preferNativePlatform = preferNativePlatform;
this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors; this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors;
this._extraNodeModules = extraNodeModules;
this._resetResolutionCache(); 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); const resHash = resolutionHash(fromModule.path, toModuleName);
if (this._immediateResolutionCache[resHash]) { if (this._immediateResolutionCache[resHash]) {
@ -102,14 +145,16 @@ class ResolutionRequest {
getOrderedDependencies({ getOrderedDependencies({
response, response,
mocksPattern,
transformOptions, transformOptions,
onProgress, onProgress,
recursive = true, recursive = true,
}: {
response: ResolutionResponse,
transformOptions: Object,
onProgress: () => void,
recursive: boolean,
}) { }) {
return this._getAllMocks(mocksPattern).then(allMocks => {
const entry = this._moduleCache.getModule(this._entryPath); const entry = this._moduleCache.getModule(this._entryPath);
const mocks = Object.create(null);
response.pushDependency(entry); response.pushDependency(entry);
let totalModules = 1; let totalModules = 1;
@ -123,27 +168,6 @@ class ResolutionRequest {
).then(dependencies => [dependencyNames, dependencies]) ).then(dependencies => [dependencyNames, dependencies])
); );
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 collectedDependencies = new MapWithDefaults(module => collect(module));
const crawlDependencies = (mod, [depNames, dependencies]) => { const crawlDependencies = (mod, [depNames, dependencies]) => {
const filteredPairs = []; const filteredPairs = [];
@ -151,16 +175,6 @@ class ResolutionRequest {
dependencies.forEach((modDep, i) => { dependencies.forEach((modDep, i) => {
const name = depNames[i]; const name = depNames[i];
if (modDep == null) { 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( debug(
'WARNING: Cannot find required module `%s` from module `%s`', 'WARNING: Cannot find required module `%s` from module `%s`',
name, name,
@ -196,7 +210,6 @@ class ResolutionRequest {
function collect(module) { function collect(module) {
collectionsInProgress.start(module); collectionsInProgress.start(module);
const result = resolveDependencies(module) const result = resolveDependencies(module)
.then(deps => addMockDependencies(module, deps))
.then(deps => crawlDependencies(module, deps)); .then(deps => crawlDependencies(module, deps));
const end = () => collectionsInProgress.end(module); const end = () => collectionsInProgress.end(module);
result.then(end, end); result.then(end, end);
@ -230,23 +243,7 @@ class ResolutionRequest {
} }
traverse(rootDependencies); 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;
});
}
return Promise.resolve(mocks);
} }
_resolveHasteDependency(fromModule, toModuleName) { _resolveHasteDependency(fromModule, toModuleName) {
@ -339,7 +336,10 @@ class ResolutionRequest {
if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) { if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) {
// derive absolute path /.../node_modules/fromModuleDir/realModuleName // derive absolute path /.../node_modules/fromModuleDir/realModuleName
const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13; 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); const absPath = path.join(fromModuleDir, realModuleName);
return this._resolveFileOrDir(fromModule, absPath); return this._resolveFileOrDir(fromModule, absPath);
} }
@ -349,7 +349,7 @@ class ResolutionRequest {
currDir !== '.' && currDir !== realPath.parse(fromModule.path).root; currDir !== '.' && currDir !== realPath.parse(fromModule.path).root;
currDir = path.dirname(currDir)) { currDir = path.dirname(currDir)) {
const searchPath = path.join(currDir, 'node_modules'); const searchPath = path.join(currDir, 'node_modules');
if (this._fastfs.dirExists(searchPath)) { if (this._dirExists(searchPath)) {
searchQueue.push( searchQueue.push(
path.join(searchPath, realModuleName) path.join(searchPath, realModuleName)
); );
@ -380,11 +380,12 @@ class ResolutionRequest {
if (error.type !== 'UnableToResolveError') { if (error.type !== 'UnableToResolveError') {
throw error; throw error;
} }
const hint = searchQueue.length ? ' or in these directories:' : '';
throw new UnableToResolveError( throw new UnableToResolveError(
fromModule, fromModule,
toModuleName, toModuleName,
`Module does not exist in the module map ${searchQueue.length ? 'or in these directories:' : ''}\n` + `Module does not exist in the module map${hint}\n` +
searchQueue.map(searchPath => ` ${path.dirname(searchPath)}\n`) + '\n' + searchQueue.map(searchPath => ` ${path.dirname(searchPath)}\n`).join(', ') + '\n' +
`This might be related to https://github.com/facebook/react-native/issues/4968\n` + `This might be related to https://github.com/facebook/react-native/issues/4968\n` +
`To resolve try the following:\n` + `To resolve try the following:\n` +
` 1. Clear watchman watches: \`watchman watch-del-all\`.\n` + ` 1. Clear watchman watches: \`watchman watch-del-all\`.\n` +
@ -400,7 +401,7 @@ class ResolutionRequest {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (this._helpers.isAssetFile(potentialModulePath)) { if (this._helpers.isAssetFile(potentialModulePath)) {
const dirname = path.dirname(potentialModulePath); const dirname = path.dirname(potentialModulePath);
if (!this._fastfs.dirExists(dirname)) { if (!this._dirExists(dirname)) {
throw new UnableToResolveError( throw new UnableToResolveError(
fromModule, fromModule,
toModule, toModule,
@ -410,7 +411,7 @@ class ResolutionRequest {
const {name, type} = getAssetDataFromName(potentialModulePath, this._platforms); const {name, type} = getAssetDataFromName(potentialModulePath, this._platforms);
let pattern = '^' + name + '(@[\\d\\.]+x)?'; let pattern = name + '(@[\\d\\.]+x)?';
if (this._platform != null) { if (this._platform != null) {
pattern += '(\\.' + this._platform + ')?'; pattern += '(\\.' + this._platform + ')?';
} }
@ -418,28 +419,26 @@ class ResolutionRequest {
// We arbitrarly grab the first one, because scale selection // We arbitrarly grab the first one, because scale selection
// will happen somewhere // will happen somewhere
const [assetFile] = this._fastfs.matches( const [assetFile] = this._hasteFS.matchFiles(
dirname, new RegExp(dirname + '(\/|\\\\)' + pattern)
new RegExp(pattern)
); );
if (assetFile) { if (assetFile) {
return this._moduleCache.getAssetModule(assetFile); return this._moduleCache.getAssetModule(assetFile);
} }
} }
let file; let file;
if (this._fastfs.fileExists(potentialModulePath)) { if (this._hasteFS.exists(potentialModulePath)) {
file = potentialModulePath; file = potentialModulePath;
} else if (this._platform != null && } else if (this._platform != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) { this._hasteFS.exists(potentialModulePath + '.' + this._platform + '.js')) {
file = potentialModulePath + '.' + this._platform + '.js'; file = potentialModulePath + '.' + this._platform + '.js';
} else if (this._preferNativePlatform && } else if (this._preferNativePlatform &&
this._fastfs.fileExists(potentialModulePath + '.native.js')) { this._hasteFS.exists(potentialModulePath + '.native.js')) {
file = potentialModulePath + '.native.js'; file = potentialModulePath + '.native.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) { } else if (this._hasteFS.exists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js'; file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) { } else if (this._hasteFS.exists(potentialModulePath + '.json')) {
file = potentialModulePath + '.json'; file = potentialModulePath + '.json';
} else { } else {
throw new UnableToResolveError( throw new UnableToResolveError(
@ -455,7 +454,7 @@ class ResolutionRequest {
_loadAsDir(potentialDirPath, fromModule, toModule) { _loadAsDir(potentialDirPath, fromModule, toModule) {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (!this._fastfs.dirExists(potentialDirPath)) { if (!this._dirExists(potentialDirPath)) {
throw new UnableToResolveError( throw new UnableToResolveError(
fromModule, fromModule,
toModule, toModule,
@ -464,7 +463,7 @@ class ResolutionRequest {
} }
const packageJsonPath = path.join(potentialDirPath, 'package.json'); const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (this._fastfs.fileExists(packageJsonPath)) { if (this._hasteFS.exists(packageJsonPath)) {
return this._moduleCache.getPackage(packageJsonPath) return this._moduleCache.getPackage(packageJsonPath)
.getMain().then( .getMain().then(
(main) => this._tryResolve( (main) => this._tryResolve(
@ -493,10 +492,11 @@ function resolutionHash(modulePath, depName) {
return `${path.resolve(modulePath)}:${depName}`; return `${path.resolve(modulePath)}:${depName}`;
} }
class UnableToResolveError extends Error {
type: string;
function UnableToResolveError(fromModule, toModule, message) { constructor(fromModule, toModule, message) {
Error.call(this); super();
Error.captureStackTrace(this, this.constructor);
this.message = util.format( this.message = util.format(
'Unable to resolve module %s from %s: %s', 'Unable to resolve module %s from %s: %s',
toModule, toModule,
@ -504,9 +504,9 @@ function UnableToResolveError(fromModule, toModule, message) {
message, message,
); );
this.type = this.name = 'UnableToResolveError'; this.type = this.name = 'UnableToResolveError';
} }
util.inherits(UnableToResolveError, Error); }
function normalizePath(modulePath) { function normalizePath(modulePath) {
if (path.sep === '/') { if (path.sep === '/') {

View File

@ -17,6 +17,7 @@ const TransformCache = require('../lib/TransformCache');
const chalk = require('chalk'); const chalk = require('chalk');
const crypto = require('crypto'); const crypto = require('crypto');
const docblock = require('./DependencyGraph/docblock'); const docblock = require('./DependencyGraph/docblock');
const fs = require('fs');
const invariant = require('invariant'); const invariant = require('invariant');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const jsonStableStringify = require('json-stable-stringify'); const jsonStableStringify = require('json-stable-stringify');
@ -28,7 +29,6 @@ import type {ReadTransformProps} from '../lib/TransformCache';
import type Cache from './Cache'; import type Cache from './Cache';
import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers'; import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers';
import type ModuleCache from './ModuleCache'; import type ModuleCache from './ModuleCache';
import type FastFs from './fastfs';
type ReadResult = { type ReadResult = {
code?: string, code?: string,
@ -50,7 +50,6 @@ export type Options = {
export type ConstructorArgs = { export type ConstructorArgs = {
file: string, file: string,
fastfs: FastFs,
moduleCache: ModuleCache, moduleCache: ModuleCache,
cache: Cache, cache: Cache,
transformCode: ?TransformCode, transformCode: ?TransformCode,
@ -64,7 +63,6 @@ class Module {
path: string; path: string;
type: string; type: string;
_fastfs: FastFs;
_moduleCache: ModuleCache; _moduleCache: ModuleCache;
_cache: Cache; _cache: Cache;
_transformCode: ?TransformCode; _transformCode: ?TransformCode;
@ -80,7 +78,6 @@ class Module {
constructor({ constructor({
file, file,
fastfs,
moduleCache, moduleCache,
cache, cache,
transformCode, transformCode,
@ -95,7 +92,6 @@ class Module {
this.path = file; this.path = file;
this.type = 'Module'; this.type = 'Module';
this._fastfs = fastfs;
this._moduleCache = moduleCache; this._moduleCache = moduleCache;
this._cache = cache; this._cache = cache;
this._transformCode = transformCode; this._transformCode = transformCode;
@ -190,7 +186,9 @@ class Module {
_readSourceCode() { _readSourceCode() {
if (!this._readSourceCodePromise) { if (!this._readSourceCodePromise) {
this._readSourceCodePromise = this._fastfs.readFile(this.path); this._readSourceCodePromise = new Promise(
resolve => resolve(fs.readFileSync(this.path, 'utf8'))
);
} }
return this._readSourceCodePromise; return this._readSourceCodePromise;
} }

View File

@ -22,58 +22,58 @@ import type {
TransformCode, TransformCode,
Options as ModuleOptions, Options as ModuleOptions,
} from './Module'; } from './Module';
import type FastFs from './fastfs';
type GetClosestPackageFn = (filePath: string) => ?string;
class ModuleCache { class ModuleCache {
_moduleCache: {[filePath: string]: Module};
_packageCache: {[filePath: string]: Package};
_fastfs: FastFs;
_cache: Cache;
_transformCode: TransformCode;
_transformCacheKey: string;
_depGraphHelpers: DependencyGraphHelpers;
_platforms: mixed;
_assetDependencies: mixed; _assetDependencies: mixed;
_cache: Cache;
_depGraphHelpers: DependencyGraphHelpers;
_getClosestPackage: GetClosestPackageFn;
_moduleCache: {[filePath: string]: Module};
_moduleOptions: ModuleOptions; _moduleOptions: ModuleOptions;
_packageCache: {[filePath: string]: Package};
_packageModuleMap: WeakMap<Module, string>; _packageModuleMap: WeakMap<Module, string>;
_platforms: mixed;
_transformCacheKey: string;
_transformCode: TransformCode;
constructor({ constructor({
fastfs,
cache,
extractRequires,
transformCode,
transformCacheKey,
depGraphHelpers,
assetDependencies, assetDependencies,
cache,
depGraphHelpers,
extractRequires,
getClosestPackage,
moduleOptions, moduleOptions,
transformCacheKey,
transformCode,
}: { }: {
fastfs: FastFs,
cache: Cache,
transformCode: TransformCode,
transformCacheKey: string,
depGraphHelpers: DependencyGraphHelpers,
assetDependencies: mixed, assetDependencies: mixed,
cache: Cache,
depGraphHelpers: DependencyGraphHelpers,
getClosestPackage: GetClosestPackageFn,
moduleOptions: ModuleOptions, moduleOptions: ModuleOptions,
transformCacheKey: string,
transformCode: TransformCode,
}, platforms: mixed) { }, 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._assetDependencies = assetDependencies;
this._getClosestPackage = getClosestPackage;
this._cache = cache;
this._depGraphHelpers = depGraphHelpers;
this._moduleCache = Object.create(null);
this._moduleOptions = moduleOptions; this._moduleOptions = moduleOptions;
this._packageCache = Object.create(null);
this._packageModuleMap = new WeakMap(); this._packageModuleMap = new WeakMap();
this._platforms = platforms;
this._transformCacheKey = transformCacheKey;
this._transformCode = transformCode;
} }
getModule(filePath: string) { getModule(filePath: string) {
if (!this._moduleCache[filePath]) { if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new Module({ this._moduleCache[filePath] = new Module({
file: filePath, file: filePath,
fastfs: this._fastfs,
moduleCache: this, moduleCache: this,
cache: this._cache, cache: this._cache,
transformCode: this._transformCode, transformCode: this._transformCode,
@ -93,7 +93,6 @@ class ModuleCache {
if (!this._moduleCache[filePath]) { if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new AssetModule({ this._moduleCache[filePath] = new AssetModule({
file: filePath, file: filePath,
fastfs: this._fastfs,
moduleCache: this, moduleCache: this,
cache: this._cache, cache: this._cache,
dependencies: this._assetDependencies, dependencies: this._assetDependencies,
@ -106,7 +105,6 @@ class ModuleCache {
if (!this._packageCache[filePath]) { if (!this._packageCache[filePath]) {
this._packageCache[filePath] = new Package({ this._packageCache[filePath] = new Package({
file: filePath, file: filePath,
fastfs: this._fastfs,
cache: this._cache, 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) { if (!packagePath) {
return null; return null;
} }
@ -138,7 +136,6 @@ class ModuleCache {
file, file,
cache: this._cache, cache: this._cache,
depGraphHelpers: this._depGraphHelpers, depGraphHelpers: this._depGraphHelpers,
fastfs: this._fastfs,
moduleCache: this, moduleCache: this,
transformCode: this._transformCode, transformCode: this._transformCode,
transformCacheKey: this._transformCacheKey, transformCacheKey: this._transformCacheKey,

View File

@ -11,17 +11,16 @@
'use strict'; 'use strict';
const fs = require('fs');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const path = require('path'); const path = require('path');
import type Cache from './Cache'; import type Cache from './Cache';
import type FastFs from './fastfs';
class Package { class Package {
path: string; path: string;
root: string; root: string;
_fastfs: FastFs;
type: string; type: string;
_cache: Cache; _cache: Cache;
@ -32,14 +31,12 @@ class Package {
main: ?string, main: ?string,
}>; }>;
constructor({ file, fastfs, cache }: { constructor({file, cache}: {
file: string, file: string,
fastfs: FastFs,
cache: Cache, cache: Cache,
}) { }) {
this.path = path.resolve(file); this.path = path.resolve(file);
this.root = path.dirname(this.path); this.root = path.dirname(this.path);
this._fastfs = fastfs;
this.type = 'Package'; this.type = 'Package';
this._cache = cache; this._cache = cache;
} }
@ -132,8 +129,9 @@ class Package {
read() { read() {
if (!this._reading) { if (!this._reading) {
this._reading = this._fastfs.readFile(this.path) this._reading = new Promise(
.then(jsonStr => JSON.parse(jsonStr)); resolve => resolve(JSON.parse(fs.readFileSync(this.path, 'utf8')))
);
} }
return this._reading; return this._reading;

View File

@ -8,9 +8,11 @@
*/ */
'use strict'; 'use strict';
const fs = jest.genMockFromModule('fs');
const {dirname} = require.requireActual('path'); const {dirname} = require.requireActual('path');
const fs = jest.genMockFromModule('fs');
const path = require('path');
const stream = require.requireActual('stream'); const stream = require.requireActual('stream');
const noop = () => {}; const noop = () => {};
function asyncCallback(cb) { 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; const callback = arguments[arguments.length - 1] || noop;
let data, error, fd; let data, error, fd;
try { try {
data = getToNode(path); data = getToNode(filepath);
} catch (e) { } catch (e) {
error = e; error = e;
} }
if (error || data == null) { 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) { if (data != null) {
/* global Buffer: true */ /* global Buffer: true */
@ -225,12 +227,12 @@ fs.close.mockImpl((fd, callback = noop) => {
let filesystem; let filesystem;
fs.createReadStream.mockImpl(path => { fs.createReadStream.mockImpl(filepath => {
if (!path.startsWith('/')) { if (!filepath.startsWith('/')) {
throw Error('Cannot open file ' + path); throw Error('Cannot open file ' + filepath);
} }
const parts = path.split('/').slice(1); const parts = filepath.split('/').slice(1);
let file = filesystem; let file = filesystem;
for (const part of parts) { for (const part of parts) {
@ -241,7 +243,7 @@ fs.createReadStream.mockImpl(path => {
} }
if (typeof file !== 'string') { if (typeof file !== 'string') {
throw Error('Cannot open file ' + path); throw Error('Cannot open file ' + filepath);
} }
return new stream.Readable({ return new stream.Readable({
@ -284,6 +286,9 @@ function getToNode(filepath) {
filepath = filepath.substring(2); filepath = filepath.substring(2);
} }
if (filepath.endsWith(path.sep)) {
filepath = filepath.slice(0, -1);
}
const parts = filepath.split(/[\/\\]/); const parts = filepath.split(/[\/\\]/);
if (parts[0] !== '') { if (parts[0] !== '') {
throw new Error('Make sure all paths are absolute.'); throw new Error('Make sure all paths are absolute.');

View File

@ -18,8 +18,6 @@ jest
.mock('child_process', () => ({})) .mock('child_process', () => ({}))
; ;
const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/;
// This doesn't have state, and it's huge (Babel) so it's much faster to // This doesn't have state, and it's huge (Babel) so it's much faster to
// require it only once. // require it only once.
const extractDependencies = require('../../JSTransformer/worker/extract-dependencies'); const extractDependencies = require('../../JSTransformer/worker/extract-dependencies');
@ -4848,6 +4846,7 @@ describe('DependencyGraph', function() {
]); ]);
filesystem.root['foo.png'] = ''; filesystem.root['foo.png'] = '';
dgraph._hasteFS._files[root + '/foo.png'] = ['', 8648460, 1, []];
dgraph.processFileChange('add', root + '/foo.png', mockStat); dgraph.processFileChange('add', root + '/foo.png', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { 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', () => { describe('Progress updates', () => {
let dependencyGraph, onProgress; let dependencyGraph, onProgress;

View File

@ -12,7 +12,6 @@ jest
.dontMock('absolute-path') .dontMock('absolute-path')
.dontMock('json-stable-stringify') .dontMock('json-stable-stringify')
.dontMock('imurmurhash') .dontMock('imurmurhash')
.dontMock('../fastfs')
.dontMock('../lib/replacePatterns') .dontMock('../lib/replacePatterns')
.dontMock('../DependencyGraph/docblock') .dontMock('../DependencyGraph/docblock')
.dontMock('../Module'); .dontMock('../Module');
@ -20,7 +19,6 @@ jest
jest jest
.mock('fs'); .mock('fs');
const Fastfs = require('../fastfs');
const Module = require('../Module'); const Module = require('../Module');
const ModuleCache = require('../ModuleCache'); const ModuleCache = require('../ModuleCache');
const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
@ -49,7 +47,7 @@ function mockIndexFile(indexJs) {
describe('Module', () => { describe('Module', () => {
const fileName = '/root/index.js'; const fileName = '/root/index.js';
let cache, fastfs; let cache;
const createCache = () => ({ const createCache = () => ({
get: jest.genMockFn().mockImplementation( get: jest.genMockFn().mockImplementation(
@ -70,28 +68,18 @@ describe('Module', () => {
}, },
...options, ...options,
cache, cache,
fastfs,
file: options && options.file || fileName, file: options && options.file || fileName,
depGraphHelpers: new DependencyGraphHelpers(), depGraphHelpers: new DependencyGraphHelpers(),
moduleCache: new ModuleCache({fastfs, cache}), moduleCache: new ModuleCache({cache}),
transformCacheKey, transformCacheKey,
}); });
const createFastFS = () =>
new Fastfs(
'test',
['/root'],
['/root/index.js', '/root/package.json'],
{ignore: []},
);
const createJSONModule = const createJSONModule =
(options) => createModule({...options, file: '/root/package.json'}); (options) => createModule({...options, file: '/root/package.json'});
beforeEach(function() { beforeEach(function() {
process.platform = 'linux'; process.platform = 'linux';
cache = createCache(); cache = createCache();
fastfs = createFastFS();
transformCacheKey = 'abcdef'; transformCacheKey = 'abcdef';
TransformCache.mock.reset(); TransformCache.mock.reset();
}); });
@ -425,7 +413,6 @@ describe('Module', () => {
.then(() => { .then(() => {
expect(transformCode).toHaveBeenCalledTimes(1); expect(transformCode).toHaveBeenCalledTimes(1);
cache = createCache(); cache = createCache();
fastfs = createFastFS();
mockIndexFile('test'); mockIndexFile('test');
module = createModule({transformCode}); module = createModule({transformCode});
return module.read() return module.read()

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* An arbitrary module header
* @providesModule
*/
const some: string = 'arbitrary code';
const containing: string = 'ūñïčødę';
/**
* An arbitrary class that extends some thing
* It exposes a random number, which may be reset at the callers discretion
*/
class Arbitrary extends Something {
constructor() {
this.reset();
}
/**
* Returns the random number
* @returns number
*/
get random(): number {
return this._random;
}
/**
* Re-creates the internal random number
* @returns void
*/
reset(): void {
this._random = Math.random();
}
}

View File

@ -1,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<File>;
_fastPaths: {[filePath: string]: File};
constructor(
name: string,
roots: Array<string>,
files: Array<string>,
{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<string>,
{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<string>;
_stat: ?Promise<fs.Stats>;
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;

View File

@ -13,7 +13,6 @@
const Cache = require('./Cache'); const Cache = require('./Cache');
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers'); const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
const Fastfs = require('./fastfs');
const HasteMap = require('./DependencyGraph/HasteMap'); const HasteMap = require('./DependencyGraph/HasteMap');
const JestHasteMap = require('jest-haste-map'); const JestHasteMap = require('jest-haste-map');
const Module = require('./Module'); const Module = require('./Module');
@ -22,6 +21,7 @@ const Polyfill = require('./Polyfill');
const ResolutionRequest = require('./DependencyGraph/ResolutionRequest'); const ResolutionRequest = require('./DependencyGraph/ResolutionRequest');
const ResolutionResponse = require('./DependencyGraph/ResolutionResponse'); const ResolutionResponse = require('./DependencyGraph/ResolutionResponse');
const fs = require('fs');
const getAssetDataFromName = require('./lib/getAssetDataFromName'); const getAssetDataFromName = require('./lib/getAssetDataFromName');
const getInverseDependencies = require('./lib/getInverseDependencies'); const getInverseDependencies = require('./lib/getInverseDependencies');
const getPlatformExtension = require('./lib/getPlatformExtension'); const getPlatformExtension = require('./lib/getPlatformExtension');
@ -31,46 +31,46 @@ const path = require('path');
const replacePatterns = require('./lib/replacePatterns'); const replacePatterns = require('./lib/replacePatterns');
const util = require('util'); const util = require('util');
import type {
TransformCode,
Options as ModuleOptions,
} from './Module';
const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError';
const { const {
createActionStartEntry,
createActionEndEntry, createActionEndEntry,
createActionStartEntry,
log, log,
print, print,
} = require('../Logger'); } = 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: {| _opts: {|
roots: Array<string>,
ignoreFilePath: (filePath: string) => boolean,
watch: boolean,
forceNodeFilesystemAPI: boolean,
assetExts: Array<string>, assetExts: Array<string>,
providesModuleNodeModules: Array<string>,
platforms: Set<mixed>,
preferNativePlatform: boolean,
extensions: Array<string>, extensions: Array<string>,
mocksPattern: mixed, extraNodeModules: Object,
transformCode: TransformCode, forceNodeFilesystemAPI: boolean,
transformCacheKey: string, ignoreFilePath: (filePath: string) => boolean,
shouldThrowOnUnresolvedErrors: () => boolean,
moduleOptions: ModuleOptions,
extraNodeModules: mixed,
useWatchman: boolean,
maxWorkers: number, maxWorkers: number,
mocksPattern: mixed,
moduleOptions: ModuleOptions,
platforms: Set<string>,
preferNativePlatform: boolean,
providesModuleNodeModules: Array<string>,
resetCache: boolean, resetCache: boolean,
roots: Array<string>,
shouldThrowOnUnresolvedErrors: () => boolean,
transformCacheKey: string,
transformCode: TransformCode,
useWatchman: boolean,
watch: boolean,
|}; |};
_assetDependencies: mixed; _assetDependencies: mixed;
_cache: Cache; _cache: Cache;
_fastfs: Fastfs;
_haste: JestHasteMap; _haste: JestHasteMap;
_hasteFS: HasteFS;
_hasteMap: HasteMap; _hasteMap: HasteMap;
_hasteMapError: ?Error; _hasteMapError: ?Error;
_helpers: DependencyGraphHelpers; _helpers: DependencyGraphHelpers;
@ -79,71 +79,70 @@ class DependencyGraph {
_loading: Promise<mixed>; _loading: Promise<mixed>;
constructor({ constructor({
roots, // additional arguments for jest-haste-map
ignoreFilePath, assetDependencies,
watch,
forceNodeFilesystemAPI,
assetExts, assetExts,
providesModuleNodeModules,
platforms,
preferNativePlatform,
cache, cache,
extensions, extensions,
mocksPattern,
transformCode,
transformCacheKey,
shouldThrowOnUnresolvedErrors = () => true,
assetDependencies,
moduleOptions,
extraNodeModules, extraNodeModules,
// additional arguments for jest-haste-map forceNodeFilesystemAPI,
useWatchman, ignoreFilePath,
maxWorkers, maxWorkers,
mocksPattern,
moduleOptions,
platforms,
preferNativePlatform,
providesModuleNodeModules,
resetCache, resetCache,
roots,
shouldThrowOnUnresolvedErrors = () => true,
transformCacheKey,
transformCode,
useWatchman,
watch,
}: { }: {
roots: Array<string>, assetDependencies: mixed,
ignoreFilePath: (filePath: string) => boolean,
watch: boolean,
forceNodeFilesystemAPI?: boolean,
assetExts: Array<string>, assetExts: Array<string>,
providesModuleNodeModules: Array<string>,
platforms: mixed,
preferNativePlatform: boolean,
cache: Cache, cache: Cache,
extensions: Array<string>, extensions: Array<string>,
mocksPattern: mixed, extraNodeModules: Object,
transformCode: TransformCode, forceNodeFilesystemAPI?: boolean,
transformCacheKey: string, ignoreFilePath: (filePath: string) => boolean,
shouldThrowOnUnresolvedErrors: () => boolean,
assetDependencies: mixed,
moduleOptions: ?ModuleOptions,
extraNodeModules: mixed,
useWatchman: boolean,
maxWorkers: number, maxWorkers: number,
mocksPattern: mixed,
moduleOptions: ?ModuleOptions,
platforms: mixed,
preferNativePlatform: boolean,
providesModuleNodeModules: Array<string>,
resetCache: boolean, resetCache: boolean,
roots: Array<string>,
shouldThrowOnUnresolvedErrors: () => boolean,
transformCacheKey: string,
transformCode: TransformCode,
useWatchman: boolean,
watch: boolean,
}) { }) {
this._opts = { this._opts = {
roots,
ignoreFilePath: ignoreFilePath || (() => {}),
watch: !!watch,
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
assetExts: assetExts || [], assetExts: assetExts || [],
providesModuleNodeModules,
platforms: new Set(platforms || []),
preferNativePlatform: preferNativePlatform || false,
extensions: extensions || ['js', 'json'], extensions: extensions || ['js', 'json'],
extraNodeModules,
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
ignoreFilePath: ignoreFilePath || (() => {}),
maxWorkers,
mocksPattern, mocksPattern,
transformCode,
transformCacheKey,
shouldThrowOnUnresolvedErrors,
moduleOptions: moduleOptions || { moduleOptions: moduleOptions || {
cacheTransformResults: true, cacheTransformResults: true,
}, },
extraNodeModules, platforms: new Set(platforms || []),
// additional arguments for jest-haste-map & defaults preferNativePlatform: preferNativePlatform || false,
useWatchman: useWatchman !== false, providesModuleNodeModules,
maxWorkers,
resetCache, resetCache,
roots,
shouldThrowOnUnresolvedErrors,
transformCacheKey,
transformCode,
useWatchman: useWatchman !== false,
watch: !!watch,
}; };
this._cache = cache; this._cache = cache;
@ -174,33 +173,34 @@ class DependencyGraph {
watch: this._opts.watch, watch: this._opts.watch,
}); });
this._loading = this._haste.build().then(hasteMap => {
const initializingPackagerLogEntry = const initializingPackagerLogEntry =
print(log(createActionStartEntry('Initializing Packager'))); print(log(createActionStartEntry('Initializing Packager')));
this._loading = this._haste.build().then(({hasteFS}) => {
const hasteFSFiles = hasteMap.hasteFS.getAllFiles(); this._hasteFS = hasteFS;
const hasteFSFiles = hasteFS.getAllFiles();
this._fastfs = new Fastfs(
'JavaScript',
this._opts.roots,
hasteFSFiles,
{
ignore: this._opts.ignoreFilePath,
}
);
this._moduleCache = new ModuleCache({ this._moduleCache = new ModuleCache({
fastfs: this._fastfs,
cache: this._cache, cache: this._cache,
transformCode: this._opts.transformCode, transformCode: this._opts.transformCode,
transformCacheKey: this._opts.transformCacheKey, transformCacheKey: this._opts.transformCacheKey,
depGraphHelpers: this._helpers, depGraphHelpers: this._helpers,
assetDependencies: this._assetDependencies, assetDependencies: this._assetDependencies,
moduleOptions: this._opts.moduleOptions, 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._opts.platforms);
this._hasteMap = new HasteMap({ this._hasteMap = new HasteMap({
fastfs: this._fastfs, files: hasteFSFiles,
extensions: this._opts.extensions, extensions: this._opts.extensions,
moduleCache: this._moduleCache, moduleCache: this._moduleCache,
preferNativePlatform: this._opts.preferNativePlatform, preferNativePlatform: this._opts.preferNativePlatform,
@ -208,11 +208,12 @@ class DependencyGraph {
platforms: this._opts.platforms, platforms: this._opts.platforms,
}); });
this._haste.on('change', ({eventsQueue}) => this._haste.on('change', ({eventsQueue, hasteFS: newHasteFS}) => {
this._hasteFS = newHasteFS;
eventsQueue.forEach(({type, filePath, stat}) => eventsQueue.forEach(({type, filePath, stat}) =>
this.processFileChange(type, filePath, stat) this.processFileChange(type, filePath, stat)
)
); );
});
const buildingHasteMapLogEntry = const buildingHasteMapLogEntry =
print(log(createActionStartEntry('Building Haste Map'))); print(log(createActionStartEntry('Building Haste Map')));
@ -248,10 +249,6 @@ class DependencyGraph {
.getDependencies(transformOptions); .getDependencies(transformOptions);
} }
getFS() {
return this._fastfs;
}
getWatcher() { getWatcher() {
return this._haste; return this._haste;
} }
@ -283,24 +280,30 @@ class DependencyGraph {
return this.load().then(() => { return this.load().then(() => {
platform = this._getRequestPlatform(entryPath, platform); platform = this._getRequestPlatform(entryPath, platform);
const absPath = this._getAbsolutePath(entryPath); const absPath = this._getAbsolutePath(entryPath);
const dirExists = filePath => {
try {
return fs.lstatSync(filePath).isDirectory();
} catch (e) {}
return false;
};
const req = new ResolutionRequest({ const req = new ResolutionRequest({
platform, dirExists,
platforms: this._opts.platforms,
preferNativePlatform: this._opts.preferNativePlatform,
entryPath: absPath, entryPath: absPath,
extraNodeModules: this._opts.extraNodeModules,
hasteFS: this._hasteFS,
hasteMap: this._hasteMap, hasteMap: this._hasteMap,
helpers: this._helpers, helpers: this._helpers,
moduleCache: this._moduleCache, moduleCache: this._moduleCache,
fastfs: this._fastfs, platform,
platforms: this._opts.platforms,
preferNativePlatform: this._opts.preferNativePlatform,
shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors, shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors,
extraNodeModules: this._opts.extraNodeModules,
}); });
const response = new ResolutionResponse({transformOptions}); const response = new ResolutionResponse({transformOptions});
return req.getOrderedDependencies({ return req.getOrderedDependencies({
response, response,
mocksPattern: this._opts.mocksPattern,
transformOptions, transformOptions,
onProgress, onProgress,
recursive, recursive,
@ -309,7 +312,7 @@ class DependencyGraph {
} }
matchFilesByPattern(pattern: RegExp) { matchFilesByPattern(pattern: RegExp) {
return this.load().then(() => this._fastfs.matchFilesByPattern(pattern)); return this.load().then(() => this._hasteFS.matchFiles(pattern));
} }
_getRequestPlatform(entryPath: string, platform: string) { _getRequestPlatform(entryPath: string, platform: string) {
@ -329,7 +332,7 @@ class DependencyGraph {
for (let i = 0; i < this._opts.roots.length; i++) { for (let i = 0; i < this._opts.roots.length; i++) {
const root = this._opts.roots[i]; const root = this._opts.roots[i];
const potentialAbsPath = path.join(root, filePath); const potentialAbsPath = path.join(root, filePath);
if (this._fastfs.fileExists(potentialAbsPath)) { if (this._hasteFS.exists(potentialAbsPath)) {
return path.resolve(potentialAbsPath); return path.resolve(potentialAbsPath);
} }
} }
@ -342,7 +345,6 @@ class DependencyGraph {
} }
processFileChange(type: string, filePath: string, stat: Object) { processFileChange(type: string, filePath: string, stat: Object) {
this._fastfs.processFileChange(type, filePath, stat);
this._moduleCache.processFileChange(type, filePath, stat); this._moduleCache.processFileChange(type, filePath, stat);
// This code reports failures but doesn't block recovery in the dev server // This code reports failures but doesn't block recovery in the dev server
@ -379,7 +381,6 @@ class DependencyGraph {
} }
static Cache; static Cache;
static Fastfs;
static Module; static Module;
static Polyfill; static Polyfill;
static getAssetDataFromName; static getAssetDataFromName;
@ -391,7 +392,6 @@ class DependencyGraph {
Object.assign(DependencyGraph, { Object.assign(DependencyGraph, {
Cache, Cache,
Fastfs,
Module, Module,
Polyfill, Polyfill,
getAssetDataFromName, getAssetDataFromName,

17
react-packager/src/node-haste/types.js vendored Normal file
View File

@ -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<string>,
matchFiles(pattern: RegExp | string): Array<string>,
};