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