or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-management.mderror-handling.mdfile-map-core.mdfile-system.mdhaste-system.mdindex.mdplugin-system.md

plugin-system.mddocs/

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

```