1import { ExpoConfig, getConfig } from '@expo/config'; 2import chalk from 'chalk'; 3 4import { validateDependenciesVersionsAsync } from './doctor/dependencies/validateDependenciesVersions'; 5import { WebSupportProjectPrerequisite } from './doctor/web/WebSupportProjectPrerequisite'; 6import { startInterfaceAsync } from './interface/startInterface'; 7import { Options, resolvePortsAsync } from './resolveOptions'; 8import { BundlerStartOptions } from './server/BundlerDevServer'; 9import { DevServerManager, MultiBundlerStartOptions } from './server/DevServerManager'; 10import { openPlatformsAsync } from './server/openPlatforms'; 11import { getPlatformBundlers, PlatformBundlers } from './server/platformBundlers'; 12import * as Log from '../log'; 13import getDevClientProperties from '../utils/analytics/getDevClientProperties'; 14import { logEventAsync } from '../utils/analytics/rudderstackClient'; 15import { installExitHooks } from '../utils/exit'; 16import { isInteractive } from '../utils/interactive'; 17import { setNodeEnv } from '../utils/nodeEnv'; 18import { profile } from '../utils/profile'; 19 20async function getMultiBundlerStartOptions( 21 projectRoot: string, 22 options: Options, 23 settings: { webOnly?: boolean }, 24 platformBundlers: PlatformBundlers 25): Promise<[BundlerStartOptions, MultiBundlerStartOptions]> { 26 const commonOptions: BundlerStartOptions = { 27 mode: options.dev ? 'development' : 'production', 28 devClient: options.devClient, 29 privateKeyPath: options.privateKeyPath ?? undefined, 30 https: options.https, 31 maxWorkers: options.maxWorkers, 32 resetDevServer: options.clear, 33 minify: options.minify, 34 location: { 35 hostType: options.host, 36 scheme: options.scheme, 37 }, 38 }; 39 const multiBundlerSettings = await resolvePortsAsync(projectRoot, options, settings); 40 41 const optionalBundlers: Partial<PlatformBundlers> = { ...platformBundlers }; 42 // In the default case, we don't want to start multiple bundlers since this is 43 // a bit slower. Our priority (for legacy) is native platforms. 44 if (!options.web) { 45 delete optionalBundlers['web']; 46 } 47 48 const bundlers = [...new Set(Object.values(optionalBundlers))]; 49 const multiBundlerStartOptions = bundlers.map((bundler) => { 50 const port = 51 bundler === 'webpack' ? multiBundlerSettings.webpackPort : multiBundlerSettings.metroPort; 52 return { 53 type: bundler, 54 options: { 55 ...commonOptions, 56 port, 57 }, 58 }; 59 }); 60 61 return [commonOptions, multiBundlerStartOptions]; 62} 63 64export async function startAsync( 65 projectRoot: string, 66 options: Options, 67 settings: { webOnly?: boolean } 68) { 69 Log.log(chalk.gray(`Starting project at ${projectRoot}`)); 70 71 setNodeEnv(options.dev ? 'development' : 'production'); 72 require('@expo/env').load(projectRoot); 73 const { exp, pkg } = profile(getConfig)(projectRoot); 74 75 const platformBundlers = getPlatformBundlers(exp); 76 77 const [defaultOptions, startOptions] = await getMultiBundlerStartOptions( 78 projectRoot, 79 options, 80 settings, 81 platformBundlers 82 ); 83 84 const devServerManager = new DevServerManager(projectRoot, defaultOptions); 85 86 // Validations 87 88 if (options.web || settings.webOnly) { 89 await devServerManager.ensureProjectPrerequisiteAsync(WebSupportProjectPrerequisite); 90 } 91 92 // Start the server as soon as possible. 93 await profile(devServerManager.startAsync.bind(devServerManager))(startOptions); 94 95 if (!settings.webOnly) { 96 await devServerManager.watchEnvironmentVariables(); 97 98 // After the server starts, we can start attempting to bootstrap TypeScript. 99 await devServerManager.bootstrapTypeScriptAsync(); 100 } 101 102 if (!settings.webOnly && !options.devClient) { 103 await profile(validateDependenciesVersionsAsync)(projectRoot, exp, pkg); 104 } 105 106 // Some tracking thing 107 108 if (options.devClient) { 109 await trackAsync(projectRoot, exp); 110 } 111 112 // Open project on devices. 113 await profile(openPlatformsAsync)(devServerManager, options); 114 115 // Present the Terminal UI. 116 if (isInteractive()) { 117 await profile(startInterfaceAsync)(devServerManager, { 118 platforms: exp.platforms ?? ['ios', 'android', 'web'], 119 }); 120 } else { 121 // Display the server location in CI... 122 const url = devServerManager.getDefaultDevServer()?.getDevServerUrl(); 123 if (url) { 124 Log.log(chalk`Waiting on {underline ${url}}`); 125 } 126 } 127 128 // Final note about closing the server. 129 const logLocation = settings.webOnly ? 'in the browser console' : 'below'; 130 Log.log( 131 chalk`Logs for your project will appear ${logLocation}.${ 132 isInteractive() ? chalk.dim(` Press Ctrl+C to exit.`) : '' 133 }` 134 ); 135} 136 137async function trackAsync(projectRoot: string, exp: ExpoConfig): Promise<void> { 138 await logEventAsync('dev client start command', { 139 status: 'started', 140 ...getDevClientProperties(projectRoot, exp), 141 }); 142 installExitHooks(async () => { 143 await logEventAsync('dev client start command', { 144 status: 'finished', 145 ...getDevClientProperties(projectRoot, exp), 146 }); 147 // UnifiedAnalytics.flush(); 148 }); 149} 150