Fingerprint your native runtime with @expo/fingerprint
Tags: reactnative, javascript, programming, native
Published: 2024-02-06 23:47:00

At Expo, we’re always looking to find systematic solutions to problems that developers encounter when building apps with React Native. There’s a set of time-consuming questions related to the native project runtime that comes up again and again, and up until now, there hasn’t been a good systematic approach to answering them.

The questions go something like this:

- Does a pull request include native code changes and need to initiate a new build for testing?
- Is my update compatible with the runtime in my production app? Or will it crash the app?
- Does a project require a development build, or can I experiment with it in Expo Go?

While these issues may seem quite different initially, on closer examination they all come down to one key problem: "Given a project, can we deterministically generate a hash, identity, or fingerprint that represents its unique native characteristics?"

##Meet @expo/fingerprint

Now, by running `npx @expo/fingerprint@latest /path/to/yourProject`, you can get a full picture of your project's native setup. `@expo/fingerprint` also comes with an API, so you can integrate this tool programmatically into your own projects.

We think this is a massive milestone for the whole React Native ecosystem and it's already unlocking some super interesting use cases that are not immediately obvious. Krzysztof Magiera is using it to make startup of his RN IDE plugin for vscode much faster.

Here’s an example of what the fingerprint looks like:

```
{
  "sources": [
    {
      "type": "file",
      "filePath": "app.json",
      "reasons": ["expoConfig"],
      "hash": "378083de0c6e6bb6caf8fb72df658b0b26fb29ef"
    },
    {
      "type": "file",
      "filePath": "eas.json",
      "reasons": ["easBuild"],
      "hash": "f723802b6ea916d1a6c4767b2299cc81ddb22eb4"
    },
    {
      "type": "dir",
      "filePath": "node_modules/expo",
      "reasons": ["expoAutolinkingIos", "expoAutolinkingAndroid", "bareRncliAutolinking"],
      "hash": "1faee4057fa943300905750b51c3b0cbf05f4b0d"
    }
  ],
  "hash": "bf8a3b08935f056270b1688333b02f1ef5fa25bf"
}
```
Developers can represent the entire native state of their project with a single, succinct hash value, which is immensely valuable for easily detecting native changes. But there are several other important considerations that we accounted for in order to maximize the usefulness in various use cases:

- 
**Speed**: we’ve tried to make this tool as fast as possible so you can run it often, and we’ll continue to push on that to make it even faster.

- 
**Precision**: we should only consider aspects of the project that are actually relevant (it can’t just be a hash of the entire project and all of the .DS_Store and .swp files or the value would not be useful).

- 
**Support for diffing**: You should be able to compare fingerprints to determine what’s different between them.

- 
**Customizability**: The API exposes some options that you can use to customize for your workflows — most notably, you can either add extra files to hashing or exclude files. We also read the .fingerprintignore file from your project root, which works similarly to .gitignore.

##Try out @expo/fingerprint 🪄

`@expo/fingerprint` fully supports projects made with Expo CLI and also works with any bare React Native apps, whether they use Expo tools or not. Follow these steps to see it in action:

- 
Generate a fingerprint JSON file: `run npx @expo/fingerprint@latest /path/to/yourProject > fingerprint.json`

- 
Change your native runtime: one easy way to do this is to install an npm package with native dependencies, for example: `npx expo install expo-camera` or `npm install react-native-vision-camera`.

- 
Diff the fingerprint: run `npx @expo/fingerprint@latest /path/to/yourProject fingerprint.json`. Notice that it will identify what changed, for example if you installed expo-camera, you will see something like this:

```
[
  {
    "type": "dir",
    "filePath": "../../node_modules/expo-camera/android",
    "reasons": [
      "expoAutolinkingAndroid"
    ],
    "hash": "7682b28ed4ef8356faa5d9ad5b71e76e53198375"
  },
  {
    "type": "dir",
    "filePath": "../../node_modules/expo-camera/ios",
    "reasons": [
      "expoAutolinkingIos"
    ],
    "hash": "a80c2b1abb73157b772a8d0a351920465894bb0d"
  },
	// the rest is redacted for brevity
]
```

You can use this API programmatically with the `Fingerprint.diffFingerprintChangesAsync(fingerprint, projectRoot)` function.

##What’s next: collect feedback and refine workflows

We think `@expo/fingerprint` is really going to help with automating decisions like when you can safely send out updates and analyzing your team's pull-requests to rebuild the native app only when it’s really needed.

Even though this is the first time we’re publicly talking about `@expo/fingerprint`, we’ve already had some early adopters who have opened PRs / issues to help make the tool better accommodate their projects.

We also know that the tool’s built-in opinions might not be perfect for every project. We want help from the community to test this tool thoroughly, and one way that we’ve made it very easy to do so is with our GitHub Actions integration, which is available now! (Blog post on github actions coming soon.)

##Using @expo/fingerprint with EAS

In the future, we plan to build first-class integration for `@expo/fingerprint` into EAS services. You can use `@expo/fingerprint` in your EAS projects today by generating your fingerprint with the CLI and populating your runtimeVersion field with it, then comparing that value with the value generated at the time when you would like to do an update or build. If you use GitHub Actions to kick off your builds and updates, then may also want to use the GitHub Actions integration!