expo-build-properties
Version:
Config plugin to customize native build properties on prebuild
736 lines (696 loc) • 23.9 kB
text/typescript
import Ajv, { JSONSchemaType } from 'ajv';
import semver from 'semver';
/**
* The minimal supported versions. These values should align to SDK.
* @ignore
*/
const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = {
android: {
minSdkVersion: 21,
compileSdkVersion: 31,
targetSdkVersion: 31,
kotlinVersion: '1.6.10',
},
ios: {
deploymentTarget: '15.1',
},
};
/**
* Interface representing base build properties configuration.
*/
export interface PluginConfigType {
/**
* Interface representing available configuration for Android native build properties.
* @platform android
*/
android?: PluginConfigTypeAndroid;
/**
* Interface representing available configuration for iOS native build properties.
* @platform ios
*/
ios?: PluginConfigTypeIos;
}
/**
* Interface representing available configuration for Android native build properties.
* @platform android
*/
export interface PluginConfigTypeAndroid {
/**
* @deprecated Use app config [`newArchEnabled`](https://docs.expo.dev/versions/latest/config/app/#newarchenabled) instead.
* Enable React Native new architecture for Android platform.
*/
newArchEnabled?: boolean;
/**
* Override the default `minSdkVersion` version number in **build.gradle**.
* */
minSdkVersion?: number;
/**
* Override the default `compileSdkVersion` version number in **build.gradle**.
*/
compileSdkVersion?: number;
/**
* Override the default `targetSdkVersion` version number in **build.gradle**.
*/
targetSdkVersion?: number;
/**
* Override the default `buildToolsVersion` version number in **build.gradle**.
*/
buildToolsVersion?: string;
/**
* Override the Kotlin version used when building the app.
*/
kotlinVersion?: string;
/**
* Enable [Proguard or R8](https://developer.android.com/studio/build/shrink-code) in release builds to obfuscate Java code and reduce app size.
*/
enableProguardInReleaseBuilds?: boolean;
/**
* Enable [`shrinkResources`](https://developer.android.com/studio/build/shrink-code#shrink-resources) in release builds to remove unused resources from the app.
* This property should be used in combination with `enableProguardInReleaseBuilds`.
*/
enableShrinkResourcesInReleaseBuilds?: boolean;
/**
* Enable [`crunchPngs`](https://developer.android.com/topic/performance/reduce-apk-size#crunch) in release builds to optimize PNG files.
* This property is enabled by default, but "might inflate PNG files that are already compressed", so you may want to disable it if you do your own PNG optimization.
*
* @default true
*/
enablePngCrunchInReleaseBuilds?: boolean;
/**
* Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to **android/app/proguard-rules.pro**.
*/
extraProguardRules?: string;
/**
* Interface representing available configuration for Android Gradle plugin [`PackagingOptions`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions).
*/
packagingOptions?: PluginConfigTypeAndroidPackagingOptions;
/**
* Enable the Network Inspector.
*
* @default true
*/
networkInspector?: boolean;
// For the implementation details, this property is actually handled by `expo-modules-autolinking`
// not the config-plugins inside `expo-build-properties`
/**
* Add extra maven repositories to all gradle projects.
*
* Takes an array of objects or strings.
* Strings are passed as the `url` property of the object with no credentials or authentication scheme.
*
* This adds the following code to **android/build.gradle**:
* ```groovy
* allprojects {
* repositories {
* maven {
* url "https://foo.com/maven-releases"
* }
* }
* ```
*
* By using an `AndroidMavenRepository` object, you can specify credentials and an authentication scheme.
* ```groovy
* allprojects {
* repositories {
* maven {
* url "https://foo.com/maven-releases"
* credentials {
* username = "bar"
* password = "baz"
* }
* authentication {
* basic(BasicAuthentication)
* }
* }
* }
* }
* ```
*
* @see [Gradle documentation](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:case-for-maven)
*/
extraMavenRepos?: (AndroidMavenRepository | string)[];
/**
* Indicates whether the app intends to use cleartext network traffic.
*
* @default false
*
* @see [Android documentation](https://developer.android.com/guide/topics/manifest/application-element#usesCleartextTraffic)
*/
usesCleartextTraffic?: boolean;
/**
* Instructs the Android Gradle plugin to compress native libraries in the APK using the legacy packaging system.
*
* @default false
*
* @see [Android documentation](https://developer.android.com/build/releases/past-releases/agp-4-2-0-release-notes#compress-native-libs-dsl)
*/
useLegacyPackaging?: boolean;
/**
* Specifies the set of other apps that an app intends to interact with. These other apps are specified by package name,
* by intent signature, or by provider authority.
*
* @see [Android documentation](https://developer.android.com/guide/topics/manifest/queries-element)
*/
manifestQueries?: PluginConfigTypeAndroidQueries;
/**
* Changes the apps theme to a DayNight variant to correctly support dark mode.
*
* @see [Android documentation](https://developer.android.com/develop/ui/views/theming/darktheme)
*/
useDayNightTheme?: boolean;
/**
* Enable JavaScript Bundle compression. Turning this on will result in a smaller APK size but may have slower app startup times.
*
* @see [Faster App Startup](https://reactnative.dev/blog/2025/04/08/react-native-0.79#android-faster-app-startup)
*
* @default false
*/
enableBundleCompression?: boolean;
}
// @docsMissing
/**
* @platform android
*/
export interface AndroidMavenRepository {
/**
* The URL of the Maven repository.
*/
url: string;
/**
* The credentials to use when accessing the Maven repository.
* May be of type PasswordCredentials, HttpHeaderCredentials, or AWSCredentials.
*
* @see The authentication schemes section of [Gradle documentation](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:authentication_schemes) for more information.
*/
credentials?: AndroidMavenRepositoryCredentials;
/**
* The authentication scheme to use when accessing the Maven repository.
*/
authentication?: 'basic' | 'digest' | 'header';
}
// @docsMissing
/**
* @platform android
*/
export interface AndroidMavenRepositoryPasswordCredentials {
username: string;
password: string;
}
// @docsMissing
/**
* @platform android
*/
export interface AndroidMavenRepositoryHttpHeaderCredentials {
name: string;
value: string;
}
// @docsMissing
/**
* @platform android
*/
export interface AndroidMavenRepositoryAWSCredentials {
accessKey: string;
secretKey: string;
sessionToken?: string;
}
// @docsMissing
/**
* @platform android
*/
export type AndroidMavenRepositoryCredentials =
| AndroidMavenRepositoryPasswordCredentials
| AndroidMavenRepositoryHttpHeaderCredentials
| AndroidMavenRepositoryAWSCredentials;
/**
* Interface representing available configuration for iOS native build properties.
* @platform ios
*/
export interface PluginConfigTypeIos {
/**
* @deprecated Use app config [`newArchEnabled`](https://docs.expo.dev/versions/latest/config/app/#newarchenabled) instead.
* Enable React Native new architecture for iOS platform.
*/
newArchEnabled?: boolean;
/**
* Override the default iOS "Deployment Target" version in the following projects:
* - in CocoaPods projects,
* - `PBXNativeTarget` with "com.apple.product-type.application" `productType` in the app project.
*/
deploymentTarget?: string;
/**
* Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang)
* in `Podfile` to use frameworks instead of static libraries for Pods.
*/
useFrameworks?: 'static' | 'dynamic';
/**
* Enable the Network Inspector.
*
* @default true
*/
networkInspector?: boolean;
// For the implementation details, this property is actually handled by `expo-modules-autolinking`
// but not the config-plugins inside `expo-build-properties`.
/**
* Add extra CocoaPods dependencies for all targets.
*
* This configuration is responsible for adding the new Pod entries to **ios/Podfile**.
*
* @example
* Creating entry in the configuration like below:
* ```json
* [
* {
* name: "Protobuf",
* version: "~> 3.14.0",
* }
* ]
* ```
* Will produce the following entry in the generated **ios/Podfile**:
* ```ruby
* pod 'Protobuf', '~> 3.14.0'
* ```
*/
extraPods?: ExtraIosPodDependency[];
/**
* Enable C++ compiler cache for iOS builds.
*
* This speeds up compiling C++ code by caching the results of previous compilations.
*
* @see [React Native's documentation on local caches](https://reactnative.dev/docs/build-speed#local-caches) and
* [Ccache documentation](https://ccache.dev/).
*/
ccacheEnabled?: boolean;
/**
* Enable aggregation of Privacy Manifests (`PrivacyInfo.xcprivacy`) from
* CocoaPods resource bundles. If enabled, the manifests will be merged into a
* single file. If not enabled, developers will need to manually aggregate them.
*
* @see [Privacy manifests](https://docs.expo.dev/guides/apple-privacy/) guide
* and [Apple's documentation on Privacy manifest files](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files).
*/
privacyManifestAggregationEnabled?: boolean;
}
/**
* Interface representing extra CocoaPods dependency.
* @see [Podfile syntax reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
* @platform ios
*/
export interface ExtraIosPodDependency {
/**
* Name of the pod.
*/
name: string;
/**
* Version of the pod.
* CocoaPods supports various [versioning options](https://guides.cocoapods.org/using/the-podfile.html#pod).
* @example
* ```
* ~> 0.1.2
* ```
*/
version?: string;
/**
* Build configurations for which the pod should be installed.
* @example
* ```
* ['Debug', 'Release']
* ```
*/
configurations?: string[];
/**
* Whether this pod should use modular headers.
*/
modular_headers?: boolean;
/**
* Custom source to search for this dependency.
* @example
* ```
* https://github.com/CocoaPods/Specs.git
* ```
*/
source?: string;
/**
* Custom local filesystem path to add the dependency.
* @example
* ```
* ~/Documents/AFNetworking
* ```
*/
path?: string;
/**
* Custom podspec path.
* @example
* ```https://example.com/JSONKit.podspec```
*/
podspec?: string;
/**
* Test specs can be optionally included via the :testspecs option. By default, none of a Pod's test specs are included.
* @example
* ```
* ['UnitTests', 'SomeOtherTests']
* ```
*/
testspecs?: string[];
/**
* Use the bleeding edge version of a Pod.
*
* @example
* ```json
* {
* "name": "AFNetworking",
* "git": "https://github.com/gowalla/AFNetworking.git",
* "tag": "0.7.0"
* }
* ```
*
* This acts like to add this pod dependency statement:
*
* ```rb
* pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
* ```
*/
git?: string;
/**
* The git branch to fetch. See the `git` property for more information.
*/
branch?: string;
/**
* The git tag to fetch. See the `git` property for more information.
*/
tag?: string;
/**
* The git commit to fetch. See the `git` property for more information.
*/
commit?: string;
}
/**
* Interface representing available configuration for Android Gradle plugin [`PackagingOptions`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions).
* @platform android
*/
export interface PluginConfigTypeAndroidPackagingOptions {
/**
* Array of patterns for native libraries where only the first occurrence is packaged in the APK.
*/
pickFirst?: string[];
/**
* Array of patterns for native libraries that should be excluded from being packaged in the APK.
*/
exclude?: string[];
/**
* Array of patterns for native libraries where all occurrences are concatenated and packaged in the APK.
*/
merge?: string[];
/**
* Array of patterns for native libraries that should not be stripped of debug symbols.
*/
doNotStrip?: string[];
}
// @docsMissing
/**
* @platform android
*/
export interface PluginConfigTypeAndroidQueries {
/**
* Specifies one or more apps that your app intends to access. These other apps might integrate with your app, or your app might use services that these other apps provide.
*/
package?: string[];
/**
* Specifies an intent filter signature. Your app can discover other apps that have matching `<intent-filter>` elements.
* These intents have restrictions compared to typical intent filter signatures.
*
* @see [Android documentation](https://developer.android.com/training/package-visibility/declaring#intent-filter-signature) for more information.
*/
intent?: PluginConfigTypeAndroidQueriesIntent[];
/**
* Specifies one or more content provider authorities. Your app can discover other apps whose content providers use the specified authorities.
* There are some restrictions on the options that you can include in this `<provider>` element, compared to a typical `<provider>` manifest element.
* You may only specify the `android:authorities` attribute.
*/
provider?: string[];
}
// @docsMissing
/**
* @platform android
*/
export interface PluginConfigTypeAndroidQueriesIntent {
/**
* A string naming the action to perform. Usually one of the platform-defined values, such as `SEND` or `VIEW`.
*
* @see [Android documentation](https://developer.android.com/guide/topics/manifest/action-element) for more information.
*/
action?: string;
/**
* A description of the data associated with the intent.
*/
data?: PluginConfigTypeAndroidQueriesData;
/**
* Provides an additional way to characterize the activity handling the intent,
* usually related to the user gesture or location from which it's started.
*/
category?: string | string[];
}
// @docsMissing
/**
* @platform android
*/
export interface PluginConfigTypeAndroidQueriesData {
/**
* Specify a URI scheme that is handled
*/
scheme?: string;
/**
* Specify a URI authority host that is handled
*/
host?: string;
/**
* Specify a MIME type that is handled
*/
mimeType?: string;
}
const schema: JSONSchemaType<PluginConfigType> = {
type: 'object',
properties: {
android: {
type: 'object',
properties: {
newArchEnabled: { type: 'boolean', nullable: true },
minSdkVersion: { type: 'integer', nullable: true },
compileSdkVersion: { type: 'integer', nullable: true },
targetSdkVersion: { type: 'integer', nullable: true },
buildToolsVersion: { type: 'string', nullable: true },
kotlinVersion: { type: 'string', nullable: true },
enableProguardInReleaseBuilds: { type: 'boolean', nullable: true },
enableShrinkResourcesInReleaseBuilds: { type: 'boolean', nullable: true },
enablePngCrunchInReleaseBuilds: { type: 'boolean', nullable: true },
extraProguardRules: { type: 'string', nullable: true },
packagingOptions: {
type: 'object',
properties: {
pickFirst: { type: 'array', items: { type: 'string' }, nullable: true },
exclude: { type: 'array', items: { type: 'string' }, nullable: true },
merge: { type: 'array', items: { type: 'string' }, nullable: true },
doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true },
},
nullable: true,
},
networkInspector: { type: 'boolean', nullable: true },
extraMavenRepos: {
type: 'array',
items: {
type: ['string', 'object'],
anyOf: [
{ type: 'string', nullable: false },
{
type: 'object',
required: ['url'],
properties: {
url: { type: 'string', nullable: false },
credentials: {
type: 'object',
oneOf: [
{
type: 'object',
properties: {
username: { type: 'string' },
password: { type: 'string' },
},
required: ['username', 'password'],
additionalProperties: false,
},
{
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' },
},
required: ['name', 'value'],
additionalProperties: false,
},
{
type: 'object',
properties: {
accessKey: { type: 'string' },
secretKey: { type: 'string' },
sessionToken: { type: 'string', nullable: true },
},
required: ['accessKey', 'secretKey'],
additionalProperties: false,
},
],
nullable: true,
},
authentication: {
type: 'string',
enum: ['basic', 'digest', 'header'],
nullable: true,
},
},
additionalProperties: false,
},
],
},
nullable: true,
},
usesCleartextTraffic: { type: 'boolean', nullable: true },
useLegacyPackaging: { type: 'boolean', nullable: true },
useDayNightTheme: { type: 'boolean', nullable: true },
manifestQueries: {
type: 'object',
properties: {
package: { type: 'array', items: { type: 'string' }, minItems: 1, nullable: true },
intent: {
type: 'array',
items: {
type: 'object',
properties: {
action: { type: 'string', nullable: true },
data: {
type: 'object',
properties: {
scheme: { type: 'string', nullable: true },
host: { type: 'string', nullable: true },
mimeType: { type: 'string', nullable: true },
},
nullable: true,
},
category: { type: 'array', items: { type: 'string' }, nullable: true },
},
},
nullable: true,
},
provider: { type: 'array', items: { type: 'string' }, minItems: 1, nullable: true },
},
nullable: true,
},
enableBundleCompression: { type: 'boolean', nullable: true },
},
nullable: true,
},
ios: {
type: 'object',
properties: {
newArchEnabled: { type: 'boolean', nullable: true },
deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true },
useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true },
networkInspector: { type: 'boolean', nullable: true },
ccacheEnabled: { type: 'boolean', nullable: true },
privacyManifestAggregationEnabled: { type: 'boolean', nullable: true },
extraPods: {
type: 'array',
items: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string' },
version: { type: 'string', nullable: true },
configurations: { type: 'array', items: { type: 'string' }, nullable: true },
modular_headers: { type: 'boolean', nullable: true },
source: { type: 'string', nullable: true },
path: { type: 'string', nullable: true },
podspec: { type: 'string', nullable: true },
testspecs: { type: 'array', items: { type: 'string' }, nullable: true },
git: { type: 'string', nullable: true },
branch: { type: 'string', nullable: true },
tag: { type: 'string', nullable: true },
commit: { type: 'string', nullable: true },
},
},
nullable: true,
},
},
nullable: true,
},
},
};
// note(Kudo): For the implementation, we check items one by one because Ajv does not well support custom error message.
/**
* Checks if specified versions meets Expo minimal supported versions.
* Will throw error message whenever there are invalid versions.
*
* @param config The validated config passed from Ajv.
* @ignore
*/
function maybeThrowInvalidVersions(config: PluginConfigType) {
const checkItems = [
{
name: 'android.minSdkVersion',
configVersion: config.android?.minSdkVersion,
minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion,
},
{
name: 'android.compileSdkVersion',
configVersion: config.android?.compileSdkVersion,
minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion,
},
{
name: 'android.targetSdkVersion',
configVersion: config.android?.targetSdkVersion,
minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion,
},
{
name: 'android.kotlinVersion',
configVersion: config.android?.kotlinVersion,
minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion,
},
{
name: 'ios.deploymentTarget',
configVersion: config.ios?.deploymentTarget,
minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget,
},
];
for (const { name, configVersion, minimalVersion } of checkItems) {
if (
typeof configVersion === 'number' &&
typeof minimalVersion === 'number' &&
configVersion < minimalVersion
) {
throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
}
if (
typeof configVersion === 'string' &&
typeof minimalVersion === 'string' &&
semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0')
) {
throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
}
}
}
/**
* @ignore
*/
export function validateConfig(config: any): PluginConfigType {
const validate = new Ajv({ allowUnionTypes: true }).compile(schema);
if (!validate(config)) {
throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors));
}
maybeThrowInvalidVersions(config);
if (
config.android?.enableShrinkResourcesInReleaseBuilds === true &&
config.android?.enableProguardInReleaseBuilds !== true
) {
throw new Error(
'`android.enableShrinkResourcesInReleaseBuilds` requires `android.enableProguardInReleaseBuilds` to be enabled.'
);
}
return config;
}