Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Struggling to set up a monorepo with 2 RN apps and a shared package that includes native code #826

Closed
maxkorp opened this issue Oct 24, 2019 · 20 comments
Assignees
Labels
bug Something isn't working question Further information is requested

Comments

@maxkorp
Copy link

maxkorp commented Oct 24, 2019

Ask your Question

Sorry for the impending wall of text, and thank you in advance for any help.

I have a monorepo (using yarn workspaces) that has several non-rn apps (a graphql server, 2 different websites, others) and as of now a single react-native ios/android app. This works by just nohoisting everything RN related, and some metro config to make sure it can find the js in the other packages. Everything related I've found (mostly issues in this repo, honestly) is more about getting a single RN app to work in a monorepo.

I'm looking for help with configuring everything to make this work. This is rapidly approaching being a blocker for some of my team, so I can definitely dedicate some decent time to working on cli if needed. My native-fu is somewhat weak, but I'm happy to jump in if I can get some some guidance. I am flexible.

Here is my desired project structure:

.
├── package.json
└── packages
    |
    ├── shared (this is shared between everything that wants to use it)
    |   ├── package.json
    |   └── js (does not reference native code)
    |
    ├── shared-browser
    |   ├── package.json
    |   └── js
    |
    ├── shared-mobile
    |   ├── package.json
    |   └── js (references native code)
    |
    ├── mobile-professional (first-mobile-app)
    |   ├── package.json
    |   ├── ios
    |   ├── android
    |   └── js (references native code, shared-mobile, and shared-everywhere)
    |
    ├── mobile-business (second-mobile-app)
    |   ├── package.json
    |   ├── ios
    |   ├── android
    |   └── js (references native code)
    |
    ├── first-web-app
    |   ├── package.json
    |   └── js
    |
    ├── second-web-app
    |   ├── package.json
    |   └── js
    |
    ├── non-js-package
    ├── non-js-package
    └── non-js-package

Currently, it's basically the same as that except that there is only one mobile app, and the shared-mobile folder doesn't reference any native code.

We're trying to add a second RN android/ios app to the mix. Currently we have a single shared folder for stuff accessible everywhere. I'm first trying to get the single app to work with the native code hoisted, since that seems prerequisite to the shared code being able to be hoisted. Assuming that works, I'd be able to get stuff working by not having any shared native code, but this involves a ton of code duplication with tedious reference changes.

Ideally, we'd like to add the majority of the native stuff to our shared-mobile folder. I was able to get IOS to work with a bit of a hack, but I've been unable to get android working. I've been trying both with what ships with RN 0.61.2, as well as upgrading the cli to the latest 3 alpha (7?).

For the ioS stuff, to get it working, I simply updated the xcode project and podfile to look for stuff at ../../../node_modules instead of ../node_modules, and then I duplicated the .command file that gets opened in a new window and added a step to CD into the packages/mobile-professional folder (otherwise it runs in the wrong directory and metro doesn't pick up the config that I've got inside that folder).

For the android stuff, I first adjusted the build.grade, app/build.gradle and settings.graddle to look up 2 directories more than before, which gets me farther but then i get A problem occurred evaluating settings ... Text must not be null or empty. I've tried various react-native.config.js values in both the root and my folder (and pointing the android dir within both to ./android and ./packages/mobile-professional/android) but that does not work with the old cli or the new. If I modify the native_modules.gradle file that gets installed as part of cli, (or the android subcomponent), so that it runs react-native config in the mobile folder rather than the root, I finds what it wants, but then I get some build errors about not being able to find android.support.annotations.nullable in one of the native components, saying more specifically that android.support.annotations doesn't exist, which feels like a linking issue.

Just to make sure it's not a hangup, re: react-native.config.js, i've also moved the limited rnpm stuff (just assets) in there as well out of the package.json

I'm trying to set up an example repo, as ours is closed and stripping things out has proven time consuming (we have a lot of process going on, everything is built in reasonml and compiled with bucklescript down to JS). In the meantime I'm posting original versions of my build and config files, and new versions.

app-build.gradle-modified.txt
app-build.gradle-original.txt
build.gradle-modified.txt
build.gradle-original.txt
metro.config.js.txt
mobile-professional-package.json.txt
rn-cli.config.js.txt
settings.gradle-modified.txt
settings.gradle-original.txt

Sorry again for the huge wall of text, but I figured it was better to provide everything relevant rather than too little.

@maxkorp maxkorp added the question Further information is requested label Oct 24, 2019
@maxkorp
Copy link
Author

maxkorp commented Oct 28, 2019

Update, added a single-native-app monorepo that breaks when I include a native module (react-native-add-calendar-event, in this case)

https://github.com/maxkorp/my-new-monorepo

This works (mostly) with iOS. Just yarn && cd packages/mobileProfessional/ios && pod install && cd ../../.. && yarn workspace mobileProfessional ios. However I get some error package android.support.annotation does not exist (referenced inside /Users/maxkorp/Development/work/my-new-repo/node_modules/react-native-add-calendar-event/android/src/main/java/com/vonovak/Utils.java). I get somewhat similar errors with other native modules.

