Skip to main content

Module Resolution

Module resolution is the process of translating module names to module paths at build time. For example, if your project contains the code:

// src/App.js
import {View} from 'react-native';
// ...

Metro needs to know where in your project to load the react-native module from. This will typically resolve to something like node_modules/react-native/index.js.

Likewise, if your project contains the (similar) code:

// src/App.js
import Comp from './Component';
// ...

Metro needs to understand that you are referring to, say, src/Component.js, and not another file named Component that may also exist elsewhere.

Metro implements a version of Node's module resolution algorithm, augmented with additional Metro-specific features.

These Metro-specific features include:

  • Haste: An opt-in mechanism for importing modules by their globally-unique name anywhere in the project, e.g. import Foo from 'Foo'.
  • Platform extensions: Used by React Native to allow developers to write platform-specific versions of their JavaScript modules.
  • Asset extensions and image resolutions: Used by React Native to automatically select the best version of an image asset based on the device's screen density at runtime.
  • Custom resolvers: Metro integrators can provide their own resolver implementations to override almost everything about how modules are resolved.

Resolution algorithm​

Given a resolution context context, a module name moduleName, and an optional platform identifier platform, Metro's resolver performs RESOLVE(context, moduleName, platform), which either returns one of the resolution types, or throws an error.

Resolution types​

Source file​

The request is resolved to some absolute path representing a physical file on disk.

Asset files​

The request is resolved to one or more absolute paths representing physical files on disk.

Empty module​

The request is resolved to a built-in empty module, namely the one specified in resolver.emptyModulePath.

Algorithm​

note

These are the rules that Metro's default resolver follows. Refer to metro-resolver's source code for more details.

RESOLVE​

Parameters: (context, moduleName, platform)

  1. If a custom resolver is defined, then
    1. Return the result of the custom resolver.
  2. Otherwise, attempt to resolve moduleName as a path
    1. Let absoluteModuleName be the result of prepending the current directory (i.e. parent of context.originModulePath) with moduleName.
    2. Return the result of RESOLVE_MODULE(context, absoluteModuleName, platform), or continue.
  3. Apply redirections to moduleName. If this results in an empty module, then
    1. Return the empty module.
  4. If Haste resolutions are allowed, then
    1. Get the result of RESOLVE_HASTE(context, moduleName, platform).
    2. If resolved as a Haste package path, then
      1. Perform the algorithm for resolving a path (step 2 above). Throw an error if this resolution fails. For example, if the Haste package path for 'a/b' is foo/package.json, perform step 2 as if moduleName was foo/c.
  5. If context.disableHierarchicalLookup is not true, then
    1. Try resolving moduleName under node_modules from the current directory (i.e. parent of context.originModulePath) up to the root directory.
    2. Perform RESOLVE_PACKAGE(context, modulePath, platform) for each candidate path.
  6. For each element nodeModulesPath of context.nodeModulesPaths:
    1. Try resolving moduleName under nodeModulesPath as if the latter was another node_modules directory (similar to step 5 above).
    2. Perform RESOLVE_PACKAGE(context, modulePath, platform) for each candidate path.
  7. If context.extraNodeModules is set:
    1. Split moduleName into a package name (including an optional scope) and relative path.
    2. Look up the package name in context.extraNodeModules. If found, then
      1. Construct a path modulePath by replacing the package name part of moduleName with the value found in context.extraNodeModules
      2. Return the result of RESOLVE_PACKAGE(context, modulePath, platform).
  8. If no valid resolution has been found, throw a resolution failure error.

RESOLVE_MODULE​

Parameters: (context, moduleName, platform)

  1. Let filePath be the result of applying redirections to moduleName. This may locate a replacement subpath from a containing package.json file based on the browser field spec.
  2. Return the result of RESOLVE_FILE(context, filePath, platform), or continue.
  3. Otherwise, let dirPath be the directory path of filePath.
  4. If a file dirPath + 'package.json' exists, resolve based on the browser field spec:
    1. Let mainModulePath be the result of reading the package's entry path using context.mainFields.
    2. Return the result of RESOLVE_FILE(context, mainModulePath, platform), or continue.
    3. Return the result of RESOLVE_FILE(context, mainModulePath + '/index', platform).
    4. Throw an error if no resolution could be found.

