Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 101 additions & 13 deletions packages/metro/src/node-haste/DependencyGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import type {
FileSystem,
HasteMap,
HealthCheckResult,
StaticFile,
WatcherStatus,
default as MetroFileMap,
} from 'metro-file-map';
import type {PackageJson} from 'metro-resolver/private/types';

import createFileMap from './DependencyGraph/createFileMap';
import createModuleResolver from './DependencyGraph/createModuleResolver';
Expand All @@ -35,7 +37,11 @@ import {
PackageResolutionError,
} from 'metro-core';
import canonicalize from 'metro-core/private/canonicalize';
import {DuplicateHasteCandidatesError} from 'metro-file-map';
import {
DuplicateHasteCandidatesError,
NoopCacheManager,
createStaticCrawler,
} from 'metro-file-map';
import {InvalidPackageError} from 'metro-resolver';
import EventEmitter from 'node:events';
import path from 'node:path';
Expand All @@ -45,6 +51,46 @@ const {createActionStartEntry, createActionEndEntry, log} = Logger;

const NULL_PLATFORM = Symbol();

function toStaticFiles(
files: Iterable<Readonly<{path: string, hasteId?: ?string}>>,
): ReadonlyArray<StaticFile> {
const staticFiles: Array<StaticFile> = [];
for (const file of files) {
staticFiles.push({
path: file.path,
pluginData: file.hasteId != null ? {haste: file.hasteId} : null,
});
}
return staticFiles;
}

/**
* Options for building a graph from a known file listing rather than by
* crawling the filesystem. Unstable: the shape is still settling.
*/
export type StaticFileMapOptions = Readonly<{
/**
* Every file in the graph. Paths are absolute, or relative to
* `config.projectRoot`, using system separators.
*
* `hasteId` is registered as-is - unlike a crawl, it is not filtered by
* extension or by `node_modules`. Callers must apply their own policy.
*/
files: Iterable<Readonly<{path: string, hasteId?: ?string}>>,

/**
* Supplies parsed `package.json` contents in place of reading them.
*/
readPackageJson?: (absolutePackageJsonPath: string) => PackageJson,

/**
* Let `DuplicateHasteCandidatesError` and `InvalidPackageError` propagate
* from `resolveDependency` instead of being wrapped in
* `AmbiguousModuleResolutionError` / `PackageResolutionError`.
*/
unstable_rawResolutionErrors?: boolean,
}>;