I modifed the root level native_modules.gradle (at ../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle) to have println(reactNativeConfigOutput) right before it parses the output, and everything looks to be in the correct location (output below)

output.txt

Also of note that metro starts in the wrong directory (for both ios and android), but if I also run yarn workspace mobileProfessional start then it works properly.

@maxkorp
Copy link
Author

maxkorp commented Oct 29, 2019

Aha! So it turns out the jetifier runs automatically, but is not finding the modules at the root directory;
if I run cd packages/mobileProfessional && npx jetifier it finds nothing, but if I just run npx jetifier at the root it works. Still does not fix the metro pathing.

@grabbou
Copy link
Member

grabbou commented Nov 14, 2019

Hey,

Thanks for deep explanation of your issue. Please run react-native config from within your React Native mobile project and paste the output here or email me and we will go from there.

As a principle, you should always run the CLI from the root of your React Native project (especially, when you have multiple React Native projects).

Are you using Yarn Workspaces? If not, you should, and each package that references React Native should have it as its dependency. I suspect that React Native lives at the root of your monorepo, which is good.

CLI shouldn't have issues finding and linking things in such scenario, so I guess we have a small configuration issue to resolve.

(PS. Updating paths a bit is a normal thing to do, so don't worry. Ideally, I'd like to replace paths with React Native CLI calls so you dont have to update that).

@maxkorp
Copy link
Author

maxkorp commented Nov 14, 2019

Thanks for getting back to me!

So we do use yarn workspaces sortof , but due to how we have our bucklescript setup, we can't really do the dependency thing where moduleA uses moduleB so it has moduleB in it's dependencies and it gets linked in (the ocaml compiler doesn't work with the symlinks, everything is compiled in place basically eg packages/packageA/myfile.re becomes packages/packageA/myfile.bs.js which would just require('../../packageB/myOtherFile.bs.js)).

I have a bunch of new notes on this as we've found stuff. Currently we have almost working with some hackery, except for loading images out of hoisted node_modules on ios only during production builds. I'll try to write everything up today and update the example repo

@maxkorp
Copy link
Author

maxkorp commented Nov 14, 2019

So there are currently a few issues. A lot of this is metro related I suspect more than anything else, but I don't know how much of it is something to resolve here in the meantime. At least one or two issues are with scripts from react-native itself, but
I don't know how much of that actually belongs here or is influenced by this project etc.

If you would like me to make individual issues for any or all of these, or file issues against other projects (like react-native or metro), just let me know and I am happy to do so! Or if you need any help digging into this all, I can make myself available at any time of day (IIRC You are in Europe, I can sync up with you during your workday). I'm also happy to crank on the tooling and submit PRs if you know which direction you want to go on things.

As a principle, you should always run the CLI from the root of your React Native project (especially, when you have multiple React Native projects).

Do you mean in this case the root of the monorepo, or the specific workspace? We've been always doing it from the workspace (e.g. monorepo/packages/myApp). I get results indicating I've not nothing initialized when done at the monorepo root, which I'd kind of expect.

I've attached the output of npx react-native config from my actual repo (not the example repo). I couldn't find anything that needed redaction and I don't have the example app all up to date with my changes.

rn-config-output.txt

Here are all of the issues i've found trying to get things set up, that aren't as simple as changing some paths.

Jetifier does not get run at the root properly

This is the actual cause of the issue described here: #826 (comment). It seems like it runs jetify inside the rn project workspace, so no node_modules get found (since they're all hoisted to the monorepo root),

Once I run npx jetify in the root of my monorepo, it is good. I just have this in the beggining of my scripts that run or build android.

Metro gets started in the wrong place and does not pick up the config

In the "start packager" step in xcode (I forget where it happens for android, somewhere in react-native in a gradle file IIRC), it kicks off the metro server with open somepath/run-metro.command. That opens metro relative to where it is installed, instead of in the actual RN project.

I've solved this for now by duplicating that file into my project, and adding a CD to the project directory first.

SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
cd "$SCRIPT_DIR"

Not ideal, but it works. perhaps run-metro.command could take an arg or an environment variable to cd to the project dir? I don't know if open supports that or not.

files loaded out of node_modules or shared get paths messed up on android

I found an issue about this in metro (I can't find it now), it seems okhttp resolves relative paths away before making requests (e.g. assets/../shared-mobile/someFile gets loaded as shared-mobile/someFile which is not in the search directories). It seems they intend to move this to the query string rather than the request path, which would resolve this particular issue (although the relative pathing still causes issues with prod I'll outline below)

I fixed this in a bit of a hack by adding the root of my monorepo as well as the packages folder to the metro config search directories.

platform specific images do not resolve out if assets is not in the request path

