Implementation of Metro's resolution logic for JavaScript modules, assets, and packages within React Native and Metro bundler projects.
npx @tessl/cli install tessl/npm-metro-resolver@0.83.0Metro Resolver is a JavaScript library that implements Metro's module resolution logic, providing the core functionality for resolving JavaScript modules, assets, and packages within React Native and Metro bundler projects. It handles complex resolution scenarios including package.json exports, imports, platform-specific files, asset resolution, and custom resolver implementations.
npm install metro-resolverconst Resolver = require("metro-resolver");
const { resolve } = Resolver;For ES modules:
import Resolver from "metro-resolver";
const { resolve } = Resolver;TypeScript imports (with type definitions):
import {
resolve,
type ResolutionContext,
type Resolution,
type CustomResolver
} from "metro-resolver";const Resolver = require("metro-resolver");
// Create a resolution context (typically provided by Metro bundler)
const context = {
allowHaste: false,
assetExts: new Set(['png', 'jpg', 'jpeg', 'gif']),
sourceExts: ['js', 'json', 'ts', 'tsx'],
mainFields: ['browser', 'main'],
fileSystemLookup: (path) => ({ exists: true, type: 'f', realPath: path }),
doesFileExist: (path) => true,
getPackage: (packagePath) => require(packagePath),
getPackageForModule: (modulePath) => null,
nodeModulesPaths: ['/node_modules'],
originModulePath: '/app/src/index.js',
preferNativePlatform: false,
resolveAsset: (dirPath, name, ext) => [`${dirPath}/${name}${ext}`],
resolveHasteModule: () => undefined,
resolveHastePackage: () => undefined,
redirectModulePath: (path) => path,
customResolverOptions: {},
disableHierarchicalLookup: false,
extraNodeModules: null,
dev: false,
unstable_conditionNames: ['import', 'require'],
unstable_conditionsByPlatform: {},
unstable_enablePackageExports: true,
unstable_logWarning: console.warn
};
// Resolve a module
const resolution = Resolver.resolve(context, 'react-native', 'ios');
console.log(resolution);
// { type: 'sourceFile', filePath: '/node_modules/react-native/index.js' }Metro Resolver is built around several key components:
resolve function that orchestrates the resolution processMain resolution function that handles all module resolution scenarios including relative paths, absolute paths, bare specifiers, and Haste modules.
function resolve(
context: ResolutionContext,
moduleName: string,
platform: string | null
): Resolution;
type Resolution = FileResolution | Readonly<{ type: 'empty' }>;
type FileResolution = AssetResolution | SourceFileResolution;
interface SourceFileResolution {
readonly type: 'sourceFile';
readonly filePath: string;
}
interface AssetResolution {
readonly type: 'assetFiles';
readonly filePaths: ReadonlyArray<string>;
}Configuration system that provides all the settings, file system operations, and helper functions needed for module resolution.
interface ResolutionContext {
readonly allowHaste: boolean;
readonly assetExts: ReadonlySet<string>;
readonly sourceExts: ReadonlyArray<string>;
readonly customResolverOptions: CustomResolverOptions;
readonly disableHierarchicalLookup: boolean;
readonly doesFileExist: DoesFileExist;
readonly extraNodeModules?: Readonly<{ [string]: string }> | null;
readonly dev: boolean;
readonly fileSystemLookup: FileSystemLookup;
readonly getPackage: (packageJsonPath: string) => PackageJson | null;
readonly getPackageForModule: (absoluteModulePath: string) => PackageForModule | null;
readonly mainFields: ReadonlyArray<string>;
readonly originModulePath: string;
readonly nodeModulesPaths: ReadonlyArray<string>;
readonly preferNativePlatform: boolean;
readonly resolveAsset: ResolveAsset;
readonly redirectModulePath: (modulePath: string) => string | false;
readonly resolveHasteModule: (name: string) => string | undefined;
readonly resolveHastePackage: (name: string) => string | undefined;
readonly resolveRequest?: CustomResolver;
readonly dependency?: any;
readonly isESMImport?: boolean;
readonly unstable_conditionNames: ReadonlyArray<string>;
readonly unstable_conditionsByPlatform: Readonly<{ [platform: string]: ReadonlyArray<string> }>;
readonly unstable_enablePackageExports: boolean;
readonly unstable_logWarning: (message: string) => void;
}Comprehensive error types for different resolution failure scenarios with detailed diagnostic information.
class FailedToResolveNameError extends Error {
dirPaths: ReadonlyArray<string>;
extraPaths: ReadonlyArray<string>;
}
class FailedToResolvePathError extends Error {
candidates: FileAndDirCandidates;
}
class InvalidPackageError extends Error {
fileCandidates: FileCandidates;
indexCandidates: FileCandidates;
mainModulePath: string;
packageJsonPath: string;
}
class FailedToResolveUnsupportedError extends Error {
constructor(message: string);
}Advanced package resolution supporting modern Node.js features like package.json exports/imports fields and conditional exports.
interface PackageJson {
readonly name?: string;
readonly main?: string;
readonly exports?: ExportsField;
readonly imports?: ExportsLikeMap;
}
interface PackageInfo {
readonly packageJson: PackageJson;
readonly rootPath: string;
}Specialized handling for asset files including support for scaling variants and platform-specific assets.
type ResolveAsset = (
dirPath: string,
assetName: string,
extension: string
) => ReadonlyArray<string> | undefined;
function resolveAsset(
context: ResolutionContext,
filePath: string
): AssetResolution | null;Extensibility system allowing custom resolution logic to be integrated into the resolution pipeline.
type CustomResolver = (
context: CustomResolutionContext,
moduleName: string,
platform: string | null
) => Resolution;
interface CustomResolutionContext extends ResolutionContext {
readonly resolveRequest: CustomResolver;
}
type CustomResolverOptions = Readonly<{
[option: string]: unknown;
}>;Utility functions for working with resolution candidates and formatting.
function formatFileCandidates(candidates: FileCandidates): string;type Result<TResolution, TCandidates> =
| { readonly type: 'resolved'; readonly resolution: TResolution }
| { readonly type: 'failed'; readonly candidates: TCandidates };
type AssetFileResolution = ReadonlyArray<string>;
type FileCandidates =
| { readonly type: 'asset'; readonly name: string }
| {
readonly type: 'sourceFile';
filePathPrefix: string;
readonly candidateExts: ReadonlyArray<string>;
};
interface FileAndDirCandidates {
readonly dir: FileCandidates | null;
readonly file: FileCandidates | null;
}
type DoesFileExist = (filePath: string) => boolean;
type FileSystemLookup = (
absoluteOrProjectRelativePath: string
) => { exists: false } | { exists: true; type: 'f' | 'd'; realPath: string };
interface PackageForModule extends PackageInfo {
readonly packageRelativePath: string;
}
type ExportsLikeMap = Readonly<{
[subpathOrCondition: string]: string | ExportsLikeMap | null;
}>;
type ExportMapWithFallbacks = Readonly<{
[subpath: string]: string | ExportsLikeMap | null | ExportValueWithFallback;
}>;
type ExportValueWithFallback =
| ReadonlyArray<ExportsLikeMap | string>
| ReadonlyArray<ReadonlyArray<unknown>>;
type ExportsField =
| string
| ReadonlyArray<string>
| ExportValueWithFallback
| ExportsLikeMap
| ExportMapWithFallbacks;
type FlattenedExportMap = ReadonlyMap<string, string | null>;
type NormalizedExportsLikeMap = Map<string, null | string | ExportsLikeMap>;