Reference version

This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo Expo Fingerprint iconExpo Fingerprint

一个从 React Native 项目生成指纹的库。

Node
Recommended version:
~0.21.0

@expo/fingerprint 提供了一个 API,用于生成项目的指纹(哈希),以确定应用原生层与 JavaScript 层之间的兼容性。哈希计算可配置,但默认情况下会根据应用依赖、自定义原生代码、原生项目文件和配置进行哈希计算。

安装

默认情况下,@expo/fingerprint 已包含在 expo 和 expo-updates 中。

如果你希望将 @expo/fingerprint 作为独立软件包使用,可以运行以下命令进行安装:

Terminal
- npx expo install @expo/fingerprint
- yarn expo install @expo/fingerprint
- pnpm expo install @expo/fingerprint
- bun expo install @expo/fingerprint

CLI 用法

Terminal
- npx @expo/fingerprint --help
- yarn dlx @expo/fingerprint --help
- pnpm dlx @expo/fingerprint --help
- bunx @expo/fingerprint --help

配置

@expo/fingerprint 提供了适用于大多数项目的默认值,同时也提供了几种配置指纹计算过程的方式,以更好地适配你的应用结构和工作流。

.fingerprintignore

.fingerprintignore 文件放置在项目根目录中,是一种类似于 .gitignore 的忽略机制,用于从哈希计算中排除文件。所有模式路径都相对于项目根目录。它的行为类似于 .gitignore,但使用 minimatch 进行模式匹配,因此存在一些限制(参见选项中的 ignorePaths 文档)。

下面是一个 .fingerprintignore 配置示例:

.fingerprintignore
# Ignore the entire android directory android/**/* # Ignore the entire ios directory but still keep ios/Podfile and ios/Podfile.lock ios/**/* !ios/Podfile !ios/Podfile.lock # Ignore specific package in node_modules node_modules/some-package/**/* # Same as above but having broader scope because packages may be nested **/node_modules/some-package/**/*

fingerprint.config.js

fingerprint.config.js 文件放置在项目根目录中,可以让你指定超出 .fingerprintignore 可配置范围的自定义哈希计算配置。有关支持的配置,请参阅 Config、FingerprintPreset 和 SourceSkips。

以下是一个 fingerprint.config.js 配置示例,假设你已将 @expo/fingerprint 安装为直接依赖:

