1import path from 'path'; 2 3import type { ServerLike } from '../BundlerDevServer'; 4 5const debug = require('debug')( 6 'expo:start:server:metro:metroWatchTypeScriptFiles' 7) as typeof console.log; 8 9export interface MetroWatchTypeScriptFilesOptions { 10 projectRoot: string; 11 metro: import('metro').Server; 12 server: ServerLike; 13 /* Include tsconfig.json in the watcher */ 14 tsconfig?: boolean; 15 callback: (event: WatchEvent) => void; 16 /* Array of eventTypes to watch. Defaults to all events */ 17 eventTypes?: string[]; 18 /* Throlle the callback. When true and a group of events are recieved, callback it will only be called with the 19 * first event */ 20 throttle?: boolean; 21} 22 23interface WatchEvent { 24 filePath: string; 25 metadata?: { 26 type: 'f' | 'd' | 'l'; // Regular file / Directory / Symlink 27 } | null; 28 type: string; 29} 30 31/** 32 * Use the native file watcher / Metro ruleset to detect if a 33 * TypeScript file is added to the project during development. 34 */ 35export function metroWatchTypeScriptFiles({ 36 metro, 37 server, 38 projectRoot, 39 callback, 40 tsconfig = false, 41 throttle = false, 42 eventTypes = ['add', 'change', 'delete'], 43}: MetroWatchTypeScriptFilesOptions): () => void { 44 const watcher = metro.getBundler().getBundler().getWatcher(); 45 46 const tsconfigPath = path.join(projectRoot, 'tsconfig.json'); 47 48 const listener = ({ eventsQueue }: { eventsQueue: WatchEvent[] }) => { 49 for (const event of eventsQueue) { 50 if ( 51 eventTypes.includes(event.type) && 52 event.metadata?.type !== 'd' && 53 // We need to ignore node_modules because Metro will add all of the files in node_modules to the watcher. 54 !/node_modules/.test(event.filePath) && 55 // Ignore declaration files 56 !/\.d\.ts$/.test(event.filePath) 57 ) { 58 const { filePath } = event; 59 // Is TypeScript? 60 if ( 61 // If the user adds a TypeScript file to the observable files in their project. 62 /\.tsx?$/.test(filePath) || 63 // Or if the user adds a tsconfig.json file to the project root. 64 (tsconfig && filePath === tsconfigPath) 65 ) { 66 debug('Detected TypeScript file changed in the project: ', filePath); 67 callback(event); 68 69 if (throttle) { 70 return; 71 } 72 } 73 } 74 } 75 }; 76 77 debug('Waiting for TypeScript files to be added to the project...'); 78 watcher.addListener('change', listener); 79 watcher.addListener('add', listener); 80 81 const off = () => { 82 watcher.removeListener('change', listener); 83 watcher.removeListener('add', listener); 84 }; 85 86 server.addListener?.('close', off); 87 return off; 88} 89