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) {
return this._resolver.getModuleForPath(entryFile);
}

View File

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

View File

@ -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<T> = (path: string) => Promise<T>;
type GetClosestPackageFn = (filePath: string) => ?string;
module.exports = class ModuleCache {
fastfs: FastFS;
_getClosestPackage: GetClosestPackageFn;
getPackageData: GetFn<PackageData>;
getTransformedFile: GetFn<TransformedFile>;
modules: Map<string, Module>;
packages: Map<string, Package>;
constructor(fastfs: FastFS, getTransformedFile: GetFn<TransformedFile>) {
this.fastfs = fastfs;
constructor(getClosestPackage: GetClosestPackageFn, getTransformedFile: GetFn<TransformedFile>) {
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;
}
};

View File

@ -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<string>,
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<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
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<HasteMapT> = 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<ResolutionRequestT> = 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,

View File

@ -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);
}

View File

@ -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

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

View File

@ -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));

View File

@ -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<string>,
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<string>;
_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);

View File

@ -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;
}

View File

@ -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<Module, string>;
_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,

View File

@ -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;

View File

@ -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.');

View File

@ -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;

View File

@ -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()

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 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<string>,
ignoreFilePath: (filePath: string) => boolean,
watch: boolean,
forceNodeFilesystemAPI: boolean,
assetExts: Array<string>,
providesModuleNodeModules: Array<string>,
platforms: Set<mixed>,
preferNativePlatform: boolean,
extensions: Array<string>,
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<string>,
preferNativePlatform: boolean,
providesModuleNodeModules: Array<string>,
resetCache: boolean,
roots: Array<string>,
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<mixed>;
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<string>,
ignoreFilePath: (filePath: string) => boolean,
watch: boolean,
forceNodeFilesystemAPI?: boolean,
assetDependencies: mixed,
assetExts: Array<string>,
providesModuleNodeModules: Array<string>,
platforms: mixed,
preferNativePlatform: boolean,
cache: Cache,
extensions: Array<string>,
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<string>,
resetCache: boolean,
roots: Array<string>,
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,

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>,
};