1/** 2 * Copyright © 2022 650 Industries. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7import path from 'node:path'; 8 9import { logMetroErrorAsync } from './metroErrorInterface'; 10import { getApiRoutesForDirectory } from './router'; 11import { requireFileContentsWithMetro } from '../getStaticRenderFunctions'; 12 13const debug = require('debug')('expo:server-routes') as typeof console.log; 14 15const pendingRouteOperations = new Map<string, Promise<string | null>>(); 16 17export type ApiRouteOptions = { 18 mode?: string; 19 appDir: string; 20 port?: number; 21 shouldThrow?: boolean; 22}; 23 24// Bundle the API Route with Metro and return the string contents to be evaluated in the server. 25export async function bundleApiRoute( 26 projectRoot: string, 27 filepath: string, 28 options: ApiRouteOptions 29): Promise<string | null | undefined> { 30 if (pendingRouteOperations.has(filepath)) { 31 return pendingRouteOperations.get(filepath); 32 } 33 34 const devServerUrl = `http://localhost:${options.port}`; 35 36 async function bundleAsync() { 37 try { 38 debug('Check API route:', options.appDir, filepath); 39 40 const middleware = await requireFileContentsWithMetro(projectRoot, devServerUrl, filepath, { 41 minify: options.mode === 'production', 42 dev: options.mode !== 'production', 43 // Ensure Node.js 44 environment: 'node', 45 }); 46 47 return middleware; 48 } catch (error: any) { 49 if (error instanceof Error) { 50 await logMetroErrorAsync({ error, projectRoot }); 51 } 52 if (options.shouldThrow) { 53 throw error; 54 } 55 // TODO: improve error handling, maybe have this be a mock function which returns the static error html 56 return null; 57 } finally { 58 // pendingRouteOperations.delete(filepath); 59 } 60 } 61 const route = bundleAsync(); 62 63 pendingRouteOperations.set(filepath, route); 64 return route; 65} 66 67export async function rebundleApiRoute( 68 projectRoot: string, 69 filepath: string, 70 options: ApiRouteOptions 71) { 72 pendingRouteOperations.delete(filepath); 73 return bundleApiRoute(projectRoot, filepath, options); 74} 75 76export async function exportAllApiRoutesAsync(projectRoot: string, options: ApiRouteOptions) { 77 const files: Map<string, string> = new Map(); 78 79 await Promise.all( 80 getApiRoutesForDirectory(options.appDir).map(async (filepath) => { 81 const contents = await bundleApiRoute(projectRoot, filepath, options); 82 files.set(path.relative(options.appDir, filepath.replace(/\.[tj]sx?$/, '.js')), contents!); 83 }) 84 ); 85 86 return files; 87} 88