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 { withMetroMultiPlatformAsync } 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, { 48 skipSDKVersionRequirement: true, 49 skipPlugins: true, 50 }); 51 const platformBundlers = getPlatformBundlers(exp); 52 metroConfig = await withMetroMultiPlatformAsync(projectRoot, metroConfig, platformBundlers); 53 54 const { 55 middleware, 56 attachToServer, 57 58 // New 59 websocketEndpoints, 60 eventsSocketEndpoint, 61 messageSocketEndpoint, 62 } = createDevServerMiddleware(projectRoot, { 63 port: metroConfig.server.port, 64 watchFolders: metroConfig.watchFolders, 65 }); 66 67 const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware; 68 // @ts-ignore can't mutate readonly config 69 metroConfig.server.enhanceMiddleware = (metroMiddleware: any, server: Metro.Server) => { 70 if (customEnhanceMiddleware) { 71 metroMiddleware = customEnhanceMiddleware(metroMiddleware, server); 72 } 73 return middleware.use(metroMiddleware); 74 }; 75 76 const server = await Metro.runServer(metroConfig, { 77 hmrEnabled: true, 78 websocketEndpoints, 79 }); 80 81 if (attachToServer) { 82 // Expo SDK 44 and lower 83 const { messageSocket, eventsSocket } = attachToServer(server); 84 reportEvent = eventsSocket.reportEvent; 85 86 return { 87 server, 88 middleware, 89 messageSocket, 90 }; 91 } else { 92 // RN +68 -- Expo SDK +45 93 reportEvent = eventsSocketEndpoint.reportEvent; 94 95 return { 96 server, 97 middleware, 98 messageSocket: messageSocketEndpoint, 99 }; 100 } 101} 102