How to import files from outside of root directory with React Native Metro bundler

Dushyant Bansal
2 min readJan 25, 2020

There will be many use cases of where you’ll want to import any file outside of the root directory e.g. you may want to share code with your web project.

A simple solution could be to create a symlink inside the root directory but metro bundler doesn’t support symlink. So we’re going to see how to directly reference any file outside of root directory..

Consider this example:

common
- components
- Utils.ts, ....code
web
- node_modules
- ....code
app
- node_modules
- src/
- ...code, metro.config.js, rn-cli.config.js, etc...

Let’s say you want to access common/ from within app/ :

//Project/app/src/App.jsimport { commonFunction } from '../../common/Utils';

But this doesn’t work. When you run npm run start, metro bundler will raise the following error:

error: bundling failed: Error: Unable to resolve module `../../common/Utils` from `src/App.js`:

Solution

We can take advantage of extraNodeModules to include this parent directory and update Metro configuration.

Modified app/metro.config.js :

/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
const path = require('path');
const extraNodeModules = {
'common': path.resolve(__dirname + '/../common'),
};
const watchFolders = [
path.resolve(__dirname + '/../common')
];
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
extraNodeModules
},
watchFolders,
};

Here is the link to the extraNodeModules documentation: https://facebook.github.io/metro/docs/en/configuration#resolver-options.

Since we have mapped common key to common/ path, we can include any files inside common/ relative to this path:

//Project/app/src/App.jsimport {commonFunction} from 'common/Utils';

Metro bundler will happily resolve this for us.

Now what if we want to use a dependency in commmon/ which has been installed in app/node_modules/ .

e.g. lets try to use lodash incommon/Utils.ts :

import _ from 'lodash';

where lodash is installed in app/node_modules/ .

import _ from 'lodash';common
- components
- Utils.ts, ....code
app
- node_modules
--- lodash
- src/

Run the metro bundler again and it will throw this error:

error: bundling failed: Error: Unable to resolve module `@babel/runtime/helpers/interopRequireDefault` from `../common/Utils.ts`: @babel/runtime/helpers/interopRequireDefault could not be found within the project.

Basically, metro bundler is not able to resolve dependencies used inside common/ because it’s not looking for those dependencies in app/node_modules .

Lets modify app/metro.config.js again:

/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
const path = require('path');
const extraNodeModules = {
'common': path.resolve(__dirname + '/../common'),
};
const watchFolders = [
path.resolve(__dirname + '/../common')
];
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
extraNodeModules: new Proxy(extraNodeModules, {
get: (target, name) =>
//redirects dependencies referenced from common/ to local node_modules
name in target ? target[name] : path.join(process.cwd(), `node_modules/${name}`),
}),
},
watchFolders,
};

You can read more about Proxy.

Here we have configured metro bundler resolver to look for any unresolved references in app/node_modules . You can now use dependencies installed in app/node_modules from common/ .

More discussions on this topic:

--

--