expo-dev-launcher 6.1.0-canary-20251015-a6a1272 → 6.1.0-canary-20251023-4c86f95

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,7 @@
7
7
  ### 🎉 New features
8
8
 
9
9
  - Remove `ExpoAppDelegate` inheritance requirement ([#39417](https://github.com/expo/expo/pull/39417) by [@gabrieldonadel](https://github.com/gabrieldonadel))
10
+ - [iOS] Add brownfield support ([#40463](https://github.com/expo/expo/pull/40463) by [@gabrieldonadel](https://github.com/gabrieldonadel))
10
11
 
11
12
  ### 🐛 Bug fixes
12
13
 
@@ -20,21 +20,32 @@ expoModule {
20
20
  }
21
21
 
22
22
  group = "host.exp.exponent"
23
- version = "6.1.0-canary-20251015-a6a1272"
23
+ version = "6.1.0-canary-20251023-4c86f95"
24
24
 
25
25
  android {
26
26
  namespace "expo.modules.devlauncher"
27
27
  defaultConfig {
28
28
  versionCode 9
29
- versionName "6.1.0-canary-20251015-a6a1272"
29
+ versionName "6.1.0-canary-20251023-4c86f95"
30
30
  }
31
31
 
32
32
  buildTypes {
33
+ create("debugOptimized") {
34
+ initWith(buildTypes.debug)
35
+ matchingFallbacks += ["release"]
36
+ }
37
+
33
38
  buildTypes.each {
34
39
  it.buildConfigField 'String', 'VERSION', "\"${defaultConfig.versionName}\""
35
40
  }
36
41
  }
37
42
 
43
+ sourceSets {
44
+ debugOptimized {
45
+ setRoot 'src/debug'
46
+ }
47
+ }
48
+
38
49
  buildFeatures {
39
50
  buildConfig true
40
51
  viewBinding true
@@ -51,7 +51,7 @@ abstract class DevLauncherPlugin : Plugin<Project> {
51
51
  return if (System.getenv("EX_UPDATES_NATIVE_DEBUG") != "1" && reactExtension != null) {
52
52
  reactExtension.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }
53
53
  } else {
54
- variant.buildType == "debug"
54
+ variant.buildType == "debug" || variant.buildType == "debugOptimized"
55
55
  }
56
56
  }
57
57
 
@@ -46,15 +46,10 @@ NS_ASSUME_NONNULL_BEGIN
46
46
  @property (nonatomic, strong) EXDevLauncherPendingDeepLinkRegistry *pendingDeepLinkRegistry;
47
47
  @property (nonatomic, strong) EXDevLauncherRecentlyOpenedAppsRegistry *recentlyOpenedAppsRegistry;
48
48
  @property (nonatomic, strong) id<EXUpdatesExternalInterface> updatesInterface;
49
- @property (nonatomic, readonly, assign) BOOL isStarted;
50
49
 
51
50
  + (instancetype)sharedInstance;
52
51
 
53
- - (void)startWithWindow:(UIWindow *)window;
54
-
55
- - (void)autoSetupPrepare:(id<EXDevLauncherControllerDelegate>)delegate launchOptions:(NSDictionary * _Nullable)launchOptions;
56
-
57
- - (void)autoSetupStart:(UIWindow *)window;
52
+ - (void)start:(id<EXDevLauncherControllerDelegate>)delegate launchOptions:(NSDictionary * _Nullable)launchOptions;
58
53
 
59
54
  - (nullable NSURL *)sourceUrl;
60
55
 
@@ -78,8 +73,6 @@ NS_ASSUME_NONNULL_BEGIN
78
73
 
79
74
  - (BOOL)isAppRunning;
80
75
 
81
- - (BOOL)isStarted;
82
-
83
76
  - (UIWindow * _Nullable)currentWindow;
84
77
 
85
78
  - (EXDevLauncherErrorManager *)errorManager;
@@ -40,9 +40,6 @@
40
40
  #define VERSION @ STRINGIZE2(EX_DEV_LAUNCHER_VERSION)
41
41
  #endif
42
42
 
43
- #define EX_DEV_LAUNCHER_PACKAGER_PATH @"packages/expo-dev-launcher/index.bundle?platform=ios&dev=true&minify=false"
44
-
45
-
46
43
  @interface EXDevLauncherController ()
47
44
 
48
45
  @property (nonatomic, weak) UIWindow *window;
@@ -56,8 +53,8 @@
56
53
  @property (nonatomic, strong) EXDevLauncherErrorManager *errorManager;
57
54
  @property (nonatomic, strong) EXDevLauncherInstallationIDHelper *installationIDHelper;
58
55
  @property (nonatomic, strong, nullable) EXDevLauncherNetworkInterceptor *networkInterceptor;
59
- @property (nonatomic, assign) BOOL isStarted;
60
56
  @property (nonatomic, strong) EXDevLauncherReactNativeFactory *reactNativeFactory;
57
+ @property (nonatomic, strong) DevLauncherViewController *devLauncherViewController;
61
58
  @property (nonatomic, strong) NSURL *lastOpenedAppUrl;
62
59
 
63
60
  @end
@@ -91,19 +88,6 @@
91
88
  return self;
92
89
  }
93
90
 
94
- - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
95
- {
96
- NSMutableArray<id<RCTBridgeModule>> *modules = [NSMutableArray new];
97
-
98
- [modules addObject:[RCTDevMenu new]];
99
- #ifndef EX_DEV_LAUNCHER_URL
100
- [modules addObject:[EXDevLauncherRCTDevSettings new]];
101
- #endif
102
- [modules addObject:[DevClientNoOpLoadingView new]];
103
-
104
- return modules;
105
- }
106
-
107
91
  + (NSString * _Nullable)version {
108
92
  #ifdef VERSION
109
93
  return VERSION;
@@ -111,88 +95,6 @@
111
95
  return nil;
112
96
  }
113
97
 
114
- // Expo developers: Enable the below code by running
115
- // export EX_DEV_LAUNCHER_URL=http://localhost:8090
116
- // in your shell before doing pod install. This will cause the controller to see if
117
- // the expo-launcher packager is running, and if so, use that instead of
118
- // the prebuilt bundle.
119
- // See the pod_target_xcconfig definition in expo-dev-launcher.podspec
120
-
121
- - (nullable NSURL *)devLauncherBaseURL
122
- {
123
- #ifdef EX_DEV_LAUNCHER_URL
124
- return [NSURL URLWithString:@EX_DEV_LAUNCHER_URL];
125
- #endif
126
- return nil;
127
- }
128
- - (nullable NSURL *)devLauncherURL
129
- {
130
- #ifdef EX_DEV_LAUNCHER_URL
131
- return [NSURL URLWithString:EX_DEV_LAUNCHER_PACKAGER_PATH
132
- relativeToURL:[self devLauncherBaseURL]];
133
- #endif
134
- return nil;
135
- }
136
-
137
- - (nullable NSURL *)devLauncherStatusURL
138
- {
139
- #ifdef EX_DEV_LAUNCHER_URL
140
- return [NSURL URLWithString:@"status"
141
- relativeToURL:[self devLauncherBaseURL]];
142
- #endif
143
- return nil;
144
- }
145
-
146
- - (BOOL)isLauncherPackagerRunning
147
- {
148
- // Shamelessly copied from RN core (RCTBundleURLProvider)
149
-
150
- // If we are not running in the main thread, run away
151
- if (![NSThread isMainThread]) {
152
- return NO;
153
- }
154
-
155
- NSURL *url = [self devLauncherStatusURL];
156
- NSURLSession *session = [NSURLSession sharedSession];
157
- NSURLRequest *request = [NSURLRequest requestWithURL:url
158
- cachePolicy:NSURLRequestUseProtocolCachePolicy
159
- timeoutInterval:1];
160
- __block NSURLResponse *response;
161
- __block NSData *data;
162
-
163
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
164
- [[session dataTaskWithRequest:request
165
- completionHandler:^(NSData *d, NSURLResponse *res, __unused NSError *err) {
166
- data = d;
167
- response = res;
168
- dispatch_semaphore_signal(semaphore);
169
- }] resume];
170
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
171
-
172
- NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
173
- return [status isEqualToString:@"packager-status:running"];
174
- }
175
-
176
- - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
177
- {
178
- return [self getSourceURL];
179
- }
180
-
181
- - (NSURL *)bundleURL
182
- {
183
- return [self getSourceURL];
184
- }
185
-
186
- - (NSURL *)getSourceURL
187
- {
188
- NSURL *launcherURL = [self devLauncherURL];
189
- if (launcherURL != nil && [self isLauncherPackagerRunning]) {
190
- return launcherURL;
191
- }
192
- NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"EXDevLauncher" withExtension:@"bundle"];
193
- return [[NSBundle bundleWithURL:bundleURL] URLForResource:@"main" withExtension:@"jsbundle"];
194
- }
195
-
196
98
  - (void)clearRecentlyOpenedApps
197
99
  {
198
100
  return [_recentlyOpenedAppsRegistry clearRegistry];
@@ -242,27 +144,22 @@
242
144
  return _possibleManifestURL;
243
145
  }
244
146
 
245
- - (UIWindow *)currentWindow
246
- {
247
- return _window;
248
- }
249
147
 
250
148
  - (EXDevLauncherErrorManager *)errorManage
251
149
  {
252
150
  return _errorManager;
253
151
  }
254
152
 
255
- - (void)startWithWindow:(UIWindow *)window
153
+ - (void)start:(id<EXDevLauncherControllerDelegate>)delegate launchOptions:(NSDictionary * _Nullable)launchOptions
256
154
  {
257
- _isStarted = YES;
258
- _window = window;
259
- EXDevLauncherUncaughtExceptionHandler.isInstalled = true;
260
-
261
- if (_launchOptions[UIApplicationLaunchOptionsURLKey]) {
262
- // For deeplink launch, we need the keyWindow for expo-splash-screen to setup correctly.
263
- [_window makeKeyWindow];
264
- return;
155
+ _delegate = delegate;
156
+ _launchOptions = launchOptions;
157
+ NSDictionary *lastOpenedApp = [self.recentlyOpenedAppsRegistry mostRecentApp];
158
+ if (lastOpenedApp != nil) {
159
+ _lastOpenedAppUrl = [NSURL URLWithString:lastOpenedApp[@"url"]];
265
160
  }
161
+ EXDevLauncherBundleURLProviderInterceptor.isInstalled = true;
162
+ EXDevLauncherUncaughtExceptionHandler.isInstalled = true;
266
163
 
267
164
  void (^navigateToLauncher)(NSError *) = ^(NSError *error) {
268
165
  __weak typeof(self) weakSelf = self;
@@ -294,26 +191,6 @@
294
191
  [self navigateToLauncher];
295
192
  }
296
193
 
297
- - (void)autoSetupPrepare:(id<EXDevLauncherControllerDelegate>)delegate launchOptions:(NSDictionary * _Nullable)launchOptions
298
- {
299
- _delegate = delegate;
300
- _launchOptions = launchOptions;
301
- NSDictionary *lastOpenedApp = [self.recentlyOpenedAppsRegistry mostRecentApp];
302
- if (lastOpenedApp != nil) {
303
- _lastOpenedAppUrl = [NSURL URLWithString:lastOpenedApp[@"url"]];
304
- }
305
- EXDevLauncherBundleURLProviderInterceptor.isInstalled = true;
306
- }
307
-
308
- - (void)autoSetupStart:(UIWindow *)window
309
- {
310
- if (_delegate != nil) {
311
- [self startWithWindow:window];
312
- } else {
313
- @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"[EXDevLauncherController autoSetupStart:] was called before autoSetupPrepare:. Make sure you've set up expo-modules correctly in AppDelegate and are using ReactDelegate to create a bridge before calling [super application:didFinishLaunchingWithOptions:]." userInfo:nil];
314
- }
315
- }
316
-
317
194
  - (void)navigateToLauncher
318
195
  {
319
196
  NSAssert([NSThread isMainThread], @"This function must be called on main thread");
@@ -330,23 +207,11 @@
330
207
  [self.delegate destroyReactInstance];
331
208
 
332
209
  #if RCT_DEV
333
- NSURL *url = [self devLauncherURL];
334
- if (url != nil) {
335
- // Connect to the websocket
336
- [[RCTPackagerConnection sharedPackagerConnection] setSocketConnectionURL:url];
337
- }
338
-
339
210
  [self _addInitModuleObserver];
340
211
  #endif
341
-
342
- DevLauncherViewController *swiftUIViewController = [[DevLauncherViewController alloc] init];
343
-
344
- _window.rootViewController = swiftUIViewController;
345
- [_window makeKeyAndVisible];
346
-
347
- dispatch_async(dispatch_get_main_queue(), ^{
348
- [self onAppContentDidAppear];
349
- });
212
+ if (_devLauncherViewController != nil) {
213
+ [_devLauncherViewController resetHostingController];
214
+ }
350
215
  }
351
216
 
352
217
  - (BOOL)onDeepLink:(NSURL *)url options:(NSDictionary *)options
@@ -863,7 +728,10 @@
863
728
 
864
729
  - (UIViewController *)createRootViewController
865
730
  {
866
- return [[DevLauncherViewController alloc] init];
731
+ if (_devLauncherViewController == nil){
732
+ _devLauncherViewController = [[DevLauncherViewController alloc] init];
733
+ }
734
+ return _devLauncherViewController;
867
735
  }
868
736
 
869
737
  - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController
@@ -3,14 +3,6 @@
3
3
  import ExpoModulesCore
4
4
 
5
5
  public class ExpoDevLauncherAppDelegateSubscriber: ExpoAppDelegateSubscriber {
6
- public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
7
- guard let window = UIApplication.shared.delegate?.window ?? UIApplication.shared.windows.filter { $0.isKeyWindow }.first else {
8
- fatalError("Cannot find the keyWindow. Make sure to call `window.makeKeyAndVisible()`.")
9
- }
10
- EXDevLauncherController.sharedInstance().autoSetupStart(window)
11
- return false
12
- }
13
-
14
6
  public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
15
7
  return EXDevLauncherController.sharedInstance().onDeepLink(url, options: options)
16
8
  }
@@ -8,9 +8,9 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe
8
8
  private weak var reactNativeFactory: RCTReactNativeFactory?
9
9
  private weak var reactDelegate: ExpoReactDelegate?
10
10
  private var launchOptions: [AnyHashable: Any]?
11
- private var deferredRootView: EXDevLauncherDeferredRCTRootView?
12
11
  private var rootViewModuleName: String?
13
12
  private var rootViewInitialProperties: [AnyHashable: Any]?
13
+ private weak var rootViewController: UIViewController?
14
14
 
15
15
  public override func createReactRootView(
16
16
  reactDelegate: ExpoReactDelegate,
@@ -24,7 +24,7 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe
24
24
 
25
25
  self.reactDelegate = reactDelegate
26
26
  self.launchOptions = launchOptions
27
- EXDevLauncherController.sharedInstance().autoSetupPrepare(self, launchOptions: launchOptions)
27
+ EXDevLauncherController.sharedInstance().start(self, launchOptions: launchOptions)
28
28
  if let sharedController = UpdatesControllerRegistry.sharedInstance.controller {
29
29
  // for some reason the swift compiler and bridge are having issues here
30
30
  EXDevLauncherController.sharedInstance().updatesInterface = sharedController
@@ -33,8 +33,22 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe
33
33
 
34
34
  self.rootViewModuleName = moduleName
35
35
  self.rootViewInitialProperties = initialProperties
36
- self.deferredRootView = EXDevLauncherDeferredRCTRootView()
37
- return self.deferredRootView
36
+
37
+ let viewController = EXDevLauncherController.sharedInstance().createRootViewController()
38
+ rootViewController = viewController
39
+
40
+ // We need to create a wrapper View because React Native Factory will reassign rootViewController later
41
+ let wrapperView = UIView()
42
+ wrapperView.addSubview(viewController.view)
43
+ viewController.view.translatesAutoresizingMaskIntoConstraints = false
44
+ NSLayoutConstraint.activate([
45
+ viewController.view.topAnchor.constraint(equalTo: wrapperView.topAnchor),
46
+ viewController.view.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor),
47
+ viewController.view.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor),
48
+ viewController.view.bottomAnchor.constraint(equalTo: wrapperView.bottomAnchor)
49
+ ])
50
+
51
+ return wrapperView
38
52
  }
39
53
 
40
54
  @objc
@@ -71,29 +85,13 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe
71
85
  launchOptions: developmentClientController.getLaunchOptions()
72
86
  )
73
87
  developmentClientController.appBridge = RCTBridge.current()
74
- rootView.backgroundColor = self.deferredRootView?.backgroundColor ?? UIColor.white
75
- let window = getWindow()
76
88
 
77
- // NOTE: this order of assignment seems to actually have an effect on behaviour
78
- // direct assignment of window.rootViewController.view = rootView does not work
79
- guard let rootViewController = self.reactDelegate?.createRootViewController() else {
89
+ guard let rootViewController = rootViewController ?? self.reactDelegate?.createRootViewController() else {
80
90
  fatalError("Invalid rootViewController returned from ExpoReactDelegate")
81
91
  }
82
92
  rootViewController.view = rootView
83
- window.rootViewController = rootViewController
84
- window.makeKeyAndVisible()
85
-
86
93
  // it is purposeful that we don't clean up saved properties here, because we may initialize
87
94
  // several React instances over a single app lifetime and we want them all to have the same
88
95
  // initial properties
89
96
  }
90
-
91
- // MARK: Internals
92
-
93
- private func getWindow() -> UIWindow {
94
- guard let window = UIApplication.shared.windows.filter(\.isKeyWindow).first ?? UIApplication.shared.delegate?.window as? UIWindow else {
95
- fatalError("Cannot find the current window.")
96
- }
97
- return window
98
- }
99
97
  }
