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