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

asset-resolution.mddocs/

Asset Resolution

Specialized handling for asset files including support for scaling variants, platform-specific assets, and custom asset resolution strategies. Integrates with Metro bundler's asset system for React Native development.

Capabilities

Asset Resolution Function

Core function for resolving asset files with scaling variants.

/**
 * Resolve a file path as an asset with scaling variants
 * @param context - Resolution context containing asset configuration
 * @param filePath - Path to the asset file
 * @returns Asset resolution with file paths or null if not an asset
 */
function resolveAsset(
  context: ResolutionContext,
  filePath: string
): AssetResolution | null;

interface AssetResolution {
  readonly type: 'assetFiles';
  readonly filePaths: ReadonlyArray<string>;
}

Usage Example:

const { resolveAsset } = require("metro-resolver/src/resolveAsset");

const context = {
  assetExts: ['png', 'jpg', 'gif'],
  resolveAsset: (dirPath, name, ext) => [
    `${dirPath}/${name}@3x${ext}`,
    `${dirPath}/${name}@2x${ext}`,
    `${dirPath}/${name}${ext}`
  ]
};

const result = resolveAsset(context, '/app/assets/icon.png');
// Result:
// {
//   type: 'assetFiles',
//   filePaths: [
//     '/app/assets/icon@3x.png',
//     '/app/assets/icon@2x.png', 
//     '/app/assets/icon.png'
//   ]
// }

ResolveAsset Function Type

Custom asset resolution function interface.

/**
 * Function to resolve asset files in a directory
 * @param dirPath - Directory containing the asset
 * @param assetName - Base name of the asset (without extension)
 * @param extension - File extension including the dot
 * @returns Array of resolved asset file paths, or undefined if not found
 */
type ResolveAsset = (
  dirPath: string,
  assetName: string,
  extension: string
) => ReadonlyArray<string> | undefined;

Asset Detection

Utility function to determine if a filename represents an asset file.

/**
 * Check if a filename represents an asset based on extension
 * @param fileName - Filename to check
 * @param assetExts - Array of asset extensions (without dots)
 * @returns True if the file is an asset
 */
function isAssetFile(fileName: string, assetExts: ReadonlyArray<string>): boolean;

Usage Example:

const { isAssetFile } = require("metro-resolver/src/utils/isAssetFile");

const assetExts = ['png', 'jpg', 'gif', 'svg'];

console.log(isAssetFile('icon.png', assetExts));     // true
console.log(isAssetFile('script.js', assetExts));    // false
console.log(isAssetFile('image.jpeg', assetExts));   // false (jpeg not in list)

Asset Extensions Configuration

Common asset file extensions supported by Metro bundler.

interface AssetConfiguration {
  /** File extensions considered as assets (without dots) */
  readonly assetExts: ReadonlyArray<string>;
}

Common Asset Extensions:

const assetExts = [
  // Images
  'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg',
  
  // Fonts
  'ttf', 'otf', 'woff', 'woff2',
  
  // Audio/Video
  'mp3', 'mp4', 'wav', 'mov', 'avi',
  
  // Documents
  'pdf', 'zip'
];

Scaling Variants

Support for device pixel ratio scaling variants in asset resolution.

Scaling Suffix Patterns:

  • @1x - Standard density (optional, default)
  • @2x - Double density (Retina)
  • @3x - Triple density (iPhone Plus)
  • @4x - Quad density (future devices)

Asset Resolution Priority:

  1. Exact density match for target device
  2. Higher density variants (downscaled)
  3. Lower density variants (upscaled)
  4. Base variant (no suffix)

Example Asset Structure:

assets/
├── icon.png          # Base 1x version
├── icon@2x.png       # 2x version for Retina
├── icon@3x.png       # 3x version for iPhone Plus
└── logo.svg          # Vector graphics (no scaling needed)

Platform-Specific Assets

Support for platform-specific asset variants.

Platform Suffix Patterns:

  • .ios.png - iOS specific
  • .android.png - Android specific
  • .web.png - Web specific
  • .native.png - React Native (both iOS and Android)

Combined Platform and Scaling:

assets/
├── icon.ios.png
├── icon.ios@2x.png
├── icon.ios@3x.png
├── icon.android.png
├── icon.android@2x.png
└── icon.android@3x.png

Custom Asset Resolution

Implementation of custom asset resolution strategies.

Example: Custom Resolution with Platform Support:

const customResolveAsset = (dirPath, assetName, extension) => {
  const fs = require('fs');
  const path = require('path');
  
  const variants = [];
  const scales = ['@3x', '@2x', ''];
  const platforms = context.platform ? [`.${context.platform}`, '.native', ''] : [''];
  
  // Check all combinations of platform and scale
  for (const platform of platforms) {
    for (const scale of scales) {
      const filename = `${assetName}${platform}${scale}${extension}`;
      const fullPath = path.join(dirPath, filename);
      
      if (fs.existsSync(fullPath)) {
        variants.push(fullPath);
      }
    }
  }
  
  return variants.length > 0 ? variants : undefined;
};

const context = {
  assetExts: ['png', 'jpg', 'gif'],
  resolveAsset: customResolveAsset,
  platform: 'ios'
};

Asset Scaling Detection

Detection and handling of pre-scaled assets to prevent double-processing.

/**
 * Regular expression to detect scaling suffixes in asset names
 */
const SCALING_REGEX = /@\d+(?:\.\d+)?x$/;

Usage in Resolution:

function resolveAsset(context, filePath) {
  const path = require('path');
  const basename = path.basename(filePath, path.extname(filePath));
  
  // Skip resolution for already-scaled assets
  if (/@\d+(?:\.\d+)?x$/.test(basename)) {
    return null;
  }
  
  // Proceed with normal asset resolution
  const dirPath = path.dirname(filePath);
  const extension = path.extname(filePath);
  const assetName = path.basename(filePath, extension);
  
  const assets = context.resolveAsset(dirPath, assetName, extension);
  return assets ? { type: 'assetFiles', filePaths: assets } : null;
}

Asset Resolution Context

Configuration options specific to asset resolution.

interface AssetResolutionContext {
  /** File extensions considered as assets */
  assetExts: ReadonlyArray<string>;
  
  /** Custom asset resolution function */
  resolveAsset: ResolveAsset;
  
  /** Current platform for platform-specific assets */
  platform?: string;
  
  /** Whether to prefer native variants over platform-specific ones */
  preferNativePlatform?: boolean;
}

Error Handling for Assets

Asset-specific error handling and fallback strategies.

interface AssetCandidates {
  readonly type: 'asset';
  readonly name: string;
}

Error Example:

try {
  const result = Resolver.resolve(context, './missing-image.png', 'ios');
} catch (error) {
  if (error instanceof Resolver.FailedToResolvePathError) {
    const { candidates } = error;
    if (candidates.file?.type === 'asset') {
      console.log(`Asset not found: ${candidates.file.name}`);
    }
  }
}

Asset Bundle Integration

Integration with Metro bundler's asset processing pipeline.

Asset Resolution Flow:

  1. Detect asset file by extension
  2. Resolve scaling variants
  3. Apply platform-specific filtering
  4. Return ordered array of asset paths
  5. Metro processes assets for bundling

Asset Metadata:

interface AssetMetadata {
  name: string;
  type: string;
  hash: string;
  scales: number[];
  platform?: string;
  width?: number;
  height?: number;
}

Performance Considerations

Optimization strategies for asset resolution.

Caching Strategy:

class CachedAssetResolver {
  constructor() {
    this.cache = new Map();
  }
  
  resolveAsset(dirPath, assetName, extension) {
    const key = `${dirPath}/${assetName}${extension}`;
    
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    
    const result = this.performResolution(dirPath, assetName, extension);
    this.cache.set(key, result);
    return result;
  }
  
  performResolution(dirPath, assetName, extension) {
    // Actual resolution logic
  }
}

Batch Resolution:

function batchResolveAssets(context, assetPaths) {
  const results = new Map();
  
  for (const assetPath of assetPaths) {
    try {
      const result = resolveAsset(context, assetPath);
      if (result) {
        results.set(assetPath, result);
      }
    } catch (error) {
      // Handle individual asset errors
      results.set(assetPath, { error: error.message });
    }
  }
  
  return results;
}

Asset Development Workflow

Best practices for organizing assets in development.

Recommended Directory Structure:

src/
├── assets/
│   ├── images/
│   │   ├── icons/
│   │   │   ├── home.png
│   │   │   ├── home@2x.png
│   │   │   └── home@3x.png
│   │   └── backgrounds/
│   ├── fonts/
│   └── sounds/
└── components/

Asset Import Patterns:

// Direct asset imports
import icon from '../assets/images/icons/home.png';

// Dynamic asset imports
const getAsset = (name) => require(`../assets/images/${name}.png`);

// Platform-specific imports
import iconIOS from '../assets/images/icon.ios.png';
import iconAndroid from '../assets/images/icon.android.png';

docs

asset-resolution.md

custom-resolvers.md

error-handling.md

index.md

package-resolution.md

resolution-context.md

resolution-engine.md

tile.json