metro: Modules required from outside of root directory does not find node_modules
Hi! I hope I post this in the right place, sorry if I’m not.
I’m trying to setup requiring files outside of the root project directory, as I’m building both a web app and a native app that’ll share some code. I’ve configured this using rn-cli.config.js, and I can successfully import my shared files from outside of my root.
However, the files imported from outside of the root does not find any modules from node_modules (React for example). Files required from inside of the project root does find them (which I guess is because node_modules is in a parent directory to them). I just get a Cannot resolve module 'React'.
The folder structure looks like this:
common
- components
- - ....code
web
- node_modules
- ....code
native
- node_modules
- ...code, rn-cli.config.js, etc...
rn-cli.config.js:
const path = require('path');
module.exports = {
getProjectRoots() {
return [
path.resolve(__dirname, '../common'),
path.resolve(__dirname, 'node_modules'),
path.resolve('.')
]
}
};
If I add a package.json to common and install react there it works. So I guess this has to do with the packager walking the file upwards and not finding node_modules as it’s in a sibling directory and not a parent directory?
Am I missing something obvious here? I’ve tried clearing the cache etc. I’m on the latest Expo which I’m pretty sure uses RN 0.43.
Thanks in advance!
About this issue
- Original URL
- State: open
- Created 7 years ago
- Reactions: 51
- Comments: 39 (4 by maintainers)
I’m seeing the same thing too @Edmond-XavierCollot - thank you @denwakeup for the fix. Here’s an updated
metro.config.jsfile that should be used for RN 0.59+.I came to the same conclusion too! Yes I end up with a larger workspace but the benefits of not having to hunt these things down far outweigh the cons of a larger workspace size.
As mentioned above, for RN 0.57 you have to put your
extraNodeModulesinside aresolverobject in yourrn-cli.config.js, and alsogetProjectRoots()is no longer a thing. The example provided above would be rewritten as follows for RN 0.57+:Has there been any progress on this issue? It seems to be a major show stopper for a unified codebase.
Yes. If you do
require('foo'), then it looksnode_modules/foofirst in the directory of the module you’re requiring from, then for each parent directory in turn. So if there’s a node_module somewhere in another nested folder it won’t find it. The roots are not used for the resolution of node-style packages, purposefully. This resolution pattern follow closely the algorithm specified by Node.js: https://nodejs.org/api/modules.html#modules_all_together.So one solution is to have all common dependencies be installed in the root directory, that can be resolved from anywhere. Another solution might be to use the
extraNodeModulesfield to specify things you want available manually. Ex. something like:@reggie3 @CompuIves Here is the link to the
extraNodeModulesdocumentation: https://facebook.github.io/metro/docs/en/configuration#resolver-options@jeanlauliac’s syntax is the one I’ve been using.
I’m working in a monorepo with yarn workspaces. I’m using a combination of getProjectRoots to tell metro where to find packages. And then using extraNodeModules to make sure that the local package goes looking for the
react,react-native, etc module in the right place.My use case is probably not the same as yours but I hope it helps.
Also I’m using
"react-native": "0.55.4".For the record, my final config file for making Storybook find the upper folder
@rafeca - can we get some docs on the above? it’s painful to update to the latest version of react-native on a project that uses rn-cli.config.js right now
So I managed to make this work following these steps:
Added a package.json to my “common” folder.
In the dependencies of my “app1” folder, I added:
“dependencies”: { “common”: “file:…/common/” }
My folder organization is: – app1: app to do X for users of type 1 – app2: app to do X for users of type 2 – common
I’m sure there’s something bad about this structure because I ended up with different node_modules folders: — one under app1: —> this one includes “common”. — another under common — another under app2
Not sure about the side effects here (larger binary?) but this works for now.
The solution provided by @fiznool works. But in RN59,
rn-cli.config.jsis replaced bymetro.config.jsHere’s my solution for workspaces and importing shared code (on a non-Expo app)
@JKillian
This setup works when used with yarn nohoist. nohoist allows you to force modules to stay in project folders and stop them from being hoisted up to the monorepo root.
RN doesn’t work if
react-nativegets hoisted. Therefore, we needed to nohoist it to get RN working in our monorepo.Nohoist is how you can guarantee that the modules end up insider your RN project folder.
Also, any library that has native code has to be no hoisted so that you can
react-native linkit.Be careful though, no hoisting everything reduces the benefits of yarn workspaces and increases install times so I only use it if I have to.
My team and I are hoping that metro (and as a side note: react-scripts) start working nicer with yarn workspaces.
In RN if you import a component from “…/…/components/Button” and that Button component imports
View“react-native”, metro will go looking for “react-native” in the Button package as per the normal module resolution rules.It starts in the component’s folder “…/…/components/Button/node_modules/react-native” and if it doesn’t find it node_modules it climbs up a level checks in node_modules .
By telling metro that react and react-native are located inside the React Native project folder then you override this behaviour so it goes looking in:
I start no hoisting or adding entries to extra node modules when I get a metro errors like:
@willgriffiths thank you for that solution. Based on yours, I made a more generic one:
“dependencies”: { “common”: “file:…/common/” } + npm link works as a workaround
In RN 0.57 the format of the Metro config options has changed slightly, now the
extraNodeModulesparam is inside theresolverfield (there’s more information in the metro docs).@RomualdPercereau are you configuring the
extraNodeModules?With the 0.57 of react-native, it looks like the extraNodeModules feature doesn’t work anymore?
Something that worked for me, mixture of existing solutions, for an Expo app that is not yet part of a monorepo:
The solution of @fiznool works but if I import a file that needs transpiling by
babelI got an errorUnable to resolve "@babel/runtime/helpers/interopRequireDefault" from "../../outsideFile.js"And adding
"@babel": path.resolve(__dirname, "node_modules/@babel")inextraNodeModulesdoes not work.Any idea ?
i am in version RN0.57, latest version i still have this issue, any solutions? none of the solutions aboved worked
@willgriffiths and @Strate solutions work great. If you’re using TS don’t forget to add your shared code path to
in
tsconfig.jsonas well.For non expo users using this in the root folder package.json:
here was the metro.config.js that worked for me:
@Edmond-XavierCollot I had same problem. @jgcmarins solution works great for me.
Hi!
We added a util method in the
metro-configpackage that should help migrating the old configuration format to the new one.You can call it by doing:
This works as a temporary workaround when upgrading to the latest metro version.
@fredbt yes they do. But that’s not very useful when you’re working on one of your common components. Every time you save the file you need to run npm install. It’s not a very nice workflow at all
Tested with fresh
react-native initand version 0.45, same issue still. How does the packager resolve modules? Is it somehow relative to the file being required, not primarily from the project roots…?I didn’t know about the
convertOldToNewutils, looks good!About the docs: I meant adding it in the release notes the next time (since it’s release-specific).
That said, I’ll try to write a few lines in the weekend 👍
Thanks for the detailed info @willgriffiths 😄
Your example with importing
"../../components/Button"is interesting! I believe you’re doing cross-workspace relative imports here. I may need to accomplish the exact same thing in my situation, so your solution seems perfect.Yep, agreed with the downsides. We actually ended up adding
nohoist: ['**']to our react-native yarn workspace because it was easier than figuring out which needed to be where. (It’s possible that the install times might grow to be obnoxious, we’ll see.)Yes, I hope so too! So far it’s been a bit of a hassle to get the react-native toolchain (metro) to work in a nice way with our old web-only yarn workspaces setup. Hopefully FB will have the resources to improve things, maybe the metro team and the yarn team can have a nice fun offsite together and work on these issues 😆 (Thanks to both teams for all the work they’ve put in to their tools so far!)
Hi @zth I’m facing a similar issue here.
So, if you do the following setup:
Does it work? I agree it sucks to have two node_modules folder, but this could be a temporary solution for me until there’s a proper solution from metro.