1import { getConfig } from '@expo/config'; 2import { MetroDevServerOptions } from '@expo/dev-server'; 3import chalk from 'chalk'; 4import http from 'http'; 5import Metro from 'metro'; 6import { Terminal } from 'metro-core'; 7 8import { Log } from '../../../log'; 9import { getMetroProperties } from '../../../utils/analytics/getMetroProperties'; 10import { createDebuggerTelemetryMiddleware } from '../../../utils/analytics/metroDebuggerMiddleware'; 11import { logEventAsync } from '../../../utils/analytics/rudderstackClient'; 12import { env } from '../../../utils/env'; 13import { createDevServerMiddleware } from '../middleware/createDevServerMiddleware'; 14import { getPlatformBundlers } from '../platformBundlers'; 15import { MetroTerminalReporter } from './MetroTerminalReporter'; 16import { importExpoMetroConfigFromProject, importMetroFromProject } from './resolveFromProject'; 17import { withMetroMultiPlatformAsync } from './withMetroMultiPlatform'; 18 19// From expo/dev-server but with ability to use custom logger. 20type MessageSocket = { 21 broadcast: (method: string, params?: Record<string, any> | undefined) => void; 22}; 23 24/** The most generic possible setup for Metro bundler. */ 25export async function instantiateMetroAsync( 26 projectRoot: string, 27 options: Omit<MetroDevServerOptions, 'logger'> 28): Promise<{ 29 server: http.Server; 30 middleware: any; 31 messageSocket: MessageSocket; 32}> { 33 let reportEvent: ((event: any) => void) | undefined; 34 35 const Metro = importMetroFromProject(projectRoot); 36 const ExpoMetroConfig = importExpoMetroConfigFromProject(projectRoot); 37 38 const terminal = new Terminal(process.stdout); 39 const terminalReporter = new MetroTerminalReporter(projectRoot, terminal); 40 41 const reporter = { 42 update(event: any) { 43 terminalReporter.update(event); 44 if (reportEvent) { 45 reportEvent(event); 46 } 47 }, 48 }; 49 50 let metroConfig = await ExpoMetroConfig.loadAsync(projectRoot, { reporter, ...options }); 51 52 // TODO: When we bring expo/metro-config into the expo/expo repo, then we can upstream this. 53 const { exp } = getConfig(projectRoot, { 54 skipSDKVersionRequirement: true, 55 skipPlugins: true, 56 }); 57 58 const platformBundlers = getPlatformBundlers(exp); 59 metroConfig = await withMetroMultiPlatformAsync(projectRoot, metroConfig, platformBundlers); 60 61 logEventAsync('metro config', getMetroProperties(projectRoot, exp, metroConfig)); 62 63 const { 64 middleware, 65 attachToServer, 66 67 // New 68 websocketEndpoints, 69 eventsSocketEndpoint, 70 messageSocketEndpoint, 71 } = createDevServerMiddleware(projectRoot, { 72 port: metroConfig.server.port, 73 watchFolders: metroConfig.watchFolders, 74 }); 75 76 const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware; 77 // @ts-ignore can't mutate readonly config 78 metroConfig.server.enhanceMiddleware = (metroMiddleware: any, server: Metro.Server) => { 79 if (customEnhanceMiddleware) { 80 metroMiddleware = customEnhanceMiddleware(metroMiddleware, server); 81 } 82 return middleware.use(metroMiddleware); 83 }; 84 85 middleware.use(createDebuggerTelemetryMiddleware(projectRoot, exp)); 86 87 const server = await Metro.runServer(metroConfig, { 88 hmrEnabled: true, 89 websocketEndpoints, 90 // @ts-expect-error Property was added in 0.73.4, remove this statement when updating Metro 91 watch: isWatchEnabled(), 92 }); 93 94 if (attachToServer) { 95 // Expo SDK 44 and lower 96 const { messageSocket, eventsSocket } = attachToServer(server); 97 reportEvent = eventsSocket.reportEvent; 98 99 return { 100 server, 101 middleware, 102 messageSocket, 103 }; 104 } else { 105 // RN +68 -- Expo SDK +45 106 reportEvent = eventsSocketEndpoint.reportEvent; 107 108 return { 109 server, 110 middleware, 111 messageSocket: messageSocketEndpoint, 112 }; 113 } 114} 115 116/** 117 * Simplify and communicate if Metro is running without watching file updates,. 118 * Exposed for testing. 119 */ 120export function isWatchEnabled() { 121 if (env.CI) { 122 Log.log( 123 chalk`Metro is running in CI mode, reloads are disabled. Remove {bold CI=true} to enable watch mode.` 124 ); 125 } 126 127 return !env.CI; 128} 129