RESOLVE_PACKAGE​

Parameters: (context, moduleName, platform)

  1. If context.enablePackageExports is enabled, and a containing package.json file contains the field "exports", get result of RESOLVE_PACKAGE_EXPORTS(context, packagePath, filePath, exportsField, platform).
    1. If resolved path exists, return result.
    2. Else, log either a package configuration or package encapsulation warning.
  2. Return the result of RESOLVE_MODULE(context, filePath, platform).

RESOLVE_PACKAGE_EXPORTS​

Parameters: (context, packagePath, filePath, exportsField, platform)

Resolves a package subpath based on the Package Entry Points spec (the "exports" field), when resolver.unstable_enablePackageExports is enabled.

  1. Let subpath be the relative path from packagePath to filePath, or '.'.
  2. If exportsField contains an invalid configuration or values, raise an InvalidPackageConfigurationError.
  3. If subpath is not defined by exportsField, raise a PackagePathNotExportedError.
  4. Let target be the result of matching subpath in exportsField after applying any conditional exports and/or substituting a subpath pattern match.
    1. Condition names will be asserted from the union of context.unstable_conditionNames and context.unstable_conditionNamesByPlatform for platform, in the order defined by exportsField.
  5. If target refers to an asset, then
    1. Return the result of RESOLVE_ASSET(context, target, platform).
  6. Return target as a source file resolution without applying redirections or trying any platform or extension variants.

RESOLVE_FILE​

Parameters: (context, filePath, platform)

  1. If the path refers to an asset, then
    1. Return the result of RESOLVE_ASSET(context, filePath, platform).
  2. Otherwise, if the path exists, then
    1. Try all platform and extension variants in sequence. Return a source file resolution for the first one that exists after applying redirections. For example, if platform is android and context.sourceExts is ['js', 'jsx'], try this sequence of potential file names:
      1. moduleName + '.android.js'
      2. moduleName + '.native.js' (if context.preferNativePlatform is true)
      3. moduleName + '.js'
      4. moduleName + '.android.jsx'
      5. moduleName + '.native.jsx' (if context.preferNativePlatform is true)
      6. moduleName + '.jsx'

RESOLVE_ASSET​

Parameters: (context, filePath, platform)

  1. Use context.resolveAsset to collect all asset variants.
  2. Return an asset resolution containing the collected asset paths.

RESOLVE_HASTE​

Parameters: (context, moduleName, platform)

  1. Try resolving moduleName as a Haste module. If found, then
    1. Return result as a source file resolution without applying redirections or trying any platform or extension variants.
  2. Try resolving moduleName as a Haste (global) package, or a path relative to a Haste package. For example, if moduleName is 'a/b/c', try the following potential Haste package names:
    1. 'a/b/c', relative path ''
    2. 'a/b', relative path './c'
    3. 'a', with relative path './b/c'

Resolution context​

assetExts: $ReadOnlySet<string>​

The set of file extensions used to identify asset files. Defaults to resolver.assetExts.

dev: boolean​

true if the resolution is for a development bundle, or false otherwise.

doesFileExist: string => boolean​

Returns true if the file with the given path exists, or false otherwise.

By default, Metro implements this by consulting an in-memory map of the filesystem that has been prepared in advance. This approach avoids disk I/O during module resolution.

nodeModulesPaths: $ReadOnlyArray<string>​

A list of paths to check for modules after looking through all node_modules directories.

By default this is set to resolver.nodeModulesPaths

preferNativePlatform: boolean​

If true, try .native.${ext} before .${ext} and after .${platform}.${ext} during resolution. Metro sets this to true.

redirectModulePath: string => string | false​

Rewrites a module path, or returns false to redirect to the special empty module. In the default resolver, the resolution algorithm terminates with an empty module result if redirectModulePath returns false.

Metro uses this to implement the package.json browser field spec, particularly the ability to replace and ignore specific files.

The default implementation of this function respects resolver.resolverMainFields.

