Skip to content

Instantly share code, notes, and snippets.

@budiadiono
Created December 25, 2022 14:33
Show Gist options
  • Save budiadiono/a132efb8a00e7e5b5105385497f01c3d to your computer and use it in GitHub Desktop.
Save budiadiono/a132efb8a00e7e5b5105385497f01c3d to your computer and use it in GitHub Desktop.
patch for metro/src/node-haste/DependencyGraph.js (version 0.73.6)
"use strict";
var _metroFileMap = require("metro-file-map");
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
const canonicalize = require("metro-core/src/canonicalize");
const createHasteMap = require("./DependencyGraph/createHasteMap");
const { ModuleResolver } = require("./DependencyGraph/ModuleResolution");
const ModuleCache = require("./ModuleCache");
const { EventEmitter } = require("events");
const fs = require("fs");
const {
AmbiguousModuleResolutionError,
Logger: { createActionStartEntry, createActionEndEntry, log },
PackageResolutionError,
} = require("metro-core");
const { InvalidPackageError } = require("metro-resolver");
const nullthrows = require("nullthrows");
const path = require("path");
const NULL_PLATFORM = Symbol();
function getOrCreateMap(map, field) {
let subMap = map.get(field);
if (!subMap) {
subMap = new Map();
map.set(field, subMap);
}
return subMap;
}
class DependencyGraph extends EventEmitter {
constructor(config, options) {
super();
this._config = config;
this._assetExtensions = new Set(
config.resolver.assetExts.map((asset) => "." + asset)
);
const { hasReducedPerformance, watch } =
options !== null && options !== void 0 ? options : {};
const initializingMetroLogEntry = log(
createActionStartEntry("Initializing Metro")
);
config.reporter.update({
type: "dep_graph_loading",
hasReducedPerformance: !!hasReducedPerformance,
});
const haste = createHasteMap(config, {
watch,
});
// We can have a lot of graphs listening to Haste for changes.
// Bump this up to silence the max listeners EventEmitter warning.
haste.setMaxListeners(1000);
this._haste = haste;
this._haste.on("status", (status) => this._onWatcherStatus(status));
this._readyPromise = haste
.build()
.then(({ fileSystem, hasteModuleMap }) => {
log(createActionEndEntry(initializingMetroLogEntry));
config.reporter.update({
type: "dep_graph_loaded",
});
this._fileSystem = fileSystem;
this._hasteModuleMap = hasteModuleMap;
this._haste.on("change", (changeEvent) =>
this._onHasteChange(changeEvent)
);
this._haste.on("healthCheck", (result) =>
this._onWatcherHealthCheck(result)
);
this._resolutionCache = new Map();
this._moduleCache = this._createModuleCache();
this._createModuleResolver();
});
}
_onWatcherHealthCheck(result) {
this._config.reporter.update({
type: "watcher_health_check_result",
result,
});
}
_onWatcherStatus(status) {
this._config.reporter.update({
type: "watcher_status",
status,
});
}
// Waits for the dependency graph to become ready after initialisation.
// Don't read anything from the graph until this resolves.
async ready() {
await this._readyPromise;
}
// Creates the dependency graph and waits for it to become ready.
// @deprecated Use the constructor + ready() directly.
static async load(config, options) {
const self = new DependencyGraph(config, options);
await self.ready();
return self;
}
_getClosestPackage(filePath) {
const parsedPath = path.parse(filePath);
const root = parsedPath.root;
let dir = parsedPath.dir;
do {
const candidate = path.join(dir, "package.json");
if (this._fileSystem.exists(candidate)) {
return candidate;
}
dir = path.dirname(dir);
} while (dir !== "." && dir !== root);
return null;
}
_onHasteChange({ eventsQueue }) {
this._resolutionCache = new Map();
eventsQueue.forEach(({ filePath }) =>
this._moduleCache.invalidate(filePath)
);
this._createModuleResolver();
this.emit("change");
}
_createModuleResolver() {
this._moduleResolver = new ModuleResolver({
dirExists: (filePath) => {
try {
return fs.lstatSync(filePath).isDirectory();
} catch (e) {}
return false;
},
disableHierarchicalLookup:
this._config.resolver.disableHierarchicalLookup,
doesFileExist: this._doesFileExist,
emptyModulePath: this._config.resolver.emptyModulePath,
extraNodeModules: this._config.resolver.extraNodeModules,
getHasteModulePath: (name, platform) =>
this._hasteModuleMap.getModule(name, platform, true),
getHastePackagePath: (name, platform) =>
this._hasteModuleMap.getPackage(name, platform, true),
isAssetFile: (file) => this._assetExtensions.has(path.extname(file)),
mainFields: this._config.resolver.resolverMainFields,
moduleCache: this._moduleCache,
nodeModulesPaths: this._config.resolver.nodeModulesPaths,
preferNativePlatform: true,
projectRoot: this._config.projectRoot,
resolveAsset: (dirPath, assetName, extension) => {
const basePath = dirPath + path.sep + assetName;
const assets = [
basePath + extension,
...this._config.resolver.assetResolutions.map(
(resolution) => basePath + "@" + resolution + "x" + extension
),
].filter((candidate) => this._fileSystem.exists(candidate));
return assets.length ? assets : null;
},
resolveRequest: this._config.resolver.resolveRequest,
sourceExts: this._config.resolver.sourceExts,
});
}
_createModuleCache() {
return new ModuleCache({
getClosestPackage: (filePath) => this._getClosestPackage(filePath),
});
}
getAllFiles() {
return nullthrows(this._fileSystem).getAllFiles();
}
getSha1(filename) {
// TODO If it looks like we're trying to get the sha1 from a file located
// within a Zip archive, then we instead compute the sha1 for what looks
// like the Zip archive itself.
const splitIndex = filename.indexOf(".zip/");
const containerName =
splitIndex !== -1 ? filename.slice(0, splitIndex + 4) : filename;
// TODO Calling realpath allows us to get a hash for a given path even when
// it's a symlink to a file, which prevents Metro from crashing in such a
// case. However, it doesn't allow Metro to track changes to the target file
// of the symlink. We should fix this by implementing a symlink map into
// Metro (or maybe by implementing those "extra transformation sources" we've
// been talking about for stuff like CSS or WASM).
const resolvedPath = fs.realpathSync(containerName);
const sha1 = this._fileSystem.getSha1(resolvedPath);
// ref: https://github.com/facebook/metro/issues/330#issue-389608574
if (!sha1) {
return getFileHash(resolvedPath)
function getFileHash(file) {
return require('crypto')
.createHash('sha1')
.update(fs.readFileSync(file))
.digest('hex')
}
}
return sha1;
}
getWatcher() {
return this._haste;
}
end() {
this._haste.end();
}
/** Given a search context, return a list of file paths matching the query. */
matchFilesWithContext(from, context) {
return this._fileSystem.matchFilesWithContext(from, context);
}
resolveDependency(
from,
to,
platform,
resolverOptions,
// TODO: Fold assumeFlatNodeModules into resolverOptions and add to graphId
{ assumeFlatNodeModules } = {
assumeFlatNodeModules: false,
}
) {
var _JSON$stringify, _resolverOptions$cust;
const isSensitiveToOriginFolder =
// Resolution is always relative to the origin folder unless we assume a flat node_modules
!assumeFlatNodeModules ||
// Path requests are resolved relative to the origin folder
to.includes("/") ||
to === "." ||
to === ".." ||
// Preserve standard assumptions under node_modules
from.includes(path.sep + "node_modules" + path.sep);
// Compound key for the resolver cache
const resolverOptionsKey =
(_JSON$stringify = JSON.stringify(
(_resolverOptions$cust = resolverOptions.customResolverOptions) !==
null && _resolverOptions$cust !== void 0
? _resolverOptions$cust
: {},
canonicalize
)) !== null && _JSON$stringify !== void 0
? _JSON$stringify
: "";
const originKey = isSensitiveToOriginFolder ? path.dirname(from) : "";
const targetKey = to;
const platformKey =
platform !== null && platform !== void 0 ? platform : NULL_PLATFORM;
// Traverse the resolver cache, which is a tree of maps
const mapByResolverOptions = this._resolutionCache;
const mapByOrigin = getOrCreateMap(
mapByResolverOptions,
resolverOptionsKey
);
const mapByTarget = getOrCreateMap(mapByOrigin, originKey);
const mapByPlatform = getOrCreateMap(mapByTarget, targetKey);
let modulePath = mapByPlatform.get(platformKey);
if (!modulePath) {
try {
modulePath = this._moduleResolver.resolveDependency(
this._moduleCache.getModule(from),
to,
true,
platform,
resolverOptions
).path;
} catch (error) {
if (error instanceof _metroFileMap.DuplicateHasteCandidatesError) {
throw new AmbiguousModuleResolutionError(from, error);
}
if (error instanceof InvalidPackageError) {
throw new PackageResolutionError({
packageError: error,
originModulePath: from,
targetModuleName: to,
});
}
throw error;
}
}
mapByPlatform.set(platformKey, modulePath);
return modulePath;
}
_doesFileExist = (filePath) => {
return this._fileSystem.exists(filePath);
};
getHasteName(filePath) {
const hasteName = this._fileSystem.getModuleName(filePath);
if (hasteName) {
return hasteName;
}
return path.relative(this._config.projectRoot, filePath);
}
getDependencies(filePath) {
return nullthrows(this._fileSystem.getDependencies(filePath));
}
}
module.exports = DependencyGraph;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment