0
# Plugin System
1
2
Extensible plugin system for Haste modules, mocks, and custom file processing. Plugins enable extending metro-file-map functionality with custom file analysis, module resolution strategies, and data processing workflows.
3
4
## Capabilities
5
6
### FileMapPlugin Interface
7
8
Core interface that all plugins must implement.
9
10
```javascript { .api }
11
/**
12
* Plugin interface for extending FileMap functionality
13
*/
14
interface FileMapPlugin<SerializableState = unknown> {
15
/** Unique plugin name for identification */
16
+name: string;
17
18
/**
19
* Initialize plugin with file system state and cached plugin data
20
* @param initOptions - Initialization options with files and plugin state
21
* @returns Promise that resolves when initialization completes
22
*/
23
initialize(
24
initOptions: FileMapPluginInitOptions<SerializableState>
25
): Promise<void>;
26
27
/**
28
* Validate plugin state and throw if invalid
29
* Used to detect conflicts or inconsistencies
30
*/
31
assertValid(): void;
32
33
/**
34
* Apply bulk file system changes
35
* @param delta - File system changes to process
36
* @returns Promise that resolves when update completes
37
*/
38
bulkUpdate(delta: FileMapDelta): Promise<void>;
39
40
/**
41
* Get serializable plugin state for caching
42
* @returns Serializable state data
43
*/
44
getSerializableSnapshot(): SerializableState;
45
46
/**
47
* Handle individual file removal
48
* @param relativeFilePath - Path of removed file
49
* @param fileMetadata - Metadata of removed file
50
*/
51
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
52
53
/**
54
* Handle individual file addition or modification
55
* @param relativeFilePath - Path of added/modified file
56
* @param fileMetadata - Metadata of added/modified file
57
*/
58
onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
59
60
/**
61
* Get cache key for plugin configuration
62
* @returns Cache key string for invalidation
63
*/
64
getCacheKey(): string;
65
}
66
```
67
68
**Usage Examples:**
69
70
```javascript
71
// Implement custom plugin
72
class CustomAnalysisPlugin {
73
constructor(options) {
74
this.name = 'CustomAnalysisPlugin';
75
this.options = options;
76
this.analysisData = new Map();
77
}
78
79
async initialize({ files, pluginState }) {
80
if (pluginState) {
81
// Restore from cached state
82
this.analysisData = new Map(pluginState.analysisData);
83
} else {
84
// Initialize from file system
85
for (const { canonicalPath, metadata } of files.metadataIterator({
86
includeNodeModules: false,
87
includeSymlinks: false
88
})) {
89
await this.analyzeFile(canonicalPath, metadata);
90
}
91
}
92
}
93
94
assertValid() {
95
// Check for any validation errors
96
if (this.analysisData.size === 0) {
97
throw new Error('No files analyzed');
98
}
99
}
100
101
async bulkUpdate(delta) {
102
// Process removed files
103
for (const [path, metadata] of delta.removed) {
104
this.analysisData.delete(path);
105
}
106
107
// Process added/modified files
108
for (const [path, metadata] of delta.addedOrModified) {
109
await this.analyzeFile(path, metadata);
110
}
111
}
112
113
getSerializableSnapshot() {
114
return {
115
analysisData: Array.from(this.analysisData.entries())
116
};
117
}
118
119
onRemovedFile(path, metadata) {
120
this.analysisData.delete(path);
121
}
122
123
onNewOrModifiedFile(path, metadata) {
124
this.analyzeFile(path, metadata);
125
}
126
127
getCacheKey() {
128
return JSON.stringify(this.options);
129
}
130
131
async analyzeFile(path, metadata) {
132
// Custom analysis logic
133
this.analysisData.set(path, {
134
analyzed: true,
135
timestamp: Date.now()
136
});
137
}
138
}
139
140
// Use custom plugin
141
const fileMap = new FileMap({
142
// ... other options
143
plugins: [new CustomAnalysisPlugin({ enableDeepAnalysis: true })]
144
});
145
```
146
147
### Plugin Initialization
148
149
Plugins are initialized with file system state and cached data.
150
151
```javascript { .api }
152
/**
153
* Options passed to plugin initialization
154
*/
155
interface FileMapPluginInitOptions<SerializableState> {
156
/** File system state for initial processing */
157
files: FileSystemState;
158
/** Previously cached plugin state (null if no cache) */
159
pluginState: SerializableState | null;
160
}
161
162
/**
163
* File system state interface for plugin initialization
164
*/
165
interface FileSystemState {
166
/**
167
* Iterate over file metadata
168
* @param opts - Iteration options
169
* @returns Iterable of file information
170
*/
171
metadataIterator(opts: {
172
includeNodeModules: boolean;
173
includeSymlinks: boolean;
174
}): Iterable<{
175
baseName: string;
176
canonicalPath: string;
177
metadata: FileMetadata;
178
}>;
179
}
180
```
181
182
**Usage Examples:**
183
184
```javascript
185
class FileCountPlugin {
186
async initialize({ files, pluginState }) {
187
this.counts = pluginState || { js: 0, ts: 0, json: 0, other: 0 };
188
189
if (!pluginState) {
190
// Count files by extension
191
for (const { canonicalPath } of files.metadataIterator({
192
includeNodeModules: false,
193
includeSymlinks: false
194
})) {
195
this.incrementCount(canonicalPath);
196
}
197
}
198
199
console.log('File counts:', this.counts);
200
}
201
202
incrementCount(path) {
203
if (path.endsWith('.js')) this.counts.js++;
204
else if (path.endsWith('.ts')) this.counts.ts++;
205
else if (path.endsWith('.json')) this.counts.json++;
206
else this.counts.other++;
207
}
208
209
getSerializableSnapshot() {
210
return this.counts;
211
}
212
}
213
```
214
215
### Built-in Plugins
216
217
Metro-file-map includes several built-in plugins.
218
219
#### HastePlugin
220
221
```javascript { .api }
222
/**
223
* Built-in Haste module resolution plugin
224
*/
225
class HastePlugin implements FileMapPlugin {
226
constructor(options: HastePluginOptions);
227
228
// FileMapPlugin interface
229
name: 'HastePlugin';
230
initialize(initOptions: FileMapPluginInitOptions): Promise<void>;
231
assertValid(): void;
232
bulkUpdate(delta: FileMapDelta): Promise<void>;
233
getSerializableSnapshot(): HasteMapData;
234
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
235
onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
236
getCacheKey(): string;
237
238
// HasteMap interface
239
getModule(name: string, platform?: string, supportsNativePlatform?: boolean, type?: number): string | null;
240
getPackage(name: string, platform?: string, supportsNativePlatform?: boolean): string | null;
241
computeConflicts(): Array<HasteConflict>;
242
}
243
244
interface HastePluginOptions {
245
console?: Console;
246
enableHastePackages: boolean;
247
perfLogger?: PerfLogger;
248
platforms: Set<string>;
249
rootDir: string;
250
failValidationOnConflicts: boolean;
251
}
252
```
253
254
#### MockPlugin
255
256
```javascript { .api }
257
/**
258
* Built-in mock module plugin
259
*/
260
class MockPlugin implements FileMapPlugin {
261
constructor(options: MockPluginOptions);
262
263
// FileMapPlugin interface
264
name: 'MockPlugin';
265
initialize(initOptions: FileMapPluginInitOptions): Promise<void>;
266
assertValid(): void;
267
bulkUpdate(delta: FileMapDelta): Promise<void>;
268
getSerializableSnapshot(): RawMockMap;
269
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
270
onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
271
getCacheKey(): string;
272
273
// MockMap interface
274
getMockModule(name: string): string | null;
275
}
276
277
interface MockPluginOptions {
278
console?: Console;
279
mocksPattern: RegExp;
280
rootDir: string;
281
throwOnModuleCollision: boolean;
282
}
283
```
284
285
**Usage Examples:**
286
287
```javascript
288
import { HastePlugin, MockPlugin } from "metro-file-map";
289
290
// Configure built-in plugins
291
const hastePlugin = new HastePlugin({
292
enableHastePackages: true,
293
platforms: new Set(['ios', 'android', 'web']),
294
rootDir: process.cwd(),
295
failValidationOnConflicts: true
296
});
297
298
const mockPlugin = new MockPlugin({
299
mocksPattern: /\/__mocks__\//,
300
rootDir: process.cwd(),
301
throwOnModuleCollision: false,
302
console: console
303
});
304
305
// Use with FileMap
306
const fileMap = new FileMap({
307
// ... other options
308
plugins: [hastePlugin],
309
mocksPattern: '__mocks__' // This automatically creates MockPlugin
310
});
311
```
312
313
### File System Delta Updates
314
315
Plugins receive file system changes through delta updates.
316
317
```javascript { .api }
318
/**
319
* File system changes passed to plugins
320
*/
321
interface FileMapDelta {
322
/** Files that were removed */
323
removed: Iterable<[string, FileMetadata]>;
324
/** Files that were added or modified */
325
addedOrModified: Iterable<[string, FileMetadata]>;
326
}
327
```
328
329
**Usage Examples:**
330
331
```javascript
332
class ChangeTrackingPlugin {
333
constructor() {
334
this.name = 'ChangeTrackingPlugin';
335
this.changeHistory = [];
336
}
337
338
async bulkUpdate(delta) {
339
const changes = {
340
timestamp: Date.now(),
341
removed: [],
342
modified: []
343
};
344
345
// Track removed files
346
for (const [path, metadata] of delta.removed) {
347
changes.removed.push(path);
348
this.onRemovedFile(path, metadata);
349
}
350
351
// Track added/modified files
352
for (const [path, metadata] of delta.addedOrModified) {
353
changes.modified.push(path);
354
this.onNewOrModifiedFile(path, metadata);
355
}
356
357
this.changeHistory.push(changes);
358
359
// Keep only last 100 changes
360
if (this.changeHistory.length > 100) {
361
this.changeHistory.shift();
362
}
363
}
364
365
onRemovedFile(path, metadata) {
366
console.log(`File removed: ${path}`);
367
}
368
369
onNewOrModifiedFile(path, metadata) {
370
console.log(`File changed: ${path}`);
371
}
372
373
getChangeHistory() {
374
return this.changeHistory;
375
}
376
}
377
```
378
379
### Plugin Configuration
380
381
Plugins can be configured through FileMap options.
382
383
```javascript { .api }
384
/**
385
* Plugin configuration in InputOptions
386
*/
387
interface InputOptions {
388
/** Array of custom plugins to use */
389
plugins?: ReadonlyArray<FileMapPlugin>;
390
/** Mock pattern (creates MockPlugin automatically) */
391
mocksPattern?: string;
392
/** Enable Haste packages in default HastePlugin */
393
enableHastePackages?: boolean;
394
/** Throw on module collisions */
395
throwOnModuleCollision?: boolean;
396
}
397
```
398
399
**Usage Examples:**
400
401
```javascript
402
// Multiple plugin configuration
403
const fileMap = new FileMap({
404
extensions: ['.js', '.ts'],
405
platforms: ['ios', 'android'],
406
retainAllFiles: false,
407
rootDir: process.cwd(),
408
roots: ['./src'],
409
maxWorkers: 4,
410
healthCheck: { enabled: false, interval: 30000, timeout: 5000, filePrefix: 'test' },
411
412
// Plugin configuration
413
plugins: [
414
new CustomAnalysisPlugin(),
415
new ChangeTrackingPlugin(),
416
new HastePlugin({
417
enableHastePackages: true,
418
platforms: new Set(['ios', 'android']),
419
rootDir: process.cwd(),
420
failValidationOnConflicts: false
421
})
422
],
423
424
// Mock configuration (creates MockPlugin)
425
mocksPattern: '__mocks__',
426
throwOnModuleCollision: false
427
});
428
429
// Access plugins from build result
430
const { hasteMap, mockMap } = await fileMap.build();
431
```
432
433
## Types
434
435
```javascript { .api }
436
type FileMetadata = [
437
string, // id
438
number, // mtime
439
number, // size
440
0 | 1, // visited
441
string, // dependencies
442
string, // sha1
443
0 | 1 | string // symlink
444
];
445
446
interface RawMockMap {
447
duplicates: Map<string, Set<string>>;
448
mocks: Map<string, string>;
449
version: number;
450
}
451
452
interface HasteMapData {
453
modules: Map<string, HasteMapItem>;
454
}
455
456
interface HasteMapItem {
457
[platform: string]: HasteMapItemMetadata;
458
}
459
460
type HasteMapItemMetadata = [string, number]; // [path, type]
461
462
interface HasteConflict {
463
id: string;
464
platform: string | null;
465
absolutePaths: Array<string>;
466
type: 'duplicate' | 'shadowing';
467
}
468
```