File system crawling, watching and mapping library designed for Metro bundler ecosystem
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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.jsThe Haste system follows a specific resolution order for platform-specific modules:
ModuleName.platform.jsModuleName.native.jsModuleName.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)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)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);
}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')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]>;
}