Create Metro config transformers that work in all possible combinations
π Handles objects, functions, promises, and any combination with full type safety
Metro config transformers are a powerful way to customize your Metro bundler configuration, but there's a critical problem: there's no standardization for how transformers should work.
-
No Standardization: Different transformer libraries handle Metro configs differently. Some expect objects, others expect functions, and some return Promises.
-
Order dependency issues: Certain transformers need to return a Promise (async operations), which means they must be applied last. However, there's nothing preventing users from calling them first, leading to:
- Silent failures
- Runtime errors
- Frustration and confusion
- GitHub issues being created unnecessarily
-
Composition complexity: Manually composing transformers that handle different input/output types (objects, functions, promises) is error-prone and requires deep understanding of Metro's internals.
This library serves as a foundation for config transformers, ensuring they work correctly in all possible combinations. Developers no longer need to be vigilant about:
- β Order of transformers - works correctly regardless of composition order
- β Input/output types - handles objects, functions, promises seamlessly
- β Async operations - Promise-based transformers work correctly in any position
npm install metro-config-transformers
# or
yarn add metro-config-transformers
# or
pnpm add metro-config-transformers
# or
bun add metro-config-transformersUse createMetroConfigTransformer to create a transformer that handles all Metro config formats:
import { createMetroConfigTransformer } from "metro-config-transformers";
import type { ConfigT as MetroConfig } from "metro-config";
const withCustomTransformer = createMetroConfigTransformer(
(config: MetroConfig) => {
return {
...config,
resolver: {
...config.resolver,
sourceExts: [...(config.resolver.sourceExts || []), "custom"],
},
};
}
);
// Works with all config formats:
const config1 = withCustomTransformer({
/* config object */
});
const config2 = withCustomTransformer(() => ({
/* config object */
}));
const config3 = withCustomTransformer(
Promise.resolve({
/* config object */
})
);
const config4 = withCustomTransformer(async () => ({
/* config object */
}));Transformers can be async and work correctly regardless of order:
const withAsyncTransformer = createMetroConfigTransformer(
async (config: MetroConfig) => {
const someAsyncData = await fetchSomeData();
return {
...config,
// Use asyncData in config
};
}
);Transformers can accept options:
const withOptionsTransformer = createMetroConfigTransformer<{
projectRoot: string;
}>((config: MetroConfig, options) => {
return {
...config,
projectRoot: options?.projectRoot || config.projectRoot,
};
});
// Usage
const config = withOptionsTransformer(baseConfig, { projectRoot: __dirname });Use composeMetroConfigTransformers to combine multiple transformers:
import { composeMetroConfigTransformers } from "metro-config-transformers";
const combinedTransformer = composeMetroConfigTransformers(
withCustomTransformer,
[withOptionsTransformer, { projectRoot: __dirname }], // Tuple syntax for options
withAsyncTransformer, // Works correctly even if async!
anotherTransformer
);
// Apply to your config
module.exports = combinedTransformer(getDefaultConfig(__dirname));Creates a Metro config transformer that handles all possible config formats.
Parameters:
mutate:(config: MetroConfig, options?: TOptions) => MetroConfig | Promise<MetroConfig>- Function that performs the actual config transformation
- Can be sync or async
Returns: A transformer function that accepts any Metro config format and preserves its "shape":
- Object input β Object output (or Promise if transformation is async)
- Promise input β Promise output
- Function input β Function output
- Async function input β Async function output
- Promise of function input β Promise of function output
Composes multiple transformers together using a Babel-like tuple syntax.
Parameters:
entries: Array ofTransformerEntry- Can be a plain transformer function:
withTransformer - Or a tuple with options:
[withTransformer, options]
- Can be a plain transformer function:
Returns: A composed transformer function that applies all transformers in sequence
Type:
type TransformerEntry = Transformer<void> | [Transformer<TOptions>, TOptions];Contributions are welcome! Please feel free to submit a pull request.
MIT Β© Szymon Chmal