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
Pluggable caching system with disk-based implementation for persistent storage of file system state, module maps, and plugin data. The cache system significantly improves startup performance by avoiding full file system crawls when possible.
Core interface for implementing custom cache managers.
/**
* Interface for cache management implementations
*/
interface CacheManager {
/**
* Read cached data during startup
* @returns Promise resolving to cached data or null if not available
*/
read(): Promise<CacheData | null>;
/**
* Write current state to cache
* @param getSnapshot - Function returning current state snapshot
* @param opts - Write options with event handling
* @returns Promise that resolves when write completes
*/
write(
getSnapshot: () => CacheData,
opts: CacheManagerWriteOptions
): Promise<void>;
/**
* Clean up cache manager resources
* @returns Promise that resolves when cleanup completes
*/
end(): Promise<void>;
}Usage Examples:
// Implement custom cache manager
class MemoryCacheManager {
constructor() {
this.cache = null;
}
async read() {
return this.cache;
}
async write(getSnapshot, opts) {
this.cache = getSnapshot();
console.log('Cache updated in memory');
}
async end() {
this.cache = null;
}
}
// Use custom cache manager
const fileMap = new FileMap({
// ... other options
cacheManagerFactory: () => new MemoryCacheManager()
});Default disk-based cache manager with automatic persistence and debouncing.
/**
* Disk-based cache manager with auto-save capabilities
*/
class DiskCacheManager implements CacheManager {
/**
* Create disk cache manager
* @param options - Factory options with build parameters
* @param config - Disk-specific configuration
*/
constructor(
options: CacheManagerFactoryOptions,
config?: DiskCacheConfig
);
/**
* Get cache file path for given parameters
* @param buildParameters - Build configuration parameters
* @param cacheFilePrefix - Optional file prefix (default: 'metro-file-map')
* @param cacheDirectory - Optional directory (default: OS temp dir)
* @returns Absolute path to cache file
*/
static getCacheFilePath(
buildParameters: BuildParameters,
cacheFilePrefix?: string,
cacheDirectory?: string
): string;
/**
* Get cache file path for this instance
* @returns Absolute path to cache file
*/
getCacheFilePath(): string;
// CacheManager interface implementation
read(): Promise<CacheData | null>;
write(getSnapshot: () => CacheData, opts: CacheManagerWriteOptions): Promise<void>;
end(): Promise<void>;
}Usage Examples:
import { DiskCacheManager } from "metro-file-map";
// Create with default configuration
const cacheManager = new DiskCacheManager({
buildParameters: {
// ... build parameters
}
});
// Create with custom configuration
const customCacheManager = new DiskCacheManager(
{ buildParameters },
{
cacheDirectory: './my-cache',
cacheFilePrefix: 'my-app-cache',
autoSave: {
debounceMs: 2000
}
}
);
// Get cache file path
const cachePath = DiskCacheManager.getCacheFilePath(
buildParameters,
'my-prefix',
'/custom/cache/dir'
);
console.log('Cache will be stored at:', cachePath);
// Use with FileMap
const fileMap = new FileMap({
// ... other options
cacheManagerFactory: (options) => new DiskCacheManager(options, {
cacheDirectory: process.env.CACHE_DIR || './cache',
autoSave: true
})
});Structure of data stored in the cache.
/**
* Cache data structure containing file system and plugin state
*/
interface CacheData {
/** Watchman clocks for incremental updates */
clocks: WatchmanClocks;
/** Serialized file system data */
fileSystemData: unknown;
/** Plugin state data keyed by plugin name */
plugins: ReadonlyMap<string, unknown>;
}
type WatchmanClocks = Map<string, WatchmanClockSpec>;
type WatchmanClockSpec = string | {
scm: {
'mergebase-with': string;
};
};Usage Examples:
// Read and inspect cache data
const cacheData = await fileMap.read();
if (cacheData) {
console.log('Watchman clocks:', cacheData.clocks.size);
console.log('Plugin states:', Array.from(cacheData.plugins.keys()));
// Check if specific plugin data exists
if (cacheData.plugins.has('HastePlugin')) {
console.log('Haste plugin state cached');
}
}Configuration for cache write operations with event handling.
/**
* Options for cache write operations
*/
interface CacheManagerWriteOptions {
/** Whether data changed since last cache read */
changedSinceCacheRead: boolean;
/** Event source for change notifications */
eventSource: CacheManagerEventSource;
/** Error handler for write failures */
onWriteError: (error: Error) => void;
}
/**
* Event source for cache change notifications
*/
interface CacheManagerEventSource {
/**
* Subscribe to change events
* @param listener - Change event listener
* @returns Unsubscribe function
*/
onChange(listener: () => void): () => void;
}Usage Examples:
// Custom cache manager with event handling
class LoggingCacheManager {
async write(getSnapshot, opts) {
if (opts.changedSinceCacheRead) {
console.log('Data changed since cache read, updating...');
}
// Subscribe to future changes
const unsubscribe = opts.eventSource.onChange(() => {
console.log('File system changed, cache may need update');
});
try {
const data = getSnapshot();
await this.persistData(data);
} catch (error) {
opts.onWriteError(error);
}
// Cleanup subscription when appropriate
setTimeout(unsubscribe, 60000); // Unsubscribe after 1 minute
}
}Factory function pattern for creating cache managers.
/**
* Factory function for creating cache managers
*/
type CacheManagerFactory = (
options: CacheManagerFactoryOptions
) => CacheManager;
/**
* Options passed to cache manager factories
*/
interface CacheManagerFactoryOptions {
/** Build parameters for cache key generation */
buildParameters: BuildParameters;
}Usage Examples:
// Simple factory
const simpleCacheFactory = (options) => {
return new DiskCacheManager(options, {
cacheDirectory: './cache'
});
};
// Environment-aware factory
const environmentCacheFactory = (options) => {
if (process.env.NODE_ENV === 'test') {
return new MemoryCacheManager();
} else if (process.env.CACHE_DISABLED) {
return new NullCacheManager(); // No-op cache
} else {
return new DiskCacheManager(options, {
cacheDirectory: process.env.CACHE_DIR || './cache',
autoSave: process.env.NODE_ENV === 'production'
});
}
};
// Use factory with FileMap
const fileMap = new FileMap({
// ... other options
cacheManagerFactory: environmentCacheFactory
});Automatic cache persistence with debouncing to reduce I/O overhead.
/**
* Disk cache configuration options
*/
interface DiskCacheConfig {
/** Auto-save configuration (boolean or options) */
autoSave?: boolean | Partial<AutoSaveOptions>;
/** Cache file prefix (default: 'metro-file-map') */
cacheFilePrefix?: string;
/** Cache directory (default: OS temp directory) */
cacheDirectory?: string;
}
/**
* Auto-save configuration options
*/
interface AutoSaveOptions {
/** Debounce time in milliseconds (default: 5000) */
debounceMs: number;
}Usage Examples:
// Enable auto-save with default settings
const autoSaveCacheManager = new DiskCacheManager(options, {
autoSave: true
});
// Custom auto-save configuration
const customAutoSave = new DiskCacheManager(options, {
autoSave: {
debounceMs: 1000 // Save 1 second after last change
},
cacheDirectory: './build/cache',
cacheFilePrefix: 'my-app'
});
// Disable auto-save (manual control)
const manualCacheManager = new DiskCacheManager(options, {
autoSave: false
});
// Manual cache operations
await manualCacheManager.write(() => getCurrentState(), writeOptions);interface BuildParameters {
computeDependencies: boolean;
computeSha1: boolean;
enableHastePackages: boolean;
enableSymlinks: boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
ignorePattern: RegExp;
plugins: ReadonlyArray<FileMapPlugin>;
retainAllFiles: boolean;
rootDir: string;
roots: ReadonlyArray<string>;
skipPackageJson: boolean;
dependencyExtractor: string | null;
hasteImplModulePath: string | null;
cacheBreaker: string;
}