Skip to content

Instantly share code, notes, and snippets.

@artyorsh
Last active June 21, 2023 16:36
Show Gist options
  • Save artyorsh/6be25e4724173843d4baef1b284b91bc to your computer and use it in GitHub Desktop.
Save artyorsh/6be25e4724173843d4baef1b284b91bc to your computer and use it in GitHub Desktop.
Metro and Babel monorepo resolver functions

React Native Metro and Babel resolver functions for monorepo projects

Have you ever faced troubles when configuring a fresh react-native app to test your framework locally? These utility functions will help you to configure metro and babel to run your monorepo-structured framework and test it's modules without a need to publish them.

Simply copy and paste the files below into your project root and follow the guides explained in both of them.

See an Example project


Usage:

  • Install both framework and application dependencies with npm i && ( cd ./path-to/framework && npm i )
  • Install babel-plugin-module-resolver with npm i -D babel-plugin-module-resolver
  • Copy and paste babel.resolver.js and metro.resolver.js to your project root

Configure babel.config.js

const monorepoResolver = require('./babel.resolver');
const { root, alias } = monorepoResolver({
  path: '../path-to/monorepo-project',
  modules: [
    'subpath-to/module-1',
    'subpath-to/module-2',
    // ...etc
  ],
});

const moduleResolverConfig = {
  root,
  alias: {
    ...alias,
    // additional aliases
  }
};

module.exports = function (api) {
  api.cache(true);

  const presets = [
    'module:metro-react-native-babel-preset',
  ];

  const plugins = [
    ['module-resolver', moduleResolverConfig],
  ];

  return { presets, plugins };
};

Configure metro.config.js

const monorepoResolver = require('./metro.resolver');
const { extraNodeModules, watchFolders } = monorepoResolver(structure);

module.exports = {
  resolver: {
    extraNodeModules: {
      ...extraNodeModules,
      // any other node modules if needed
    },
  },
  watchFolders: [
    ...watchFolders,
    // any other watch folders if needed
  ],
};

Useful links:

/**
* Babel monorepo resolver function
* More details: https://babeljs.io/docs/en/configuration#babelconfigjs
* Make sure to install monorepo project dependencies and current project dependencies
*
* ```
* npm i && (cd ./path-to/monorepo-project && npm i)
* ```
*
* Also you have to install `babel-plugin-module-resolver`
* ```
* npm i -D babel-plugin-module-resolver
* ```
*
* Usage (in babel.config.js):
*
* ```
* const monorepoResolver = require('./babel.resolver');
*
* const { root, alias } = monorepoResolver({
* path: 'path-to/monorepo-project',
* modules: [
* 'path-to/module-1',
* 'path-to/module-2',
* ...
* ],
* });
*
* const moduleResolverConfig = {
* root,
* alias: {
* ...alias,
* // additional aliases
* },
* };
*
* module.exports = function (api) {
* api.cache(true);
*
* const presets = [
* 'module:metro-react-native-babel-preset',
* ];
*
* const plugins = [
* ['module-resolver', moduleResolverConfig],
* ];
*
* return { presets, plugins };
* };
* ```
*/
const path = require('path');
module.exports = (structure) => {
const frameworkPath = path.resolve(__dirname, structure.path);
const alias = structure.modules.reduce((acc, frameworkModule) => {
const frameworkModulePath = path.resolve(frameworkPath, frameworkModule);
const frameworkModuleConfig = path.resolve(frameworkModulePath, 'package.json');
return { ...acc, [frameworkModuleConfig.name]: frameworkModulePath }
}, {});
return {
root: path.resolve('./'),
alias,
};
};
/**
* Metro monorepo resolver function
* More details: https://facebook.github.io/metro/docs/en/configuration
* Make sure to install monorepo project dependencies and current project dependencies
*
* ```
* npm i && (cd ./path-to/monorepo-project && npm i)
* ```
*
* Usage (in metro.config.js):
*
* ```
* const monorepoResolver = require('./metro.resolver');
*
* const { extraNodeModules, watchFolders } = monorepoResolver({
* path: 'path-to/monorepo-project',
* modules: [
* 'path-to/module-1',
* 'path-to/module-2',
* // etc
* ],
* });
*
* module.exports = {
* resolver: {
* extraNodeModules: {
* ...extraNodeModules,
* // additional modules
* }
* },
* watchFolders: [
* ...watchFolders,
* // additional watch folders
* ]
* };
* ```
*/
const path = require('path');
const appDependenciesPath = path.resolve('./node_modules');
const getModuleConfig = (modulePath) => {
const packageJsonPath = path.resolve(modulePath, 'package.json');
return require(packageJsonPath);
};
const getModuleDependencies = (config, name) => {
if (config[name]) {
return Object.keys(config[name]);
}
return [];
};
const createResolver = (dependencies, basePath) => {
return dependencies.reduce((acc, dep) => {
return {
...acc,
[dep]: path.resolve(basePath, dep),
};
}, {});
};
const createWatchFolders = (dependencies, basePath) => {
return dependencies.map((dep) => {
return path.resolve(basePath, dep);
});
};
module.exports = function (structure) {
const frameworkPath = path.resolve(__dirname, structure.path);
const frameworkDependenciesPath = path.resolve(frameworkPath, 'node_modules');
const extraNodeModules = structure.modules.reduce((acc, frameworkModule) => {
const moduleConfig = getModuleConfig(path.resolve(frameworkPath, frameworkModule));
const moduleDependencies = getModuleDependencies(moduleConfig, 'dependencies');
const modulePeerDependencies = getModuleDependencies(moduleConfig, 'peerDependencies');
return {
...acc,
...createResolver(moduleDependencies, frameworkDependenciesPath),
...createResolver(modulePeerDependencies, appDependenciesPath),
};
}, {});
const watchFolders = structure.modules.reduce((acc, frameworkModule) => {
const moduleConfig = getModuleConfig(path.resolve(frameworkPath, frameworkModule));
const moduleDependencies = getModuleDependencies(moduleConfig, 'dependencies');
return [
...acc,
...createWatchFolders(moduleDependencies, frameworkDependenciesPath),
path.resolve(structure.path, frameworkModule),
];
}, []);
return {
extraNodeModules,
watchFolders,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment