Implementation of Metro's resolution logic for JavaScript modules, assets, and packages within React Native and Metro bundler projects.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced package resolution supporting modern Node.js features like package.json exports/imports fields, conditional exports, and legacy browser field specifications. Handles both standard npm packages and complex multi-entry packages.
Resolves the main entry point of a package based on package.json fields.
/**
* Resolve the main entry point subpath for a package
* @param context - Resolution context
* @param packageInfo - Package information including package.json
* @param platform - Target platform for conditional resolution
* @returns Entry point subpath relative to package root
*/
function getPackageEntryPoint(
context: ResolutionContext,
packageInfo: PackageInfo,
platform: string | null
): string;
interface PackageInfo {
readonly packageJson: PackageJson;
readonly rootPath: string;
}Usage Example:
const { getPackageEntryPoint } = require("metro-resolver/src/PackageResolve");
const packageInfo = {
packageJson: {
name: "my-package",
main: "./dist/index.js",
browser: "./dist/browser.js",
module: "./dist/esm.js"
},
rootPath: "/node_modules/my-package"
};
// With mainFields: ['browser', 'module', 'main']
const entryPoint = getPackageEntryPoint(context, packageInfo, null);
// Result: "./dist/browser.js"Standard package.json fields supported for resolution.
interface PackageJson {
readonly name?: string;
readonly main?: string;
readonly exports?: string | ExportMap;
readonly imports?: ExportMap;
readonly browser?: string | BrowserFieldMap;
readonly module?: string;
readonly types?: string;
}
type BrowserFieldMap = Readonly<{
[key: string]: string | false;
}>;
type ExportMap = Readonly<{
[subpathOrCondition: string]: ExportMap | string | null;
}>;Modern package.json exports field resolution with conditional exports support.
/**
* Resolve a package target using the exports field
* @param context - Resolution context
* @param packagePath - Absolute path to package root
* @param absoluteCandidatePath - Absolute path being resolved
* @param packageRelativePath - Relative path within package
* @param exportsField - Package exports configuration
* @param platform - Target platform
* @returns Resolved file resolution or null if not found
*/
function resolvePackageTargetFromExports(
context: ResolutionContext,
packagePath: string,
absoluteCandidatePath: string,
packageRelativePath: string,
exportsField: ExportsField,
platform: string | null
): FileResolution | null;
type ExportsField = string | ExportMap;Exports Field Examples:
{
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js",
"./package.json": "./package.json"
}
}{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"browser": "./dist/browser/index.js"
}
}
}Platform-Specific Exports:
{
"exports": {
".": {
"react-native": "./dist/native.js",
"browser": "./dist/browser.js",
"default": "./dist/node.js"
}
}
}Package.json imports field resolution for subpath imports (starting with #).
/**
* Resolve a package target using the imports field
* @param context - Resolution context
* @param packagePath - Absolute path to package root
* @param importSpecifier - Import specifier (e.g., "#utils/helper")
* @param importsField - Package imports configuration
* @param platform - Target platform
* @returns Resolved file resolution or null if not found
*/
function resolvePackageTargetFromImports(
context: ResolutionContext,
packagePath: string,
importSpecifier: string,
importsField: ExportMap,
platform: string | null
): FileResolution | null;Imports Field Example:
{
"imports": {
"#utils/*": "./src/utils/*.js",
"#config": {
"development": "./config/dev.js",
"production": "./config/prod.js"
}
}
}Usage Example:
// In a file within the package, you can use:
import helper from "#utils/helper"; // Resolves to ./src/utils/helper.js
import config from "#config"; // Resolves based on conditionSupport for conditional exports based on environment and platform.
interface ConditionalExports {
[condition: string]: string | ConditionalExports | null;
}Common Conditions:
import: ESM importrequire: CommonJS requirebrowser: Browser environmentnode: Node.js environmentreact-native: React Native platformdevelopment/production: Build modeExample:
{
"exports": {
".": {
"import": {
"browser": "./dist/esm/browser.js",
"node": "./dist/esm/node.js"
},
"require": {
"browser": "./dist/cjs/browser.js",
"node": "./dist/cjs/node.js"
}
}
}
}Module path redirection through package.json browser field and custom redirections.
/**
* Redirect a module path based on package configuration
* @param context - Resolution context
* @param modulePath - Original module path
* @returns Redirected path, original path, or false to exclude
*/
function redirectModulePath(
context: ResolutionContext,
modulePath: string
): string | false;Browser Field Redirection:
{
"browser": {
"./src/node-specific.js": "./src/browser-specific.js",
"fs": false,
"path": "path-browserify"
}
}Support for legacy package resolution patterns.
Main Field Priority:
mainFields in orderBrowser Field Specification:
false value: Exclude module from bundleThe complete package resolution process:
unstable_enablePackageExports is true and exports field existsSpecific errors thrown during package resolution:
class PackagePathNotExportedError extends Error {
packagePath: string;
subpath: string;
}
class InvalidPackageConfigurationError extends Error {
reason: string;
}
class PackageImportNotResolvedError extends Error {
importSpecifier: string;
reason: string;
}Pattern matching for exports and imports with wildcards.
/**
* Match a subpath against a pattern with wildcards
* @param pattern - Pattern with optional wildcards (*)
* @param subpath - Subpath to match against
* @returns Match result with captured wildcard values
*/
function matchSubpathPattern(
pattern: string,
subpath: string
): { matched: boolean; captured: string[] };Pattern Examples:
{
"exports": {
"./lib/*": "./dist/*.js",
"./utils/*/helper": "./dist/utils/*/helper.js"
}
}Determine package boundaries and extract package information.
interface PackageForModule extends PackageInfo {
/** System-separated subpath relative to package root */
readonly packageRelativePath: string;
}
/**
* Get package information for a module path
* @param absoluteModulePath - Absolute path to check
* @returns Package information or null if no package found
*/
function getPackageForModule(
absoluteModulePath: string
): PackageForModule | null;Usage Example:
const packageInfo = context.getPackageForModule('/app/node_modules/lodash/lib/map.js');
// Result:
// {
// packageJson: { name: 'lodash', version: '4.17.21', ... },
// rootPath: '/app/node_modules/lodash',
// packageRelativePath: 'lib/map.js'
// }Handling platform-specific conditions and files at the package level.
Condition Names Configuration:
const context = {
unstable_conditionNames: ['import', 'require'],
unstable_conditionsByPlatform: {
ios: ['react-native', 'ios', 'native', 'import'],
android: ['react-native', 'android', 'native', 'import'],
web: ['browser', 'import']
}
};Platform-Specific Package Structure:
package/
├── dist/
│ ├── index.js # Default
│ ├── index.native.js # React Native
│ ├── index.ios.js # iOS specific
│ └── index.web.js # Web specific
└── package.json