mirror of https://github.com/status-im/metro.git
packager: index files by dir for fast matching
Summary: This removes the call to `HasteFS#matchFiles()`, that has linear complexity. Instead, we index all the files by directory from `HasteFS` once loaded, a linear operation. Then, we can filter files from a particular directory much quicker. Reviewed By: davidaurelio Differential Revision: D4826721 fbshipit-source-id: c31a0ed9a354dbc7f2dcd56179b859e491faa16c
This commit is contained in:
parent
d1ea86b84c
commit
8d34f16a70
|
@ -22,6 +22,7 @@ import type {
|
||||||
} from '../types.flow';
|
} from '../types.flow';
|
||||||
|
|
||||||
const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
|
const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
|
||||||
|
const FilesByDirNameIndex = require('../../node-haste/FilesByDirNameIndex');
|
||||||
const HasteFS = require('./HasteFS');
|
const HasteFS = require('./HasteFS');
|
||||||
const HasteMap = require('../../node-haste/DependencyGraph/HasteMap');
|
const HasteMap = require('../../node-haste/DependencyGraph/HasteMap');
|
||||||
const Module = require('./Module');
|
const Module = require('./Module');
|
||||||
|
@ -92,6 +93,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
|
||||||
|
|
||||||
const hasteMapBuilt = hasteMap.build();
|
const hasteMapBuilt = hasteMap.build();
|
||||||
const resolutionRequests = {};
|
const resolutionRequests = {};
|
||||||
|
const filesByDirNameIndex = new FilesByDirNameIndex(hasteMap.getAllFiles());
|
||||||
return (id, source, platform, _, callback) => {
|
return (id, source, platform, _, callback) => {
|
||||||
let resolutionRequest = resolutionRequests[platform];
|
let resolutionRequest = resolutionRequests[platform];
|
||||||
if (!resolutionRequest) {
|
if (!resolutionRequest) {
|
||||||
|
@ -102,6 +104,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
|
||||||
hasteFS,
|
hasteFS,
|
||||||
hasteMap,
|
hasteMap,
|
||||||
helpers,
|
helpers,
|
||||||
|
matchFiles: filesByDirNameIndex.match.bind(filesByDirNameIndex),
|
||||||
moduleCache,
|
moduleCache,
|
||||||
moduleMap: getFakeModuleMap(hasteMap),
|
moduleMap: getFakeModuleMap(hasteMap),
|
||||||
platform,
|
platform,
|
||||||
|
|
|
@ -53,12 +53,15 @@ type ModuleishCache<TModule, TPackage> = {
|
||||||
getAssetModule(path: string): TModule,
|
getAssetModule(path: string): TModule,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MatchFilesByDirAndPattern = (dirName: string, pattern: RegExp) => Array<string>;
|
||||||
|
|
||||||
type Options<TModule, TPackage> = {
|
type Options<TModule, TPackage> = {
|
||||||
dirExists: DirExistsFn,
|
dirExists: DirExistsFn,
|
||||||
entryPath: string,
|
entryPath: string,
|
||||||
extraNodeModules: ?Object,
|
extraNodeModules: ?Object,
|
||||||
hasteFS: HasteFS,
|
hasteFS: HasteFS,
|
||||||
helpers: DependencyGraphHelpers,
|
helpers: DependencyGraphHelpers,
|
||||||
|
matchFiles: MatchFilesByDirAndPattern,
|
||||||
moduleCache: ModuleishCache<TModule, TPackage>,
|
moduleCache: ModuleishCache<TModule, TPackage>,
|
||||||
moduleMap: ModuleMap,
|
moduleMap: ModuleMap,
|
||||||
platform: string,
|
platform: string,
|
||||||
|
@ -89,6 +92,7 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
|
||||||
_hasteFS: HasteFS;
|
_hasteFS: HasteFS;
|
||||||
_helpers: DependencyGraphHelpers;
|
_helpers: DependencyGraphHelpers;
|
||||||
_immediateResolutionCache: {[key: string]: TModule};
|
_immediateResolutionCache: {[key: string]: TModule};
|
||||||
|
_matchFiles: MatchFilesByDirAndPattern;
|
||||||
_moduleCache: ModuleishCache<TModule, TPackage>;
|
_moduleCache: ModuleishCache<TModule, TPackage>;
|
||||||
_moduleMap: ModuleMap;
|
_moduleMap: ModuleMap;
|
||||||
_platform: string;
|
_platform: string;
|
||||||
|
@ -102,6 +106,7 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
|
||||||
extraNodeModules,
|
extraNodeModules,
|
||||||
hasteFS,
|
hasteFS,
|
||||||
helpers,
|
helpers,
|
||||||
|
matchFiles,
|
||||||
moduleCache,
|
moduleCache,
|
||||||
moduleMap,
|
moduleMap,
|
||||||
platform,
|
platform,
|
||||||
|
@ -113,6 +118,7 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
|
||||||
this._extraNodeModules = extraNodeModules;
|
this._extraNodeModules = extraNodeModules;
|
||||||
this._hasteFS = hasteFS;
|
this._hasteFS = hasteFS;
|
||||||
this._helpers = helpers;
|
this._helpers = helpers;
|
||||||
|
this._matchFiles = matchFiles;
|
||||||
this._moduleCache = moduleCache;
|
this._moduleCache = moduleCache;
|
||||||
this._moduleMap = moduleMap;
|
this._moduleMap = moduleMap;
|
||||||
this._platform = platform;
|
this._platform = platform;
|
||||||
|
@ -442,7 +448,7 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
|
||||||
|
|
||||||
_loadAsFile(potentialModulePath: string, fromModule: TModule, toModule: string): TModule {
|
_loadAsFile(potentialModulePath: string, fromModule: TModule, toModule: string): TModule {
|
||||||
if (this._helpers.isAssetFile(potentialModulePath)) {
|
if (this._helpers.isAssetFile(potentialModulePath)) {
|
||||||
let dirname = path.dirname(potentialModulePath);
|
const dirname = path.dirname(potentialModulePath);
|
||||||
if (!this._dirExists(dirname)) {
|
if (!this._dirExists(dirname)) {
|
||||||
throw new UnableToResolveError(
|
throw new UnableToResolveError(
|
||||||
fromModule,
|
fromModule,
|
||||||
|
@ -453,22 +459,16 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
|
||||||
|
|
||||||
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 + ')?';
|
||||||
}
|
}
|
||||||
pattern += '\\.' + type;
|
pattern += '\\.' + type + '$';
|
||||||
|
|
||||||
// Escape backslashes in the path to be able to use it in the regex
|
const assetFiles = this._matchFiles(dirname, new RegExp(pattern));
|
||||||
if (path.sep === '\\') {
|
// We arbitrarly grab the lowest, because scale selection will happen
|
||||||
dirname = dirname.replace(/\\/g, '\\\\');
|
// somewhere else. Always the lowest so that it's stable between builds.
|
||||||
}
|
const assetFile = getArrayLowestItem(assetFiles);
|
||||||
|
|
||||||
// We arbitrarly grab the first one, because scale selection
|
|
||||||
// will happen somewhere
|
|
||||||
const [assetFile] = this._hasteFS.matchFiles(
|
|
||||||
new RegExp(dirname + '(\/|\\\\)' + pattern)
|
|
||||||
);
|
|
||||||
if (assetFile) {
|
if (assetFile) {
|
||||||
return this._moduleCache.getAssetModule(assetFile);
|
return this._moduleCache.getAssetModule(assetFile);
|
||||||
}
|
}
|
||||||
|
@ -582,6 +582,19 @@ function isRelativeImport(filePath) {
|
||||||
return /^[.][.]?(?:[/]|$)/.test(filePath);
|
return /^[.][.]?(?:[/]|$)/.test(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getArrayLowestItem(a: Array<string>): string | void {
|
||||||
|
if (a.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let lowest = a[0];
|
||||||
|
for (let i = 1; i < a.length; ++i) {
|
||||||
|
if (a[i] < lowest) {
|
||||||
|
lowest = a[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lowest;
|
||||||
|
}
|
||||||
|
|
||||||
ResolutionRequest.emptyModule = require.resolve('./assets/empty-module.js');
|
ResolutionRequest.emptyModule = require.resolve('./assets/empty-module.js');
|
||||||
|
|
||||||
module.exports = ResolutionRequest;
|
module.exports = ResolutionRequest;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* 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 path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a way to find files quickly given a RegExp, in a specific directory.
|
||||||
|
* This is must faster than iterating over all the files and matching both
|
||||||
|
* directory and RegExp at the same time.
|
||||||
|
*
|
||||||
|
* This was first implemented to support finding assets fast, for which we know
|
||||||
|
* the directory, but we want to identify all variants (ex. @2x, @1x, for
|
||||||
|
* a picture's different definition levels).
|
||||||
|
*/
|
||||||
|
class FilesByDirNameIndex {
|
||||||
|
_filesByDirName: Map<string, Array<string>>;
|
||||||
|
|
||||||
|
constructor(allFilePaths: Array<string>) {
|
||||||
|
this._filesByDirName = new Map();
|
||||||
|
for (let i = 0; i < allFilePaths.length; ++i) {
|
||||||
|
const filePath = allFilePaths[i];
|
||||||
|
const dirName = path.dirname(filePath);
|
||||||
|
let dir = this._filesByDirName.get(dirName);
|
||||||
|
if (dir === undefined) {
|
||||||
|
dir = [];
|
||||||
|
this._filesByDirName.set(dirName, dir);
|
||||||
|
}
|
||||||
|
dir.push(path.basename(filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match(dirName: string, pattern: RegExp): Array<string> {
|
||||||
|
const results = [];
|
||||||
|
const dir = this._filesByDirName.get(dirName);
|
||||||
|
if (dir === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < dir.length; ++i) {
|
||||||
|
if (pattern.test(dir[i])) {
|
||||||
|
results.push(path.join(dirName, dir[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FilesByDirNameIndex;
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
const Cache = require('./Cache');
|
const Cache = require('./Cache');
|
||||||
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
|
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
|
||||||
|
const FilesByDirNameIndex = require('./FilesByDirNameIndex');
|
||||||
const JestHasteMap = require('jest-haste-map');
|
const JestHasteMap = require('jest-haste-map');
|
||||||
const Module = require('./Module');
|
const Module = require('./Module');
|
||||||
const ModuleCache = require('./ModuleCache');
|
const ModuleCache = require('./ModuleCache');
|
||||||
|
@ -76,6 +77,7 @@ const JEST_HASTE_MAP_CACHE_BREAKER = 1;
|
||||||
class DependencyGraph extends EventEmitter {
|
class DependencyGraph extends EventEmitter {
|
||||||
|
|
||||||
_opts: Options;
|
_opts: Options;
|
||||||
|
_filesByDirNameIndex: FilesByDirNameIndex;
|
||||||
_haste: JestHasteMap;
|
_haste: JestHasteMap;
|
||||||
_helpers: DependencyGraphHelpers;
|
_helpers: DependencyGraphHelpers;
|
||||||
_moduleCache: ModuleCache;
|
_moduleCache: ModuleCache;
|
||||||
|
@ -91,12 +93,14 @@ class DependencyGraph extends EventEmitter {
|
||||||
super();
|
super();
|
||||||
invariant(config.opts.maxWorkerCount >= 1, 'worker count must be greater or equal to 1');
|
invariant(config.opts.maxWorkerCount >= 1, 'worker count must be greater or equal to 1');
|
||||||
this._opts = config.opts;
|
this._opts = config.opts;
|
||||||
|
this._filesByDirNameIndex = new FilesByDirNameIndex(config.initialHasteFS.getAllFiles());
|
||||||
this._haste = config.haste;
|
this._haste = config.haste;
|
||||||
this._hasteFS = config.initialHasteFS;
|
this._hasteFS = config.initialHasteFS;
|
||||||
this._moduleMap = config.initialModuleMap;
|
this._moduleMap = config.initialModuleMap;
|
||||||
this._helpers = new DependencyGraphHelpers(this._opts);
|
this._helpers = new DependencyGraphHelpers(this._opts);
|
||||||
this._haste.on('change', this._onHasteChange.bind(this));
|
this._haste.on('change', this._onHasteChange.bind(this));
|
||||||
this._moduleCache = this._createModuleCache();
|
this._moduleCache = this._createModuleCache();
|
||||||
|
(this: any)._matchFilesByDirAndPattern = this._matchFilesByDirAndPattern.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _createHaste(opts: Options): JestHasteMap {
|
static _createHaste(opts: Options): JestHasteMap {
|
||||||
|
@ -149,6 +153,7 @@ class DependencyGraph extends EventEmitter {
|
||||||
|
|
||||||
_onHasteChange({eventsQueue, hasteFS, moduleMap}) {
|
_onHasteChange({eventsQueue, hasteFS, moduleMap}) {
|
||||||
this._hasteFS = hasteFS;
|
this._hasteFS = hasteFS;
|
||||||
|
this._filesByDirNameIndex = new FilesByDirNameIndex(hasteFS.getAllFiles());
|
||||||
this._moduleMap = moduleMap;
|
this._moduleMap = moduleMap;
|
||||||
eventsQueue.forEach(({type, filePath, stat}) =>
|
eventsQueue.forEach(({type, filePath, stat}) =>
|
||||||
this._moduleCache.processFileChange(type, filePath, stat)
|
this._moduleCache.processFileChange(type, filePath, stat)
|
||||||
|
@ -226,6 +231,7 @@ class DependencyGraph extends EventEmitter {
|
||||||
extraNodeModules: this._opts.extraNodeModules,
|
extraNodeModules: this._opts.extraNodeModules,
|
||||||
hasteFS: this._hasteFS,
|
hasteFS: this._hasteFS,
|
||||||
helpers: this._helpers,
|
helpers: this._helpers,
|
||||||
|
matchFiles: this._matchFilesByDirAndPattern,
|
||||||
moduleCache: this._moduleCache,
|
moduleCache: this._moduleCache,
|
||||||
moduleMap: this._moduleMap,
|
moduleMap: this._moduleMap,
|
||||||
platform,
|
platform,
|
||||||
|
@ -243,6 +249,10 @@ class DependencyGraph extends EventEmitter {
|
||||||
}).then(() => response);
|
}).then(() => response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_matchFilesByDirAndPattern(dirName: string, pattern: RegExp) {
|
||||||
|
return this._filesByDirNameIndex.match(dirName, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
matchFilesByPattern(pattern: RegExp) {
|
matchFilesByPattern(pattern: RegExp) {
|
||||||
return Promise.resolve(this._hasteFS.matchFiles(pattern));
|
return Promise.resolve(this._hasteFS.matchFiles(pattern));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue