diff --git a/.changeset/gentle-hands-give.md b/.changeset/gentle-hands-give.md new file mode 100644 index 0000000000..7d24605975 --- /dev/null +++ b/.changeset/gentle-hands-give.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/metro-transformer": major +--- + +Add @rnx-kit/metro-transformer with support for transformer merging and selecting babel transformers based on file type diff --git a/.changeset/ninety-cows-build.md b/.changeset/ninety-cows-build.md new file mode 100644 index 0000000000..294a1f1a9a --- /dev/null +++ b/.changeset/ninety-cows-build.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/metro-serializer": patch +--- + +Use new types package for serializer types diff --git a/.changeset/thirty-jeans-read.md b/.changeset/thirty-jeans-read.md new file mode 100644 index 0000000000..24b0bc8403 --- /dev/null +++ b/.changeset/thirty-jeans-read.md @@ -0,0 +1,6 @@ +--- +"@rnx-kit/cli": minor +"@rnx-kit/metro-serializer": patch +--- + +Consume new metro-transformer package diff --git a/.changeset/true-symbols-carry.md b/.changeset/true-symbols-carry.md new file mode 100644 index 0000000000..2ee59c8ec9 --- /dev/null +++ b/.changeset/true-symbols-carry.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/types-metro-config": major +--- + +Add types package for common rnx-kit metro configuration types diff --git a/.vscode/settings.json b/.vscode/settings.json index 9ed7f42236..9ba50f8256 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,12 +34,12 @@ "**/lib-amd": true }, - "js/ts.experimental.useTsgo": true, + "js/ts.experimental.useTsgo": false, "js/ts.preferences.quoteStyle": "single", "js/ts.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, "js/ts.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, - "js/ts.tsdk.path": "./node_modules/@typescript/native-preview/lib", + "js/ts.tsdk.path": "./node_modules/typescript/lib", "search.exclude": { "**/node_modules": true, diff --git a/package.json b/package.json index 1a8f138a68..88d27b0487 100644 --- a/package.json +++ b/package.json @@ -275,6 +275,11 @@ "@babel/preset-env" ] }, + "packages/metro-transformer": { + "ignoreDependencies": [ + "@react-native/metro-babel-transformer" + ] + }, "packages/oxlint-config": { "entry": [ "src/**/*.ts" diff --git a/packages/cli/package.json b/packages/cli/package.json index 5c02995f6a..8c0d418bf4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,6 +49,7 @@ "@rnx-kit/metro-serializer": "^2.0.0", "@rnx-kit/metro-serializer-esbuild": "^0.3.1", "@rnx-kit/metro-service": "^4.1.3", + "@rnx-kit/metro-transformer": "^0.0.1", "@rnx-kit/third-party-notices": "^2.0.0", "@rnx-kit/tools-android": "^0.2.2", "@rnx-kit/tools-apple": "^0.2.2", @@ -58,6 +59,7 @@ "@rnx-kit/tools-react-native": "^2.3.4", "@rnx-kit/types-bundle-config": "^1.0.0", "@rnx-kit/types-kit-config": "^1.0.0", + "@rnx-kit/types-metro-config": "^0.0.1", "@rnx-kit/types-node": "^1.0.0", "commander": "^11.1.0", "ora": "^5.4.1", diff --git a/packages/cli/src/helpers/metro-config.ts b/packages/cli/src/helpers/metro-config.ts index 77e576b495..a4343d7484 100644 --- a/packages/cli/src/helpers/metro-config.ts +++ b/packages/cli/src/helpers/metro-config.ts @@ -2,14 +2,24 @@ import { warn } from "@rnx-kit/console"; import { CyclicDependencies } from "@rnx-kit/metro-plugin-cyclic-dependencies-detector"; import { DuplicateDependencies } from "@rnx-kit/metro-plugin-duplicates-checker"; import { TypeScriptPlugin } from "@rnx-kit/metro-plugin-typescript"; -import type { MetroPlugin } from "@rnx-kit/metro-serializer"; import { MetroSerializer } from "@rnx-kit/metro-serializer"; import { esbuildTransformerConfig, MetroSerializer as MetroSerializerEsbuild, } from "@rnx-kit/metro-serializer-esbuild"; +import { MetroTransformer } from "@rnx-kit/metro-transformer"; import type { BundleParameters } from "@rnx-kit/types-bundle-config"; -import type { ConfigT, SerializerConfigT } from "metro-config"; +import type { + TransformerPlugin, + SerializerHookPlugin, + SerializerPlugin, + ExtendedTransformerConfig, +} from "@rnx-kit/types-metro-config"; +import type { + ConfigT, + SerializerConfigT, + TransformerConfigT, +} from "metro-config"; import type { WritableDeep } from "type-fest"; import { getDefaultBundlerPlugins } from "../bundle/defaultPlugins.ts"; @@ -104,11 +114,11 @@ export function customizeMetroConfig( // with readonly props to a type where the props are writeable. const metroConfig = metroConfigReadonly as WritableDeep; - const metroPlugins: MetroPlugin[] = []; - const serializerHooks: Record< - string, - SerializerConfigT["experimentalSerializerHook"] - > = {}; + const metroPlugins: SerializerPlugin[] = []; + const serializerHooks: Record = {}; + const transformers: ExtendedTransformerConfig[] = metroConfig.transformer + ? [metroConfig.transformer] + : []; const legacyWarning = readLegacyOptions(extraParams); if (legacyWarning) { @@ -152,6 +162,15 @@ export function customizeMetroConfig( serializerHooks[module] = plugin(options, print); break; + case "transformer": + { + const transformerPlugin: TransformerPlugin = plugin(options, print); + if (transformerPlugin.transformer) { + transformers.unshift(transformerPlugin.transformer); + } + } + break; + default: throw new Error(`${module}: unknown plugin type: ${plugin.type}`); } @@ -170,7 +189,8 @@ export function customizeMetroConfig( ? extraParams.treeShake : undefined ); - Object.assign(metroConfig.transformer, esbuildTransformerConfig); + // otherwise, add it to the list to be merged by the MetroTransformer + transformers.push(esbuildTransformerConfig); } else if (metroPlugins.length > 0) { // MetroSerializer acts as a CustomSerializer, and it works with both // older and newer versions of Metro. Older versions expect a return @@ -189,6 +209,12 @@ export function customizeMetroConfig( // We don't want this set if unused delete metroConfig.serializer.customSerializer; } + // Use the MetroTransformer to resolve the final transformer, if there are no valid transformers it will return an empty + // config. If there is one and it is already valid it will return it as is, if there are multiple or if they use extended + // options it will merge and transform them. + metroConfig.transformer = MetroTransformer( + ...transformers + ) as WritableDeep; const hooks = Object.values(serializerHooks); switch (hooks.length) { diff --git a/packages/metro-serializer/package.json b/packages/metro-serializer/package.json index ca5944f45e..f8599937b9 100644 --- a/packages/metro-serializer/package.json +++ b/packages/metro-serializer/package.json @@ -34,7 +34,8 @@ "test": "rnx-kit-scripts test" }, "dependencies": { - "@rnx-kit/tools-react-native": "^2.3.0" + "@rnx-kit/tools-react-native": "^2.3.0", + "@rnx-kit/types-metro-config": "^0.0.1" }, "devDependencies": { "@rnx-kit/scripts": "*", diff --git a/packages/metro-serializer/src/index.ts b/packages/metro-serializer/src/index.ts index 101bb087b6..cb06c46c8b 100644 --- a/packages/metro-serializer/src/index.ts +++ b/packages/metro-serializer/src/index.ts @@ -3,34 +3,19 @@ import { requireModuleFromMetro, } from "@rnx-kit/tools-react-native/metro"; import type { - MixedOutput, - Module, - ReadOnlyGraph, - SerializerOptions, -} from "metro"; - -export type MetroPlugin = ( - entryPoint: string, - preModules: readonly Module[], - graph: ReadOnlyGraph, - options: SerializerOptions -) => void; - -export type Bundle = { - modules: readonly [number, string][]; - post: string; - pre: string; + Bundle, + CustomSerializer, + CustomSerializerResult, + SerializerPlugin, +} from "@rnx-kit/types-metro-config"; +import type { Module, ReadOnlyGraph, SerializerOptions } from "metro"; +export type { + Bundle, + CustomSerializer, + CustomSerializerResult, + SerializerPlugin as MetroPlugin, }; -export type CustomSerializerResult = string | { code: string; map: string }; - -export type CustomSerializer = ( - entryPoint: string, - preModules: readonly Module[], - graph: ReadOnlyGraph, - options: SerializerOptions -) => Promise | CustomSerializerResult; - export type TestMocks = { baseJSBundle?: ( entryPoint: string, @@ -61,7 +46,7 @@ function getMetroVersion(): number { * @see https://github.com/facebook/metro/blob/af23a1b27bcaaff2e43cb795744b003e145e78dd/packages/metro/src/Server.js#L228 */ export function MetroSerializer( - plugins: MetroPlugin[], + plugins: SerializerPlugin[], __mocks: TestMocks = {} ): CustomSerializer { const baseJSBundle = diff --git a/packages/metro-transformer/README.md b/packages/metro-transformer/README.md new file mode 100644 index 0000000000..38cac866a0 --- /dev/null +++ b/packages/metro-transformer/README.md @@ -0,0 +1,78 @@ +# @rnx-kit/metro-transformer + +[![Build](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml/badge.svg)](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml) +[![npm version](https://img.shields.io/npm/v/@rnx-kit/metro-transformer)](https://www.npmjs.com/package/@rnx-kit/metro-transformer) + +`@rnx-kit/metro-transformer` is a pluggable Metro transformer that lets you +route files to different Babel transformers based on glob patterns and merge transformer configuration +from multiple sources. + +## Installation + +```sh +yarn add @rnx-kit/metro-transformer --dev +``` + +## Usage + +Use `MetroTransformer` to build the `transformer` section of your Metro config: + +```js +// metro.config.js +const { makeMetroConfig } = require("@rnx-kit/metro-config"); +const { MetroTransformer } = require("@rnx-kit/metro-transformer"); +const { resolve } = require("node:path"); + +module.exports = makeMetroConfig({ + transformer: MetroTransformer({ + // any standard options can be used... + getTransformOptions: async () => ({ + // options here + }), + // add in a specific file babel transformer + babelTransformers: { + // Route .svg files through react-native-svg-transformer + "**/*.svg": resolve( + require.resolve("react-native-svg-transformer/package.json"), + "../transformer.js" + ), + }, + }), +}); +``` + +## Configuration + +`MetroTransformer(config)` accepts any number of `ExtendedTransformerConfig` objects +and returns a `TransformerConfigT` suitable for Metro's `transformer` field. + +### `babelTransformers` + +`Record` — Maps glob patterns to absolute paths of Babel +transformers. When Metro processes a file, patterns are tested in insertion +order and the first match wins. Patterns are matched with +[micromatch](https://github.com/micromatch/micromatch) against the full file +path, so use `**` to match across directories (e.g. `"**/*.svg"` rather than +`"*.svg"`). + +```js +MetroTransformer({ + babelTransformers: { + "**/*.svg": require.resolve("react-native-svg-transformer/transformer"), + "**/*.png": require.resolve("./myImageTransformer"), + }, +}); +``` + +### `customTransformerOptions` + +`Record` — Arbitrary options merged into +`customTransformOptions` and forwarded to every Babel transformer. Useful for +passing configuration through to delegate transformers. + +### Standard Metro transformer options + +Any other field accepted by Metro's `TransformerConfigT` (e.g. +`getTransformOptions`, `babelTransformerPath`) can be included and will be +merged into the final config. Multiple configs are merged left-to-right, with +later values overwriting earlier ones. diff --git a/packages/metro-transformer/package.json b/packages/metro-transformer/package.json new file mode 100644 index 0000000000..5044a37206 --- /dev/null +++ b/packages/metro-transformer/package.json @@ -0,0 +1,47 @@ +{ + "name": "@rnx-kit/metro-transformer", + "version": "0.0.1", + "description": "metro-transformer", + "homepage": "https://github.com/microsoft/rnx-kit/tree/main/packages/metro-transformer#readme", + "license": "MIT", + "author": { + "name": "Microsoft Open Source", + "email": "microsoftopensource@users.noreply.github.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/rnx-kit", + "directory": "packages/metro-transformer" + }, + "files": [ + "lib/**/*.d.ts", + "lib/**/*.js" + ], + "type": "commonjs", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rnx-kit-scripts build", + "format": "rnx-kit-scripts format", + "lint": "rnx-kit-scripts lint", + "test": "rnx-kit-scripts test" + }, + "dependencies": { + "@rnx-kit/types-metro-config": "^0.0.1", + "micromatch": "^4.0.8" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@react-native/metro-babel-transformer": "^0.83.0", + "@rnx-kit/scripts": "*", + "@rnx-kit/tsconfig": "*", + "@types/babel__core": "^7.20.0", + "@types/micromatch": "^4.0.10", + "metro": "^0.83.3", + "metro-babel-transformer": "^0.83.3", + "metro-config": "^0.83.3" + }, + "engines": { + "node": ">=18.12" + } +} diff --git a/packages/metro-transformer/src/babelTransformer.ts b/packages/metro-transformer/src/babelTransformer.ts new file mode 100644 index 0000000000..23d1a9a35c --- /dev/null +++ b/packages/metro-transformer/src/babelTransformer.ts @@ -0,0 +1,92 @@ +import type { BabelFileResult } from "@babel/core"; +import type { CustomTransformerOptions } from "@rnx-kit/types-metro-config"; +import micromatch from "micromatch"; +import crypto from "node:crypto"; +import fs from "node:fs"; + +type TransformerModule = { + transform: ( + args: BabelTransformerArgs + ) => BabelFileResult | Promise; + getCacheKey?: () => string; +}; + +import type { BabelTransformerArgs as BaseTransformerArgs } from "metro-babel-transformer"; + +/** + * Types for babel transformers that can be set as the babelTransformerPath in the Metro transformer config. + */ + +/** + * Options passed in to the transform function of a babel transformer. + */ +type WithCustomOptions = Omit< + T, + "customTransformOptions" +> & { + customTransformOptions: CustomTransformerOptions; +}; + +/** + * Arguments passed in to the transform function of a babel transformer. + */ +export type BabelTransformerArgs = Omit & { + options: WithCustomOptions; +}; + +// start with a hash of this file's contents as the cache key +const cacheKeyParts: (string | Buffer)[] = [fs.readFileSync(__filename)]; + +/** + * Get the cached cache key. Will be recalculated if additional parts are added to cacheKeyParts + */ +export const getCacheKey = (() => { + let cacheKey: string | undefined; + let processedParts = -1; + return () => { + if (!cacheKey || processedParts !== cacheKeyParts.length) { + const hash = crypto.createHash("md5"); + for (const part of cacheKeyParts) { + hash.update(part); + } + cacheKey = hash.digest("hex"); + processedParts = cacheKeyParts.length; + } + return cacheKey; + }; +})(); + +function findTransformerForFile( + filename: string, + babelTransformers: Record +): string | undefined { + for (const [pattern, transformerPath] of Object.entries(babelTransformers)) { + if (micromatch.isMatch(filename, pattern)) { + return transformerPath; + } + } + return undefined; +} + +export function transform( + args: BabelTransformerArgs +): BabelFileResult | Promise { + const { customTransformerOptions } = args.options + .customTransformOptions as unknown as { + customTransformerOptions: CustomTransformerOptions; + }; + + const { upstreamTransformerPath, babelTransformers } = + customTransformerOptions; + + let transformerPath = upstreamTransformerPath; + if (babelTransformers) { + const matched = findTransformerForFile(args.filename, babelTransformers); + if (matched) { + transformerPath = matched; + } + } + + const transformer = require(transformerPath) as TransformerModule; + return transformer.transform(args); +} diff --git a/packages/metro-transformer/src/index.ts b/packages/metro-transformer/src/index.ts new file mode 100644 index 0000000000..a0557e6d6b --- /dev/null +++ b/packages/metro-transformer/src/index.ts @@ -0,0 +1 @@ +export { MetroTransformer } from "./transformer"; diff --git a/packages/metro-transformer/src/transformer.ts b/packages/metro-transformer/src/transformer.ts new file mode 100644 index 0000000000..4a37c1d697 --- /dev/null +++ b/packages/metro-transformer/src/transformer.ts @@ -0,0 +1,97 @@ +import type { + ExtendedTransformerConfig, + TransformerConfigT, + CustomTransformerOptions, +} from "@rnx-kit/types-metro-config"; +import { + simpleObjectMerge, + isBaseConfig, + getDefaultTransformerPath, +} from "./utilities"; + +type GetOptions = TransformerConfigT["getTransformOptions"]; + +export function MetroTransformer( + ...configs: ExtendedTransformerConfig[] +): Partial { + // remove any empty configs to avoid unnecessary merging and processing + configs = configs.filter((config) => Object.keys(config).length > 0); + if (configs.length === 0) { + return {}; + } else if (configs.length === 1 && isBaseConfig(configs[0])) { + // if we only have one config and it is a base config, we can return it as is without merging or other processing + return configs[0] as Partial; + } + + const result: Partial = {}; + const customOptions = {} as CustomTransformerOptions; + const conditionalTransformers: Record = {}; + const optionFunctions: GetOptions[] = []; + + for (const config of configs) { + const { + customTransformerOptions = {}, + babelTransformers = {}, + getTransformOptions, + ...additionalConfig + } = config; + // merge in any transformers or custom options + Object.assign(conditionalTransformers, babelTransformers); + Object.assign(customOptions, customTransformerOptions); + + if (getTransformOptions) { + optionFunctions.push(getTransformOptions); + } + // merge the result of the options into the final transformer config + Object.assign(result, additionalConfig); + } + + // remember any conditional transformers if they were set + if (Object.keys(conditionalTransformers).length > 0) { + customOptions.babelTransformers = conditionalTransformers; + } + + // ensure the upstream transformer is available to the babel transformers + customOptions.upstreamTransformerPath ??= + result.babelTransformerPath ?? getDefaultTransformerPath(); + + // create the merged getTransformOptions function + result.getTransformOptions = createGetTransformOptions( + optionFunctions, + customOptions + ); + + // if we are doing custom work that requires the custom babel transformer, make sure to set it as the transformer to use + if (customOptions.babelTransformers) { + Object.assign(result, { + babelTransformerPath: require.resolve("./babelTransformer"), + }); + } + + return result; +} + +/** + * Create an aggregated getTransformOptions function that will call multiple provided functions, + * then aggregate the results together. + */ +function createGetTransformOptions( + optionFunctions: GetOptions[], + customTransformerOptions: CustomTransformerOptions +): GetOptions { + return async ( + entryPoints: Parameters[0], + options: Parameters[1], + getDependenciesOf: Parameters[2] + ) => { + const results = + optionFunctions.length > 0 + ? await Promise.all( + optionFunctions.map((func) => + func(entryPoints, options, getDependenciesOf) + ) + ) + : []; + return simpleObjectMerge(...results, { customTransformerOptions }); + }; +} diff --git a/packages/metro-transformer/src/utilities.ts b/packages/metro-transformer/src/utilities.ts new file mode 100644 index 0000000000..9b3adbd11e --- /dev/null +++ b/packages/metro-transformer/src/utilities.ts @@ -0,0 +1,74 @@ +import type { ExtendedTransformerConfig } from "@rnx-kit/types-metro-config"; +import type { TransformerConfigT } from "metro-config"; +import path from "node:path"; + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** + * Simple merge helper that recursively merges plain objects but does not attempt to merge arrays or other types. + */ +export function simpleObjectMerge( + ...options: Record[] +): Record { + const result: Record = {}; + for (const option of options) { + for (const [key, value] of Object.entries(option)) { + if (isRecord(value) && isRecord(result[key])) { + result[key] = simpleObjectMerge(result[key], value); + } else { + result[key] = value; + } + } + } + return result; +} + +function tryRequireResolve( + moduleName: string, + cwd?: string +): string | undefined { + try { + return require.resolve(moduleName, cwd ? { paths: [cwd] } : undefined); + } catch { + return undefined; + } +} + +export function getDefaultTransformerPath(): string { + const baseTransformer = "@react-native/metro-babel-transformer"; + // try to resolve from the package root + let transformerPath = tryRequireResolve(baseTransformer, process.cwd()); + if (!transformerPath) { + // next try to get metro-config from the package root and resolve from there + const metroConfigPath = tryRequireResolve( + `@react-native/metro-config/package.json`, + process.cwd() + ); + if (metroConfigPath) { + transformerPath = tryRequireResolve( + baseTransformer, + path.dirname(metroConfigPath) + ); + } + } + if (!transformerPath) { + throw new Error( + `Unable to resolve the default transformer '${baseTransformer}'. Please ensure it is installed in your project.` + ); + } + return transformerPath; +} + +/** + * Determine if the given configuration is a base transformer config. + * @param config configuration which may be a base config or an extended config + * @returns true if the config is a base transformer config, false otherwise + */ +export function isBaseConfig( + config: ExtendedTransformerConfig | Partial +): config is Partial { + const asExtended = config as ExtendedTransformerConfig; + return !asExtended.babelTransformers && !asExtended.customTransformerOptions; +} diff --git a/packages/metro-transformer/test/__fixtures__/svgTransformer.js b/packages/metro-transformer/test/__fixtures__/svgTransformer.js new file mode 100644 index 0000000000..b70db4dc67 --- /dev/null +++ b/packages/metro-transformer/test/__fixtures__/svgTransformer.js @@ -0,0 +1,7 @@ +"use strict"; + +function transform(args) { + return { ast: { type: "File" }, metadata: { transformer: "svg", filename: args.filename } }; +} + +module.exports = { transform }; diff --git a/packages/metro-transformer/test/__fixtures__/upstreamTransformer.js b/packages/metro-transformer/test/__fixtures__/upstreamTransformer.js new file mode 100644 index 0000000000..944089dc3b --- /dev/null +++ b/packages/metro-transformer/test/__fixtures__/upstreamTransformer.js @@ -0,0 +1,11 @@ +"use strict"; + +function transform(args) { + return { ast: { type: "File" }, metadata: { transformer: "upstream", filename: args.filename } }; +} + +function getCacheKey() { + return "upstream-cache-key"; +} + +module.exports = { transform, getCacheKey }; diff --git a/packages/metro-transformer/test/babelTransformer.test.ts b/packages/metro-transformer/test/babelTransformer.test.ts new file mode 100644 index 0000000000..d03ccc64c0 --- /dev/null +++ b/packages/metro-transformer/test/babelTransformer.test.ts @@ -0,0 +1,113 @@ +import type { BabelFileResult } from "@babel/core"; +import type { CustomTransformerOptions } from "@rnx-kit/types-metro-config"; +import { equal, match } from "node:assert/strict"; +import path from "node:path"; +import { describe, it } from "node:test"; +import type { BabelTransformerArgs } from "../src/babelTransformer.ts"; +import { getCacheKey, transform } from "../src/babelTransformer.ts"; + +const fixturesDir = path.join(__dirname, "__fixtures__"); +const upstreamTransformerPath = path.join( + fixturesDir, + "upstreamTransformer.js" +); +const svgTransformerPath = path.join(fixturesDir, "svgTransformer.js"); + +/** Build a minimal BabelTransformerArgs — transform() only reads filename and customTransformerOptions. */ +function makeArgs( + filename: string, + customTransformerOptions: CustomTransformerOptions +): BabelTransformerArgs { + return { + filename, + src: "", + plugins: [], + options: { + customTransformOptions: { + customTransformerOptions, + rnxTransformer: { babelTransformerPath: "" }, + }, + }, + } as unknown as BabelTransformerArgs; +} + +// --------------------------------------------------------------------------- +// getCacheKey +// --------------------------------------------------------------------------- + +describe("getCacheKey", () => { + it("returns a 32-character MD5 hex string", () => { + match(getCacheKey(), /^[0-9a-f]{32}$/); + }); + + it("returns the same value on repeated calls", () => { + equal(getCacheKey(), getCacheKey()); + }); +}); + +// --------------------------------------------------------------------------- +// transform — transformer selection +// --------------------------------------------------------------------------- + +type ResultWithTestData = BabelFileResult & { + metadata: { transformer?: string; filename?: string }; +}; + +describe("transform", () => { + it("delegates to upstreamTransformerPath when babelTransformers is not set", async () => { + const result = (await transform( + makeArgs("index.js", { upstreamTransformerPath }) + )) as ResultWithTestData; + + equal(result.metadata.transformer, "upstream"); + }); + + it("delegates to upstreamTransformerPath when no babelTransformer pattern matches", async () => { + const result = (await transform( + makeArgs("index.js", { + upstreamTransformerPath, + babelTransformers: { "**/*.svg": svgTransformerPath }, + }) + )) as ResultWithTestData; + + equal(result.metadata.transformer, "upstream"); + }); + + it("delegates to the matching babelTransformer when a glob matches the filename", async () => { + const result = (await transform( + makeArgs("/project/src/icon.svg", { + upstreamTransformerPath, + babelTransformers: { "**/*.svg": svgTransformerPath }, + }) + )) as ResultWithTestData; + + equal(result.metadata.transformer, "svg"); + }); + + it("uses the first matching pattern when multiple patterns match", async () => { + const result = (await transform( + makeArgs("/project/icon.svg", { + upstreamTransformerPath, + // svg comes first — upstream is a catch-all that would also match + babelTransformers: { + "**/*.svg": svgTransformerPath, + "**/*": upstreamTransformerPath, + }, + }) + )) as ResultWithTestData; + + equal(result.metadata.transformer, "svg"); + }); + + it("passes args through to the selected transformer", async () => { + const filename = "/project/src/special.svg"; + const result = (await transform( + makeArgs(filename, { + upstreamTransformerPath, + babelTransformers: { "**/*.svg": svgTransformerPath }, + }) + )) as ResultWithTestData; + + equal(result.metadata.filename, filename); + }); +}); diff --git a/packages/metro-transformer/tsconfig.json b/packages/metro-transformer/tsconfig.json new file mode 100644 index 0000000000..b2ba99487b --- /dev/null +++ b/packages/metro-transformer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rnx-kit/tsconfig/tsconfig.node.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/types-metro-config/README.md b/packages/types-metro-config/README.md new file mode 100644 index 0000000000..19ea76afb8 --- /dev/null +++ b/packages/types-metro-config/README.md @@ -0,0 +1,94 @@ +# @rnx-kit/types-metro-config + +[![Build](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml/badge.svg)](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml) + +Shared TypeScript types for `rnx-kit` Metro plugins and configuration. This is +an internal package used by `@rnx-kit/metro-transformer`, +`@rnx-kit/metro-serializer`, and related packages. + +## Types + +### Serializer types + +#### `SerializerPlugin` + +A Metro serializer plugin. Called once per bundle with the full module graph +before serialization. Use this to inspect or validate bundle contents. + +```ts +type SerializerPlugin = ( + entryPoint: string, + preModules: readonly Module[], + graph: ReadOnlyGraph, + options: SerializerOptions +) => void; +``` + +#### `SerializerHookPlugin` + +A serializer hook plugin. Called on every delta with the full module graph and +the delta result. + +#### `CustomSerializer` + +Signature of a Metro custom serializer, as set on +`serializer.customSerializer` in the Metro config. + +#### `CustomSerializerResult` + +The value returned by a Metro custom serializer — either a raw code string or +an object with separate code and source-map strings. + +#### `Bundle` + +Intermediate bundle representation produced by Metro's `baseJSBundle`. + +### Transformer types + +#### `ExtendedTransformerConfig` + +Extends Metro's `TransformerConfigT` with additional options: + +- **`babelTransformers`** — `Record` — Maps glob patterns to + paths of Babel transformers. Patterns are matched with + [micromatch](https://github.com/micromatch/micromatch) against the full file + path. +- **`upstreamBabelOverridePath`** — `string` — Path to a Babel transformer to + use as the upstream fallback instead of + `@react-native/metro-babel-transformer`. When set, delegate transformers that + internally require `@react-native/metro-babel-transformer` are redirected to + this path. +- **`customTransformerOptions`** — `Record` — Arbitrary + options merged into `customTransformOptions` and forwarded to every Babel + transformer. + +#### `CustomTransformerOptions` + +Options set up as part of the `customTransformOptions` passed to Babel +transformers at transform time: + +- **`upstreamTransformerPath`** — resolved path to the upstream Babel + transformer +- **`upstreamTransformerAliases`** — module paths to reroute to the upstream + transformer +- **`babelTransformers`** — conditional transformer map from the config + +#### `TransformerPlugin` + +A plugin object that contributes transformer configuration: + +- **`transformer`** — `ExtendedTransformerConfig` — config settings to merge + +### Plugin factory types + +#### `SerializerPluginFactory` + +Default export shape for a serializer plugin package. + +#### `SerializerHookPluginFactory` + +Default export shape for a serializer hook plugin package. + +#### `TransformerPluginFactory` + +Default export shape for a transformer plugin package. diff --git a/packages/types-metro-config/oxlint.config.ts b/packages/types-metro-config/oxlint.config.ts new file mode 100644 index 0000000000..5318fa1578 --- /dev/null +++ b/packages/types-metro-config/oxlint.config.ts @@ -0,0 +1 @@ +export { default } from "@rnx-kit/oxlint-config/types-only"; diff --git a/packages/types-metro-config/package.json b/packages/types-metro-config/package.json new file mode 100644 index 0000000000..e560cea672 --- /dev/null +++ b/packages/types-metro-config/package.json @@ -0,0 +1,54 @@ +{ + "name": "@rnx-kit/types-metro-config", + "version": "0.0.1", + "description": "Common types for rnx-kit metro plugins and configuration", + "homepage": "https://github.com/microsoft/rnx-kit/tree/main/packages/types-metro-config#readme", + "license": "MIT", + "author": { + "name": "Microsoft Open Source", + "email": "microsoftopensource@users.noreply.github.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/rnx-kit", + "directory": "packages/types-metro-config" + }, + "files": [ + "lib/**/*.d.ts", + "lib/**/*.js" + ], + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "build": "rnx-kit-scripts build", + "format": "rnx-kit-scripts format", + "lint": "rnx-kit-scripts lint" + }, + "devDependencies": { + "@rnx-kit/oxlint-config": "*", + "@rnx-kit/scripts": "*", + "@rnx-kit/tsconfig": "*", + "metro": "^0.83.3", + "metro-config": "^0.83.3" + }, + "peerDependencies": { + "metro": "*", + "metro-config": "*" + }, + "peerDependenciesMeta": { + "metro": { + "optional": true + }, + "metro-config": { + "optional": true + } + }, + "engines": { + "node": ">=18.12" + } +} diff --git a/packages/types-metro-config/src/index.ts b/packages/types-metro-config/src/index.ts new file mode 100644 index 0000000000..09de856ad7 --- /dev/null +++ b/packages/types-metro-config/src/index.ts @@ -0,0 +1,19 @@ +export type { + PluginType, + PrintMessage, + SerializerPluginFactory, + TransformerPluginFactory, +} from "./plugins"; +export type { + Bundle, + CustomSerializer, + CustomSerializerResult, + SerializerHookPlugin, + SerializerPlugin, +} from "./serializer"; +export type { + CustomTransformerOptions, + ExtendedTransformerConfig, + TransformerConfigT, + TransformerPlugin, +} from "./transformer"; diff --git a/packages/types-metro-config/src/plugins.ts b/packages/types-metro-config/src/plugins.ts new file mode 100644 index 0000000000..18b059e749 --- /dev/null +++ b/packages/types-metro-config/src/plugins.ts @@ -0,0 +1,38 @@ +import type { MixedOutput } from "metro"; +import type { SerializerHookPlugin, SerializerPlugin } from "./serializer"; +import type { TransformerPlugin } from "./transformer.ts"; + +export type PluginType = "serializer" | "serializerHook" | "transformer"; +export type PrintMessage = (message: string) => void; + +/** + * Serializer plugin factory type, should be the default export of the serializer plugin package + */ +export type SerializerPluginFactory< + TOptions extends object = Record, + T = MixedOutput, +> = { + (options?: TOptions, print?: PrintMessage): SerializerPlugin; + type?: "serializer"; +}; + +/** + * Serializer hook plugin factory type, should be the default export of the serializer hook plugin package + */ +export type SerializerHookPluginFactory< + TOptions extends object = Record, + T = MixedOutput, +> = { + (options?: TOptions, print?: PrintMessage): SerializerHookPlugin; + type: "serializerHook"; +}; + +/** + * Transformer plugin type, should be the default export of the transformer plugin package + */ +export type TransformerPluginFactory< + TOptions extends object = Record, +> = { + (options?: TOptions, print?: PrintMessage): TransformerPlugin; + type: "transformer"; +}; diff --git a/packages/types-metro-config/src/serializer.ts b/packages/types-metro-config/src/serializer.ts new file mode 100644 index 0000000000..55dd179320 --- /dev/null +++ b/packages/types-metro-config/src/serializer.ts @@ -0,0 +1,56 @@ +import type { + MixedOutput, + Module, + ReadOnlyGraph, + SerializerOptions, + DeltaResult, +} from "metro"; + +// --------------------------------------------------------------------------- +// Serializer plugin types +// --------------------------------------------------------------------------- + +/** + * A Metro serializer plugin. Called once per bundle with the full module graph + * before serialization. Use this to inspect or validate the bundle contents. + */ +export type SerializerPlugin = ( + entryPoint: string, + preModules: readonly Module[], + graph: ReadOnlyGraph, + options: SerializerOptions +) => void; + +/** + * A serializer hook plugin. Called on every delta with the full module graph and the delta result. + */ +export type SerializerHookPlugin = ( + graph: ReadOnlyGraph, + delta: DeltaResult +) => unknown; + +/** + * Intermediate bundle representation produced by Metro's baseJSBundle. + */ +export type Bundle = { + modules: readonly [number, string][]; + post: string; + pre: string; +}; + +/** + * The value returned by a Metro custom serializer — either a raw code string + * or an object with separate code and source-map strings. + */ +export type CustomSerializerResult = string | { code: string; map: string }; + +/** + * Signature of a Metro custom serializer, as set on + * `serializer.customSerializer` in the Metro config. + */ +export type CustomSerializer = ( + entryPoint: string, + preModules: readonly Module[], + graph: ReadOnlyGraph, + options: SerializerOptions +) => Promise | CustomSerializerResult; diff --git a/packages/types-metro-config/src/transformer.ts b/packages/types-metro-config/src/transformer.ts new file mode 100644 index 0000000000..478bfbb322 --- /dev/null +++ b/packages/types-metro-config/src/transformer.ts @@ -0,0 +1,59 @@ +import type { TransformerConfigT } from "metro-config"; +export type { TransformerConfigT } from "metro-config"; + +/** + * Extended transformer config that includes additional options for configuring various options + */ +export type ExtendedTransformerConfig = Partial & { + /** + * Babel transformers to apply to types of files that match the specified glob patterns. The key is a glob + * pattern that matches files to apply the transformer to, and the value is the path to a babel transformer. + * - These patterns will use micromatch for matching and will be tested against the full filename, so a pattern of + * *.svg will likely fail due to the attached path meaning the ** pattern is likely needed. + * - Note these use file paths just like babelTransformerPath as they will be serialized and sent to worker + * processes where they will be required via paths in that process. + */ + babelTransformers?: Record; + + /** + * A convenience helper to add custom options to the babel transformers. These will be aggregated and set + * in the getTransformerOptions return value to be passed to the babel transformers. + */ + customTransformerOptions?: Record; +}; + +/** + * Options this package will set up as part of the customTransformOptions passed to the babel transformers. + */ +export type CustomTransformerOptions = { + /** + * What babel transformer to use as the final upstream transformer. By default will be the resolved path to + * @react-native/metro-babel-transformer, but if overridden this will reflect that overridden path. + */ + upstreamTransformerPath: string; + + /** + * Resolutions to reroute to the upstream transformer. + */ + upstreamTransformerAliases?: string[]; + + /** + * Conditional babel transformers from the transformer config + */ + babelTransformers?: Record; + + /** + * Index signature for any unknown custom options + */ + [key: string]: unknown; +}; + +/** + * A transformer plugin will allow + */ +export type TransformerPlugin = { + /** + * transformer configuration settings to merge in with user settings and other plugin settings. + */ + transformer?: ExtendedTransformerConfig; +}; diff --git a/packages/types-metro-config/tsconfig.json b/packages/types-metro-config/tsconfig.json new file mode 100644 index 0000000000..0f23e8994b --- /dev/null +++ b/packages/types-metro-config/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@rnx-kit/tsconfig/tsconfig.node.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + }, + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index ccd333f581..310f9700d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4488,6 +4488,16 @@ __metadata: languageName: node linkType: hard +"@react-native/babel-plugin-codegen@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/babel-plugin-codegen@npm:0.83.4" + dependencies: + "@babel/traverse": "npm:^7.25.3" + "@react-native/codegen": "npm:0.83.4" + checksum: 10c0/f33af98ee3256e6ab1f8b4828c00ec92fa5f10ceeba22336fd4cd837525c348c8d595709902785bd38f8e8bd8a45c0a4d518d7e9f1d8c4acf8e207995f54fb12 + languageName: node + linkType: hard + "@react-native/babel-preset@npm:0.78.3, @react-native/babel-preset@npm:^0.78.0": version: 0.78.3 resolution: "@react-native/babel-preset@npm:0.78.3" @@ -4598,7 +4608,7 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.83.1, @react-native/babel-preset@npm:^0.83.0": +"@react-native/babel-preset@npm:0.83.1": version: 0.83.1 resolution: "@react-native/babel-preset@npm:0.83.1" dependencies: @@ -4653,6 +4663,61 @@ __metadata: languageName: node linkType: hard +"@react-native/babel-preset@npm:0.83.4, @react-native/babel-preset@npm:^0.83.0": + version: 0.83.4 + resolution: "@react-native/babel-preset@npm:0.83.4" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" + "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-computed-properties": "npm:^7.24.7" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-for-of": "npm:^7.24.7" + "@babel/plugin-transform-function-name": "npm:^7.25.1" + "@babel/plugin-transform-literals": "npm:^7.25.2" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" + "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" + "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" + "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-react-display-name": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx": "npm:^7.25.2" + "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" + "@babel/plugin-transform-regenerator": "npm:^7.24.7" + "@babel/plugin-transform-runtime": "npm:^7.24.7" + "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" + "@babel/plugin-transform-spread": "npm:^7.24.7" + "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.25.2" + "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" + "@babel/template": "npm:^7.25.0" + "@react-native/babel-plugin-codegen": "npm:0.83.4" + babel-plugin-syntax-hermes-parser: "npm:0.32.0" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + react-refresh: "npm:^0.14.0" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/1c1e80d77b513b0762426a207fbcf04335bc41afe077d48b95a304f3a31dcf317d16ad1a6ad7badf04fc5aa7058365db40d3daf8a2503fe16b9e58a7080b7946 + languageName: node + linkType: hard + "@react-native/codegen@npm:0.78.3, @react-native/codegen@npm:^0.78.0": version: 0.78.3 resolution: "@react-native/codegen@npm:0.78.3" @@ -4702,6 +4767,23 @@ __metadata: languageName: node linkType: hard +"@react-native/codegen@npm:0.83.4": + version: 0.83.4 + resolution: "@react-native/codegen@npm:0.83.4" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/parser": "npm:^7.25.3" + glob: "npm:^7.1.1" + hermes-parser: "npm:0.32.0" + invariant: "npm:^2.2.4" + nullthrows: "npm:^1.1.1" + yargs: "npm:^17.6.2" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/00b781097fece80cf004f8156ae0d0e24936bfad87bf1305c2d8c946b7bb18f924b0a19697d041a9ac5df3af765c62139cfabd9994d040fc60f79734adab471e + languageName: node + linkType: hard + "@react-native/community-cli-plugin@npm:0.78.3, @react-native/community-cli-plugin@npm:^0.78.0": version: 0.78.3 resolution: "@react-native/community-cli-plugin@npm:0.78.3" @@ -4950,6 +5032,20 @@ __metadata: languageName: node linkType: hard +"@react-native/metro-babel-transformer@npm:^0.83.0": + version: 0.83.4 + resolution: "@react-native/metro-babel-transformer@npm:0.83.4" + dependencies: + "@babel/core": "npm:^7.25.2" + "@react-native/babel-preset": "npm:0.83.4" + hermes-parser: "npm:0.32.0" + nullthrows: "npm:^1.1.1" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/f02166751d746033a271c6f6aae9c0a2800219127b0d0267047d1baef48b933518cfe8302059f6377cc349af161d5c131b62e0823a3451d3cd02fffec597d60f + languageName: node + linkType: hard + "@react-native/metro-config@npm:^0.78.0": version: 0.78.3 resolution: "@react-native/metro-config@npm:0.78.3" @@ -5206,6 +5302,7 @@ __metadata: "@rnx-kit/metro-serializer": "npm:^2.0.0" "@rnx-kit/metro-serializer-esbuild": "npm:^0.3.1" "@rnx-kit/metro-service": "npm:^4.1.3" + "@rnx-kit/metro-transformer": "npm:^0.0.1" "@rnx-kit/scripts": "npm:*" "@rnx-kit/third-party-notices": "npm:^2.0.0" "@rnx-kit/tools-android": "npm:^0.2.2" @@ -5217,6 +5314,7 @@ __metadata: "@rnx-kit/tsconfig": "npm:*" "@rnx-kit/types-bundle-config": "npm:^1.0.0" "@rnx-kit/types-kit-config": "npm:^1.0.0" + "@rnx-kit/types-metro-config": "npm:^0.0.1" "@rnx-kit/types-node": "npm:^1.0.0" "@types/connect": "npm:^3.4.36" "@types/node": "npm:^24.0.0" @@ -5541,6 +5639,7 @@ __metadata: "@rnx-kit/scripts": "npm:*" "@rnx-kit/tools-react-native": "npm:^2.3.0" "@rnx-kit/tsconfig": "npm:*" + "@rnx-kit/types-metro-config": "npm:^0.0.1" "@types/node": "npm:^24.0.0" metro: "npm:^0.83.3" languageName: unknown @@ -5578,6 +5677,24 @@ __metadata: languageName: unknown linkType: soft +"@rnx-kit/metro-transformer@npm:^0.0.1, @rnx-kit/metro-transformer@workspace:packages/metro-transformer": + version: 0.0.0-use.local + resolution: "@rnx-kit/metro-transformer@workspace:packages/metro-transformer" + dependencies: + "@babel/core": "npm:^7.20.0" + "@react-native/metro-babel-transformer": "npm:^0.83.0" + "@rnx-kit/scripts": "npm:*" + "@rnx-kit/tsconfig": "npm:*" + "@rnx-kit/types-metro-config": "npm:^0.0.1" + "@types/babel__core": "npm:^7.20.0" + "@types/micromatch": "npm:^4.0.10" + metro: "npm:^0.83.3" + metro-babel-transformer: "npm:^0.83.3" + metro-config: "npm:^0.83.3" + micromatch: "npm:^4.0.8" + languageName: unknown + linkType: soft + "@rnx-kit/oxlint-config@npm:*, @rnx-kit/oxlint-config@workspace:packages/oxlint-config": version: 0.0.0-use.local resolution: "@rnx-kit/oxlint-config@workspace:packages/oxlint-config" @@ -6082,6 +6199,26 @@ __metadata: languageName: unknown linkType: soft +"@rnx-kit/types-metro-config@npm:^0.0.1, @rnx-kit/types-metro-config@workspace:packages/types-metro-config": + version: 0.0.0-use.local + resolution: "@rnx-kit/types-metro-config@workspace:packages/types-metro-config" + dependencies: + "@rnx-kit/oxlint-config": "npm:*" + "@rnx-kit/scripts": "npm:*" + "@rnx-kit/tsconfig": "npm:*" + metro: "npm:^0.83.3" + metro-config: "npm:^0.83.3" + peerDependencies: + metro: "*" + metro-config: "*" + peerDependenciesMeta: + metro: + optional: true + metro-config: + optional: true + languageName: unknown + linkType: soft + "@rnx-kit/types-metro-serializer-esbuild@npm:^1.0.0, @rnx-kit/types-metro-serializer-esbuild@workspace:packages/types-metro-serializer-esbuild": version: 0.0.0-use.local resolution: "@rnx-kit/types-metro-serializer-esbuild@workspace:packages/types-metro-serializer-esbuild" @@ -6417,7 +6554,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:*, @types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.14": +"@types/babel__core@npm:*, @types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.0": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -6625,7 +6762,7 @@ __metadata: languageName: node linkType: hard -"@types/micromatch@npm:^4.0.0": +"@types/micromatch@npm:^4.0.0, @types/micromatch@npm:^4.0.10": version: 4.0.10 resolution: "@types/micromatch@npm:4.0.10" dependencies: @@ -11029,6 +11166,13 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.33.3": + version: 0.33.3 + resolution: "hermes-estree@npm:0.33.3" + checksum: 10c0/4e04e767a706a93c59d64ef3f114075aeb93b08433655d4f11d310f0785c2a74d5b5041b80bc34d22630dece54865dd93a53fde160d48b8369cfef10dbd0520b + languageName: node + linkType: hard + "hermes-parser@npm:0.25.1": version: 0.25.1 resolution: "hermes-parser@npm:0.25.1" @@ -11056,6 +11200,15 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.33.3": + version: 0.33.3 + resolution: "hermes-parser@npm:0.33.3" + dependencies: + hermes-estree: "npm:0.33.3" + checksum: 10c0/f7d69de54c77321d8481e37a323bbac01d180ec982275ef8925ceaaf7e501fc3062593e84cf5da50852f36daffb34d0f5d6cbbef079fd0125a7b91c1fe84f225 + languageName: node + linkType: hard + "hpagent@npm:^1.2.0": version: 1.2.0 resolution: "hpagent@npm:1.2.0" @@ -13019,7 +13172,7 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.3, metro-babel-transformer@npm:^0.83.1": +"metro-babel-transformer@npm:0.83.3": version: 0.83.3 resolution: "metro-babel-transformer@npm:0.83.3" dependencies: @@ -13031,6 +13184,18 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:^0.83.1, metro-babel-transformer@npm:^0.83.3": + version: 0.83.5 + resolution: "metro-babel-transformer@npm:0.83.5" + dependencies: + "@babel/core": "npm:^7.25.2" + flow-enums-runtime: "npm:^0.0.6" + hermes-parser: "npm:0.33.3" + nullthrows: "npm:^1.1.1" + checksum: 10c0/b1448241d5d7a77eeca758226bde5fc44da9f2e63f4e67037c289fe006c0f047b84fc3e77be61ba14ea605b0890232813ab75b1915faad21796b9bb873458506 + languageName: node + linkType: hard + "metro-cache-key@npm:0.81.5": version: 0.81.5 resolution: "metro-cache-key@npm:0.81.5"