diff --git a/README.md b/README.md index cfac889..1224871 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # react-native-app-clip -> **Warning** -> Starting with version 0.6.0, react-native-app-clip requires **Expo SDK 53** and **React Native 0.79**. Downgrade to 0.5.1 if you wish to use **Expo SDK 52** and **React Native 0.76**. - Expo Config Plugin that generates an App Clip for iOS apps built with Expo. ## Installation @@ -58,9 +55,9 @@ NOTE: You can find the simulator device UUID by running `xcrun simctl list`. The - **requestLocationConfirmation** (boolean): Allow App Clip access to location data (see [Apple Developer Docs](https://developer.apple.com/documentation/app_clips/confirming_the_user_s_physical_location)) - **appleSignin** (boolean): Enable "Sign in with Apple" for the App Clip - **applePayMerchantIds** (string[]): Enable Apple Pay capability with provided merchant IDs. -- **excludedPackages** (string[]): Packages to exclude from autolinking for the App Clip to reduce bundle size (see below). - **pushNotifications** (boolean): Enable push notification compatibility for the App Clip - **enableCompression** (boolean): Enables gzip compression of the App Clip's JavaScript bundle to reduce its size. Please note: This may increase the final binary size in some cases (see [App Clip Size Limits](#app-clip-size-limits)). +- **excludedPackages** (string[]): node module names to exclude from autolinking for the App Clip to reduce binary size (see [App Clip Size Limits](#app-clip-size-limits)). ## App Clip Size Limits @@ -76,7 +73,16 @@ For iOS 17+, the 100 MB limit has additional requirements: - Requires reliable internet connection usage scenarios - Does not support iOS 16 and earlier -You can exclude packages (via `excludedPackages` parameter) and use compression (via `enableCompression` parameter) to help stay within these limits. However, since the App Clip binary itself is compressed by Apple, pre-compressing the JS bundle with `enableCompression` might sometimes be counterproductive. Always verify the final size in TestFlight or the App Store Connect dashboard. +You can exclude packages (via `excludedPackages`) and use compression (via `enableCompression`) to help stay within these limits. However, since the App Clip binary itself is compressed by Apple, pre-compressing the JS bundle with `enableCompression` might sometimes be counterproductive. Always verify the final size in TestFlight or the App Store Connect dashboard. + +Excluded packages are removed from Expo's autolinking for the App Clip target via `use_expo_modules!`. Use node module package names: + +```json +"excludedPackages": [ + "expo-notifications", + "react-native-nfc-manager" +] +``` ## Native capabilities diff --git a/plugin/src/withPodfile.ts b/plugin/src/withPodfile.ts index db38328..1da12a9 100644 --- a/plugin/src/withPodfile.ts +++ b/plugin/src/withPodfile.ts @@ -1,4 +1,3 @@ -import { mergeContents } from "@expo/config-plugins/build/utils/generateCode"; import { type ConfigPlugin, withDangerousMod } from "expo/config-plugins"; import fs from "node:fs"; import path from "node:path"; @@ -7,8 +6,6 @@ export const withPodfile: ConfigPlugin<{ targetName: string; excludedPackages?: string[]; }> = (config, { targetName, excludedPackages }) => { - // return config; - return withDangerousMod(config, [ "ios", (config) => { @@ -20,66 +17,66 @@ export const withPodfile: ConfigPlugin<{ const useExpoModules = excludedPackages && excludedPackages.length > 0 - ? `exclude = ["${excludedPackages.join(`", "`)}"]\n use_expo_modules!(exclude: exclude)` + ? `exclude = ["${excludedPackages.join(`", "`)}"]\n use_expo_modules!(exclude: exclude)` : "use_expo_modules!"; const appClipTarget = ` - target '${targetName}' do - ${useExpoModules} - - if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' - config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; - else - config_command = [ - 'npx', - 'expo-modules-autolinking', - 'react-native-config', - '--json', - '--platform', - 'ios' - ] - end +# @generated begin react-native-app-clip +target '${targetName}' do + ${useExpoModules} - # Running the command in the same manner as \`use_react_native\` then running that result through our cliPlugin - json, message, status = Pod::Executable.capture_command(config_command[0], config_command[1..], capture: :both) - if not status.success? - Pod::UI.warn "The command: '#{config_command.join(" ").bold.yellow}' returned a status code of #{status.exitstatus.to_s.bold.red}, #{message}", [ - "App Clip autolinking failed. Please ensure autolinking works correctly for the main app target and try again.", - ] - exit(status.exitstatus) - end + if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' + config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; + else + config_command = [ + 'npx', + 'expo-modules-autolinking', + 'react-native-config', + '--json', + '--platform', + 'ios' + ] + end - # \`react-native-app-clip\` resolves to react-native-app-clip/build/index.js - clip_command = [ - 'node', - '--no-warnings', - '--eval', - 'require(require.resolve(\\'react-native-app-clip\\')+\\'/../../plugin/build/cliPlugin.js\\').run(' + json + ', [${(excludedPackages ?? []).map((packageName) => `"${packageName}"`).join(", ")}])' + # Running the command in the same manner as \`use_react_native\` then running that result through our cliPlugin + json, message, status = Pod::Executable.capture_command(config_command[0], config_command[1..], capture: :both) + if not status.success? + Pod::UI.warn "The command: '#{config_command.join(" ").bold.yellow}' returned a status code of #{status.exitstatus.to_s.bold.red}, #{message}", [ + "App Clip autolinking failed. Please ensure autolinking works correctly for the main app target and try again.", ] + exit(status.exitstatus) + end - config = use_native_modules!(clip_command) + # \`react-native-app-clip\` resolves to react-native-app-clip/build/index.js + clip_command = [ + 'node', + '--no-warnings', + '--eval', + 'require(require.resolve(\\'react-native-app-clip\\')+\\'/../../plugin/build/cliPlugin.js\\').run(' + json + ', [])' + ] - use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] - use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] + config = use_native_modules!(clip_command) - use_react_native!( - :path => config[:reactNativePath], - :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', - # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/..", - :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', - ) - end - `; + use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] - podfileContent = mergeContents({ - tag: "Generated by react-native-app-clip", - src: podfileContent, - newSrc: appClipTarget, - anchor: "use_expo_modules!", - offset: 0, - comment: "#", - }).contents; + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/..", + :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', + ) +end +# @generated end react-native-app-clip`; + + // Strip any existing block then re-append at end of file (idempotent) + const blockRegex = new RegExp( + `\\n*# @generated begin react-native-app-clip[\\s\\S]*?# @generated end react-native-app-clip`, + "g", + ); + podfileContent = podfileContent.replace(blockRegex, "").trimEnd(); + podfileContent += `\n${appClipTarget}\n`; fs.writeFileSync(podFilePath, podfileContent);