CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metro-config

Config parser and resolver for Metro bundler with support for loading, merging, and validating configuration files.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

config-merging.mddocs/

Configuration Merging

Deep merge multiple Metro configurations with validation and type safety.

Capabilities

Merge Configuration

Deeply merges multiple Metro configuration objects with intelligent handling of arrays, objects, and functions.

/**
 * Deep merges multiple Metro configuration objects
 * @param defaultConfig - Base configuration object  
 * @param configs - Additional configurations to merge (later configs take precedence)
 * @returns Complete merged Metro configuration
 */
function mergeConfig<T: $ReadOnly<InputConfigT>>(
  defaultConfig: T,
  ...configs: Array<InputConfigT>
): T;

Usage Examples:

import { getDefaultConfig, loadConfig, mergeConfig } from "metro-config";

// Merge user config with defaults
const defaultConfig = await getDefaultConfig();
const userConfig = await loadConfig();
const finalConfig = mergeConfig(defaultConfig, userConfig);

// Merge multiple configurations
const baseConfig = {
  resolver: {
    sourceExts: ['js', 'jsx'],
    platforms: ['ios', 'android']
  },
  server: {
    port: 8081
  }
};

const platformConfig = {
  resolver: {
    sourceExts: ['js', 'jsx', 'ts', 'tsx'], // Replaces array
    assetExts: ['png', 'jpg'] // Adds new property
  }
};

const customConfig = {
  transformer: {
    babelTransformerPath: 'custom-transformer'
  }
};

// Merge multiple configs - later configs take precedence
const merged = mergeConfig(baseConfig, platformConfig, customConfig);
// Result:
// {
//   resolver: {
//     sourceExts: ['js', 'jsx', 'ts', 'tsx'],
//     platforms: ['ios', 'android'],
//     assetExts: ['png', 'jpg']
//   },
//   server: {
//     port: 8081
//   },
//   transformer: {
//     babelTransformerPath: 'custom-transformer'
//   }
// }

Merge Behavior

Object Properties

Objects are deeply merged, with properties from the second config taking precedence:

const config1 = {
  resolver: {
    sourceExts: ['js'],
    platforms: ['ios'],
    blockList: /node_modules/
  }
};

const config2 = {
  resolver: {
    sourceExts: ['js', 'ts'], // Overrides
    assetExts: ['png'] // Adds new property
    // platforms and blockList preserved from config1
  }
};

const merged = mergeConfig(config1, config2);
// resolver.sourceExts = ['js', 'ts']
// resolver.platforms = ['ios'] (preserved)
// resolver.assetExts = ['png'] (added)
// resolver.blockList = /node_modules/ (preserved)

Array Properties

Arrays from the second configuration completely replace arrays from the first:

const config1 = {
  resolver: {
    sourceExts: ['js', 'jsx']
  }
};

const config2 = {
  resolver: {
    sourceExts: ['ts', 'tsx'] // Completely replaces
  }
};

const merged = mergeConfig(config1, config2);
// resolver.sourceExts = ['ts', 'tsx'] (not merged)

Function Properties

Functions from the second configuration replace functions from the first:

const config1 = {
  transformer: {
    getTransformOptions: async () => ({ 
      transform: { inlineRequires: false } 
    })
  }
};

const config2 = {
  transformer: {
    getTransformOptions: async () => ({ 
      transform: { inlineRequires: true } 
    })
  }
};

const merged = mergeConfig(config1, config2);
// Uses config2's getTransformOptions function

Primitive Properties

Primitive values (strings, numbers, booleans) from the second config override the first:

const config1 = {
  server: {
    port: 8081,
    forwardClientLogs: false
  }
};

const config2 = {
  server: {
    port: 3000 // Overrides
    // forwardClientLogs preserved from config1
  }
};

const merged = mergeConfig(config1, config2);
// server.port = 3000
// server.forwardClientLogs = false (preserved)

Advanced Merging Patterns

Extending Array Configuration

To extend rather than replace arrays, pre-process your configuration:

import { getDefaultConfig, mergeConfig } from "metro-config";

async function createExtendedConfig(userConfig) {
  const defaultConfig = await getDefaultConfig();
  
  // Extend sourceExts rather than replace
  const extendedConfig = {
    ...userConfig,
    resolver: {
      ...userConfig.resolver,
      sourceExts: [
        ...defaultConfig.resolver.sourceExts,
        ...(userConfig.resolver?.sourceExts || [])
      ]
    }
  };
  
  return mergeConfig(defaultConfig, extendedConfig);
}

Conditional Configuration Merging

Handle environment-specific configurations:

import { getDefaultConfig, mergeConfig } from "metro-config";

async function createConfig() {
  const baseConfig = await getDefaultConfig();
  
  const developmentConfig = {
    server: {
      port: 8081,
      forwardClientLogs: true
    },
    resetCache: true
  };
  
  const productionConfig = {
    transformer: {
      minifierPath: 'metro-minify-terser',
      minifierConfig: {
        mangle: { toplevel: true },
        compress: { drop_console: true }
      }
    }
  };
  
  const envConfig = process.env.NODE_ENV === 'production' 
    ? productionConfig 
    : developmentConfig;
    
  return mergeConfig(baseConfig, envConfig);
}

Function Configuration Merging

Compose transform functions when merging:

const baseConfig = {
  transformer: {
    getTransformOptions: async (entryPoints, options) => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false
      }
    })
  }
};

const enhancedConfig = {
  transformer: {
    getTransformOptions: async (entryPoints, options) => {
      // Get base transform options
      const baseTransform = await baseConfig.transformer.getTransformOptions(
        entryPoints, 
        options
      );
      
      // Enhance with additional options
      return {
        ...baseTransform,
        transform: {
          ...baseTransform.transform,
          inlineRequires: true // Override specific option
        },
        preloadedModules: false
      };
    }
  }
};

const merged = mergeConfig(baseConfig, enhancedConfig);

Validation After Merging

The merged configuration is automatically validated:

import { mergeConfig } from "metro-config";

try {
  const merged = mergeConfig(config1, config2);
  // Configuration is valid and ready to use
} catch (error) {
  if (error.name === 'ValidationError') {
    console.error('Merged configuration is invalid:', error.message);
    // Handle validation errors
  }
}

Type Safety

When using TypeScript, the merge function provides type safety:

import type { InputConfigT } from 'metro-config';
import { mergeConfig } from 'metro-config';

const config1: InputConfigT = {
  resolver: {
    sourceExts: ['js', 'jsx']
  }
};

const config2: InputConfigT = {
  resolver: {
    sourceExts: ['ts', 'tsx'],
    // TypeScript ensures all properties are valid
  }
};

const merged = mergeConfig(config1, config2);
// merged has complete ConfigT type

docs

config-loading.md

config-merging.md

default-config.md

index.md

tile.json