зеркало из https://github.com/microsoft/rnx-kit.git
fix: ensure correct metro packages are used (#2730)
This commit is contained in:
Родитель
c94377564b
Коммит
2885f73cca
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
4
packages/tools-react-native/test/__fixtures__/metro-resolver-duplicates/node_modules/@react-native-community/cli-plugin-metro/package.json
сгенерированный
поставляемый
Normal file
4
packages/tools-react-native/test/__fixtures__/metro-resolver-duplicates/node_modules/@react-native-community/cli-plugin-metro/package.json
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "@react-native-community/cli-plugin-metro",
|
||||
"version": "1.0.0"
|
||||
}
|
0
packages/metro-resolver-symlinks/test/__fixtures__/metro-resolver-duplicates/node_modules/react-native/node_modules/metro-resolver/index.js → packages/tools-react-native/test/__fixtures__/metro-resolver-duplicates/node_modules/react-native/node_modules/metro-resolver/index.js
сгенерированный
поставляемый
0
packages/metro-resolver-symlinks/test/__fixtures__/metro-resolver-duplicates/node_modules/react-native/node_modules/metro-resolver/index.js → packages/tools-react-native/test/__fixtures__/metro-resolver-duplicates/node_modules/react-native/node_modules/metro-resolver/index.js
сгенерированный
поставляемый
0
packages/metro-resolver-symlinks/test/__fixtures__/pnpm/node_modules/.pnpm/@react-native-community+cli-plugin-metro@7.0.3/node_modules/metro → packages/tools-react-native/test/__fixtures__/pnpm/node_modules/.pnpm/@react-native-community+cli-plugin-metro@7.0.3/node_modules/metro
сгенерированный
поставляемый
0
packages/metro-resolver-symlinks/test/__fixtures__/pnpm/node_modules/.pnpm/@react-native-community+cli-plugin-metro@7.0.3/node_modules/metro → packages/tools-react-native/test/__fixtures__/pnpm/node_modules/.pnpm/@react-native-community+cli-plugin-metro@7.0.3/node_modules/metro
сгенерированный
поставляемый
0
packages/metro-resolver-symlinks/test/__fixtures__/pnpm/node_modules/.pnpm/metro-resolver@0.67.0/node_modules/metro-resolver/package.json → packages/tools-react-native/test/__fixtures__/pnpm/node_modules/.pnpm/metro-resolver@0.67.0/node_modules/metro-resolver/package.json
сгенерированный
поставляемый
0
packages/metro-resolver-symlinks/test/__fixtures__/pnpm/node_modules/.pnpm/metro-resolver@0.67.0/node_modules/metro-resolver/package.json → packages/tools-react-native/test/__fixtures__/pnpm/node_modules/.pnpm/metro-resolver@0.67.0/node_modules/metro-resolver/package.json
сгенерированный
поставляемый
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
21
yarn.lock
21
yarn.lock
|
@ -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:
|
||||
|
|
Загрузка…
Ссылка в новой задаче