CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metro-file-map

File system crawling, watching and mapping library designed for Metro bundler ecosystem

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

haste-system.mddocs/

Haste Module System

Haste module resolution and conflict detection system for fast module lookup by name. The Haste system enables importing modules by their declared name rather than relative paths, similar to Node.js resolution but optimized for Metro bundler workflows.

Capabilities

Module Resolution

Resolve modules and packages by name with platform-specific support.

/**
 * Get module path by name
 * @param name - Module name to resolve
 * @param platform - Platform identifier (e.g., 'ios', 'android', 'web')
 * @param supportsNativePlatform - Whether to support 'native' platform fallback
 * @param type - Module type (MODULE=0, PACKAGE=1)
 * @returns Resolved module path or null if not found
 */
getModule(
  name: string,
  platform?: string,
  supportsNativePlatform?: boolean,
  type?: 0 | 1
): string | null;

/**
 * Get package path by name
 * @param name - Package name to resolve
 * @param platform - Platform identifier
 * @param supportsNativePlatform - Whether to support 'native' platform fallback
 * @returns Resolved package path or null if not found
 */
getPackage(
  name: string,
  platform?: string,
  supportsNativePlatform?: boolean
): string | null;

Usage Examples:

const { hasteMap } = await fileMap.build();

// Resolve module by name
const buttonPath = hasteMap.getModule('Button');
console.log('Button module:', buttonPath);
// Example output: "src/components/Button.js"

// Platform-specific resolution
const iosButton = hasteMap.getModule('Button', 'ios');
const androidButton = hasteMap.getModule('Button', 'android');

console.log('iOS Button:', iosButton);     // "src/components/Button.ios.js"
console.log('Android Button:', androidButton); // "src/components/Button.android.js"

// Package resolution
const utilsPackage = hasteMap.getPackage('utils');
console.log('Utils package:', utilsPackage);
// Example output: "src/utils/package.json"

// Native platform fallback
const nativeModule = hasteMap.getModule('NativeHelper', 'ios', true);
// Will try: NativeHelper.ios.js -> NativeHelper.native.js -> NativeHelper.js

Platform Resolution Strategy

The Haste system follows a specific resolution order for platform-specific modules:

  1. Exact platform match: ModuleName.platform.js
  2. Native fallback (if enabled): ModuleName.native.js
  3. Generic fallback: ModuleName.js
// Example resolution for getModule('Button', 'ios', true):
// 1. Button.ios.js
// 2. Button.native.js (if supportsNativePlatform=true)
// 3. Button.js

// File structure example:
// src/components/
//   Button.js          <- Generic implementation
//   Button.ios.js      <- iOS-specific
//   Button.android.js  <- Android-specific
//   Button.native.js   <- Native fallback (for ios/android)

const genericButton = hasteMap.getModule('Button');        // -> Button.js
const iosButton = hasteMap.getModule('Button', 'ios');     // -> Button.ios.js
const webButton = hasteMap.getModule('Button', 'web');     // -> Button.js (fallback)

Conflict Detection

Detect and analyze Haste module naming conflicts.

/**
 * Compute all Haste conflicts in the module map
 * @returns Array of conflict descriptions
 */
computeConflicts(): Array<HasteConflict>;

interface HasteConflict {
  /** Module name with conflict */
  id: string;
  /** Platform where conflict occurs (null for all platforms) */
  platform: string | null;
  /** Absolute paths of conflicting files */
  absolutePaths: Array<string>;
  /** Type of conflict */
  type: 'duplicate' | 'shadowing';
}

Usage Examples:

// Check for module conflicts
const conflicts = hasteMap.computeConflicts();

if (conflicts.length > 0) {
  console.log(`Found ${conflicts.length} Haste conflicts:`);
  
  conflicts.forEach(conflict => {
    console.log(`\nConflict: ${conflict.id}`);
    console.log(`Platform: ${conflict.platform || 'all'}`);
    console.log(`Type: ${conflict.type}`);
    console.log('Paths:');
    conflict.absolutePaths.forEach(path => {
      console.log(`  - ${path}`);
    });
  });
} else {
  console.log('No Haste conflicts detected');
}

// Example conflict scenarios:

// Duplicate conflict - same module name in different locations:
// src/components/Button.js  (exports @providesModule Button)
// src/ui/Button.js          (exports @providesModule Button)

// Shadowing conflict - platform-specific shadowing generic:
// src/Button.js           (generic)
// lib/Button.ios.js       (shadows generic for iOS)

HastePlugin Integration

The HastePlugin class implements the Haste system functionality.

/**
 * HastePlugin class implementing Haste module resolution
 */
class HastePlugin implements FileMapPlugin {
  constructor(options: HastePluginOptions);
  
  // Plugin interface methods
  initialize(initOptions: FileMapPluginInitOptions): Promise<void>;
  assertValid(): void;
  bulkUpdate(delta: FileMapDelta): Promise<void>;
  getSerializableSnapshot(): HasteMapData;
  onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
  onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
  getCacheKey(): string;
  
  // HasteMap interface methods
  getModule(name: string, platform?: string, supportsNativePlatform?: boolean, type?: number): string | null;
  getPackage(name: string, platform?: string, supportsNativePlatform?: boolean): string | null;
  computeConflicts(): Array<HasteConflict>;
}

interface HastePluginOptions {
  console?: Console;
  enableHastePackages: boolean;
  perfLogger?: PerfLogger;
  platforms: Set<string>;
  rootDir: string;
  failValidationOnConflicts: boolean;
}

Usage Examples:

import { HastePlugin } from "metro-file-map";

// Create custom Haste plugin
const hastePlugin = new HastePlugin({
  enableHastePackages: true,
  platforms: new Set(['ios', 'android', 'web']),
  rootDir: process.cwd(),
  failValidationOnConflicts: true,
  console: console
});

// Use in FileMap configuration
const fileMap = new FileMap({
  // ... other options
  plugins: [hastePlugin],
  throwOnModuleCollision: true
});

// The plugin will be available in build results
const { hasteMap } = await fileMap.build();

// Validate Haste map (throws on conflicts if configured)
try {
  hastePlugin.assertValid();
  console.log('Haste map is valid');
} catch (error) {
  console.error('Haste conflicts detected:', error.message);
}

Module Name Declaration

Modules declare their Haste names using comment annotations:

// In your JavaScript/TypeScript files:

/**
 * @providesModule Button
 */
// or
/**
 * @flow
 * @providesModule Button  
 */

// Alternative Flow syntax:
// @providesModule Button

export default class Button extends React.Component {
  // Component implementation
}
// Package.json based modules (if enableHastePackages: true):
{
  "name": "my-utils",
  "main": "index.js"
}
// This package can be resolved as: hasteMap.getPackage('my-utils')

Types

interface HasteMapData {
  /** Map of module names to platform-specific metadata */
  modules: Map<string, HasteMapItem>;
}

interface HasteMapItem {
  /** Platform-specific module metadata */
  [platform: string]: HasteMapItemMetadata;
}

type HasteMapItemMetadata = [
  string, // path
  number  // type (MODULE=0, PACKAGE=1)
];

type DuplicatesSet = Map<string, number>;
type DuplicatesIndex = Map<string, Map<string, DuplicatesSet>>;

interface FileMapDelta {
  removed: Iterable<[string, FileMetadata]>;
  addedOrModified: Iterable<[string, FileMetadata]>;
}

docs

cache-management.md

error-handling.md

file-map-core.md

file-system.md

haste-system.md

index.md

plugin-system.md

tile.json