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