CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metro-resolver

Implementation of Metro's resolution logic for JavaScript modules, assets, and packages within React Native and Metro bundler projects.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

package-resolution.mddocs/

Package Resolution

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.

Capabilities

Package Entry Point Resolution

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"

Package.json Structure

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;
}>;

Package Exports Resolution

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 Imports Resolution

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 condition

Conditional Exports

Support for conditional exports based on environment and platform.

interface ConditionalExports {
  [condition: string]: string | ConditionalExports | null;
}

Common Conditions:

  • import: ESM import
  • require: CommonJS require
  • browser: Browser environment
  • node: Node.js environment
  • react-native: React Native platform
  • development/production: Build mode

Example:

{
  "exports": {
    ".": {
      "import": {
        "browser": "./dist/esm/browser.js",
        "node": "./dist/esm/node.js"
      },
      "require": {
        "browser": "./dist/cjs/browser.js",
        "node": "./dist/cjs/node.js"
      }
    }
  }
}

Package Redirection

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"
  }
}

Legacy Resolution

Support for legacy package resolution patterns.

Main Field Priority:

  1. Check configured mainFields in order
  2. Default to "index" if no main field found
  3. Attempt file resolution with extensions
  4. Fallback to index file in directory

Browser Field Specification:

  • String value: Replace main entry point
  • Object value: Path-specific redirections
  • false value: Exclude module from bundle

Package Resolution Flow

The complete package resolution process:

  1. Exports Field Resolution: If unstable_enablePackageExports is true and exports field exists
  2. Legacy Resolution: Fallback to main fields and browser field
  3. Entry Point Resolution: Resolve the determined entry point as a file
  4. Index Fallback: If entry point resolution fails, try index file

Error Handling in Package Resolution

Specific 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;
}

Subpath Pattern Matching

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"
  }
}

Package Detection

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'
// }

Platform-Specific Package Resolution

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

docs

asset-resolution.md

custom-resolvers.md

error-handling.md

index.md

package-resolution.md

resolution-context.md

resolution-engine.md

tile.json