resolveAsset: (dirPath: string, assetName: string, extension: string) => ?$ReadOnlyArray<string>​

Given a directory path, the base asset name and an extension, returns a list of all the asset file names that match the given base name in that directory, or null if no such files are found. The default implementation considers each of resolver.assetResolutions and uses the ${assetName}@${resolution}${extension} format for asset variant file names.

See also Static Image Resources in the React Native docs.

sourceExts: $ReadOnlyArray<string>​

The list of file extensions to try, in order, when resolving a module path that does not exist on disk. Defaults to resolver.sourceExts.

mainFields: $ReadOnlyArray<string>​

The ordered list of fields in package.json that should be read to resolve a package's main entry point (and any subpath file replacements) per the "browser" field spec. Defaults to resolver.resolverMainFields.

getPackage: string => PackageJson​

Given the path to a package.json file, returns the parsed file contents.

getPackageForModule: (modulePath: string) => ?PackageInfo
Deprecated
​

Given a module path that may exist under an npm package, locates and returns the package root path and parsed package.json contents.

resolveHasteModule: string => ?string​

Resolves a Haste module name to an absolute path. Returns null if no such module exists.

The default implementation of this function uses metro-file-map's getModule method.

resolveHastePackage: string => ?string​

Resolves a Haste (global) package name to an absolute package.json path. Returns null if no such package exists.

The default implementation of this function uses metro-file-map's getPackage method and can be turned on or off using resolver.enableGlobalPackages.

allowHaste: boolean​

true if Haste resolutions are allowed in the current context, false otherwise.

disableHierarchicalLookup: boolean​

If true, the resolver should not perform lookup in node_modules directories per the Node resolution algorithm. Defaults to resolver.disableHierarchicalLookup.

extraNodeModules: ?{[string]: string}​

A mapping of package names to directories that is consulted after the standard lookup through node_modules as well as any nodeModulesPaths.

originModulePath: string​

The path to the current module, e.g. the one containing the import we are currently resolving.

customResolverOptions: {[string]: mixed}​

Any custom options passed to the resolver. By default, Metro populates this based on URL parameters in the bundle request, e.g. http://localhost:8081/index.bundle?resolver.key=value becomes {key: 'value'}.

resolveRequest: CustomResolver​

A alternative resolver function to which the current request may be delegated. Defaults to resolver.resolveRequest.

Metro expects resolveRequest to have the following signature:

function resolveRequest(
context: ResolutionContext,
moduleName: string,
platform: string | null,
): Resolution {
// ...
}

type Resolution =
| {type: 'empty'}
| {type: 'sourceFile', filePath: string}
| {type: 'assetFiles', filePaths: $ReadOnlyArray<string>};

When calling the default resolver with a non-null resolveRequest function, it represents a custom resolver and will always be called, fully replacing the default resolution logic.

Inside a custom resolver, resolveRequest is set to the default resolver function, for easy chaining and customization.

dependency: ?Dependency​

A dependency descriptor corresponding to the current resolution request. This is provided for diagnostic purposes only and may not be used for semantic purposes. See the Caching section for more information.

type Dependency = {
// The literal name provided to a require or import call. For example 'foo' in
// case of `require('foo')`.
name: string,

data: {
// A locally unique key for this dependency within the origin module.
key: string,

// Source locations from the Babel AST, relative to the origin module, where
// this dependency was encountered. This may be an empty array.
locs: $ReadOnlyArray<BabelSourceLocation>,

asyncType: 'async' | 'prefetch' | 'weak' | null,

// Other properties are considered internal and may change in the future.
...
},
};

Caching​

Resolver results may be cached under the following conditions:

  1. For given origin module paths A and B and target module name M, the resolution for M may be reused if all of the following conditions hold:
    1. A and B are in the same directory.
    2. The contents of dev and customResolverOptions are equivalent ( = serialize to JSON the same) in both calls to the resolver.
  2. Any cache of resolutions must be invalidated if any file in the project has changed.

Custom resolvers must adhere to these assumptions, e.g. they may not return different resolutions for origin modules in the same directory under the same customResolverOptions.