function getOrCreateMap<T>(
map: Map<string | symbol, Map<string | symbol, T>>,
field: string,
Expand Down Expand Up @@ -83,28 +129,63 @@ export default class DependencyGraph extends EventEmitter {
>,
>;
_initializedPromise: Promise<void>;
#staticOptions: ?StaticFileMapOptions;

/**
* Build a graph from a known set of files, without crawling or reading the
* filesystem. Resolves once the graph is ready to use.
*/
static async unstable_fromStaticFileMap(
config: ConfigT,
options: StaticFileMapOptions,
): Promise<DependencyGraph> {
const graph = new DependencyGraph(config, {
unstable_staticFileMap: options,
watch: false,
});
await graph.ready();
return graph;
}

constructor(
config: ConfigT,
options?: {
readonly hasReducedPerformance?: boolean,
readonly watch?: boolean,
readonly unstable_staticFileMap?: ?StaticFileMapOptions,
},
) {
super();

this._config = config;

const {hasReducedPerformance, watch} = options ?? {};
const initializingMetroLogEntry = log(
createActionStartEntry('Initializing Metro'),
);

config.reporter.update({
type: 'dep_graph_loading',
hasReducedPerformance: !!hasReducedPerformance,
});
const {hasReducedPerformance, watch, unstable_staticFileMap} =
options ?? {};
this.#staticOptions = unstable_staticFileMap;

// Startup progress reporting describes crawling a filesystem and starting a
// server. Neither applies when the graph is built from a supplied listing,
// and consumers there may not have a terminal to report to.
const isStatic = unstable_staticFileMap != null;
const initializingMetroLogEntry = isStatic
? null
: log(createActionStartEntry('Initializing Metro'));

if (!isStatic) {
config.reporter.update({
type: 'dep_graph_loading',
hasReducedPerformance: !!hasReducedPerformance,
});
}
const {fileMap, hasteMap, dependencyPlugin} = createFileMap(config, {
// A supplied listing is already in memory - there is nothing to warm up.
cacheManagerFactory: isStatic ? () => new NoopCacheManager() : null,
crawlerFactory:
unstable_staticFileMap != null
? createStaticCrawler({
files: toStaticFiles(unstable_staticFileMap.files),
})
: null,
throwOnModuleCollision: false,
watch,
});
Expand All @@ -117,8 +198,10 @@ export default class DependencyGraph extends EventEmitter {
this._haste.on('status', status => this._onWatcherStatus(status));

this._initializedPromise = fileMap.build().then(({fileSystem}) => {
log(createActionEndEntry(initializingMetroLogEntry));
config.reporter.update({type: 'dep_graph_loaded'});
if (initializingMetroLogEntry != null) {
log(createActionEndEntry(initializingMetroLogEntry));
config.reporter.update({type: 'dep_graph_loaded'});
}

this._fileSystem = fileSystem;
this._hasteMap = hasteMap;
Expand All @@ -132,6 +215,7 @@ export default class DependencyGraph extends EventEmitter {
this.#packageCache = new PackageCache({
getClosestPackage: absoluteModulePath =>
this._getClosestPackage(absoluteModulePath),
readPackageJson: this.#staticOptions?.readPackageJson,
});
this._createModuleResolver();
});
Expand Down Expand Up @@ -165,9 +249,10 @@ export default class DependencyGraph extends EventEmitter {
}

_createModuleResolver() {
const fileSystem = this._fileSystem;
this._moduleResolver = createModuleResolver({
config: this._config,
fileSystem: this._fileSystem,
fileSystem,
hasteMap: this._hasteMap,
packageCache: this.#packageCache,
});
Expand Down Expand Up @@ -295,6 +380,9 @@ export default class DependencyGraph extends EventEmitter {
resolverOptions,
);
} catch (error) {
if (this.#staticOptions?.unstable_rawResolutionErrors === true) {
throw error;
}
if (error instanceof DuplicateHasteCandidatesError) {
throw new AmbiguousModuleResolutionError(originModulePath, error);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/metro/src/node-haste/DependencyGraph/createFileMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
*/

import type {ConfigT} from 'metro-config';
import type {HasteMap, InputFileMapPlugin} from 'metro-file-map';
import type {
CacheManagerFactory,
CrawlerFactory,
HasteMap,
InputFileMapPlugin,
} from 'metro-file-map';

import MetroFileMap, {
DependencyPlugin,
Expand Down Expand Up @@ -54,6 +59,8 @@ export default function createFileMap(
watch?: boolean,
throwOnModuleCollision?: boolean,
cacheFilePrefix?: string,
cacheManagerFactory?: ?CacheManagerFactory,
crawlerFactory?: ?CrawlerFactory,
}>,
): {
fileMap: MetroFileMap,
Expand Down Expand Up @@ -97,6 +104,7 @@ export default function createFileMap(

const fileMap = new MetroFileMap({
cacheManagerFactory:
options?.cacheManagerFactory ??
config?.unstable_fileMapCacheManagerFactory ??
(factoryParams =>
new DiskCacheManager(factoryParams, {
Expand All @@ -105,6 +113,7 @@ export default function createFileMap(
cacheFilePrefix: options?.cacheFilePrefix,
autoSave,
})),
crawlerFactory: options?.crawlerFactory,
perfLoggerFactory: config.unstable_perfLoggerFactory,
computeSha1: !config.watcher.unstable_lazySha1,
enableSymlinks: true,
Expand Down
Loading
Loading