UNPKG

expo-modules-autolinking

Version:
206 lines (190 loc) 7.52 kB
import fs from 'fs'; import type { PackageRevision, SupportedPlatform } from '../types'; import { scanDependenciesRecursively } from './resolution'; import { scanDependenciesFromRNProjectConfig } from './rncliLocal'; import { scanDependenciesInSearchPath } from './scanning'; import { type DependencyResolution, type ResolutionResult, DependencyResolutionSource, } from './types'; import { filterMapResolutionResult, mergeResolutionResults } from './utils'; import { resolveExpoModule } from '../autolinking/findModules'; import type { AutolinkingOptions } from '../commands/autolinkingOptions'; import { createAutolinkingOptionsLoader } from '../commands/autolinkingOptions'; import { createMemoizer, type Memoizer } from '../memoize'; import type { RNConfigReactNativeProjectConfig } from '../reactNativeConfig'; import { resolveReactNativeModule } from '../reactNativeConfig'; import { loadConfigAsync } from '../reactNativeConfig/config'; export interface CachedDependenciesSearchOptions { includeNames: Set<string>; excludeNames: Set<string>; searchPaths: string[]; } export interface CachedDependenciesLinker { memoizer: Memoizer; getOptionsForPlatform( platform: SupportedPlatform, extraInclude?: string[] ): Promise<CachedDependenciesSearchOptions>; loadReactNativeProjectConfig(): Promise<RNConfigReactNativeProjectConfig | null>; scanDependenciesFromRNProjectConfig(): Promise<ResolutionResult>; scanDependenciesRecursively(): Promise<ResolutionResult>; scanDependenciesInSearchPath(searchPath: string): Promise<ResolutionResult>; } export function makeCachedDependenciesLinker(params: { projectRoot: string; }): CachedDependenciesLinker { const memoizer = createMemoizer(); const autolinkingOptionsLoader = createAutolinkingOptionsLoader({ projectRoot: params.projectRoot, }); let appRoot: Promise<string> | undefined; const getAppRoot = () => appRoot || (appRoot = autolinkingOptionsLoader.getAppRoot()); const dependenciesResultBySearchPath = new Map<string, Promise<ResolutionResult>>(); let reactNativeProjectConfig: Promise<RNConfigReactNativeProjectConfig | null> | undefined; let reactNativeProjectConfigDependencies: Promise<ResolutionResult> | undefined; let recursiveDependencies: Promise<ResolutionResult> | undefined; return { memoizer, async getOptionsForPlatform(platform, extraInclude) { const options = await autolinkingOptionsLoader.getPlatformOptions(platform); return makeCachedDependenciesSearchOptions(options, extraInclude); }, async loadReactNativeProjectConfig() { if (reactNativeProjectConfig === undefined) { reactNativeProjectConfig = memoizer.call( loadConfigAsync, await getAppRoot() ) as Promise<RNConfigReactNativeProjectConfig | null>; } return reactNativeProjectConfig; }, async scanDependenciesFromRNProjectConfig() { if (reactNativeProjectConfigDependencies === undefined) { reactNativeProjectConfigDependencies = memoizer.withMemoizer(async () => { return await scanDependenciesFromRNProjectConfig( await getAppRoot(), await this.loadReactNativeProjectConfig() ); }); } return reactNativeProjectConfigDependencies; }, async scanDependenciesRecursively() { if (recursiveDependencies === undefined) { recursiveDependencies = memoizer.withMemoizer(async () => { return scanDependenciesRecursively(await getAppRoot()); }); } return recursiveDependencies; }, async scanDependenciesInSearchPath(searchPath: string) { let result = dependenciesResultBySearchPath.get(searchPath); if (!result) { dependenciesResultBySearchPath.set( searchPath, (result = memoizer.withMemoizer(scanDependenciesInSearchPath, searchPath)) ); } return result; }, }; } export async function isNativeModuleAsync( resolution: DependencyResolution, reactNativeProjectConfig: RNConfigReactNativeProjectConfig | null, platform: SupportedPlatform, excludeNames: Set<string> ) { const [reactNativeModule, expoModule] = await Promise.all([ resolveReactNativeModule(resolution, reactNativeProjectConfig, platform, excludeNames), resolveExpoModule(resolution, platform, excludeNames), ]); return !!reactNativeModule || !!expoModule; } export async function scanDependencyResolutionsForPlatform( linker: CachedDependenciesLinker, platform: SupportedPlatform, extraInclude?: string[] ): Promise<ResolutionResult> { const opts = await linker.getOptionsForPlatform(platform, extraInclude); const reactNativeProjectConfig = await linker.loadReactNativeProjectConfig(); const resolutions = mergeResolutionResults( await Promise.all([ linker.scanDependenciesFromRNProjectConfig(), ...opts.searchPaths.map((searchPath) => { return linker.scanDependenciesInSearchPath(searchPath); }), linker.scanDependenciesRecursively(), ]) ); return await linker.memoizer.withMemoizer(async () => { const dependencies = await filterMapResolutionResult(resolutions, async (resolution) => { if (opts.excludeNames.has(resolution.name)) { return null; } else if (opts.includeNames.has(resolution.name)) { return resolution; } else if (resolution.source === DependencyResolutionSource.RN_CLI_LOCAL) { // If the dependency was resolved frpom the React Native project config, we'll only // attempt to resolve it as a React Native module const reactNativeModuleDesc = await resolveReactNativeModule( resolution, reactNativeProjectConfig, platform, opts.excludeNames ); if (!reactNativeModuleDesc) { return null; } } else { const isNativeModule = await isNativeModuleAsync( resolution, reactNativeProjectConfig, platform, opts.excludeNames ); if (!isNativeModule) { return null; } } return resolution; }); return dependencies; }); } export async function scanExpoModuleResolutionsForPlatform( linker: CachedDependenciesLinker, platform: SupportedPlatform, extraInclude?: string[] ): Promise<Record<string, PackageRevision>> { const { excludeNames, searchPaths } = await linker.getOptionsForPlatform(platform, extraInclude); const resolutions = mergeResolutionResults( await Promise.all( [ ...searchPaths.map((searchPath) => { return linker.scanDependenciesInSearchPath(searchPath); }), linker.scanDependenciesRecursively(), ].filter((x) => x != null) ) ); return await linker.memoizer.withMemoizer(async () => { return await filterMapResolutionResult(resolutions, async (resolution) => { return !excludeNames.has(resolution.name) ? await resolveExpoModule(resolution, platform, excludeNames) : null; }); }); } const makeCachedDependenciesSearchOptions = ( options: AutolinkingOptions, extraInclude?: string[] ) => ({ excludeNames: new Set(options.exclude), includeNames: new Set(extraInclude ? [...options.include, ...extraInclude] : options.include), searchPaths: options.nativeModulesDir && fs.existsSync(options.nativeModulesDir) ? [options.nativeModulesDir, ...(options.searchPaths ?? [])] : (options.searchPaths ?? []), });