Expo 指纹
一个从 React Native 项目生成指纹的库。
@expo/fingerprint
提供了一个 API 来生成项目的指纹(哈希),用于确定应用的原生层和 JavaScript 层之间的兼容性。哈希计算是可配置的,但默认情况下源自哈希应用依赖、自定义原生代码、原生项目文件和配置。
¥@expo/fingerprint
provides an API to generate a fingerprint (hash) of your project for use in determining compatibility between the native layer and JavaScript layer of your app. The hash calculation is configurable, but is by default derived from hashing app dependencies, custom native code, native project files, and configuration.
安装
¥Installation
@expo/fingerprint
默认包含在 expo
和 expo-updates
中。
¥@expo/fingerprint
is included with expo
and expo-updates
by default.
如果你希望将 @expo/fingerprint
用作独立包,则可以通过运行以下命令进行安装:
¥If you wish to use @expo/fingerprint
as a standalone package, you can install it by running the command:
-
npx expo install @expo/fingerprint
CLI 使用
¥CLI Usage
-
npx @expo/fingerprint --help
配置
¥Configuration
@expo/fingerprint
提供的默认值应该适用于大多数项目,但也提供了几种配置指纹识别过程的方法,以更好地适应你的应用结构和工作流程。
¥@expo/fingerprint
provides defaults that should work for most projects, but also provides a few ways to configure the fingerprinting process to better fit your app structure and workflow.
.fingerprintignore
.fingerprintignore 位于项目根目录中,是一种类似 .gitignore 的忽略机制,用于将文件排除在哈希计算之外。它的行为类似,但使用 minimatch
进行模式匹配,其中有一些 limitations(请参阅 选项 下 ignorePaths
的文档)。
¥Placed in your project root, .fingerprintignore is a .gitignore-like ignore mechanism used to exclude files from hash calculation. It behaves similarly but instead uses minimatch
for pattern matching which has some limitations (see documentation for ignorePaths
under Options).
例如,要跳过文件夹但保留一些文件:
¥For example, to skip a folder but keep some files:
# Ignore the entire /app/ios folder
/app/ios/**/*
# But still keep /app/ios/Podfile and /app/ios/Podfile.lock
!/app/ios/Podfile
!/app/ios/Podfile.lock
fingerprint.config.js
fingerprint.config.js 位于你的项目根目录中,允许你指定自定义哈希计算配置,超出 .fingerprintignore 中可配置的内容。有关支持的配置,请参阅 配置 和 SourceSkips
。
¥Placed in your project root, fingerprint.config.js allows you to specify custom hash calculation configuration beyond what is configurable in the .fingerprintignore. For supported configurations, see Config and SourceSkips
.
以下是示例 finger.config.js 配置,假设你已将 @expo/fingerprint
安装为直接依赖:
¥Below is an example fingerprint.config.js configuration, assuming you have @expo/fingerprint
installed as a direct dependency:
/** @type {import('@expo/fingerprint').Config} */
const config = {
sourceSkips: [
'ExpoConfigRuntimeVersionIfString',
'ExpoConfigVersions',
'PackageJsonAndroidAndIosScriptsIfNotContainRun',
],
};
module.exports = config;
如果你正在使用 @expo/fingerprint
到 expo
(其中 @expo/fingerprint
作为传递依赖安装),则可以从 expo/fingerprint
导入指纹:
¥If you are using @expo/fingerprint
through expo
(where @expo/fingerprint
is installed as a transitive dependency), you can import fingerprint from expo/fingerprint
:
/** @type {import('expo/fingerprint').Config} */
局限性
¥Limitations
Limited support for @expo/config-plugins
raw functions
When using config plugins with raw functions, it's essential to be aware of certain limitations, particularly in the context of fingerprinting. The library makes a best effort to generate fingerprints for changes made through config plugins; however, raw functions pose specific challenges. Raw functions are not serializable as fingerprints, which means they cannot be directly used for generating unique hashes.
To work around this limitation, the library employs one of the following strategies to create serializable fingerprints for raw functions:
-
Using
Function.name
: The library utilizes theFunction.name
property if available for named raw functions. This property provides a recognizable name for the function, which can be used as a fingerprint property. -
Using
withAnonymous
: For anonymous raw functions without aFunction.name
, the library resorts to usingwithAnonymous
as the fingerprint property. This is a generic identifier for anonymous functions.
Here's an example to illustrate a case in which the library will use [withMyPlugin
, withAnonymous
] as plugin properties for fingerprint hashing:
!!!IG9!!!
!!!IG3!!!
It's important to note that due to this design, if you make changes to the implementation of raw config plugins functions, such as altering the Info.plist value within withMyPlugin
, the fingerprint will still generate the same hash value. To ensure unique fingerprints when modifying config plugins implementations, consider the following options:
-
Avoid Anonymous Functions: Avoid using anonymous raw config plugins functions. Instead, use named functions whenever possible, and ensure that their names remain consistent as long as the implementation changes.
-
Use Local config plugins: Alternatively, you can create local config plugins as separate modules, each with its own export. This approach allows you to specify a different function name when making changes to the config plugins implementations.
Here's an example of using a local config plugin:
!!!IG4!!!
!!!IG10!!!
!!!IG5!!!
By following these guidelines, you can effectively manage changes to config plugins and ensure that fingerprinting remains consistent and reliable.
API
!!!IG6!!!
Constants
Methods
Parameter | Type |
---|---|
projectRoot | string |
options(optional) | Options |
Create a fingerprint for a project.
Promise<Fingerprint>
Example
const fingerprint = await createFingerprintAsync('/app');
console.log(fingerprint);
Parameter | Type |
---|---|
projectRoot | string |
options(optional) | Options |
Create a native hash value for a project.
Promise<string>
Example
const hash = await createProjectHashAsync('/app');
console.log(hash);
Parameter | Type |
---|---|
fingerprint | Fingerprint |
projectRoot | string |
options(optional) | Options |
Diff the fingerprint with the fingerprint of the provided project.
Promise<FingerprintDiffItem[]>
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);
Parameter | Type |
---|---|
fingerprint1 | Fingerprint |
fingerprint2 | Fingerprint |
Diff two fingerprints. The implementation assumes that the sources are sorted.
FingerprintDiffItem[]
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
Property | Type | Description |
---|---|---|
hash | string | - |
isTransformed(optional) | boolean | Indicates whether the source is transformed by |
Property | Type | Description |
---|---|---|
children | (undefined | DebugInfoFile | DebugInfoDir)[] | - |
hash | string | - |
path | string | - |
Property | Type | Description |
---|---|---|
hash | string | - |
isTransformed(optional) | boolean | Indicates whether the source is transformed by |
path | string | - |
Property | Type | Description |
---|---|---|
hash | string | The final hash value of the whole project fingerprint. |
sources | FingerprintSource[] | Sources and their hash values from which the project fingerprint was generated. |
Property | Type | Description |
---|---|---|
debugInfo(optional) | DebugInfoContents | - |
hex | string | - |
id | string | - |
type | 'contents' | - |
Property | Type | Description |
---|---|---|
debugInfo(optional) | DebugInfoDir | - |
hex | string | - |
id | string | - |
type | 'dir' | - |
Property | Type | Description |
---|---|---|
debugInfo(optional) | DebugInfoFile | - |
hex | string | - |
id | string | - |
type | 'file' | - |
Property | Type | Description |
---|---|---|
contents | string | Buffer | - |
id | string | - |
reasons | string[] | Reasons of this source coming from. |
type | 'contents' | - |
Property | Type | Description |
---|---|---|
filePath | string | - |
reasons | string[] | Reasons of this source coming from. |
type | 'dir' | - |
Property | Type | Description |
---|---|---|
filePath | string | - |
reasons | string[] | Reasons of this source coming from. |
type | 'file' | - |
Property | Type | Description |
---|---|---|
concurrentIoLimit(optional) | number | I/O concurrency limit. Default: The number of CPU cores. |
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 |
enableReactImportsPatcher(optional) | boolean | Enable ReactImportsPatcher to transform imports from React of the form 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 Default: 'sha1' |
ignorePaths(optional) | string[] | Ignore files and directories from hashing. The supported pattern is the same as Please note that the pattern matching is slightly different from gitignore. Partial matching is unsupported. For example,
|
platforms(optional) | Platform[] | Limit native files to those for specified platforms. Default: ['android', 'ios'] |
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 Default: true for Expo SDK 52 and higher. |
Types
Supported options for use in fingerprint.config.js
Type: Pick<Options, 'concurrentIoLimit' | 'hashAlgorithm' | 'ignorePaths' | 'extraSources' | 'enableReactImportsPatcher' | 'useRNCoreAutolinkingFromExpo' | 'debug' | 'fileHookTransform'>
extended by:
Property | Type | Description |
---|---|---|
sourceSkips(optional) | SourceSkips | SourceSkipsKeys[] | - |
Hook function to transform file content sources before hashing.
Parameter | Type |
---|---|
source | FileHookTransformSource |
chunk | Buffer | string | null |
isEndOfFile | boolean |
encoding | BufferEncoding |
Buffer | string | null
The source
parameter for FileHookTransformFunction
.
Type: object
shaped as below:
Property | Type | Description |
---|---|---|
filePath | string | - |
type | 'file' | - |
Or object shaped as below:
Property | Type | Description |
---|---|---|
id | string | - |
type | 'contents' | - |
Type: object
shaped as below:
Property | Type | Description |
---|---|---|
addedSource | FingerprintSource | The added source. |
op | 'added' | The operation type of the diff item. |
Or object shaped as below:
Property | Type | Description |
---|---|---|
op | 'removed' | The operation type of the diff item. |
removedSource | FingerprintSource | The removed source. |
Or object shaped as below:
Property | Type | Description |
---|---|---|
afterSource | FingerprintSource | The source after. |
beforeSource | FingerprintSource | The source before. |
op | 'changed' | The operation type of the diff item. |
Type: HashSource
extended by:
Property | Type | Description |
---|---|---|
debugInfo(optional) | DebugInfo | Debug info from the hashing process. Differs based on source type. Designed to be consumed by humans as opposed to programmatically. |
hash | string | null | Hash value of the |
Literal Type: union
Acceptable values are: HashResultFile
| HashResultDir
| HashResultContents
Literal Type: union
Acceptable values are: HashSourceFile
| HashSourceDir
| HashSourceContents
Enums
Bitmask of values that can be used to skip certain parts of the sourcers when generating a fingerprint.
SourceSkips.ExpoConfigVersions = 1
Versions in app.json, including Android versionCode and iOS buildNumber
SourceSkips.ExpoConfigRuntimeVersionIfString = 2
runtimeVersion in app.json if it is a string
SourceSkips.ExpoConfigNames = 4
App names in app.json, including shortName and description
SourceSkips.ExpoConfigIosBundleIdentifier = 16
iOS bundle identifier in app.json
SourceSkips.ExpoConfigAssets = 128
Assets in app.json, including icons and splash assets
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.
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.
SourceSkips.PackageJsonScriptsAll = 1024
Skip the whole scripts
section in the project's package.json.