Skip to content

feat(tools-react-native): Add mergeTransformerConfigs function to @rnx-kit/tools-react-native#4044

Open
JasonVMo wants to merge 2 commits intomainfrom
user/jasonvmo/rn-metro-utils
Open

feat(tools-react-native): Add mergeTransformerConfigs function to @rnx-kit/tools-react-native#4044
JasonVMo wants to merge 2 commits intomainfrom
user/jasonvmo/rn-metro-utils

Conversation

@JasonVMo
Copy link
Collaborator

Description

This adds a utility for merging transformer configurations to tools-react-native to help with transformer merging. I added this as part of a new submodule called metro-utils for general metro utility functions.

I considered various other places for this. The main package it would make sense in is @rnx-kit/metro-config but that isn't a utility package used by other packages in react-native and has issues if you try to include it as a dependency of the cli. Given the already existing utilities for resolving packages from metro it made the most sense to add it to tools-react-native.

The function itself is straightforward and will perform a flat merge (which is what metro does) except if there are multiple getTransformOptions implementations in which case they will be wrapped with the results merged together in order of precedence. This function is used to initialize the babel transformers, it is called once with the results being serialized via json to the metro transformers.

Having the merge function be baked into the CLI is required so that any transform options being set by a transformer configuration don't get stomped by the esbuild transformer which is always added at the end. The esbuild transform options will take precedence still, but things like custom options can still be used in this case.

@github-actions github-actions bot added the feature: cli This is related to CLI label Mar 24, 2026
* Type guard to check if a value is a plain object (i.e., a record). This is used to ensure that we only attempt to
* recursively merge plain objects in the `simpleObjectMerge` function.
*/
function isRecord(value: unknown): value is Record<string, unknown> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be parameterized?

Suggested change
function isRecord(value: unknown): value is Record<string, unknown> {
function isRecord<K extends string | number = string, V = unknown>(value: unknown): value is Record<K, V> {

function simpleObjectMerge(
...options: Record<string, unknown>[]
): Record<string, unknown> {
/** @type {Record<string, unknown>} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray JSDoc

Suggested change
/** @type {Record<string, unknown>} */

Comment on lines +65 to +67
const getTransformOptionsFns = configs
.map((config) => config?.getTransformOptions)
.filter((fn) => typeof fn === "function");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever you see .map followed by .filter, please rewrite it as .reduce or a for-loop instead:

Suggested change
const getTransformOptionsFns = configs
.map((config) => config?.getTransformOptions)
.filter((fn) => typeof fn === "function");
const getTransformOptionsFns = configs
.reduce<TransformerConfigT["getTransformOptions"][]>((result, config) => {
const getTransformOptions = config?.getTransformOptions;
if (typeof getTransformOptions === "function") {
result.push(getTransformOptions);
}
return result;
}, []);

JavaScript's implementation is eager and will cause additional unnecessary allocations.

Comment on lines +25 to +27
equal(result.minifierPath, "/minifier-b");
equal(result.hermesParser, false);
equal(result.enableBabelRuntime, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use deepEqual to ensure that nothing else gets added?

{ hermesParser: false },
{ hermesParser: true }
);
equal(result.hermesParser, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use deepEqual to ensure that nothing else gets added?

transform: {
experimentalImportSupport: true,
inlineRequires: false,
nonInlinedRequires: ["react"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for how arrays should be handled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: cli This is related to CLI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants