Fast JavaScript Bundle Review for React Native
Shopify's quality process relies on tophatting: manually testing a coworker's changes before merging a pull request. With React Native apps, that workflow was slow. Reviewers had to set aside their current branch, check out the feature branch, rebuild the app, and only then verify the changes. Because React Native builds produce a separate JavaScript bundle, rebuilding the whole app for every React-only change was unnecessary overhead. The Mobile Tooling team built a system that isolates that bundle and delivers it on demand, cutting the review cycle down to seconds.
Artifact Storage and Distribution
The core idea is straightforward: store the JavaScript bundle for every new build in cloud storage, then let reviewers fetch that artifact directly rather than compiling it themselves. The existing native tophatting tool stored full build artifacts, which worked well but required rebuilding and re-downloading an entire app even for pure React Native changes. This new tool exploits the fact that a JavaScript-only change produces exactly one needed artifact: the bundle itself.
Whenever a developer opens a pull request or pushes a commit to a React Native project, a CI pipeline in Shopify Build takes over:
- The pipeline builds the app's JavaScript bundle.
- It compresses the bundle with any referenced assets.
- It calls a backend service that records the bundle's metadata — app ID, commit SHA, and branch name — in a SQL database.
- The backend generates a unique bundle ID and a signed cloud storage upload URL.
- The pipeline uploads the artifact.
- The backend posts a comment on the pull request confirming the upload.
That PR comment gives developers three ways to pull the bundle: a QR code that opens the app on a mobile device and downloads the bundle automatically; a bundle ID that can be entered in the app's Tophat screen, which is useful on simulators or emulators; and a direct link that works from a GitHub notification email, letting developers tophat without opening the PR at all.
Loading Bundles in the App
On the client side, a React Native component library handles retrieval and loading. It registers a separate UI overlay — the Tophat screen — and a URL listener for deep link events. Developers inject the component at the root level of their application; the rest is handled for them.
The Tophat screen is a modal overlay that sits outside the app's component tree, so it doesn't intrude on the app's interface. It shows the currently loaded bundle version and includes a reset action to clear the bundle and return to a normal state.
The typical workflow through the library looks like this:
- The reviewer scans the QR code or clicks the PR link, which resolves to a URL in the format
{appId}://tophat_bundle/{bundle_id}. - The URL opens the app and fires a deep link event.
- The component library intercepts the event and parses the app ID and bundle ID.
- If the app ID matches the current application, the library requests a signed download URL and metadata from the backend.
- The Tophat screen displays the metadata and asks the developer to confirm the download.
- On confirmation, the bundle is downloaded, decompressed, and saved with its metadata to local storage; the app then restarts.
- During startup, the app detects the new bundle and runs it instead of the default one.
- Once testing is complete, the developer resets the bundle from the Tophat screen.
Why a Backend Service
The native tophatting implementation got by without a backend service, so its introduction here is a deliberate trade-off worth explaining. There are two principal advantages.
First, it abstracts third-party integration. The tool depends on Google Cloud and GitHub SDKs, both of which require authentication tokens. Without a backend, every consuming app and CI pipeline would need to manage its own tokens and agree on identical storage path formats. The backend centralizes authentication, asset uploads, and GitHub comment creation, and it generates consistent storage paths for every bundle.
Second, it provides scalable metadata management. The native project embedded metadata in the build artifact's filename — workable, but fragile. Adding a field like the commit author would change the storage path format, forcing updates across every app's CI pipeline and the component library. With a backend and a SQL database, metadata is decoupled from storage location. That separation enables richer UI features, such as the bundle confirmation step and the ability to query bundles by app ID or branch name.
Current Status and Roadmap
The first iteration is complete and in active use across nearly all React Native projects at Shopify. Developers review each other's work with a QR code scan or a bundle ID entry. Planned improvements include building and uploading bundles from the command line, showing a selectable list of available bundles in the Tophat screen, detecting native code changes automatically, and refining the Tophat screen's interface.