fingerprint.config.js
/** @type {import('@expo/fingerprint').Config} */ const config = { // Uncomment to switch presets (the default is 'balanced'). See FingerprintPreset for the options. // preset: 'strict', sourceSkips: [ 'ExpoConfigRuntimeVersionIfString', 'ExpoConfigVersions', 'PackageJsonAndroidAndIosScriptsIfNotContainRun', ], }; module.exports = config;

如果你通过 expo 使用 @expo/fingerprint(此时 @expo/fingerprint 是传递依赖),可以从 expo/fingerprint 导入 fingerprint:

/** @type {import('expo/fingerprint').Config} */
高级:在指纹哈希计算前自定义源

在某些情况下,你可能希望在计算指纹前自定义源。例如:

  • 你希望从应用配置中移除敏感数据。
  • 你希望稳定应用配置中的动态值。
  • 你希望将文件哈希转换为稳定值。

为此,你可以在 fingerprint.config.js 文件中使用 fileHookTransform 选项,在哈希计算前转换源。详细了解 fileHookTransform 选项。

fingerprint.config.js
const assert = require('node:assert'); const fileChunkMap = {}; /** @type {import('@expo/fingerprint').Config} */ const config = { fileHookTransform: (source, chunk, isEndOfFile, encoding) => { // Remove the "updates" section from the app config if (source.type === 'contents' && source.id === 'expoConfig') { assert(isEndOfFile, 'contents source is expected to have single chunk.'); const config = JSON.parse(chunk); delete config.updates; return JSON.stringify(config); } // Transform content sources to an empty string if (source.type === 'contents' && source.id === 'packageJson:scripts') { return ''; } // Transform a file source by replacing dynamic values if (source.type === 'file' && source.filePath === 'eas.json') { return chunk.toString().replace(/MyApp-Dev/g, 'MyApp'); } // Transform a large file that is processed in multiple chunks // To get the full file, buffer all chunks and return them all at once if (source.type === 'file' && source.filePath === 'assets/large-image.jpg') { let receivedBuffer = fileChunkMap[source.filePath] ?? Buffer.alloc(0); if (chunk != null) { const buffer = typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk; receivedBuffer = Buffer.concat([receivedBuffer, buffer]); fileChunkMap[source.filePath] = receivedBuffer; } if (!isEndOfFile) { return null; } fileChunkMap[source.filePath] = null; // The full payload is available here and you can transform it as needed. receivedBuffer = receivedBuffer.toString().replace(/SensitiveData/g, 'StableData'); return receivedBuffer; } // For other sources, just return the chunk return chunk; }, }; module.exports = config;

限制

对 @expo/config-plugins 原始函数的支持有限

使用带有原始函数的配置插件时,必须注意某些限制,尤其是在指纹计算的场景中。该库会尽力为通过配置插件所做的更改生成指纹;但是,原始函数会带来特定挑战。原始函数无法序列化为指纹,这意味着它们无法直接用于生成唯一哈希。

为了绕过这一限制,该库会采用以下策略之一,为原始函数创建可序列化的指纹:

  1. 使用 Function.name:对于具有名称的原始函数,如果可用,该库会使用 Function.name 属性。此属性会提供函数的可识别名称,可用作指纹属性。

  2. 使用 withAnonymous:对于没有 Function.name 的匿名原始函数,该库会改用 withAnonymous 作为指纹属性。这是匿名函数的通用标识符。

下面的示例展示了一个场景,在该场景中,该库会将 [withMyPlugin、withAnonymous] 用作指纹哈希的插件属性:

app.config.js
const { withInfoPlist } = require('expo/config-plugins'); const withMyPlugin = (config) => { return withInfoPlist(config, (config) => { config.modResults.NSLocationWhenInUseUsageDescription = 'Allow $(PRODUCT_NAME) to use your location'; return config; }); }; export default ({ config }) => { config.plugins ||= []; config.plugins.push(withMyPlugin); config.plugins.push((config) => config); return config; };

需要注意的是,由于这种设计,如果你更改原始配置插件函数的实现,例如更改 withMyPlugin 中的 Info.plist 值,指纹仍会生成相同的哈希值。若要在修改配置插件实现时确保生成唯一指纹,请考虑以下选项:

  • 避免匿名函数:避免使用匿名原始配置插件函数。相反,应尽可能使用命名函数,并确保只要实现发生更改,其名称就保持一致。

  • 使用本地配置插件:或者,你可以将本地配置插件创建为独立模块,每个模块都有自己的导出。这种方式允许你在更改配置插件实现时指定不同的函数名称。

下面是使用本地配置插件的示例:

./plugins/withMyPlugin.js
const { withInfoPlist } = require('expo/config-plugins'); const withMyPlugin = config => { return withInfoPlist(config, config => { config.modResults.NSLocationWhenInUseUsageDescription = 'Allow $(PRODUCT_NAME) to use your location'; return config; }); }; module.exports = withMyPlugin;
app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": "./plugins/withMyPlugin" } }

遵循这些指南,你可以有效管理对配置插件的更改,并确保指纹计算保持一致且可靠。

API

import * as Fingerprint from '@expo/fingerprint';

Constants

Fingerprint.DEFAULT_IGNORE_PATHS

Node

Type: string[]

Fingerprint.DEFAULT_SOURCE_SKIPS

Node

Type: SourceSkips

Methods

Fingerprint.createFingerprintAsync(projectRoot, options)

Node
ParameterType
projectRootstring
options(optional)Options

Create a fingerprint for a project.

Example

const fingerprint = await createFingerprintAsync('/app'); console.log(fingerprint);

Fingerprint.createProjectHashAsync(projectRoot, options)

Node
ParameterType
projectRootstring
options(optional)Options

Create a native hash value for a project.

Returns:
Promise<string>

Example

const hash = await createProjectHashAsync('/app'); console.log(hash);

Fingerprint.diffFingerprintChangesAsync(fingerprint, projectRoot, options)

Node
ParameterType
fingerprintFingerprint
projectRootstring
options(optional)Options

Diff the fingerprint with the fingerprint of the provided project.

Example

// Create a fingerprint for the project const fingerprint = await createFingerprintAsync('/app'); // Make some changes to the project // Calculate the diff const diff = await diffFingerprintChangesAsync(fingerprint, '/app'); console.log(diff);

Fingerprint.diffFingerprints(fingerprint1, fingerprint2)

Node
ParameterType
fingerprint1Fingerprint
fingerprint2Fingerprint

Diff two fingerprints. The implementation assumes that the sources are sorted.

Example

// Create a fingerprint for the project const fingerprint = await createFingerprintAsync('/app'); // Make some changes to the project // Create a fingerprint again const fingerprint2 = await createFingerprintAsync('/app'); const diff = await diffFingerprints(fingerprint, fingerprint2); console.log(diff);

Interfaces

DebugInfoContents

Node
PropertyTypeDescription
hashstring
-
isTransformed(optional)boolean

Indicates whether the source is transformed by fileHookTransform.

DebugInfoDir

Node
PropertyTypeDescription
children(DebugInfoFile | DebugInfoDir | undefined)[]
-
hashstring
-
pathstring
-

DebugInfoFile

Node
PropertyTypeDescription
hashstring
-
isTransformed(optional)boolean

Indicates whether the source is transformed by fileHookTransform.

pathstring
-

DebugInfoPackage

Node
PropertyTypeDescription
hashstring
-
namestring
-
pathstring
-
versionstring
-

Fingerprint

Node
PropertyTypeDescription
hashstring

The final hash value of the whole project fingerprint.

sourcesFingerprintSource[]

Sources and their hash values from which the project fingerprint was generated.

HashResultContents

Node
PropertyTypeDescription
debugInfo(optional)DebugInfoContents
-
hexstring
-
idstring
-
type'contents'
-

HashResultDir

Node
PropertyTypeDescription
debugInfo(optional)DebugInfoDir
-
hexstring
-
idstring
-
type'dir'
-

HashResultFile

Node
PropertyTypeDescription
debugInfo(optional)DebugInfoFile
-
hexstring
-
idstring
-
type'file'
-

HashResultPackage

Node
PropertyTypeDescription
debugInfo(optional)DebugInfoPackage
-
hexstring
-
idstring
-
type'package'
-

HashSourceContents

Node
PropertyTypeDescription
contentsstring | Buffer<ArrayBufferLike>
-
idstring
-
reasonsstring[]

Reasons of this source coming from.

type'contents'
-

HashSourceDir

Node
PropertyTypeDescription
filePathstring
-
overrideHashKey(optional)string

Override key for hashing. Without this key, the filePath is used as the hash key.

reasonsstring[]

Reasons of this source coming from.

type'dir'
-

HashSourceFile

Node
PropertyTypeDescription
filePathstring
-
overrideHashKey(optional)string

Override key for hashing. Without this key, the filePath is used as the hash key.

reasonsstring[]

Reasons of this source coming from.

type'file'
-

HashSourcePackage

Node
PropertyTypeDescription
filePathstring

Path to the package's package.json. Kept for ignore-path matching and debugging only — it is deliberately not part of the hash, so the fingerprint doesn't depend on where the package resolves on disk (e.g. hoisted vs. isolated installs).

namestring

Package name from its package.json. Together with version, this is the only thing hashed, so unrelated churn inside the package (or across machines) is ignored.

overrideHashKey(optional)string

Override key for hashing. Without this key, the package name@version is used as the hash key.

reasonsstring[]

Reasons of this source coming from.

type'package'
-
versionstring

Package version from its package.json.

Options

Node
PropertyTypeDescription
concurrentIoLimit(optional)number

I/O concurrency limit.

Default:The number of CPU cores.
configPluginSourceType(optional)ConfigPluginSourceType

How config-plugin modules loaded while evaluating the Expo config are hashed. Defaults to the value from the resolved preset; set this to override it.

debug(optional)boolean

Whether to include verbose debug info in source output. Useful for debugging.

dirExcludes(optional)string[]

Exclude specified directories from hashing. The supported pattern is the same as glob(). Default is ['android/build', 'android/app/build', 'android/app/.cxx', 'ios/Pods'].

enableReactImportsPatcher(optional)boolean

Enable ReactImportsPatcher to transform imports from React of the form #import "RCTBridge.h" to #import <React/RCTBridge.h>. This is useful when you want to have a stable fingerprint for Expo projects, since expo-modules-autolinking will change the import style on iOS.

Default:true for Expo SDK 51 and lower.
extraSources(optional)HashSource[]

Additional sources for hashing.

fileHookTransform(optional)FileHookTransformFunction

A custom hook function to transform file content sources before hashing.

hashAlgorithm(optional)string

The algorithm to use for crypto.createHash().

Default:'sha1'
ignorePaths(optional)string[]

Ignore files and directories from hashing. The supported pattern is the same as glob().

The pattern matching is slightly different from gitignore. Partial matching is unsupported. For example, build does not match android/build; instead, use '**' + '/build'.

nativeModuleSourceType(optional)NativeModuleSourceType

How autolinked native modules are hashed. Defaults to the value from the resolved preset; set this to override it.

platforms(optional)Platform[]

Limit native files to those for specified platforms.

Default:['android', 'ios']
preset(optional)FingerprintPreset

The preset to derive default settings from. A preset sets sourceSkips and related defaults; any explicitly provided option (e.g. sourceSkips) takes precedence over the preset.

Default:'balanced'
silent(optional)boolean

Whether running the functions should mute all console output. This is useful when fingerprinting is being done as part of a CLI that outputs a fingerprint and outputting anything else pollutes the results.

sourceSkips(optional)SourceSkips

Skips some sources from fingerprint. Value is the result of bitwise-OR'ing desired values of SourceSkips.

Default:DEFAULT_SOURCE_SKIPS
useRNCoreAutolinkingFromExpo(optional)boolean

Use the react-native core autolinking sources from expo-modules-autolinking rather than @react-native-community/cli.

Default:true for Expo SDK 52 and higher.

Types

Config

Node

Supported options for use in fingerprint.config.js

Type: Pick<Options, 'preset' | 'concurrentIoLimit' | 'hashAlgorithm' | 'ignorePaths' | 'extraSources' | 'enableReactImportsPatcher' | 'useRNCoreAutolinkingFromExpo' | 'nativeModuleSourceType' | 'configPluginSourceType' | 'debug' | 'fileHookTransform'> extended by:

PropertyTypeDescription
sourceSkips(optional)SourceSkips | SourceSkipsKeys[]
-

ConfigPluginSourceType

Node

Literal type: string

How config-plugin modules loaded while evaluating the Expo config are hashed.

  • files: hash every loaded module from its files (in-repo and node_modules).
  • package: collapse node_modules modules to their package name@version while still hashing in-repo modules from their files, trading exact node_modules plugin fidelity for far fewer false positives.

Acceptable values are: 'files' | 'package'

DebugInfo

Node

Literal type: union

Acceptable values are: DebugInfoFile | DebugInfoDir | DebugInfoContents | DebugInfoPackage

FileHookTransformFunction(source, chunk, isEndOfFile, encoding)

Node

Hook function to transform file content sources before hashing.

ParameterType
sourceFileHookTransformSource
chunkBuffer | string | null
isEndOfFileboolean
encodingBufferEncoding
Returns:

Buffer | string | null

FileHookTransformSource

Node

The source parameter for FileHookTransformFunction.

Type: object shaped as below:

PropertyTypeDescription
filePathstring
-
type'file'
-

Or object shaped as below:

PropertyTypeDescription
idstring
-
type'contents'
-

FingerprintDiffItem

Node

Type: object shaped as below:

PropertyTypeDescription
addedSourceFingerprintSource

The added source.

op'added'

The operation type of the diff item.

Or object shaped as below:

PropertyTypeDescription
op'removed'

The operation type of the diff item.

removedSourceFingerprintSource

The removed source.

Or object shaped as below:

PropertyTypeDescription
afterSourceFingerprintSource

The source after.

beforeSourceFingerprintSource

The source before.

op'changed'

The operation type of the diff item.

FingerprintPreset

Node

Literal type: string

A named preset of fingerprint settings, from strict (react to any potential native change) to relaxed (ignore changes that usually don't affect the native build).

  • strict: highest fidelity - even a version bump changes the fingerprint. The historical default.
  • balanced: the default, tuned for a good first-time experience.
  • relaxed: for building multiple variants from one native project. On top of balanced, it also ignores app identity like app icons and the Android package / iOS bundleIdentifier.

Acceptable values are: 'strict' | 'balanced' | 'relaxed'

FingerprintSource

Node

Type: HashSource extended by:

PropertyTypeDescription
debugInfo(optional)DebugInfo

Debug info from the hashing process. Differs based on source type. Designed to be consumed by humans as opposed to programmatically.

hashstring | null

Hash value of the source. If the source is excluded the value will be null.

HashResult

Node

Literal type: union

Acceptable values are: HashResultFile | HashResultDir | HashResultContents | HashResultPackage

HashSource

Node

Literal type: union

Acceptable values are: HashSourceFile | HashSourceDir | HashSourceContents | HashSourcePackage

NativeModuleSourceType

Node

Literal type: string

How an autolinked native module is hashed.

  • files: hash the module's native directory.
  • package: hash its package.json name@version, so patch-level or cross-machine churn inside the module is ignored.

Acceptable values are: 'files' | 'package'

Platform

Node

Literal type: string

Acceptable values are: 'android' | 'ios'

ProjectWorkflow

Node

Literal type: string

Acceptable values are: 'generic' | 'managed' | 'unknown'

Enums

SourceSkips

Node

Bitmask of values that can be used to skip certain parts of the sources when generating a fingerprint.

None

SourceSkips.None = 0

Skip nothing.

ExpoConfigVersions

SourceSkips.ExpoConfigVersions = 1

Versions in app.json, including version, android.versionCode, ios.buildNumber, and the platform-specific overrides ios.version and android.version (which take precedence over the top-level version).

ExpoConfigRuntimeVersionIfString

SourceSkips.ExpoConfigRuntimeVersionIfString = 2

runtimeVersion in app.json if it is a string.

ExpoConfigNames

SourceSkips.ExpoConfigNames = 4

App names in app.json, including name, description, web.name, web.shortName, and web.description.

ExpoConfigAndroidPackage

SourceSkips.ExpoConfigAndroidPackage = 8

Android package name in app.json.

ExpoConfigIosBundleIdentifier

SourceSkips.ExpoConfigIosBundleIdentifier = 16

iOS bundle identifier in app.json.

ExpoConfigSchemes

SourceSkips.ExpoConfigSchemes = 32

Schemes in app.json.

ExpoConfigEASProject

SourceSkips.ExpoConfigEASProject = 64

EAS project information in app.json.

ExpoConfigAssets

SourceSkips.ExpoConfigAssets = 128

Assets in app.json, including icons and splash assets.

ExpoConfigAll

SourceSkips.ExpoConfigAll = 256

Skip the whole ExpoConfig. Prefer the other ExpoConfig source skips when possible and use this flag with caution. This will potentially ignore some native changes that should be part of most fingerprints. E.g., adding a new config plugin, changing the app icon, or changing the app name.

PackageJsonAndroidAndIosScriptsIfNotContainRun

SourceSkips.PackageJsonAndroidAndIosScriptsIfNotContainRun = 512

package.json scripts if android and ios items do not contain "run". Because prebuild will change the scripts in package.json, this is useful to generate a consistent fingerprint before and after prebuild.

PackageJsonScriptsAll

SourceSkips.PackageJsonScriptsAll = 1024

Skip the whole scripts section in the project's package.json.

GitIgnore

SourceSkips.GitIgnore = 2048

Skip .gitignore files.

ExpoConfigExtraSection

SourceSkips.ExpoConfigExtraSection = 4096

The extra section in app.json

EasJson

SourceSkips.EasJson = 8192

Skip eas.json. Most of its content, such as build profiles and submit settings, does not affect the native project. Note that some fields do, e.g. ios.buildConfiguration or android.gradleCommand.

Easignore

SourceSkips.Easignore = 16384

Skip .easignore. The file only controls which files are uploaded to EAS Build. Note that excluding a file that does affect the native build will not change the fingerprint.

AutolinkingConfigPaths

SourceSkips.AutolinkingConfigPaths = 32768

Path fields in the resolved autolinking config from expo-modules-autolinking and react-native-config. The config itself is still hashed. Filesystem paths, and values that sit under the project root, are omitted, including scriptPhases[].path and sourceDir / podspecPath overrides from the project's react-native.config.js. Those overrides will not change the fingerprint. Linked module names and scriptPhases names remain.