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 19export function importCliServerApiFromProject( 20 projectRoot: string 21): typeof import('@react-native-community/cli-server-api') { 22 return importFromProject(projectRoot, '@react-native-community/cli-server-api'); 23} 24 25export function importMetroSourceMapComposeSourceMapsFromProject( 26 projectRoot: string 27): typeof import('metro-source-map').composeSourceMaps { 28 return importFromProject(projectRoot, 'metro-source-map/src/composeSourceMaps'); 29} 30 31export function resolveFromProject(projectRoot: string, moduleId: string) { 32 const resolvedPath = resolveFrom.silent(projectRoot, moduleId); 33 if (!resolvedPath) { 34 throw new MetroImportError(projectRoot, moduleId); 35 } 36 return resolvedPath; 37} 38 39function importFromProject(projectRoot: string, moduleId: string) { 40 return require(resolveFromProject(projectRoot, moduleId)); 41} 42 43/** Import `metro` from the project. */ 44export function importMetroFromProject(projectRoot: string): typeof import('metro') { 45 return importFromProject(projectRoot, 'metro'); 46} 47export function importMetroServerFromProject(projectRoot: string): typeof import('metro').Server { 48 return importFromProject(projectRoot, 'metro/src/Server'); 49} 50export function importMetroCreateWebsocketServerFromProject( 51 projectRoot: string 52): typeof import('metro/src/lib/createWebsocketServer').createWebsocketServer { 53 return importFromProject(projectRoot, 'metro/src/lib/createWebsocketServer'); 54} 55export function importMetroHmrServerFromProject( 56 projectRoot: string 57): typeof import('metro/src/HmrServer').MetroHmrServer { 58 return importFromProject(projectRoot, 'metro/src/HmrServer'); 59} 60 61export function importExpoMetroConfig(projectRoot: string) { 62 return importFromProjectOrFallback<typeof import('@expo/metro-config')>( 63 projectRoot, 64 '@expo/metro-config' 65 ); 66} 67 68/** 69 * Attempt to use the local version of a module or fallback on the CLI version. 70 * This should only ever happen when testing Expo CLI in development. 71 */ 72export function importFromProjectOrFallback<TModule>( 73 projectRoot: string, 74 moduleId: string 75): TModule { 76 const resolvedPath = resolveFrom.silent(projectRoot, moduleId); 77 if (!resolvedPath) { 78 debug(`requiring "${moduleId}" relative to the CLI`); 79 return require(require.resolve(moduleId)); 80 } 81 debug(`requiring "${moduleId}" from the project:`, resolvedPath); 82 return require(resolvedPath); 83} 84 85/** Import `metro-resolver` from the project. */ 86export function importMetroResolverFromProject( 87 projectRoot: string 88): typeof import('metro-resolver') { 89 return importFromProject(projectRoot, 'metro-resolver'); 90} 91 92/** Import `metro-inspector-proxy` from the project. */ 93export function importMetroInspectorProxyFromProject( 94 projectRoot: string 95): typeof import('metro-inspector-proxy') { 96 return importFromProject(projectRoot, 'metro-inspector-proxy'); 97} 98 99/** Import `metro-inspector-proxy/src/Device` from the project. */ 100export function importMetroInspectorDeviceFromProject( 101 projectRoot: string 102): typeof import('metro-inspector-proxy/src/Device') { 103 return importFromProject(projectRoot, 'metro-inspector-proxy/src/Device'); 104} 105 106/** 107 * Import the internal `saveAssets()` function from `react-native` for the purpose 108 * of saving production assets as-is instead of converting them to a hash. 109 */ 110export function importCliSaveAssetsFromProject( 111 projectRoot: string 112): typeof import('@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets').default { 113 return importFromProject( 114 projectRoot, 115 '@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets' 116 ).default; 117} 118 119/** Resolve the installed Metro version from project */ 120export function resolveMetroVersionFromProject(projectRoot: string): string { 121 return importFromProject(projectRoot, 'metro/package.json').version; 122} 123