With the hack I mentioned above, I could get everything to load with the exception of platform specific files e.g. requesting assets/../../node_modules/some-module/myfile.png?platform=androidWILL findproject/../../node_modules/some-module/myfile.android.png, but ../node_modules/some-module/myfile.png?platform=android` will NOT resolve that file.

To fix this (please forgive my sins, time was of the essence for my team), I actually went full Hackstradamus and threw up a proxy on 8081 that proxies to the metro server (which I now start on 8082) that on a 404 response, will see if the requested platform is android, if we have a file extension, and the file extension is not .android.something, and if so, tries requesting the .android file explicitly and proxies that back. This whole problem might just drop away by resolving the previous issue.

When packaging for release, iOS builds lose images from node_modules (or anything with more than one ../ in it's path)

So this one is kind of interesting, relating to how filepaths are copied over directly when bundling. On android, the paths seem to get flattened into for example drawable-hdpi/__node_modules_reactnavigationstack_lib_module_views_assets_backicon.png, so any relative path issues are resolved. On ios when called via react-native/scripts/react-native-xcode.sh the image files are resolved compared to the assets folder inside the myApp.app. So, for things with only a single updirectory in the relative path, this is ok (e.g. assets/../shared/something/myimage.png ends at someXcodeBuildFolder/myApp.app/shared/something/myimage.png), but anything farther than that it breaks because stuff is outside of the .app (e.g. assets/../../node_modules/some_module/someImage.png ends up at someXcodeBuildFolder/node_modules/some_module/someImage.png, not inside someXcodeBuildFolder/myApp.app). JS files are properly bundled into the main.jsbundle file, so it's just images (and possibly other types of asset files? I couldn't find any others). This is not an issue when running locally in the simulator, since it can properly resolve that path in my project structure.

I thought of moving everything that is resolved up from assets inside of assets and just replacing assets\/(\.\.\/)* with assets/ in the main.jsbundle, but that is messy since there is lots of weird symlinking that happens at build time and I don't know how and when all of it happens and stuff gets moved in the process. I might change the assetsdir when packaging to something more deeply nested, and then move it out and change the main.jsbundle.

For now, we've just copied those images out of node_modules and into our codebase, but this isn't a good long term solution, as we have no guarantee someone wont start relying on an image in node_modules later, and not see it since it works everywhere in dev and on android in prod.

ios hits metro on 8081 regardless of specified port

This isn't directly monorepo relevant, but I found it setting up to be able to run both apps at once by running metro on 2 ports. When you start android with run-android and give it a different packager port (e.g. --port 8082), it starts metro on that port, and then it makes requests to localhost at that port e.g. http://localhost:8082/somepath. On iOS it starts metro on that port, but it still requests http://localhost:8081/somepath.

I didn't really find a solution for this, other than running android on a different port, and ios on the default.

@grabbou grabbou self-assigned this Nov 24, 2019
@grabbou
Copy link
Member

grabbou commented Nov 24, 2019

Thank you for sending this! I will look into it this week. There's some valid points here and I'd like to take some time to understand it and see if there's anything we can do to improve your DX. Appreciate time put into describing this.

@maxkorp
Copy link
Author

maxkorp commented Nov 28, 2019

Sure thing! Please let me know if you have any questions or need me to try anything!

@sibelius
Copy link
Member

sibelius commented Dec 4, 2019

@Minishlink
Copy link

@sibelius Does your repo structure have the issue mentioned above: When packaging for release, iOS builds lose images from node_modules (or anything with more than one ../ in it's path)? This can be seen if you use react-navigation with a StackNavigator and you don't see the back arrow icon

@sibelius
Copy link
Member

sibelius commented May 4, 2020

everything is working fine for us

@Matgsan
Copy link

Matgsan commented Jun 18, 2020

@Minishlink were you able to solve? Experiencing the same issue with assets.

@sibelius
Copy link
Member

I think we got some problems with assets in both android and iOS

IOS works well in dev mode, but android and iOS file to show images

@Matgsan
Copy link

Matgsan commented Jun 18, 2020

@sibelius In DEV mod is OK. The problem is on release mode the assets got messed up as @Minishlink said

@Matgsan
Copy link

Matgsan commented Jun 18, 2020

Just to support what I am saying image

@Minishlink
Copy link

Here's a probable workaround, make sure you have the same cli and RN version. I don't give any warranty on this. If you use this, test it thoroughly in debug/release mode on Android/iOS. Also make sure to understand how patch-package work. https://gist.github.com/Minishlink/14fde025b4352d0802e25e098e357046

@maxkorp
Copy link
Author

maxkorp commented Jun 25, 2020

Here is an example repo I set up that shows what we've done to get things working

https://github.com/maxkorp/codepush-monorepo-example. There are some commits on master that are related to similar issues for codepush, but you can go back to the second commit to see how I got things working.

@Wellers0n
Copy link

Wellers0n commented Nov 19, 2020

I have a problem with my monorepo, it seems that when I run the install it does not install my modules in the global package.
image

@liamjones
Copy link
Contributor

@Wellers0n autolinking only works in the direct RN app dependencies - you'll need native modules to be nohoist-ed dependencies of the app directly as well as the global package.

@sibelius
Copy link
Member

assets are still broken on production mode

is there a workaround for this?

for instance, if you use react-navigation the hamburger menu will be missing in the prod version

@sibelius
Copy link
Member

it works on rn 63

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working question Further information is requested
Projects
None yet
Development

No branches or pull requests

7 participants