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β
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)
- If a custom resolver is defined, then
- Return the result of the custom resolver.
- If moduleName is an absolute path, or equal to
'.'
or'..'
, or begins'./'
or'../'
- Let absoluteModuleName be moduleName if it is absolute path, otherwise the result of prepending the current directory (i.e. parent of
context.originModulePath
) with moduleName. - Return the result of RESOLVE_MODULE(context, absoluteModuleName, platform), or continue.
- Let absoluteModuleName be moduleName if it is absolute path, otherwise the result of prepending the current directory (i.e. parent of
- If moduleName begins
'#'
- Throw an error. This will be replaced with subpath imports support in a non-breaking future release.
- Apply BROWSER_SPEC_REDIRECTION to moduleName. If this is
false
:- Return the empty module.
- If Haste resolutions are allowed, then
- Get the result of RESOLVE_HASTE(context, moduleName, platform).
- If resolved as a Haste package path, then
- 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'
isfoo/package.json
, perform step 2 as if moduleName wasfoo/c
.
- 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
- If
context.disableHierarchicalLookup
is nottrue
, then- Try resolving moduleName under
node_modules
from the current directory (i.e. parent ofcontext.originModulePath
) up to the root directory. - Perform RESOLVE_PACKAGE(context, modulePath, platform) for each candidate path.
- Try resolving moduleName under
- For each element nodeModulesPath of
context.nodeModulesPaths
:- Try resolving moduleName under nodeModulesPath as if the latter was another
node_modules
directory (similar to step 5 above). - Perform RESOLVE_PACKAGE(context, modulePath, platform) for each candidate path.
- Try resolving moduleName under nodeModulesPath as if the latter was another
- If
context.extraNodeModules
is set:- Split moduleName into a package name (including an optional scope) and relative path.
- Look up the package name in
context.extraNodeModules
. If found, then- Construct a path modulePath by replacing the package name part of moduleName with the value found in
context.extraNodeModules
- Return the result of RESOLVE_PACKAGE(context, modulePath, platform).
- Construct a path modulePath by replacing the package name part of moduleName with the value found in
- If no valid resolution has been found, throw a resolution failure error.
RESOLVE_MODULEβ
Parameters: (context, moduleName, platform)
- Let filePath be the result of applying BROWSER_SPEC_REDIRECTION to moduleName. This may locate a replacement subpath from a containing
package.json
file based on thebrowser
field spec. - Return the result of RESOLVE_FILE(context, filePath, platform), or continue.
- Otherwise, let dirPath be the directory path of filePath.
- If a file dirPath +
'package.json'
exists, resolve based on thebrowser
field spec:- Let mainModulePath be the result of reading the package's entry path using
context.mainFields
. - Return the result of RESOLVE_FILE(context, mainModulePath, platform), or continue.
- Return the result of RESOLVE_FILE(context, mainModulePath +
'/index'
, platform). - Throw an error if no resolution could be found.
- Let mainModulePath be the result of reading the package's entry path using
RESOLVE_PACKAGEβ
Parameters: (context, moduleName, platform)
- If
context.enablePackageExports
is enabled, and a containingpackage.json
file contains the field"exports"
, get result of RESOLVE_PACKAGE_EXPORTS(context, packagePath, filePath, exportsField, platform).- If resolved path exists, return result.
- Else, log either a package configuration or package encapsulation warning.
- 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), whenresolver.unstable_enablePackageExports
is enabled.
- Let subpath be the relative path from packagePath to filePath, or
'.'
. - If exportsField contains an invalid configuration or values, raise an
InvalidPackageConfigurationError
. - If subpath is not defined by exportsField, raise a
PackagePathNotExportedError
. - Let target be the result of matching subpath in exportsField after applying any conditional exports and/or substituting a subpath pattern match.
- Condition names will be asserted from the union of
context.unstable_conditionNames
andcontext.unstable_conditionNamesByPlatform
for platform, in the order defined by exportsField.
- Condition names will be asserted from the union of
- If target refers to an asset, then
- Return the result of RESOLVE_ASSET(context, target, platform).
- Return target as a source file resolution without applying redirections or trying any platform or extension variants.
RESOLVE_FILEβ
Parameters: (context, filePath, platform)
- If the path refers to an asset, then
- Return the result of RESOLVE_ASSET(context, filePath, platform).
- Otherwise, if the path exists, then
- Try all platform and extension variants in sequence. Return a source file resolution for the first one that exists after applying BROWSER_SPEC_REDIRECTION. For example, if platform is
android
andcontext.sourceExts
is['js', 'jsx']
, try this sequence of potential file names:- moduleName +
'.android.js'
- moduleName +
'.native.js'
(ifcontext.preferNativePlatform
istrue
) - moduleName +
'.js'
- moduleName +
'.android.jsx'
- moduleName +
'.native.jsx'
(ifcontext.preferNativePlatform
istrue
) - moduleName +
'.jsx'
- moduleName +
- Try all platform and extension variants in sequence. Return a source file resolution for the first one that exists after applying BROWSER_SPEC_REDIRECTION. For example, if platform is
RESOLVE_ASSETβ
Parameters: (context, filePath, platform)
- Use
context.resolveAsset
to collect all asset variants. - Return an asset resolution containing the collected asset paths.
RESOLVE_HASTEβ
Parameters: (context, moduleName, platform)
- Try resolving moduleName as a Haste module.
If found, then
- Return result as a source file resolution without applying redirections or trying any platform or extension variants.
- 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:'a/b/c'
, relative path''
'a/b'
, relative path'./c'
'a'
, with relative path'./b/c'
BROWSER_SPEC_REDIRECTIONβ
Parameters: (context, moduleName)
Based on defunctzombie/package-browser-field-spec
, apply redirections to specifiers, based on the closest package.json
to the origin or target module.
- Find the closest
package.json
to moduleName, if moduleName is absolute, or tocontext.originModulePath
otherwise, stopping at anynode_modules
. Let packageScope be its containing directory. - If none is found, return moduleName.
- Define subpath:
- If moduleName begins
'.'
, consider it relative tocontext.originModulePath
and let subpath be the same path relative to packageScope, prefixed'./'
. - Else if moduleName is absolute, let subpath be moduleName relative to packageScope, prefixed
'./'
. - Else let subpath be moduleName.
- If moduleName begins
- Taking each of
context.mainFields
as a key, let mainFieldValue be the value at that key within thepackage.json
at packageScope.- If mainFieldValue is an object, let expandedSubPath range over subpath, subpath +
'.js'
and subpath +'.json'
.- If mainFieldValue has the key expandedSubpath, let redirectedPath be its value, else continue.
- If moduleName is absolute or begins
'.'
, and redirectedPath is a string:- If redirectedPath is an absolute path, return redirectedPath.
- Return packageScope joined with redirectedPath.
- Return redirectedPath.
- If mainFieldValue is an object, let expandedSubPath range over subpath, subpath +
- Return moduleName.
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
Deprecatedβ
Returns true
if the file with the given path exists, or false
otherwise.
The default implementation wraps fileSystemLookup
- prefer using that directly.
fileSystemLookup: string => {exists: true, type: 'f'|'d', realPath: string} | {exists: false}
β
Added in v0.81.0
Return information about the given absolute or project-relative path, following symlinks. A file "exists" if and only if it is watched, and a directory must be non-empty. The returned realPath
is real and absolute.
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 and performs realpath resolution at negligible additional cost.
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
Deprecatedβ
The default implementation of this function is specified by BROWSER_SPEC_REDIRECTION.
Metro's default resolver does not call this function, instead using the BROWSER_SPEC_REDIRECTION implementation directly. It is exposed here for backwards-compatible use by custom resolvers, but is considered deprecated and will be removed in a future release.
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: (absoluteModulePath: string) => ?PackageInfo
Deprecatedβ
Given a candidate absolute module path that may exist under a package, locates and returns the closest package root (working upwards from the given path, stopping at the nearest node_modules
), parsed package.json
contents, and the package-relative path of the given path.
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>};
Returned paths (filePath
and filePaths
) must be absolute and real, such as the realPath
returned by fileSystemLookup
.
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:
- 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:
- A and B are in the same directory.
- The contents of
dev
andcustomResolverOptions
are equivalent ( = serialize to JSON the same) in both calls to the resolver.
- 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
.