diff --git a/.changeset/fifty-ads-drive.md b/.changeset/fifty-ads-drive.md new file mode 100644 index 000000000..a21287c46 --- /dev/null +++ b/.changeset/fifty-ads-drive.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/tools-react-native": patch +--- + +Add support for loading ESM config files diff --git a/packages/tools-react-native/README.md b/packages/tools-react-native/README.md index 816fc9afb..d28a86503 100644 --- a/packages/tools-react-native/README.md +++ b/packages/tools-react-native/README.md @@ -24,17 +24,18 @@ import * as platformTools from "@rnx-kit/tools-react-native/platform"; | -------- | ------------ | ----------------------------------------- | | platform | AllPlatforms | List of supported react-native platforms. | -| Category | Function | Description | -| -------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | -| context | `loadContext(root)` | Equivalent to calling `loadConfig()` from `@react-native-community/cli`, but the result is cached for faster subsequent accesses. | -| context | `resolveCommunityCLI(root, reactNativePath)` | Finds path to `@react-native-community/cli`. | -| metro | `findMetroPath(projectRoot)` | Finds the installation path of Metro. | -| metro | `getMetroVersion(projectRoot)` | Returns Metro version number. | -| metro | `requireModuleFromMetro(moduleName, fromDir)` | Imports specified module starting from the installation directory of the currently used `metro` version. | -| platform | `expandPlatformExtensions(platform, extensions)` | Returns a list of extensions that should be tried for the target platform in prioritized order. | -| platform | `getAvailablePlatforms(startDir)` | Returns a map of available React Native platforms. The result is cached. | -| platform | `getAvailablePlatformsUncached(startDir, platformMap)` | Returns a map of available React Native platforms. The result is NOT cached. | -| platform | `parsePlatform(val)` | Parse a string to ensure it maps to a valid react-native platform. | -| platform | `platformExtensions(platform)` | Returns file extensions that can be mapped to the target platform. | +| Category | Function | Description | +| -------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| context | `loadContext(projectRoot)` | Equivalent to calling `loadConfig()` from `@react-native-community/cli`, but the result is cached for faster subsequent accesses. | +| context | `loadContextAsync(projectRoot)` | Equivalent to calling `loadConfigAsync()` (with fallback to `loadConfig()`) from `@react-native-community/cli`, but the result is cached for faster subsequent accesses. | +| context | `resolveCommunityCLI(root, reactNativePath)` | Finds path to `@react-native-community/cli`. | +| metro | `findMetroPath(projectRoot)` | Finds the installation path of Metro. | +| metro | `getMetroVersion(projectRoot)` | Returns Metro version number. | +| metro | `requireModuleFromMetro(moduleName, fromDir)` | Imports specified module starting from the installation directory of the currently used `metro` version. | +| platform | `expandPlatformExtensions(platform, extensions)` | Returns a list of extensions that should be tried for the target platform in prioritized order. | +| platform | `getAvailablePlatforms(startDir)` | Returns a map of available React Native platforms. The result is cached. | +| platform | `getAvailablePlatformsUncached(startDir, platformMap)` | Returns a map of available React Native platforms. The result is NOT cached. | +| platform | `parsePlatform(val)` | Parse a string to ensure it maps to a valid react-native platform. | +| platform | `platformExtensions(platform)` | Returns file extensions that can be mapped to the target platform. | diff --git a/packages/tools-react-native/src/context.ts b/packages/tools-react-native/src/context.ts index c72cd1a9c..d34be3b8d 100644 --- a/packages/tools-react-native/src/context.ts +++ b/packages/tools-react-native/src/context.ts @@ -1,3 +1,4 @@ +import type { loadConfig } from "@react-native-community/cli"; import type { Config } from "@react-native-community/cli-types"; import { findPackageDependencyDir, @@ -34,6 +35,24 @@ function findStartDir(root: string, reactNativePath = ""): string { return toNumber(version) < RN_CLI_DECOUPLED ? reactNative : root; } +function getConfigOrState(projectRoot: string): Config | string { + const state = getCurrentState(projectRoot); + if (state === getSavedState(projectRoot)) { + const config = loadConfigFromCache(projectRoot); + if (config) { + return config; + } + } + + return state; +} + +function makeLoadConfigOptions(fn: typeof loadConfig, projectRoot: string) { + return fn.length === 1 + ? { projectRoot } + : (projectRoot as unknown as { projectRoot: string }); +} + /** * Finds path to `@react-native-community/cli`. * @param root Project root @@ -50,26 +69,49 @@ export function resolveCommunityCLI( /** * Equivalent to calling `loadConfig()` from `@react-native-community/cli`, but * the result is cached for faster subsequent accesses. - * @param root Project root; defaults to current working directory + * @param projectRoot Project root; defaults to current working directory */ -export function loadContext(root = process.cwd()): Config { - const state = getCurrentState(root); - if (state === getSavedState(root)) { - const config = loadConfigFromCache(root); - if (config) { - return config; - } +export function loadContext(projectRoot = process.cwd()): Config { + const state = getConfigOrState(projectRoot); + if (typeof state !== "string") { + return state; } - const rncli = resolveCommunityCLI(root); + const rncli = resolveCommunityCLI(projectRoot); const { loadConfig } = require(rncli); - const config: Config = - loadConfig.length === 1 - ? loadConfig({ projectRoot: root }) - : loadConfig(root); - - saveConfigToCache(root, state, config); - + const options = makeLoadConfigOptions(loadConfig, projectRoot); + const config = loadConfig(options); + saveConfigToCache(projectRoot, state, config); + return config; +} + +/** + * Equivalent to calling `loadConfigAsync()` (with fallback to `loadConfig()`) + * from `@react-native-community/cli`, but the result is cached for faster + * subsequent accesses. + * @param projectRoot Project root; defaults to current working directory + */ +export async function loadContextAsync( + projectRoot = process.cwd() +): Promise { + const state = getConfigOrState(projectRoot); + if (typeof state !== "string") { + return state; + } + + const rncli = resolveCommunityCLI(projectRoot); + const { loadConfig, loadConfigAsync } = require(rncli); + + const options = makeLoadConfigOptions(loadConfig, projectRoot); + + if (!loadConfigAsync) { + const config = loadConfig(options); + saveConfigToCache(projectRoot, state, config); + return config; + } + + const config = await loadConfigAsync(options); + saveConfigToCache(projectRoot, state, config); return config; } diff --git a/packages/tools-react-native/src/index.ts b/packages/tools-react-native/src/index.ts index 88b5bfff5..986fc8bd6 100644 --- a/packages/tools-react-native/src/index.ts +++ b/packages/tools-react-native/src/index.ts @@ -1,4 +1,4 @@ -export { loadContext, resolveCommunityCLI } from "./context"; +export { loadContext, loadContextAsync, resolveCommunityCLI } from "./context"; export { findMetroPath, getMetroVersion,