From 1b81ebd5b5905e8b9cd7ef9eea7206e45f94c583 Mon Sep 17 00:00:00 2001 From: gronxb Date: Wed, 21 Jan 2026 20:17:11 +0900 Subject: [PATCH 1/7] feat(ios): add bundleURL provider for dynamic bundle loading --- docs/docs/docs/api-reference/swift.mdx | 13 ++++---- .../ios/ReactNativeBrownfield.swift | 30 ++++++++++++++----- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/docs/docs/docs/api-reference/swift.mdx b/docs/docs/docs/api-reference/swift.mdx index db1cd2da..31b11687 100644 --- a/docs/docs/docs/api-reference/swift.mdx +++ b/docs/docs/docs/api-reference/swift.mdx @@ -28,12 +28,13 @@ ReactNativeBrownfield.shared #### Properties -| Property | Type | Default | Description | -| ------------------ | --------- | --------------- | --------------------------------------------------------- | -| `entryFile` | `String` | `index` | Path to JavaScript root. | -| `fallbackResource` | `String?` | `nil` | Path to bundle fallback resource. | -| `bundlePath` | `String` | `main.jsbundle` | Path to bundle fallback resource. | -| `bundle` | `Bundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. | +| Property | Type | Default | Description | +| ------------------ | ------------------ | --------------- | --------------------------------------------------------------------------------------------- | +| `entryFile` | `String` | `index` | Path to JavaScript root. | +| `fallbackResource` | `String?` | `nil` | Path to bundle fallback resource. | +| `bundlePath` | `String` | `main.jsbundle` | Path to bundle fallback resource. | +| `bundle` | `Bundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. | +| `bundleURL` | `(() -> URL?)?` | `nil` | Dynamic bundle URL provider called on every bundle load. When set, overrides default behavior. | --- diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index d52b5b2a..7e36ed95 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -7,13 +7,18 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { var entryFile = "index" var bundlePath = "main.jsbundle" var bundle = Bundle.main + var bundleURLProvider: (() -> URL?)? = nil // MARK: - RCTReactNativeFactoryDelegate Methods - + override func sourceURL(for bridge: RCTBridge) -> URL? { return bundleURL() } - + public override func bundleURL() -> URL? { + if let provider = bundleURLProvider { + return provider() + } + #if DEBUG return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: entryFile) #else @@ -21,7 +26,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { let withoutLast = resourceURLComponents[..<(resourceURLComponents.count - 1)] let resourceName = withoutLast.joined() let fileExtension = resourceURLComponents.last ?? "" - + return bundle.url(forResource: resourceName, withExtension: fileExtension) #endif } @@ -31,7 +36,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { public static let shared = ReactNativeBrownfield() private var onBundleLoaded: (() -> Void)? private var delegate = ReactNativeBrownfieldDelegate() - + /** * Path to JavaScript root. * Default value: "index" @@ -64,6 +69,17 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { delegate.bundle = bundle } } + /** + * Dynamic bundle URL provider called on every bundle load. + * When set, this overrides the default bundleURL() behavior in the delegate. + * Returns a URL to load a custom bundle, or nil to use default behavior. + * Default value: nil + */ + @objc public var bundleURL: (() -> URL?)? = nil { + didSet { + delegate.bundleURLProvider = bundleURL + } + } /** * React Native factory instance created when starting React Native. * Default value: nil @@ -112,10 +128,10 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { */ @objc public func startReactNative(onBundleLoaded: (() -> Void)?, launchOptions: [AnyHashable: Any]?) { guard reactNativeFactory == nil else { return } - + delegate.dependencyProvider = RCTAppDependencyProvider() self.reactNativeFactory = RCTReactNativeFactory(delegate: delegate) - + if let onBundleLoaded { self.onBundleLoaded = onBundleLoaded if RCTIsNewArchEnabled() { @@ -135,7 +151,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { } } } - + @objc private func jsLoaded(_ notification: Notification) { onBundleLoaded?() onBundleLoaded = nil From 1cd75e2abe6c3fc69a6baf63d52664780578195f Mon Sep 17 00:00:00 2001 From: gronxb Date: Wed, 21 Jan 2026 20:37:08 +0900 Subject: [PATCH 2/7] chore: changeset --- .changeset/social-seas-see.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/social-seas-see.md diff --git a/.changeset/social-seas-see.md b/.changeset/social-seas-see.md new file mode 100644 index 00000000..7402a991 --- /dev/null +++ b/.changeset/social-seas-see.md @@ -0,0 +1,5 @@ +--- +'@callstack/react-native-brownfield': minor +--- + +feat(ios): add bundleURL provider for dynamic bundle loading From 41ad8ef14818b479aa14de8717cee3caf05e7c29 Mon Sep 17 00:00:00 2001 From: gronxb Date: Fri, 23 Jan 2026 10:33:05 +0900 Subject: [PATCH 3/7] fix: rename bundleURLOverride --- docs/docs/docs/api-reference/swift.mdx | 14 +++++++------- .../ios/ReactNativeBrownfield.swift | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/docs/docs/api-reference/swift.mdx b/docs/docs/docs/api-reference/swift.mdx index 31b11687..a787050d 100644 --- a/docs/docs/docs/api-reference/swift.mdx +++ b/docs/docs/docs/api-reference/swift.mdx @@ -28,13 +28,13 @@ ReactNativeBrownfield.shared #### Properties -| Property | Type | Default | Description | -| ------------------ | ------------------ | --------------- | --------------------------------------------------------------------------------------------- | -| `entryFile` | `String` | `index` | Path to JavaScript root. | -| `fallbackResource` | `String?` | `nil` | Path to bundle fallback resource. | -| `bundlePath` | `String` | `main.jsbundle` | Path to bundle fallback resource. | -| `bundle` | `Bundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. | -| `bundleURL` | `(() -> URL?)?` | `nil` | Dynamic bundle URL provider called on every bundle load. When set, overrides default behavior. | +| Property | Type | Default | Description | +| ------------------- | ------------------ | --------------- | --------------------------------------------------------------------------------------------- | +| `entryFile` | `String` | `index` | Path to JavaScript root. | +| `fallbackResource` | `String?` | `nil` | Path to bundle fallback resource. | +| `bundlePath` | `String` | `main.jsbundle` | Path to bundle fallback resource. | +| `bundle` | `Bundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. | +| `bundleURLOverride` | `(() -> URL?)?` | `nil` | Dynamic bundle URL provider called on every bundle load. When set, overrides default behavior. | --- diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index 7e36ed95..69fe9d67 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -9,16 +9,16 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { var bundle = Bundle.main var bundleURLProvider: (() -> URL?)? = nil // MARK: - RCTReactNativeFactoryDelegate Methods - + override func sourceURL(for bridge: RCTBridge) -> URL? { return bundleURL() } - + public override func bundleURL() -> URL? { if let provider = bundleURLProvider { return provider() } - + #if DEBUG return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: entryFile) #else @@ -36,7 +36,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { public static let shared = ReactNativeBrownfield() private var onBundleLoaded: (() -> Void)? private var delegate = ReactNativeBrownfieldDelegate() - + /** * Path to JavaScript root. * Default value: "index" @@ -75,7 +75,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { * Returns a URL to load a custom bundle, or nil to use default behavior. * Default value: nil */ - @objc public var bundleURL: (() -> URL?)? = nil { + @objc public var bundleURLOverride: (() -> URL?)? = nil { didSet { delegate.bundleURLProvider = bundleURL } @@ -128,10 +128,10 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { */ @objc public func startReactNative(onBundleLoaded: (() -> Void)?, launchOptions: [AnyHashable: Any]?) { guard reactNativeFactory == nil else { return } - + delegate.dependencyProvider = RCTAppDependencyProvider() self.reactNativeFactory = RCTReactNativeFactory(delegate: delegate) - + if let onBundleLoaded { self.onBundleLoaded = onBundleLoaded if RCTIsNewArchEnabled() { @@ -151,7 +151,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { } } } - + @objc private func jsLoaded(_ notification: Notification) { onBundleLoaded?() onBundleLoaded = nil From 6d017028a7e06518e99d22c42d72219ff0735a2c Mon Sep 17 00:00:00 2001 From: gronxb Date: Fri, 23 Jan 2026 10:34:30 +0900 Subject: [PATCH 4/7] fix: rename --- .../react-native-brownfield/ios/ReactNativeBrownfield.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index 69fe9d67..d24bd586 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -77,7 +77,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { */ @objc public var bundleURLOverride: (() -> URL?)? = nil { didSet { - delegate.bundleURLProvider = bundleURL + delegate.bundleURLProvider = bundleURLOverride } } /** From 09b0e0c3be6935348c009aa44ef585444f76214f Mon Sep 17 00:00:00 2001 From: gronxb Date: Fri, 23 Jan 2026 10:36:45 +0900 Subject: [PATCH 5/7] fix: indent --- .../react-native-brownfield/ios/ReactNativeBrownfield.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index d24bd586..9a96414a 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -26,7 +26,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { let withoutLast = resourceURLComponents[..<(resourceURLComponents.count - 1)] let resourceName = withoutLast.joined() let fileExtension = resourceURLComponents.last ?? "" - + return bundle.url(forResource: resourceName, withExtension: fileExtension) #endif } From 48b3d351685da1eb7c0b7714126348f3a8b6e7b4 Mon Sep 17 00:00:00 2001 From: gronxb Date: Fri, 23 Jan 2026 10:41:35 +0900 Subject: [PATCH 6/7] fix: rename --- .../react-native-brownfield/ios/ReactNativeBrownfield.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index 9a96414a..15557a0a 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -7,7 +7,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { var entryFile = "index" var bundlePath = "main.jsbundle" var bundle = Bundle.main - var bundleURLProvider: (() -> URL?)? = nil + var bundleURLOverride: (() -> URL?)? = nil // MARK: - RCTReactNativeFactoryDelegate Methods override func sourceURL(for bridge: RCTBridge) -> URL? { @@ -15,7 +15,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { } public override func bundleURL() -> URL? { - if let provider = bundleURLProvider { + if let provider = bundleURLOverride { return provider() } @@ -77,7 +77,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { */ @objc public var bundleURLOverride: (() -> URL?)? = nil { didSet { - delegate.bundleURLProvider = bundleURLOverride + delegate.bundleURLOverride = bundleURLOverride } } /** From 6c5f9fd3a5034d7f471280823745f9f388cb209f Mon Sep 17 00:00:00 2001 From: gronxb Date: Fri, 23 Jan 2026 16:32:52 +0900 Subject: [PATCH 7/7] fix: renaming --- .../react-native-brownfield/ios/ReactNativeBrownfield.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift index 15557a0a..046e1cd9 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfield.swift @@ -15,8 +15,8 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate { } public override func bundleURL() -> URL? { - if let provider = bundleURLOverride { - return provider() + if let bundleURLProvider = bundleURLOverride { + return bundleURLProvider() } #if DEBUG