Fingerprints your native inputs and exports BUNDLE_HASH_STRING so caches skip native rebuilds.
Description
Computes a deterministic SHA-256 fingerprint of the inputs that determine your native build and exports it as BUNDLE_HASH_STRING, so you can key restore-cache / save-cache off it and skip the expensive native build when only JavaScript changed.
It hashes the files and folders in path_list (folders are hashed recursively), while excluding anything matched by ignore_path_list. The default covers the common React Native native inputs — JS lockfiles, app.json / app.config.js, patch-package patches, and the ios/ and android/ native projects. It deliberately does not hash your JavaScript source, so a JS-only change reuses the cached native build.
ignore_path_list is important for correctness, not just speed: it excludes build outputs that change every run (ios/Pods, android/build, …) and machine-specific files that would otherwise make the fingerprint differ between CI and local (android/local.properties, Xcode user state, …).
Use BUNDLE_HASH_STRING as the key for restore-cache / save-cache, then gate the native/bundle build on restore-cache's BITRISE_CACHE_HIT output. On a hit, repack the new JS bundle into the cached app and re-sign.
The step is skippable: if fingerprinting can't produce a key (or fails), it exports an empty BUNDLE_HASH_STRING — a cache miss that safely forces a rebuild — rather than blocking your pipeline.
Capture every native input. The fingerprint decides whether a previously compiled native app can be reused. If path_list / ignore_path_list omit an input that affects the native build (a native module dir, a config plugin, an extra lockfile), a native change may not move the key and you could repack onto a stale binary. Extend path_list for your project when needed.
This is a lightweight, do-it-yourself pattern built on free Bitrise cache steps. For a fully managed, compilation-level remote cache across Gradle, Xcode (LLVM CAS) and C++, see Bitrise Build Cache for React Native: https://bitrise.io/platform/build-cache/react-native
Add this step directly to your workflow in the Bitrise Workflow Editor.
You can also run this step directly with Bitrise CLI.
Compute the fingerprint with the default React Native inputs and use it as a cache key:
- react-native-fingerprint:
inputs:
- key_prefix: android
- restore-cache:
inputs:
- key: $BUNDLE_HASH_STRINGSkip the native rebuild when only JavaScript changed — fingerprint, restore, gate the build on the cache hit, save on a miss:
- react-native-fingerprint:
inputs:
- key_prefix: android
- restore-cache:
inputs:
- key: $BUNDLE_HASH_STRING
- android-build:
run_if: '{{not (enveq "BITRISE_CACHE_HIT" "exact")}}'
inputs:
- project_location: ./android
- variant: release
- save-cache:
run_if: '{{not (enveq "BITRISE_CACHE_HIT" "exact")}}'
inputs:
- key: $BUNDLE_HASH_STRING
- paths: android/app/build/outputs/apk/release/app-release.apk
- script:
title: Repack the new JS bundle into the cached app
run_if: '{{enveq "BITRISE_CACHE_HIT" "exact"}}'
inputs:
- content: |-
#!/usr/bin/env bash
set -euo pipefail
# The cached APK was restored — only the JS changed, so bundle it and repack:
npx react-native bundle --platform android --dev false \
--entry-file index.js --bundle-output index.android.bundle
# Replace assets/index.android.bundle inside the restored APK,
# then zipalign and re-sign (apksigner) before deploying.Fingerprint a project in a subdirectory, with native inputs beyond the defaults:
- react-native-fingerprint:
inputs:
- project_dir: ./mobile-app
- key_prefix: ios
- path_list: |-
package.json
yarn.lock
app.json
patches/
ios/
android/
Gemfile.lock
modules/my-native-module/Log every file that contributes to the fingerprint (for verifying path_list / ignore_path_list):
- react-native-fingerprint:
inputs:
- verbose: "true"Inputs
| Key | Description | Flags | Default |
|---|---|---|---|
project_dir |
Root that path_list and ignore_path_list are resolved against. The React Native project root. Entries are interpreted relative to this directory, and the fingerprint uses each file's path relative to it, so the result is independent of where the project is checked out. | required | . |
path_list |
Newline-separated files and folders whose contents determine the key. One entry per line, relative to project_dir: - a file — e.g. package.json - a folder — trailing slash, e.g. ios/ (hashed recursively) - a glob — a * / ** doublestar pattern, e.g. android/**/*.gradle The trailing slash is a readability convention — the step detects each entry's type automatically, so ios and ios/ behave the same. Missing entries are skipped with a warning. Blank lines and lines starting with # are ignored. The default covers common React Native native inputs; extend it for your project (custom native module directories, additional lockfiles, Gemfile.lock, etc.). |
required | package.json package-lock.json yarn.lock pnpm-lock.yaml app.json app.config.js patches/ ios/ android/ |
ignore_path_list |
Newline-separated paths/globs to exclude from the fingerprint. Entries (relative to project_dir) matched as directory prefixes or ** doublestar globs. This list matters for correctness: it excludes per-build outputs (ios/Pods, android/build, …) and machine-specific files (android/local.properties, Xcode user state) that would otherwise make the fingerprint differ between machines and prevent cache hits. Blank lines and # comments are ignored. |
ios/Pods ios/build ios/DerivedData ios/.xcode.env.local android/build android/app/build android/.gradle android/app/.cxx android/local.properties **/xcuserdata **/*.xcuserstate **/*.log **/.DS_Store node_modules .git |
|
key_prefix |
Optional namespace prepended to the fingerprint (joined with a hyphen). When set, it is prepended to BUNDLE_HASH_STRING (e.g. ios-<fingerprint>), for namespacing the cache key per platform or workflow so different builds don't collide. |
||
verbose |
Log the files that contribute to the fingerprint. Prints (at debug level) each file included in the fingerprint — useful for confirming that path_list and ignore_path_list match what you expect. | required | false |
Outputs
| Environment Variable | Description |
|---|---|
BUNDLE_HASH_STRING |
The computed fingerprint, prefixed with key_prefix if set. Empty when no inputs matched — treat an empty value as a cache miss and rebuild the app. Use it as the key for restore-cache / save-cache steps; gate the build on restore-cache's BITRISE_CACHE_HIT output. |
We welcome pull requests and issues against this repository.
For pull requests, work on your changes in a forked repository and use the Bitrise CLI to run step tests locally.
Learn more about developing steps: