1import { getConfig } from '@expo/config'; 2import { MetroDevServerOptions } from '@expo/dev-server'; 3import http from 'http'; 4import Metro from 'metro'; 5import { Terminal } from 'metro-core'; 6 7import { createDevServerMiddleware } from '../middleware/createDevServerMiddleware'; 8import { getPlatformBundlers } from '../platformBundlers'; 9import { MetroTerminalReporter } from './MetroTerminalReporter'; 10import { importExpoMetroConfigFromProject, importMetroFromProject } from './resolveFromProject'; 11import { withMetroMultiPlatform } from './withMetroMultiPlatform'; 12 13// From expo/dev-server but with ability to use custom logger. 14type MessageSocket = { 15 broadcast: (method: string, params?: Record<string, any> | undefined) => void; 16}; 17 18/** The most generic possible setup for Metro bundler. */ 19export async function instantiateMetroAsync( 20 projectRoot: string, 21 options: Omit<MetroDevServerOptions, 'logger'> 22): Promise<{ 23 server: http.Server; 24 middleware: any; 25 messageSocket: MessageSocket; 26}> { 27 let reportEvent: ((event: any) => void) | undefined; 28 29 const Metro = importMetroFromProject(projectRoot); 30 const ExpoMetroConfig = importExpoMetroConfigFromProject(projectRoot); 31 32 const terminal = new Terminal(process.stdout); 33 const terminalReporter = new MetroTerminalReporter(projectRoot, terminal); 34 35 const reporter = { 36 update(event: any) { 37 terminalReporter.update(event); 38 if (reportEvent) { 39 reportEvent(event); 40 } 41 }, 42 }; 43 44 let metroConfig = await ExpoMetroConfig.loadAsync(projectRoot, { reporter, ...options }); 45 46 // TODO: When we bring expo/metro-config into the expo/expo repo, then we can upstream this. 47 const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true, skipPlugins: true }); 48 const platformBundlers = getPlatformBundlers(exp); 49 metroConfig = withMetroMultiPlatform(projectRoot, metroConfig, platformBundlers); 50 51 const { 52 middleware, 53 attachToServer, 54 55 // New 56 websocketEndpoints, 57 eventsSocketEndpoint, 58 messageSocketEndpoint, 59 } = createDevServerMiddleware(projectRoot, { 60 port: metroConfig.server.port, 61 watchFolders: metroConfig.watchFolders, 62 }); 63 64 const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware; 65 // @ts-ignore can't mutate readonly config 66 metroConfig.server.enhanceMiddleware = (metroMiddleware: any, server: Metro.Server) => { 67 if (customEnhanceMiddleware) { 68 metroMiddleware = customEnhanceMiddleware(metroMiddleware, server); 69 } 70 return middleware.use(metroMiddleware); 71 }; 72 73 const server = await Metro.runServer(metroConfig, { 74 // @ts-expect-error: TODO: Update the types. 75 hmrEnabled: true, 76 websocketEndpoints, 77 }); 78 79 if (attachToServer) { 80 // Expo SDK 44 and lower 81 const { messageSocket, eventsSocket } = attachToServer(server); 82 reportEvent = eventsSocket.reportEvent; 83 84 return { 85 server, 86 middleware, 87 messageSocket, 88 }; 89 } else { 90 // RN +68 -- Expo SDK +45 91 reportEvent = eventsSocketEndpoint.reportEvent; 92 93 return { 94 server, 95 middleware, 96 messageSocket: messageSocketEndpoint, 97 }; 98 } 99} 100