fix: ensure correct metro packages are used (#2730)

This commit is contained in:
Tommy Nguyen 2023-10-13 08:51:42 +02:00 коммит произвёл GitHub
Родитель c94377564b
Коммит 2885f73cca
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
48 изменённых файлов: 314 добавлений и 212 удалений

Просмотреть файл

@ -0,0 +1,11 @@
---
"@rnx-kit/cli": patch
"@rnx-kit/metro-config": patch
"@rnx-kit/metro-resolver-symlinks": patch
"@rnx-kit/metro-serializer-esbuild": patch
"@rnx-kit/metro-serializer": patch
"@rnx-kit/metro-service": patch
"@rnx-kit/tools-react-native": patch
---
Ensure correct Metro dependencies are used by traversing the dependency chain starting from `react-native`

Просмотреть файл

@ -1,9 +1,10 @@
import type { Config } from "@react-native-community/cli-types";
import * as logger from "@rnx-kit/console";
import {
createTerminal,
isDevServerRunning,
loadMetroConfig,
makeReporter,
makeTerminal,
startServer,
} from "@rnx-kit/metro-service";
import type { ReportableEvent, Reporter, RunServerOptions } from "metro";
@ -65,20 +66,14 @@ export async function rnxStart(
: undefined),
});
// create a Metro terminal and reporter for writing to the console
const { terminal, reporter: terminalReporter } = createTerminal(
args.customLogReporterPath
);
const { projectRoot, watchFolders } = metroConfig;
const terminal = makeTerminal(projectRoot);
// customize the metro config to include plugins, presets, etc.
const log = (message: string): void => terminal.log(message);
customizeMetroConfig(metroConfig, serverConfig, log);
const {
projectRoot,
server: { port },
watchFolders,
} = metroConfig;
const { port } = metroConfig.server;
const scheme = args.https === true ? "https" : "http";
const serverStatus = await isDevServerRunning(
scheme,
@ -160,6 +155,11 @@ export async function rnxStart(
}
const help = makeHelp(terminal, { hasDebugger: Boolean(coreDevMiddleware) });
const terminalReporter = makeReporter(
args.customLogReporterPath,
terminal,
projectRoot
);
// @ts-expect-error We want to override `reporter`
metroConfig.reporter = {

Просмотреть файл

@ -28,18 +28,12 @@
},
"peerDependencies": {
"@react-native/metro-config": "*",
"metro-config": ">=0.58.0",
"metro-react-native-babel-preset": "*",
"metro-resolver": ">=0.58.0",
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"@react-native/metro-config": {
"optional": true
},
"metro-resolver": {
"optional": true
}
},
"devDependencies": {
@ -53,7 +47,6 @@
"jest": "^29.2.1",
"metro": "^0.76.5",
"metro-config": "^0.76.5",
"metro-react-native-babel-preset": "^0.76.5",
"prettier": "^3.0.0",
"react-native": "^0.72.0",
"type-fest": "^4.0.0",

Просмотреть файл

@ -1,4 +1,5 @@
// @ts-check
const { requireModuleFromMetro } = require("@rnx-kit/tools-react-native/metro");
const fs = require("fs");
const path = require("path");
@ -58,12 +59,16 @@ function redirectToPlatform(moduleName, implementation) {
/**
* @param {PlatformImplementations} implementations
* @param {string} projectRoot
*/
function outOfTreePlatformResolver(implementations) {
function outOfTreePlatformResolver(implementations, projectRoot) {
const { resolve: metroResolver } = requireModuleFromMetro(
"metro-resolver",
projectRoot
);
/** @type {(context: ResolutionContext, moduleName: string, platform: string) => Resolution} */
const platformResolver = (context, moduleName, platform) => {
const { resolve: metroResolver } = require("metro-resolver");
/** @type {CustomResolver} */
let resolve = metroResolver;
const resolveRequest = context.resolveRequest;
@ -111,8 +116,10 @@ function getDefaultConfig(projectRoot) {
const availablePlatforms = getAvailablePlatforms(projectRoot);
defaultConfig.resolver.platforms = Object.keys(availablePlatforms);
defaultConfig.resolver.resolveRequest =
outOfTreePlatformResolver(availablePlatforms);
defaultConfig.resolver.resolveRequest = outOfTreePlatformResolver(
availablePlatforms,
projectRoot
);
const preludeModules = getPreludeModules(availablePlatforms, projectRoot);
defaultConfig.serializer.getModulesRunBeforeMainModule = () => {

Просмотреть файл

@ -1,6 +1,10 @@
/* jshint esversion: 8, node: true */
// @ts-check
const {
findMetroPath,
requireModuleFromMetro,
} = require("@rnx-kit/tools-react-native/metro");
const fs = require("fs");
const path = require("path");
@ -159,13 +163,15 @@ function excludeExtraCopiesOf(packageName, searchStartDir) {
function exclusionList(additionalExclusions = [], projectRoot = process.cwd()) {
/** @type {(additionalExclusions: (string | RegExp)[]) => RegExp} */
const exclusionList = (() => {
const metroConfigDir = resolveModule(
"metro-config",
findMetroPath(projectRoot)
);
try {
// @ts-expect-error There are no type definition files for `metro-config`
return require("metro-config/src/defaults/exclusionList");
return require(`${metroConfigDir}/src/defaults/exclusionList`);
} catch (_) {
// `blacklist` was renamed to `exclusionList` in 0.60
// @ts-expect-error There are no type definition files for `metro-config`
return require("metro-config/src/defaults/blacklist");
return require(`${metroConfigDir}/src/defaults/blacklist`);
}
})();
@ -196,11 +202,12 @@ module.exports = {
* @returns {MetroConfig}
*/
makeMetroConfig: (customConfig = {}) => {
const { mergeConfig } = require("metro-config");
const projectRoot = customConfig.projectRoot || process.cwd();
const { mergeConfig } = requireModuleFromMetro("metro-config", projectRoot);
const { enhanceMiddleware } = require("./assetPluginForMonorepos");
const { getDefaultConfig } = require("./defaultConfig");
const projectRoot = customConfig.projectRoot || process.cwd();
const blockList = exclusionList([], projectRoot);
const customBlockList =
customConfig.resolver &&

Просмотреть файл

@ -1,5 +1,4 @@
import { findPackageDependencyDir } from "@rnx-kit/tools-node/package";
import { findMetroPath } from "@rnx-kit/tools-react-native/metro";
export function resolveFrom(
moduleName: string,
@ -10,34 +9,3 @@ export function resolveFrom(
resolveSymlinks: true,
});
}
/**
* Imports specified module starting from the installation directory of the
* currently used `metro` version.
*
* @todo Currently not possible to do `R = type import(Module)`:
* https://github.com/microsoft/TypeScript/issues/44636
*/
export function requireModuleFromMetro<R = unknown>(
moduleName: string,
fromDir = process.cwd()
): R {
const startDir = findMetroPath(fromDir);
if (!startDir) {
throw new Error("Cannot find module 'metro'");
}
const modulePath = resolveFrom(moduleName, startDir);
if (!modulePath) {
throw new Error(
`Cannot find module '${moduleName}'. This probably means that ` +
"'@rnx-kit/metro-resolver-symlinks' is not compatible with the " +
"version of 'metro' that you are currently using. Please update to " +
"the latest version and try again. If the issue still persists after " +
"the update, please file a bug at " +
"https://github.com/microsoft/rnx-kit/issues."
);
}
return require(modulePath);
}

Просмотреть файл

@ -1,5 +1,5 @@
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
import type { CustomResolver } from "metro-resolver";
import { requireModuleFromMetro } from "./helper";
import { applyMetroResolver, remapReactNativeModule } from "./resolver";
import type { MetroResolver, Options, ResolutionContextCompat } from "./types";
import { applyEnhancedResolver } from "./utils/enhancedResolve";
@ -10,8 +10,7 @@ import {
import { remapImportPath } from "./utils/remapImportPath";
export function makeResolver(options: Options = {}): MetroResolver {
const { resolve: metroResolver } =
requireModuleFromMetro<typeof import("metro-resolver")>("metro-resolver");
const { resolve: metroResolver } = requireModuleFromMetro("metro-resolver");
const { remapModule = (_, moduleName, __) => moduleName } = options;

Просмотреть файл

@ -1,6 +1,4 @@
import * as os from "node:os";
import * as path from "node:path";
import { requireModuleFromMetro } from "../src/helper";
import { remapReactNativeModule, resolveModulePath } from "../src/resolver";
import { useFixture } from "./fixtures";
@ -10,54 +8,6 @@ const AVAILABLE_PLATFORMS = {
windows: "react-native-windows",
};
const nixOnlyTest = os.platform() === "win32" ? test.skip : test;
describe("requireModuleFromMetro", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const context: any = {};
function getMetroResolver(fromDir: string) {
return requireModuleFromMetro<typeof import("metro-resolver")>(
"metro-resolver",
fromDir
).resolve;
}
test("returns `metro-resolver` installed by `react-native`", () => {
const p = useFixture("metro-resolver-duplicates");
expect(getMetroResolver(p)(context, "", null)).toEqual(
expect.stringContaining(
path.join(
"metro-resolver-duplicates",
"node_modules",
"@react-native-community",
"cli",
"node_modules",
"metro-resolver"
)
)
);
});
// The symlinks under `pnpm` don't work on Windows
nixOnlyTest("returns `metro-resolver` from a central storage", () => {
const p = useFixture("pnpm");
expect(getMetroResolver(p)(context, "", null)).toEqual(
expect.stringContaining(
path.join("pnpm", "node_modules", ".pnpm", "metro-resolver")
)
);
});
test("throws if `metro-resolver` cannot be found", () => {
const cwd = process.cwd();
const root = cwd.substring(0, cwd.indexOf(path.sep) + 1);
expect(() => getMetroResolver(root)(context, "", null)).toThrowError(
"Cannot find module"
);
});
});
describe("remapReactNativeModule", () => {
const context = {
originModulePath: "",

Просмотреть файл

@ -1,7 +1,10 @@
import { info, warn } from "@rnx-kit/console";
import type { MetroPlugin } from "@rnx-kit/metro-serializer";
import { findPackage, readPackage } from "@rnx-kit/tools-node/package";
import { getMetroVersion } from "@rnx-kit/tools-react-native/metro";
import {
findMetroPath,
getMetroVersion,
} from "@rnx-kit/tools-react-native/metro";
import type { BuildOptions, BuildResult, Plugin } from "esbuild";
import * as esbuild from "esbuild";
import * as fs from "fs";
@ -172,8 +175,11 @@ export function MetroSerializer(
// Signal to every plugin that we're using esbuild.
process.env["RNX_METRO_SERIALIZER_ESBUILD"] = "true";
const baseJSBundle = require("metro/src/DeltaBundler/Serializers/baseJSBundle");
const bundleToString = require("metro/src/lib/bundleToString");
const metroPath = findMetroPath() || "metro";
const baseJSBundle = require(
`${metroPath}/src/DeltaBundler/Serializers/baseJSBundle`
);
const bundleToString = require(`${metroPath}/src/lib/bundleToString`);
return (entryPoint, preModules, graph, options) => {
metroPlugins.forEach((plugin) =>

Просмотреть файл

@ -1,6 +1,5 @@
import { findMetroPath } from "@rnx-kit/tools-react-native/metro";
import type { Module } from "metro";
// @ts-expect-error No declaration file for module
import sourceMapString from "metro/src/DeltaBundler/Serializers/sourceMapString";
import * as path from "path";
const sourceMappingOptions = {
@ -26,6 +25,10 @@ export function absolutizeSourceMap(outputPath: string, text: string): string {
}
export function getInlineSourceMappingURL(modules: readonly Module[]): string {
const metroPath = findMetroPath() || "metro";
const sourceMapString = require(
`${metroPath}/src/DeltaBundler/Serializers/sourceMapString`
);
const sourceMap = sourceMapString(modules, sourceMappingOptions);
const base64 = Buffer.from(sourceMap).toString("base64");
return `data:application/json;charset=utf-8;base64,${base64}`;

Просмотреть файл

@ -21,6 +21,7 @@
"test": "rnx-kit-scripts test"
},
"dependencies": {
"@rnx-kit/tools-react-native": "^1.3.3",
"semver": "^7.0.0"
},
"devDependencies": {

Просмотреть файл

@ -1,3 +1,4 @@
import { findMetroPath } from "@rnx-kit/tools-react-native/metro";
import type {
MixedOutput,
Module,
@ -31,8 +32,11 @@ export type CustomSerializer = (
* @see https://github.com/facebook/metro/blob/af23a1b27bcaaff2e43cb795744b003e145e78dd/packages/metro/src/Server.js#L228
*/
export function MetroSerializer(plugins: MetroPlugin[]): CustomSerializer {
const baseJSBundle = require("metro/src/DeltaBundler/Serializers/baseJSBundle");
const bundleToString = require("metro/src/lib/bundleToString");
const metroPath = findMetroPath() || "metro";
const baseJSBundle = require(
`${metroPath}/src/DeltaBundler/Serializers/baseJSBundle`
);
const bundleToString = require(`${metroPath}/src/lib/bundleToString`);
const { version } = require("metro/package.json");
const shouldReturnPromise = semver.satisfies(version, ">=0.60.0");

Просмотреть файл

@ -26,30 +26,24 @@
},
"dependencies": {
"@rnx-kit/console": "^1.0.0",
"@rnx-kit/tools-language": "^2.0.0",
"@rnx-kit/tools-node": "^2.1.0",
"@rnx-kit/tools-react-native": "^1.3.3",
"chalk": "^4.1.0",
"node-fetch": "^2.6.7"
},
"peerDependencies": {
"@office-iss/react-native-win32": "*",
"@react-native-community/cli-plugin-metro": "*",
"@react-native/metro-config": "*",
"metro": ">=0.66.1",
"metro-config": ">=0.66.1",
"metro-core": ">=0.66.1",
"metro-react-native-babel-transformer": ">=0.66.1",
"metro-resolver": ">=0.66.1",
"metro-runtime": ">=0.66.1"
"metro-react-native-babel-transformer": ">=0.66.1"
},
"peerDependenciesMeta": {
"@office-iss/react-native-win32": {
"optional": true
},
"@react-native-community/cli-plugin-metro": {
"@react-native/metro-config": {
"optional": true
},
"@react-native/metro-config": {
"metro-react-native-babel-transformer": {
"optional": true
}
},

Просмотреть файл

@ -1,13 +1,12 @@
// https://github.com/react-native-community/cli/blob/716555851b442a83a1bf5e0db27b6226318c9a69/packages/cli-plugin-metro/src/commands/bundle/buildBundle.ts
import { error, info } from "@rnx-kit/console";
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
import chalk from "chalk";
import fs from "fs";
import * as fs from "fs";
import type { ConfigT } from "metro-config";
import Server from "metro/src/Server";
import Bundle from "metro/src/shared/output/bundle";
import type { BundleOptions, OutputOptions } from "metro/src/shared/types";
import path from "path";
import * as path from "path";
import { saveAssets } from "./asset";
import { saveAssetsAndroid } from "./asset/android";
import { saveAssetsDefault } from "./asset/default";
@ -76,7 +75,10 @@ function getSaveAssetsPlugin(
export async function bundle(
args: BundleArgs,
config: ConfigT,
output = Bundle
output = requireModuleFromMetro(
"metro/src/shared/output/bundle",
config.projectRoot
)
): Promise<void> {
// ensure Metro can find Babel config
ensureBabelConfig(config);
@ -113,6 +115,8 @@ export async function bundle(
sourceMapUrl = path.basename(sourceMapUrl);
}
const Server = requireModuleFromMetro("metro/src/Server", config.projectRoot);
const requestOpts: RequestOptions = {
entryFile: args.entryFile,
sourceMapUrl,

Просмотреть файл

@ -1,14 +1,14 @@
import type { Config as CLIConfig } from "@react-native-community/cli-types";
import { resolveDependencyChain } from "@rnx-kit/tools-node/package";
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
import type { ConfigT, InputConfigT } from "metro-config";
import { loadConfig } from "metro-config";
import type {
CustomResolver,
Resolution,
ResolutionContext,
} from "metro-resolver";
import { resolve as metroResolver } from "metro-resolver";
import * as path from "path";
import { requireMetroPath } from "./metro";
export type MetroConfigOverrides = {
config?: string;
@ -35,8 +35,14 @@ const INTERNAL_CALLSITES_REGEX = new RegExp(
);
function reactNativePlatformResolver(
platformImplementations: Record<string, string>
platformImplementations: Record<string, string>,
projectRoot: string
) {
const { resolve: metroResolver } = requireModuleFromMetro(
"metro-resolver",
projectRoot
);
const platformResolver = (
context: ResolutionContext,
moduleName: string,
@ -75,19 +81,29 @@ function reactNativePlatformResolver(
return platformResolver;
}
function getAsyncRequireModulePath(): string | undefined {
function getAsyncRequireModulePath(projectRoot: string): string | undefined {
const paths = { paths: [requireMetroPath(projectRoot)] };
try {
// `metro-runtime` was introduced in 0.63
return require.resolve("metro-runtime/src/modules/asyncRequire");
return require.resolve("metro-runtime/src/modules/asyncRequire", paths);
} catch (_) {
return require.resolve("metro/src/lib/bundle-modules/asyncRequire");
return require.resolve("metro/src/lib/bundle-modules/asyncRequire", paths);
}
}
function getDefaultConfigInternal(cliConfig: CLIConfig): InputConfigT {
const outOfTreePlatforms = Object.keys(cliConfig.platforms).filter(
(platform) => cliConfig.platforms[platform].npmPackageName
);
function getDefaultConfigInternal({
root,
platforms,
reactNativePath,
}: CLIConfig): InputConfigT {
const options = { paths: [root] };
const outOfTreePlatforms: [string, string][] = [];
for (const [platform, { npmPackageName }] of Object.entries(platforms)) {
if (npmPackageName) {
outOfTreePlatforms.push([platform, npmPackageName]);
}
}
// Create and return an incomplete InputConfigT. It is used as an override
// to Metro's default configuration. This has to be force-cast because
@ -99,37 +115,28 @@ function getDefaultConfigInternal(cliConfig: CLIConfig): InputConfigT {
outOfTreePlatforms.length === 0
? undefined
: reactNativePlatformResolver(
outOfTreePlatforms.reduce<Record<string, string>>(
(result, platform) => {
result[platform] =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
cliConfig.platforms[platform].npmPackageName!;
return result;
},
{}
)
Object.fromEntries(outOfTreePlatforms),
root
),
resolverMainFields: ["react-native", "browser", "main"],
platforms: [...Object.keys(cliConfig.platforms), "native"],
platforms: [...Object.keys(platforms), "native"],
},
serializer: {
// We can include multiple copies of InitializeCore here because metro will
// only add ones that are already part of the bundle
getModulesRunBeforeMainModule: () => [
require.resolve(
path.join(cliConfig.reactNativePath, "Libraries/Core/InitializeCore")
path.join(reactNativePath, "Libraries/Core/InitializeCore")
),
...outOfTreePlatforms.map((platform) =>
...outOfTreePlatforms.map(([, npmPackageName]) =>
require.resolve(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
`${cliConfig.platforms[platform]
.npmPackageName!}/Libraries/Core/InitializeCore`,
{ paths: [cliConfig.root] }
`${npmPackageName}/Libraries/Core/InitializeCore`,
options
)
),
],
getPolyfills: () =>
require(path.join(cliConfig.reactNativePath, "rn-get-polyfills"))(),
require(path.join(reactNativePath, "rn-get-polyfills"))(),
},
server: {
port: Number(process.env.RCT_METRO_PORT) || 8081,
@ -145,10 +152,11 @@ function getDefaultConfigInternal(cliConfig: CLIConfig): InputConfigT {
transformer: {
allowOptionalDependencies: true,
babelTransformerPath: require.resolve(
"metro-react-native-babel-transformer"
"metro-react-native-babel-transformer",
options
),
assetRegistryPath: "react-native/Libraries/Image/AssetRegistry",
asyncRequireModulePath: getAsyncRequireModulePath(),
asyncRequireModulePath: getAsyncRequireModulePath(root),
},
watchFolders: [],
};
@ -210,5 +218,6 @@ export function loadMetroConfig(
defaultConfig.transformer.assetPlugins = assetPlugins;
}
const { loadConfig } = requireModuleFromMetro("metro-config", cliConfig.root);
return loadConfig({ cwd: cliConfig.root, ...overrides }, defaultConfig);
}

Просмотреть файл

@ -4,5 +4,5 @@ export { loadMetroConfig } from "./config";
export type { MetroConfigOverrides } from "./config";
export { ramBundle } from "./ramBundle";
export { isDevServerRunning, startServer } from "./server";
export { createTerminal } from "./terminal";
export { makeReporter, makeTerminal } from "./terminal";
export type { MetroTerminal } from "./terminal";

Просмотреть файл

@ -0,0 +1,9 @@
import { findMetroPath } from "@rnx-kit/tools-react-native/metro";
export function requireMetroPath(projectRoot: string): string {
const p = findMetroPath(projectRoot);
if (!p) {
throw new Error("Cannot find module 'metro'");
}
return p;
}

Просмотреть файл

@ -1,9 +1,16 @@
import type { ConfigT } from "metro-config";
// @ts-expect-error no declaration file for module
import RamBundle from "metro/src/shared/output/RamBundle";
import * as path from "path";
import type { BundleArgs } from "./bundle";
import { bundle } from "./bundle";
import { requireMetroPath } from "./metro";
export function ramBundle(args: BundleArgs, config: ConfigT): Promise<void> {
return bundle(args, config, RamBundle);
const ramBundlePath = path.join(
requireMetroPath(config.projectRoot),
"src",
"shared",
"output",
"RamBundle"
);
return bundle(args, config, require(ramBundlePath));
}

Просмотреть файл

@ -1,4 +1,5 @@
import { runServer } from "metro";
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
import type { runServer } from "metro";
import net from "net";
import nodeFetch from "node-fetch";
import { ensureBabelConfig } from "./babel";
@ -65,5 +66,7 @@ export async function isDevServerRunning(
export const startServer: typeof runServer = (config, ...args) => {
ensureBabelConfig(config);
const { runServer } = requireModuleFromMetro("metro", config.projectRoot);
return runServer(config, ...args);
};

Просмотреть файл

@ -1,38 +1,34 @@
import { hasProperty } from "@rnx-kit/tools-language/properties";
import { Terminal } from "metro-core";
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
import type { Terminal } from "metro-core";
import type { TerminalReporter } from "metro/src/lib/TerminalReporter";
import path from "path";
export type MetroTerminal = {
terminal: Terminal;
reporter: TerminalReporter;
};
function getTerminalReporterClass(
customReporterPath: string | undefined
function getReporter(
customReporterPath: string | undefined,
projectRoot: string
): typeof TerminalReporter {
if (customReporterPath === undefined) {
return require("metro/src/lib/TerminalReporter");
}
try {
// First we let require resolve it, so we can require packages in node_modules
// as expected. eg: require('my-package/reporter');
return require(customReporterPath);
} catch (e) {
if (!hasProperty(e, "code") || e.code !== "MODULE_NOT_FOUND") {
throw e;
}
// If that doesn't work, then we next try relative to the cwd, eg:
// require('./reporter');
return require(path.resolve(customReporterPath));
if (customReporterPath) {
const p = require.resolve(customReporterPath, { paths: [projectRoot] });
return require(p);
}
return requireModuleFromMetro("metro/src/lib/TerminalReporter", projectRoot);
}
export function createTerminal(customReporterPath?: string): MetroTerminal {
const terminal = new Terminal(process.stdout);
const TerminalReporterClass = getTerminalReporterClass(customReporterPath);
const reporter = new TerminalReporterClass(terminal);
return { terminal, reporter };
export function makeReporter(
customReporterPath: string | undefined,
terminal: Terminal,
projectRoot: string
): TerminalReporter {
const Reporter = getReporter(customReporterPath, projectRoot);
return new Reporter(terminal);
}
export function makeTerminal(projectRoot: string): Terminal {
const { Terminal } = requireModuleFromMetro("metro-core", projectRoot);
return new Terminal(process.stdout);
}

6
packages/tools-react-native/metro.d.ts поставляемый
Просмотреть файл

@ -1 +1,5 @@
export { findMetroPath, getMetroVersion } from "./lib/metro";
export {
findMetroPath,
getMetroVersion,
requireModuleFromMetro,
} from "./lib/metro";

Просмотреть файл

@ -3,6 +3,24 @@ import {
readPackage,
} from "@rnx-kit/tools-node/package";
type MetroImport =
| typeof import("metro")
| typeof import("metro/src/Server").default
| typeof import("metro/src/lib/TerminalReporter").TerminalReporter
| typeof import("metro/src/shared/output/bundle")
| typeof import("metro-config")
| typeof import("metro-core")
| typeof import("metro-resolver");
type MetroModule =
| "metro"
| "metro/src/Server"
| "metro/src/lib/TerminalReporter"
| "metro/src/shared/output/bundle"
| "metro-config"
| "metro-core"
| "metro-resolver";
function resolveFrom(name: string, startDir: string): string | undefined {
return findPackageDependencyDir(name, {
startDir,
@ -56,3 +74,69 @@ export function getMetroVersion(
const { version } = readPackage(metroPath);
return version;
}
export function requireModuleFromMetro(
moduleName: "metro",
fromDir?: string
): typeof import("metro");
export function requireModuleFromMetro(
moduleName: "metro/src/Server",
fromDir?: string
): typeof import("metro/src/Server").default;
export function requireModuleFromMetro(
moduleName: "metro/src/lib/TerminalReporter",
fromDir?: string
): typeof import("metro/src/lib/TerminalReporter").TerminalReporter;
export function requireModuleFromMetro(
moduleName: "metro/src/shared/output/bundle",
fromDir?: string
): typeof import("metro/src/shared/output/bundle");
export function requireModuleFromMetro(
moduleName: "metro-config",
fromDir?: string
): typeof import("metro-config");
export function requireModuleFromMetro(
moduleName: "metro-core",
fromDir?: string
): typeof import("metro-core");
export function requireModuleFromMetro(
moduleName: "metro-resolver",
fromDir?: string
): typeof import("metro-resolver");
/**
* Imports specified module starting from the installation directory of the
* currently used `metro` version.
*/
export function requireModuleFromMetro(
moduleName: MetroModule,
fromDir = process.cwd()
): MetroImport {
const startDir = findMetroPath(fromDir);
if (!startDir) {
throw new Error("Cannot find module 'metro'");
}
const metroDir = "metro/";
const modulePath = moduleName.startsWith(metroDir)
? `${startDir}/${moduleName.substring(metroDir.length)}`
: resolveFrom(moduleName, startDir);
if (!modulePath) {
throw new Error(
`Cannot find module '${moduleName}'. This probably means that ` +
"'@rnx-kit/tools-react-native' is not compatible with the version " +
"of 'metro' that you are currently using. Please update to the " +
"latest version and try again. If the issue still persists after the " +
"update, please file a bug at " +
"https://github.com/microsoft/rnx-kit/issues."
);
}
return require(modulePath);
}

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "@react-native-community/cli-plugin-metro",
"version": "1.0.0"
}

Просмотреть файл

@ -0,0 +1,50 @@
import * as os from "node:os";
import * as path from "node:path";
import { requireModuleFromMetro } from "../src/metro";
const nixOnlyTest = os.platform() === "win32" ? test.skip : test;
describe("requireModuleFromMetro", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const context: any = {};
function getMetroResolver(fromDir: string) {
return requireModuleFromMetro("metro-resolver", fromDir).resolve;
}
test("returns `metro-resolver` installed by `react-native`", () => {
const p = path.join(__dirname, "__fixtures__", "metro-resolver-duplicates");
expect(getMetroResolver(p)(context, "", null)).toEqual(
expect.stringContaining(
path.join(
"metro-resolver-duplicates",
"node_modules",
"@react-native-community",
"cli-plugin-metro",
"node_modules",
"metro-resolver"
)
)
);
});
// The symlinks under `pnpm` don't work on Windows
nixOnlyTest("returns `metro-resolver` from a central storage", () => {
const p = path.join(__dirname, "__fixtures__", "pnpm");
expect(getMetroResolver(p)(context, "", null)).toEqual(
expect.stringContaining(
path.join("pnpm", "node_modules", ".pnpm", "metro-resolver")
)
);
});
test("throws if `metro-resolver` cannot be found", () => {
const cwd = process.cwd();
const root = cwd.substring(0, cwd.indexOf(path.sep) + 1);
expect(() => getMetroResolver(root)(context, "", null)).toThrowError(
"Cannot find module"
);
});
});

Просмотреть файл

@ -3713,23 +3713,17 @@ __metadata:
jest: ^29.2.1
metro: ^0.76.5
metro-config: ^0.76.5
metro-react-native-babel-preset: ^0.76.5
prettier: ^3.0.0
react-native: ^0.72.0
type-fest: ^4.0.0
typescript: ^5.0.0
peerDependencies:
"@react-native/metro-config": "*"
metro-config: ">=0.58.0"
metro-react-native-babel-preset: "*"
metro-resolver: ">=0.58.0"
react: "*"
react-native: "*"
peerDependenciesMeta:
"@react-native/metro-config":
optional: true
metro-resolver:
optional: true
languageName: unknown
linkType: soft
@ -3850,6 +3844,7 @@ __metadata:
resolution: "@rnx-kit/metro-serializer@workspace:packages/metro-serializer"
dependencies:
"@rnx-kit/scripts": "*"
"@rnx-kit/tools-react-native": ^1.3.3
"@types/semver": ^7.0.0
eslint: ^8.0.0
jest: ^29.2.1
@ -3868,8 +3863,8 @@ __metadata:
"@react-native/assets-registry": ^0.73.0
"@rnx-kit/console": ^1.0.0
"@rnx-kit/scripts": "*"
"@rnx-kit/tools-language": ^2.0.0
"@rnx-kit/tools-node": ^2.1.0
"@rnx-kit/tools-react-native": ^1.3.3
"@types/node": ^18.0.0
"@types/node-fetch": ^2.6.5
chalk: ^4.1.0
@ -3887,21 +3882,15 @@ __metadata:
typescript: ^5.0.0
peerDependencies:
"@office-iss/react-native-win32": "*"
"@react-native-community/cli-plugin-metro": "*"
"@react-native/metro-config": "*"
metro: ">=0.66.1"
metro-config: ">=0.66.1"
metro-core: ">=0.66.1"
metro-react-native-babel-transformer: ">=0.66.1"
metro-resolver: ">=0.66.1"
metro-runtime: ">=0.66.1"
peerDependenciesMeta:
"@office-iss/react-native-win32":
optional: true
"@react-native-community/cli-plugin-metro":
optional: true
"@react-native/metro-config":
optional: true
metro-react-native-babel-transformer:
optional: true
languageName: unknown
linkType: soft
@ -4170,7 +4159,7 @@ __metadata:
languageName: unknown
linkType: soft
"@rnx-kit/tools-react-native@*, @rnx-kit/tools-react-native@^1.3.1, @rnx-kit/tools-react-native@^1.3.2, @rnx-kit/tools-react-native@workspace:packages/tools-react-native":
"@rnx-kit/tools-react-native@*, @rnx-kit/tools-react-native@^1.3.1, @rnx-kit/tools-react-native@^1.3.2, @rnx-kit/tools-react-native@^1.3.3, @rnx-kit/tools-react-native@workspace:packages/tools-react-native":
version: 0.0.0-use.local
resolution: "@rnx-kit/tools-react-native@workspace:packages/tools-react-native"
dependencies: