1import { MetroDevServerOptions } from '@expo/dev-server';
2import http from 'http';
3import Metro from 'metro';
4import { Terminal } from 'metro-core';
5
6import { createDevServerMiddleware } from '../middleware/createDevServerMiddleware';
7import { MetroTerminalReporter } from './MetroTerminalReporter';
8import { importExpoMetroConfigFromProject, importMetroFromProject } from './resolveFromProject';
9
10// From expo/dev-server but with ability to use custom logger.
11type MessageSocket = {
12  broadcast: (method: string, params?: Record<string, any> | undefined) => void;
13};
14
15/** The most generic possible setup for Metro bundler. */
16export async function instantiateMetroAsync(
17  projectRoot: string,
18  options: Omit<MetroDevServerOptions, 'logger'>
19): Promise<{
20  server: http.Server;
21  middleware: any;
22  messageSocket: MessageSocket;
23}> {
24  let reportEvent: ((event: any) => void) | undefined;
25
26  const Metro = importMetroFromProject(projectRoot);
27  const ExpoMetroConfig = importExpoMetroConfigFromProject(projectRoot);
28
29  const terminal = new Terminal(process.stdout);
30  const terminalReporter = new MetroTerminalReporter(projectRoot, terminal);
31
32  const reporter = {
33    update(event: any) {
34      terminalReporter.update(event);
35      if (reportEvent) {
36        reportEvent(event);
37      }
38    },
39  };
40
41  const metroConfig = await ExpoMetroConfig.loadAsync(projectRoot, { reporter, ...options });
42
43  const { middleware, attachToServer } = createDevServerMiddleware({
44    port: metroConfig.server.port,
45    watchFolders: metroConfig.watchFolders,
46  });
47
48  const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware;
49  // @ts-ignore can't mutate readonly config
50  metroConfig.server.enhanceMiddleware = (metroMiddleware: any, server: Metro.Server) => {
51    if (customEnhanceMiddleware) {
52      metroMiddleware = customEnhanceMiddleware(metroMiddleware, server);
53    }
54    return middleware.use(metroMiddleware);
55  };
56
57  const server = await Metro.runServer(metroConfig, {
58    // @ts-expect-error: TODO: Update the types.
59    hmrEnabled: true,
60  });
61
62  const { messageSocket, eventsSocket } = attachToServer(server);
63  reportEvent = eventsSocket.reportEvent;
64
65  return {
66    server,
67    middleware,
68    messageSocket,
69  };
70}
71