Repository: expo/expo
File: packages/@expo/cli/src/start/server/metro/instantiateMetro.ts (TypeScript)

Code before:
    // See: https://github.com/facebook/metro/commit/d0d554381f119bb80ab09dbd6a1d310b54737e52
    const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware;
    asWritable(metroConfig.server).enhanceMiddleware = (
      metroMiddleware: any,
      server: MetroServer
    ) => {
      if (customEnhanceMiddleware) {
        metroMiddleware = customEnhanceMiddleware(metroMiddleware, server);
      }
      return middleware.use(metroMiddleware);
    };
  }

  // Attach Expo Atlas if enabled
  await attachAtlasAsync({
    isExporting,
    exp,
    projectRoot,
    middleware,
    metroConfig,
    // NOTE(cedric): reset the Atlas file once, and reuse it for static exports
    resetAtlasFile: isExporting,
  });

  // Support HTTPS via secureServerOptions on the metro config's server key
  const { secureServerOptions } = metroConfig.server as any;

  const { server, hmrServer, metro } = await runServer(
    metroBundler,
    metroConfig,
    {
      websocketEndpoints: {
        ...websocketEndpoints,
        ...createDevToolsPluginWebsocketEndpoint(),
      },
      watch: !isExporting && isWatchEnabled(),
      secureServerOptions,
    },
    {
      mockServer: isExporting,
    }
  );

  // Patch transform file to remove inconvenient customTransformOptions which are only used in single well-known files.
  const originalTransformFile = metro
    .getBundler()
    .getBundler()
    .transformFile.bind(metro.getBundler().getBundler());

  metro.getBundler().getBundler().transformFile = async function (
    filePath: string,

Review comment (refactor):
This doesn't exist on Metro's config, right? IIUC you're just attaching it to Metro config in order to extract it and pass it through.

It probably *should* be on Metro config though, as something like `config.server.tls`. I don't know why it was only added as an option for the `runServer` API.

Given it's trivial to start an HTTPS server in Node (and Bun), I'd lean towards that rather than recommending a proxy. We could accept a fairly generic subset of options there (cert, key) to avoid lock-in to `createServer`, if that's a concern (though, @kitten, would we want to use anything else?).

Code after:

  return {
    config,
    setEventReporter: (logger: (event: any) => void) => (reportEvent = logger),
    reporter: terminalReporter,
  };
}

interface InstantiateMetroConfigOptions extends LoadMetroConfigOptions {
  host?: string;
}

/** The most generic possible setup for Metro bundler. */
export async function instantiateMetroAsync(
  metroBundler: MetroBundlerDevServer,
  options: InstantiateMetroConfigOptions,
  {
    isExporting,
    exp = getConfig(metroBundler.projectRoot, {
      skipSDKVersionRequirement: true,
    }).exp,
  }: { isExporting: boolean; exp?: ExpoConfig }
): Promise<{
  metro: MetroServer;
  hmrServer: MetroHmrServer<MetroHmrClient> | null;
  server: http.Server;
  middleware: any;
  messageSocket: MessageSocket;
}> {
  const projectRoot = metroBundler.projectRoot;

  const {
    config: metroConfig,
    setEventReporter,
    reporter,
  } = await loadMetroConfigAsync(projectRoot, options, {
    exp,
    isExporting,
    getMetroBundler() {
      return metro.getBundler().getBundler();
    },
  });

  // Create the core middleware stack for Metro, including websocket listeners
  const { middleware, messagesSocket, eventsSocket, websocketEndpoints } =
    createMetroMiddleware(metroConfig);

  // Get local URL to Metro bundler server (typically configured as 127.0.0.1:8081)
  const serverBaseUrl = metroBundler
    .getUrlCreator()
    .constructUrl({ scheme: 'http', hostType: 'localhost' });