@@ -7,7 +7,7 @@ import SwiftUI
7
7
 
8
8
  @objc public override init(nibName: String?, bundle: Bundle?) {
9
9
  super.init(nibName: nibName, bundle: bundle)
10
- setupViewController()
10
+ addHostingController()
11
11
  }
12
12
 
13
13
  @objc public convenience init() {
@@ -16,7 +16,7 @@ import SwiftUI
16
16
 
17
17
  required init?(coder: NSCoder) {
18
18
  super.init(coder: coder)
19
- setupViewController()
19
+ addHostingController()
20
20
  }
21
21
 
22
22
  private func setupViewController() {
@@ -27,9 +27,13 @@ import SwiftUI
27
27
  hostingController?.view.backgroundColor = UIColor.clear
28
28
  }
29
29
 
30
- public override func viewDidLoad() {
31
- super.viewDidLoad()
30
+ @objc public func resetHostingController() {
31
+ hostingController = nil
32
+ view = UIView()
33
+ addHostingController()
34
+ }
32
35
 
36
+ private func addHostingController() {
33
37
  if hostingController == nil {
34
38
  setupViewController()
35
39
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "6.1.0-canary-20251015-a6a1272",
4
+ "version": "6.1.0-canary-20251023-4c86f95",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,10 +15,10 @@
15
15
  "license": "MIT",
16
16
  "homepage": "https://docs.expo.dev",
17
17
  "dependencies": {
18
- "expo-dev-menu": "7.0.15-canary-20251015-a6a1272",
19
- "expo-manifests": "1.0.9-canary-20251015-a6a1272"
18
+ "expo-dev-menu": "7.0.16-canary-20251023-4c86f95",
19
+ "expo-manifests": "1.0.9-canary-20251023-4c86f95"
20
20
  },
21
21
  "peerDependencies": {
22
- "expo": "55.0.0-canary-20251015-a6a1272"
22
+ "expo": "55.0.0-canary-20251023-4c86f95"
23
23
  }
24
24
  }
@@ -1,14 +0,0 @@
1
- // Copyright 2018-present 650 Industries. All rights reserved.
2
-
3
- #import <React/RCTRootView.h>
4
-
5
- NS_ASSUME_NONNULL_BEGIN
6
-
7
- /**
8
- EXDevLauncherDeferredRCTRootView is a special no-op class for expo-dev-launcher to defer react instance creation until `EXDevLauncherController` finishing setup.
9
- */
10
- @interface EXDevLauncherDeferredRCTRootView : UIView
11
-
12
- @end
13
-
14
- NS_ASSUME_NONNULL_END
@@ -1,47 +0,0 @@
1
- // Copyright 2018-present 650 Industries. All rights reserved.
2
-
3
- #import <EXDevLauncher/EXDevLauncherDeferredRCTRootView.h>
4
-
5
- @interface EXDevLauncherNoopUIView : UIView
6
-
7
- @end
8
-
9
- @implementation EXDevLauncherNoopUIView
10
-
11
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
12
- {
13
- NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
14
- if (!signature) {
15
- signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
16
- }
17
- return signature;
18
- }
19
-
20
- - (void)forwardInvocation:(NSInvocation *)anInvocation
21
- {
22
- id nilPtr = nil;
23
- [anInvocation setReturnValue:&nilPtr];
24
- }
25
-
26
- @end
27
-
28
- @implementation EXDevLauncherDeferredRCTRootView
29
-
30
- #pragma clang diagnostic push
31
- #pragma clang diagnostic ignored "-Wobjc-designated-initializers"
32
-
33
- - (instancetype)initWithBridge:(RCTBridge *)bridge
34
- moduleName:(NSString *)moduleName
35
- initialProperties:(NSDictionary *)initialProperties
36
- {
37
- // RCTRootView throws an exception for default initializers
38
- // and other designated initializers requires a real bridge.
39
- // We use a trick here to initialize a NoopUIView and cast back to EXDevLauncherDeferredRCTRootView.
40
- self = (EXDevLauncherDeferredRCTRootView *)[[EXDevLauncherNoopUIView alloc] initWithFrame:CGRectZero];
41
- return self;
42
- }
43
-
44
- #pragma clang diagnostic pop
45
-
46
- @end
47
-