1import resolveFrom from 'resolve-from'; 2 3const debug = require('debug')('expo:metro:import'); 4 5// These resolvers enable us to test the CLI in older projects. 6// We may be able to get rid of this in the future. 7// TODO: Maybe combine with AsyncResolver? 8class MetroImportError extends Error { 9 constructor(projectRoot: string, moduleId: string) { 10 super( 11 `Missing package "${moduleId}" in the project at: ${projectRoot}\n` + 12 'This usually means "react-native" is not installed. ' + 13 'Please verify that dependencies in package.json include "react-native" ' + 14 'and run `yarn` or `npm install`.' 15 ); 16 } 17} 18 19function resolveFromProject(projectRoot: string, moduleId: string) { 20 const resolvedPath = resolveFrom.silent(projectRoot, moduleId); 21 if (!resolvedPath) { 22 throw new MetroImportError(projectRoot, moduleId); 23 } 24 return resolvedPath; 25} 26 27function importFromProject(projectRoot: string, moduleId: string) { 28 return require(resolveFromProject(projectRoot, moduleId)); 29} 30 31/** Import `metro` from the project. */ 32export function importMetroFromProject(projectRoot: string): typeof import('metro') { 33 return importFromProject(projectRoot, 'metro'); 34} 35export function importMetroCreateWebsocketServerFromProject( 36 projectRoot: string 37): typeof import('metro/src/lib/createWebsocketServer').createWebsocketServer { 38 return importFromProject(projectRoot, 'metro/src/lib/createWebsocketServer'); 39} 40export function importMetroHmrServerFromProject( 41 projectRoot: string 42): typeof import('metro/src/HmrServer').MetroHmrServer { 43 return importFromProject(projectRoot, 'metro/src/HmrServer'); 44} 45 46export function importExpoMetroConfig(projectRoot: string) { 47 return importFromProjectOrFallback<typeof import('@expo/metro-config')>( 48 projectRoot, 49 '@expo/metro-config' 50 ); 51} 52 53/** 54 * Attempt to use the local version of a module or fallback on the CLI version. 55 * This should only ever happen when testing Expo CLI in development. 56 */ 57export function importFromProjectOrFallback<TModule>( 58 projectRoot: string, 59 moduleId: string 60): TModule { 61 const resolvedPath = resolveFrom.silent(projectRoot, moduleId); 62 if (!resolvedPath) { 63 debug(`requiring "${moduleId}" relative to the CLI`); 64 return require(require.resolve(moduleId)); 65 } 66 debug(`requiring "${moduleId}" from the project:`, resolvedPath); 67 return require(resolvedPath); 68} 69 70/** Import `metro-resolver` from the project. */ 71export function importMetroResolverFromProject( 72 projectRoot: string 73): typeof import('metro-resolver') { 74 return importFromProject(projectRoot, 'metro-resolver'); 75} 76 77/** Import `metro-inspector-proxy` from the project. */ 78export function importMetroInspectorProxyFromProject( 79 projectRoot: string 80): typeof import('metro-inspector-proxy') { 81 return importFromProject(projectRoot, 'metro-inspector-proxy'); 82} 83 84/** Import `metro-inspector-proxy/src/Device` from the project. */ 85export function importMetroInspectorDeviceFromProject( 86 projectRoot: string 87): typeof import('metro-inspector-proxy/src/Device') { 88 return importFromProject(projectRoot, 'metro-inspector-proxy/src/Device'); 89} 90 91/** 92 * Import the internal `saveAssets()` function from `react-native` for the purpose 93 * of saving production assets as-is instead of converting them to a hash. 94 */ 95export function importCliSaveAssetsFromProject( 96 projectRoot: string 97): typeof import('@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets').default { 98 return importFromProject( 99 projectRoot, 100 '@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets' 101 ).default; 102} 103 104export function importCliBuildBundleWithConfigFromProject( 105 projectRoot: string 106): typeof import('@react-native-community/cli-plugin-metro/build/commands/bundle/buildBundle').buildBundleWithConfig { 107 return importFromProject( 108 projectRoot, 109 '@react-native-community/cli-plugin-metro/build/commands/bundle/buildBundle' 110 ).buildBundleWithConfig; 111} 112 113/** Resolve the installed Metro version from project */ 114export function resolveMetroVersionFromProject(projectRoot: string): string { 115 return importFromProject(projectRoot, 'metro/package.json').version; 116} 117