Many people from the RN core team have asked how the integration between the Facebook repo and GitHub works. I wrote this up to provide some of that context.
Facebook has a monorepo that looks something like this:
root/
├── backendservices/
│ ├── servicecode1/
│ ├── servicecode2/
│ └── servicecode3/
├── crossplatform/
├── metro-bundler/
├── product-code/
| ├── facebook/
| └── oculus/
└── react-native-github/
The root/crossplatform/react-native-github
folder is the source of truth for the code we see on the React Native GitHub repo. An engineer can make a single PR that changes code across the entire monorepo. Any time a commit lands on Facebook's master that changes code in the react-native-github
folder, those changes from the commit are pushed to GitHub. The commit could also touch files in other GitHub repos like Metro. This can cause commits with the same title, summary, and source ID appear in both repos at the same time. This can sometimes cause commits on GitHub to seem cryptic because it is also part of a larger internal bug fix of which only a small portion of the relevant files are in OSS. You can see some of this information in the commit messages on GitHub. For example, on this commit, it came from the internal to Facebook change ID “D9727774”.
When we accept pull requests from GitHub, since the source of truth is the internal FB repo, we don't actually use GitHub's merge functionality. We trigger a job in Facebook's infrastructure to import that PR from GitHub, create an internal change which triggers all of the tests we have internally, and if those pass successfully then the change will land into the FB repo, which as described earlier triggers the changes to be pushed back to GitHub. You can see this information on commit messages that came from the community. It includes the pull request it originated from, the change ID that was created internally, and the ID of the “shipit” job used to sync those changes back to GitHub master.
While this workflow is much more complicated than just simply merging on GitHub, creating intermediate FB internal changes provides React Native with a ton of benefits. FB has some of the biggest React Native applications in existence in our monorepo. When an internal change is created a bunch of tests are run:
- React Native tests. This is essentially the same as what is in CircleCI with some differences (these differences are what cause open source CI to break even though things pass internally)
- Unit, Integration, and end-to-end tests written by product teams.
- Screenshot tests. Facebook has a component library written with React Native and every variant of these components have screenshot tests. Product teams also have screenshot tests for their most critical views as well. If we make a change that subtly changes the visual result, these tests catch these changes.
- Performance tests. Marketplace is the biggest feature written in React Native at Facebook with over 800 million monthly active users around the world on every device type imaginable. In collaboration with the Marketplace team we've worked on instrumentation and tooling to make sure we are making things faster. We can trigger high accuracy performance tests that can articulate what things have gotten faster or slower and by how much as well as changes in memory usage. We also have tools that identify risky changes and trigger these performance tests automatically.
- Bundle size. We measure the impact to the Facebook JavaScript bundle for all changes ensuring that we don't accidentally include a new library or break one of the Metro transforms like removing
__DEV__
code in production (both of these things have been caught before landing).
Over the last year, about once a month, all the changes on master get cut into a new release. To start, we release a few Release Candidates; releases that need to be tested and stabilized before becoming the recommended latest release. These releases are stabilized by merging specific commits that include fixes from master into the release branch. Between releases there are often hundreds of commits on master that haven't been picked into a release branch and will be included in the next release and the process starts over. It looks something like this:
Most users of React Native use a version older than the latest release. When we ship release candidates, even fewer people test out those changes on meaningful apps in production. All internal FB apps including the main Facebook app are always shipping React Native from master, ensuring that these commits are in front of millions of users before they are ever even considered for an RC. By running directly on master it is easy for Facebook infrastructure to measure the impact of every commit on core metrics like crash rate, bundle size, and performance, allowing us to bisect and revert bad commits before they impact the open source community. Facebook shipping from master to production has been helpful for the stability of releases for the community.