expo 58.0.0-preview.1 → 58.0.0-preview.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ buildscript {
10
10
  }
11
11
 
12
12
  group = 'host.exp.exponent'
13
- version = '58.0.0-preview.1'
13
+ version = '58.0.0-preview.3'
14
14
 
15
15
  expoModule {
16
16
  // We can't prebuild the module because it depends on the generated files.
@@ -21,7 +21,7 @@ android {
21
21
  namespace "expo.core"
22
22
  defaultConfig {
23
23
  versionCode 1
24
- versionName "58.0.0-preview.1"
24
+ versionName "58.0.0-preview.3"
25
25
  consumerProguardFiles("proguard-rules.pro")
26
26
  }
27
27
  testOptions {
@@ -0,0 +1,15 @@
1
+ package expo.modules
2
+
3
+ import expo.modules.v2.ExpoModulesV2Provider
4
+ import io.github.expo.modules.v2.Module
5
+ import io.github.expo.modules.v2.discoveredExpoModules
6
+
7
+ /**
8
+ * The list is filled in at compile time: the Expo Modules v2 compiler plugin, which autolinking
9
+ * applies to this project, expands `discoveredExpoModules()` into every `@ExpoModule` on the compile
10
+ * classpath. Autolinking adds every module project as a dependency of this one, so the classpath is
11
+ * exactly the set of installed modules and nothing has to be listed anywhere.
12
+ */
13
+ class ExpoModulesV2ModuleList : ExpoModulesV2Provider {
14
+ override fun getModules(): List<Class<out Module>> = discoveredExpoModules()
15
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "@expo/dom-webview": "~58.0.0",
3
- "@expo/fingerprint": "~0.21.0",
4
- "@expo/metro-runtime": "~58.0.1",
3
+ "@expo/fingerprint": "~0.21.1",
4
+ "@expo/metro-runtime": "~58.0.3",
5
5
  "@expo/vector-icons": "^15.0.2",
6
- "@expo/ui": "~58.0.1",
6
+ "@expo/ui": "~58.0.3",
7
7
  "@react-native-async-storage/async-storage": "2.2.0",
8
8
  "@react-native-community/datetimepicker": "9.2.1",
9
9
  "@react-native-masked-view/masked-view": "0.3.2",
@@ -16,88 +16,88 @@
16
16
  "eslint-config-expo": "~58.0.1",
17
17
  "expo-analytics-amplitude": "~11.3.0",
18
18
  "expo-app-auth": "~11.1.0",
19
- "expo-app-intents": "~0.3.0",
19
+ "expo-app-intents": "~0.4.1",
20
20
  "expo-app-loader-provider": "~8.0.0",
21
- "expo-app-metrics": "~58.0.1",
21
+ "expo-app-metrics": "~58.0.3",
22
22
  "expo-apple-authentication": "~58.0.0",
23
23
  "expo-application": "~58.0.1",
24
- "expo-asset": "~58.0.1",
24
+ "expo-asset": "~58.0.3",
25
25
  "expo-audio": "~58.0.0",
26
- "expo-auth-session": "~58.0.1",
27
- "expo-background-fetch": "~58.0.1",
28
- "expo-background-task": "~58.0.1",
26
+ "expo-auth-session": "~58.0.2",
27
+ "expo-background-fetch": "~58.0.3",
28
+ "expo-background-task": "~58.0.3",
29
29
  "expo-battery": "~58.0.0",
30
30
  "expo-blur": "~58.0.0",
31
31
  "expo-brightness": "~58.0.0",
32
- "expo-brownfield": "~58.0.1",
33
- "expo-build-properties": "~58.0.1",
34
- "expo-calendar": "~58.0.0",
32
+ "expo-brownfield": "~58.0.3",
33
+ "expo-build-properties": "~58.0.3",
34
+ "expo-calendar": "~58.0.2",
35
35
  "expo-camera": "~58.0.1",
36
36
  "expo-cellular": "~58.0.0",
37
37
  "expo-checkbox": "~58.0.0",
38
38
  "expo-clipboard": "~58.0.0",
39
- "expo-constants": "~58.0.1",
39
+ "expo-constants": "~58.0.3",
40
40
  "expo-contacts": "~58.0.1",
41
41
  "expo-crypto": "~58.0.1",
42
- "expo-dev-client": "~58.0.1",
42
+ "expo-dev-client": "~58.0.3",
43
43
  "expo-device": "~58.0.0",
44
44
  "expo-document-picker": "~58.0.0",
45
45
  "expo-eas-client": "~58.0.0",
46
46
  "expo-file-system": "~58.0.0",
47
- "expo-font": "~58.0.0",
47
+ "expo-font": "~58.0.1",
48
48
  "expo-gl": "~58.0.0",
49
49
  "expo-glass-effect": "~58.0.0",
50
50
  "expo-google-app-auth": "~8.3.0",
51
51
  "expo-haptics": "~58.0.1",
52
- "expo-image": "~58.0.1",
52
+ "expo-image": "~58.0.2",
53
53
  "expo-image-loader": "~58.0.1",
54
- "expo-image-manipulator": "~58.0.2",
55
- "expo-image-picker": "~58.0.1",
54
+ "expo-image-manipulator": "~58.0.4",
55
+ "expo-image-picker": "~58.0.3",
56
56
  "expo-intent-launcher": "~58.0.0",
57
- "expo-insights": "~58.0.1",
57
+ "expo-insights": "~58.0.3",
58
58
  "expo-keep-awake": "~58.0.0",
59
59
  "expo-linear-gradient": "~58.0.0",
60
- "expo-linking": "~58.0.1",
60
+ "expo-linking": "~58.0.3",
61
61
  "expo-local-authentication": "~58.0.0",
62
62
  "expo-localization": "~58.0.0",
63
- "expo-location": "~58.0.1",
63
+ "expo-location": "~58.0.3",
64
64
  "expo-mail-composer": "~58.0.0",
65
65
  "expo-manifests": "~58.0.0",
66
66
  "expo-maps": "~58.0.1",
67
67
  "expo-mcp": "~0.2.1",
68
68
  "expo-media-library": "~58.0.0",
69
69
  "expo-mesh-gradient": "~58.0.0",
70
- "expo-module-template": "~58.0.1",
71
- "expo-modules-core": "~58.0.1",
70
+ "expo-module-template": "~58.0.2",
71
+ "expo-modules-core": "~58.0.3",
72
72
  "expo-navigation-bar": "~58.0.0",
73
73
  "expo-network": "~58.0.0",
74
- "expo-notifications": "~58.0.1",
75
- "expo-observe": "~58.0.3",
74
+ "expo-notifications": "~58.0.3",
75
+ "expo-observe": "~58.0.5",
76
76
  "expo-print": "~58.0.0",
77
77
  "expo-live-photo": "~58.0.0",
78
- "expo-router": "~58.0.2",
78
+ "expo-router": "~58.0.4",
79
79
  "expo-screen-capture": "~58.0.0",
80
80
  "expo-screen-orientation": "~58.0.0",
81
81
  "expo-secure-store": "~58.0.0",
82
82
  "expo-sensors": "~58.0.0",
83
83
  "expo-server": "~58.0.0",
84
- "expo-sharing": "~58.0.3",
84
+ "expo-sharing": "~58.0.5",
85
85
  "expo-sms": "~58.0.0",
86
86
  "expo-speech": "~58.0.0",
87
87
  "expo-splash-screen": "~58.0.0",
88
- "expo-sqlite": "~58.0.2",
88
+ "expo-sqlite": "~58.0.3",
89
89
  "expo-status-bar": "~58.0.0",
90
90
  "expo-store-review": "~58.0.0",
91
- "expo-symbols": "~58.0.0",
91
+ "expo-symbols": "~58.0.1",
92
92
  "expo-system-ui": "~58.0.1",
93
- "expo-task-manager": "~58.0.2",
93
+ "expo-task-manager": "~58.0.4",
94
94
  "expo-tracking-transparency": "~58.0.0",
95
- "expo-updates": "~58.0.3",
95
+ "expo-updates": "~58.0.5",
96
96
  "expo-video-thumbnails": "~58.0.0",
97
97
  "expo-video": "~58.0.1",
98
98
  "expo-web-browser": "~58.0.0",
99
- "expo-widgets": "~58.0.1",
100
- "jest-expo": "~58.0.1",
99
+ "expo-widgets": "~58.0.3",
100
+ "jest-expo": "~58.0.2",
101
101
  "lottie-react-native": "~7.5.0",
102
102
  "react": "19.2.3",
103
103
  "react-dom": "19.2.3",
@@ -104,7 +104,13 @@ open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
104
104
  }
105
105
 
106
106
  open func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
107
- URLContexts.forEach { forwarder.open(url: $0.url, options: Self.openURLOptions(from: $0.options)) }
107
+ // React Native ignores the app-delegate URL API under the scene life cycle, so a warm link only
108
+ // reaches JS through the scene API. It reads a single context, hence one call per context.
109
+ URLContexts.forEach { context in
110
+ forwarder.open(url: context.url, options: Self.openURLOptions(from: context.options)) {
111
+ RCTLinkingManager.scene(scene, openURLContexts: [context])
112
+ }
113
+ }
108
114
  }
109
115
 
110
116
  open func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
@@ -112,7 +118,9 @@ open class ExpoAppSceneDelegate: UIResponder, UIWindowSceneDelegate {
112
118
  }
113
119
 
114
120
  open func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
115
- forwarder.continue(userActivity)
121
+ forwarder.continue(userActivity) {
122
+ RCTLinkingManager.scene(scene, continue: userActivity)
123
+ }
116
124
  }
117
125
 
118
126
  open func scene(
@@ -19,18 +19,26 @@ import React
19
19
  struct SceneEventForwarder {
20
20
  var appDelegate: () -> ExpoAppDelegate? = { UIApplication.shared.delegate as? ExpoAppDelegate }
21
21
 
22
- func open(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) {
22
+ func open(
23
+ url: URL,
24
+ options: [UIApplication.OpenURLOptionsKey: Any],
25
+ notifyReactNative: (() -> Void)? = nil
26
+ ) {
23
27
  let application = UIApplication.shared
24
28
  let delegate = appDelegate()
25
29
 
26
30
  notifyLinkingManagerUnlessAlreadyNotified(of: url) {
27
31
  _ = delegate?.application(application, open: url, options: options)
28
32
  } notify: {
29
- RCTLinkingManager.application(application, open: url, options: options)
33
+ if let notifyReactNative {
34
+ notifyReactNative()
35
+ } else {
36
+ RCTLinkingManager.application(application, open: url, options: options)
37
+ }
30
38
  }
31
39
  }
32
40
 
33
- func `continue`(_ userActivity: NSUserActivity) {
41
+ func `continue`(_ userActivity: NSUserActivity, notifyReactNative: (() -> Void)? = nil) {
34
42
  let application = UIApplication.shared
35
43
  let delegate = appDelegate()
36
44
 
@@ -39,7 +47,11 @@ struct SceneEventForwarder {
39
47
  notifyLinkingManagerUnlessAlreadyNotified(of: userActivity.webpageURL) {
40
48
  _ = delegate?.application(application, continue: userActivity, restorationHandler: { _ in })
41
49
  } notify: {
42
- RCTLinkingManager.application(application, continue: userActivity, restorationHandler: { _ in })
50
+ if let notifyReactNative {
51
+ notifyReactNative()
52
+ } else {
53
+ RCTLinkingManager.application(application, continue: userActivity, restorationHandler: { _ in })
54
+ }
43
55
  }
44
56
  }
45
57
 
@@ -244,6 +244,51 @@ struct ExpoAppSceneDelegateTests {
244
244
  #expect(recorder.count(of: webpageURL) == 1)
245
245
  }
246
246
 
247
+ @Test
248
+ @MainActor
249
+ func `notifies React Native through the injected notifier for a warm URL`() {
250
+ let spy = SpyAppDelegate()
251
+ let url = URL(string: "bareexpo://scene-delegate/warm-open-url")!
252
+ let recorder = OpenURLNotificationRecorder()
253
+ var notifications = 0
254
+ SceneEventForwarder(appDelegate: { spy }).open(url: url, options: [:]) {
255
+ notifications += 1
256
+ }
257
+ #expect(spy.openedURLs.first?.url == url)
258
+ #expect(notifications == 1)
259
+ #expect(recorder.count(of: url) == 0)
260
+ }
261
+
262
+ @Test
263
+ @MainActor
264
+ func `does not notify React Native when the app delegate already notified for a URL`() {
265
+ let delegate = LegacyLinkingAppDelegate()
266
+ let url = URL(string: "bareexpo://scene-delegate/warm-legacy-open-url")!
267
+ var notifications = 0
268
+ SceneEventForwarder(appDelegate: { delegate }).open(url: url, options: [:]) {
269
+ notifications += 1
270
+ }
271
+ #expect(delegate.openedURLs == [url])
272
+ #expect(notifications == 0)
273
+ }
274
+
275
+ @Test
276
+ @MainActor
277
+ func `notifies React Native through the injected notifier for a warm user activity`() {
278
+ let spy = SpyAppDelegate()
279
+ let userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
280
+ let webpageURL = URL(string: "https://expo.dev/scene-delegate/warm-activity")!
281
+ userActivity.webpageURL = webpageURL
282
+ let recorder = OpenURLNotificationRecorder()
283
+ var notifications = 0
284
+ SceneEventForwarder(appDelegate: { spy }).continue(userActivity) {
285
+ notifications += 1
286
+ }
287
+ #expect(spy.continuedUserActivities.first === userActivity)
288
+ #expect(notifications == 1)
289
+ #expect(recorder.count(of: webpageURL) == 0)
290
+ }
291
+
247
292
  @Test
248
293
  @MainActor
249
294
  func `does not notify subscribers without an ExpoAppDelegate`() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo",
3
- "version": "58.0.0-preview.1",
3
+ "version": "58.0.0-preview.3",
4
4
  "description": "The Expo SDK",
5
5
  "keywords": [
6
6
  "expo"
@@ -23,6 +23,8 @@
23
23
  },
24
24
  "files": [
25
25
  "android",
26
+ "!android/.gitignore",
27
+ "!android/build/**",
26
28
  "bin",
27
29
  "build",
28
30
  "internal",
@@ -198,36 +200,36 @@
198
200
  },
199
201
  "dependencies": {
200
202
  "@babel/runtime": "^7.20.0",
201
- "@expo/cli": "^58.0.1",
203
+ "@expo/cli": "^58.0.3",
202
204
  "@expo/config": "~58.0.0",
203
- "@expo/config-plugins": "~58.0.0",
205
+ "@expo/config-plugins": "~58.0.1",
204
206
  "@expo/devtools": "~58.0.0",
205
207
  "@expo/dom-webview": "~58.0.0",
206
- "@expo/fingerprint": "^0.21.0",
208
+ "@expo/fingerprint": "^0.21.1",
207
209
  "@expo/local-build-cache-provider": "^58.0.0",
208
- "@expo/log-box": "^58.0.1",
210
+ "@expo/log-box": "^58.0.3",
209
211
  "@expo/log-box-utils": "^58.0.0",
210
- "@expo/metro": "~56.1.0",
211
- "@expo/metro-config": "~58.0.1",
212
+ "@expo/metro": "~58.0.0-rc.0",
213
+ "@expo/metro-config": "~58.0.3",
212
214
  "@ungap/structured-clone": "^1.3.0",
213
- "babel-preset-expo": "~58.0.1",
214
- "expo-asset": "~58.0.1",
215
- "expo-constants": "~58.0.1",
215
+ "babel-preset-expo": "~58.0.3",
216
+ "expo-asset": "~58.0.3",
217
+ "expo-constants": "~58.0.3",
216
218
  "expo-file-system": "~58.0.0",
217
- "expo-font": "~58.0.0",
219
+ "expo-font": "~58.0.1",
218
220
  "expo-keep-awake": "~58.0.0",
219
- "expo-modules-autolinking": "~58.0.1",
220
- "expo-modules-core": "~58.0.1",
221
+ "expo-modules-autolinking": "~58.0.2",
222
+ "expo-modules-core": "~58.0.3",
221
223
  "pretty-format": "^29.7.0",
222
224
  "react-refresh": "^0.14.2",
223
225
  "whatwg-url-minimum": "^0.2.0"
224
226
  },
225
227
  "devDependencies": {
226
228
  "@expo/dom-webview": "58.0.0",
227
- "@expo/metro-runtime": "58.0.1",
229
+ "@expo/metro-runtime": "58.0.3",
228
230
  "@types/node": "^22.14.0",
229
231
  "@types/react": "~19.2.0",
230
- "expo-updates": "58.0.3",
232
+ "expo-updates": "58.0.5",
231
233
  "npm-run-all2": "^8.0.4",
232
234
  "react": "19.2.3",
233
235
  "react-dom": "19.2.3",
@@ -267,13 +269,14 @@
267
269
  "engines": {
268
270
  "node": "^22.13.0 || ^24.3.0 || ^26.0.0 || >=27.0.0"
269
271
  },
270
- "gitHead": "40afe3afa42f1dcca3dcba7ffbb3641859bdf2f0",
272
+ "gitHead": "917a61b581baeb852aa4fe331da2c1395d824351",
271
273
  "scripts": {
272
274
  "build": "../expo-module-scripts/bin/expo-build src",
273
275
  "clean": "rimraf build",
274
276
  "lint": "oxlint --config oxlint.config.mjs .",
275
277
  "format": "expo-module format",
276
- "test": "jest",
278
+ "test": "jest && jest --config scripts/spm/jest.config.js",
279
+ "test:spm-plugin": "jest --config scripts/spm/jest.config.js",
277
280
  "depscheck": "expo-module depscheck",
278
281
  "typecheck": "tsc -p tsconfig.json"
279
282
  }
@@ -47,4 +47,11 @@ module.exports = {
47
47
  windows: null,
48
48
  },
49
49
  },
50
+ // React Native SwiftPM autolinking plugin (preview). Discovered transitively by
51
+ // `react-native spm` when `expo` is an autolinked dependency; contributes Expo's
52
+ // native modules (precompiled xcframeworks + source packages) and the generated
53
+ // ExpoModulesProvider into the SwiftPM autolinking graph.
54
+ spm: {
55
+ autolinkingPlugin: './scripts/spm/plugin.js',
56
+ },
50
57
  };
@@ -0,0 +1,118 @@
1
+ # Expo SwiftPM autolinking plugin
2
+
3
+ This preview plugin is discovered through `expo/react-native.config.js` by
4
+ React Native's `react-native spm` tooling. It contributes Expo source packages,
5
+ the generated `ExpoModulesProvider.swift`, and precompiled Expo frameworks.
6
+
7
+ ## Precompiled framework contract
8
+
9
+ Every precompiled Expo framework is returned as one immutable pair:
10
+
11
+ ```js
12
+ {
13
+ id: 'expo-modules-core',
14
+ frameworkName: 'ExpoModulesCore',
15
+ linkage: 'dynamic',
16
+ flavors: {
17
+ debug: '/absolute/path/ExpoModulesCore.xcframework',
18
+ release: '/absolute/path/ExpoModulesCore.xcframework',
19
+ },
20
+ }
21
+ ```
22
+
23
+ Both flavors are mandatory. A partial build, relative path, wrong XCFramework
24
+ name, missing `Info.plist`, or non-dynamic declaration aborts autolinking. The
25
+ plugin does not create or mutate a “current flavor” symlink.
26
+
27
+ Artifact lookup is deterministic:
28
+
29
+ 1. `EXPO_PRECOMPILED_MODULES_PATH/<package>/output/<flavor>/xcframeworks`
30
+ 2. `packages/precompile/.build/<package>/output/<flavor>/xcframeworks`
31
+ 3. `<installed-package>/prebuilds/output/<flavor>/xcframeworks`
32
+
33
+ An artifact may be an expanded `.xcframework` or a bundled `.tar.gz`. Bundled
34
+ tarballs are expanded into `ios/build/expo-xcframeworks` before the plugin
35
+ returns, so `spm add` and `spm update` always receive both real paths. Expo's
36
+ prebuild command builds Debug and Release by default; do not use a single-flavor
37
+ prebuild for an app that consumes the SwiftPM plugin.
38
+
39
+ Lookup runs for **every** resolved pod, not just the core frameworks. Any module
40
+ with a `spm.config.json` product is consumed as a precompiled framework the
41
+ moment its artifact exists — no `Package.swift` is generated for it, and none is
42
+ needed. Path 3 is the published-package case: a module shipping
43
+ `prebuilds/output/<flavor>/xcframeworks` is picked up with no local build.
44
+
45
+ React Native normalizes the declared pairs into immutable app-local Debug and
46
+ Release slots. Xcode then selects, exact-links, embeds, and signs the right slot
47
+ for each configuration. Configurations containing `debug` or `development` use
48
+ Debug; all others use Release. Adding, removing, or changing a flavored Expo
49
+ framework requires `npx react-native spm update`; build-time `spm sync` only
50
+ refreshes invariant source/codegen output.
51
+
52
+ ## SwiftPM graph
53
+
54
+ Flavored runtime frameworks are not SwiftPM products or binary targets. The
55
+ plugin creates a binary-free compile interface tree containing their public
56
+ headers, module maps, and Swift module interfaces. Expo source packages compile
57
+ against that stable tree, while React Native owns the only runtime link/embed
58
+ path.
59
+
60
+ Every Expo source target uses exactly the products supplied in
61
+ `context.react.products`: `ReactHeaders`, `ReactNativeHeaders`,
62
+ `ReactNativeDependenciesHeaders`, and `ReactAppHeaders` when available. The
63
+ ExpoModulesCore module map itself declares `use React`, so these invariant
64
+ carriers are needed even when a module does not import React directly. The
65
+ plugin does not infer runtime React or Hermes products.
66
+
67
+ ## Unsupported modules
68
+
69
+ A module the plugin cannot contribute aborts autolinking. Dropping it would not
70
+ fail the build — it would fail at runtime with `Cannot find native module`, far
71
+ from the cause. Each one is reported as an `error:`-prefixed block, which Xcode
72
+ surfaces as a build error, naming the fix:
73
+
74
+ - **A prebuildable product with no built artifact** — the module declares a
75
+ product in its `spm.config.json`, so it needs no `Package.swift`; the artifact
76
+ is simply missing. `et prebuild <package>` (both flavors, so omit `--flavor`)
77
+ fixes it. This is the preferred route for a mixed-language module: the prebuild
78
+ pipeline compiles Swift, Objective-C, and C++ targets into one XCFramework, so
79
+ no source split is required.
80
+ - **Mixed Swift and Objective-C/C++ sources with neither manifest** — SwiftPM
81
+ compiles the two languages in separate targets, and only the module can
82
+ declare where its split goes. Either an `spm.config.json`
83
+ (`packages/expo-sensors` is a worked example) or a checked-in `Package.swift`
84
+ (`packages/expo-constants`) makes it consumable, persisted with
85
+ `patch-package` for a module you do not own, and upstreamed so every consumer
86
+ gets it. This mirrors what React Native's autolinker asks of community
87
+ libraries.
88
+ - **No `ios` or `apple` source directory** — an incomplete install, or a
89
+ non-standard layout that needs a `Package.swift` naming the real path.
90
+ - **Manifest targets whose sources are not on disk** — a checked-in
91
+ `Package.swift` whose targets declare no `path:` and have no sources under a
92
+ predefined source directory, so the generated targets would point at nothing.
93
+ - **Native linkage declared only in the podspec** — `s.frameworks`,
94
+ `s.weak_frameworks` or `s.libraries`. The plugin never reads linkage out of
95
+ Ruby: a misread link line fails in a shipped app, not here. Declare it as
96
+ `linkerSettings` in a `Package.swift`, which the plugin mirrors verbatim, or
97
+ ship an `spm.config.json` and prebuild the module.
98
+ - **An iOS deployment floor that is not a literal** — the plugin reads
99
+ `s.platforms = { :ios => '16.4' }` and `s.ios.deployment_target = '16.4'` as
100
+ text; anything computed would need the podspec to be run.
101
+ - **No precompiled ExpoModulesCore** — a single project-level fault reported
102
+ once, not per module, since every source module compiles against its interface
103
+ tree.
104
+
105
+ One podspec finding is a warning rather than an error: linker flags set through
106
+ `OTHER_LDFLAGS` in a `pod_target_xcconfig`. The module is still emitted, because
107
+ Swift links the system frameworks and C++ runtime its sources import; if it does
108
+ fail to link, declare the missing library as `linkerSettings` in a
109
+ `Package.swift`.
110
+
111
+ To build without a module, exclude it from autolinking in the app's
112
+ `package.json` (`expo.autolinking.exclude`); its native module is then
113
+ unavailable at runtime.
114
+
115
+ Emitted modules are also scanned for CocoaPods dependencies with no SwiftPM
116
+ counterpart (`SDWebImage`, `ZXingObjC`, …). Those are a warning, not an error:
117
+ the module enters the graph and compiles until a source file reaches the missing
118
+ dependency.