Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tender-poems-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@callstack/react-native-brownfield': minor
---

Add an opt-in iOS Debug mode for loading the embedded JavaScript bundle with `preferBundledBundleInDebug`, fix `bundleURLOverride` fallback behavior when the override returns `nil`, and add native bundle-resolution tests.
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,26 @@
run: |
yarn workspace @callstack/react-native-brownfield brownfield --version

ios-native-tests:
name: iOS native tests
runs-on: macos-26
needs: build-lint
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6

- name: Setup
uses: ./.github/actions/setup

- name: Run Swift bundle resolver tests
run: |
cd packages/react-native-brownfield/ios
mkdir -p "$RUNNER_TEMP/swift-home" "$RUNNER_TEMP/swift-cache/clang" "$RUNNER_TEMP/swift-cache/swiftpm"
HOME="$RUNNER_TEMP/swift-home" \
CLANG_MODULE_CACHE_PATH="$RUNNER_TEMP/swift-cache/clang" \
swift test --scratch-path "$RUNNER_TEMP/swift-cache/swiftpm"

android-androidapp-expo:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
name: Android road test (RNApp & AndroidApp - Expo ${{ matrix.version }})
runs-on: ubuntu-latest
needs: build-lint
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ secring.gpg

# Typescript
**/*.tsbuildinfo
packages/react-native-brownfield/ios/.build/
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ struct ContentView: View {
NavigationView {

VStack(spacing: 16) {
GreetingCard(name: "iOS Expo")
GreetingCard(name: "iOS Vanilla")

MessagesView()

ReactNativeView(
moduleName: "main",
moduleName: "RNApp",
initialProperties: [
"nativeOsVersionLabel":
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
Expand Down
14 changes: 7 additions & 7 deletions apps/RNApp/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- boost (1.84.0)
- BrownfieldNavigation (3.6.0):
- BrownfieldNavigation (3.6.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -28,7 +28,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- Brownie (3.6.0):
- Brownie (3.6.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2461,7 +2461,7 @@ PODS:
- SocketRocket
- ReactAppDependencyProvider (0.85.0):
- ReactCodegen
- ReactBrownfield (3.6.0):
- ReactBrownfield (3.6.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2898,14 +2898,14 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
BrownfieldNavigation: 0a4abcd0295639640d0222ac5c47ab63d94983c8
Brownie: c75e781646955724c3b385e1a53704cc06491bf0
BrownfieldNavigation: 814180cb04b5cef3ecc4da5f7c91e83f8b5e4d24
Brownie: cd20e6cc71ab50983941cdb371c22a8f55d3e232
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
FBLazyVector: dfb9ab6ee2eac316f7869edf6ec27b9e872329f0
fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac
glog: e56ede4028c4b7418e6b1195a36b1656bb35e225
hermes-engine: 133acc7688f66a6db232bff7de874c7129b01e1e
hermes-engine: 4d7529a5cdee0d79a872e3f164da84c1ec01f559
RCT-Folly: 36c4f904fb6cd0219dcb76b94e9502d2a72fab0b
RCTDeprecation: df7412cdad525035c3adeb14c1dc35b344e98187
RCTRequired: 28a4bf1ef190650fcd6973d8a6a8f8beb30ef807
Expand Down Expand Up @@ -2974,7 +2974,7 @@ SPEC CHECKSUMS:
React-utils: f2dc3878565c3cc54bdf7f65a106efaf93f189a6
React-webperformancenativemodule: 214e42892a044b865f73ad4f88cac6979c27aa76
ReactAppDependencyProvider: 5787b37b8e2e51dfeab697ec031cc7c4080dcea2
ReactBrownfield: 9e36bd174c53254c7a283a6305a4b26589e75f97
ReactBrownfield: 4ff15e707d420a617cb8ad1a225f03a88f0baf3f
ReactCodegen: 6ddd8f44847646a047320a22f5ddb10b27a515c9
ReactCommon: 6a42764f1136fb9ac210e05e88a0733a00ee23d3
RNScreens: e902eba58a27d3ad399a495d578e8aba3ea0f490
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ A singleton that keeps an instance of `ReactNativeBrownfield` object.
| `entryFile` | `NSString` | `index` | Path to JavaScript entry file in development. |
| `bundlePath` | `NSString` | `main.jsbundle` | Path to JavaScript bundle file. |
| `bundle` | `NSBundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. |
| `preferBundledBundleInDebug` | `BOOL` | `NO` | Prefer the embedded JavaScript bundle instead of Metro when the framework is built in Debug. |
| `bundleURLOverride` | `NSURL *(^)(void)` | `nil` | Dynamic bundle URL provider called on every bundle load. When set, overrides default bundle load behavior. |

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ReactNativeBrownfield.shared
| `entryFile` | `String` | `index` | Path to JavaScript entry file in development. |
| `bundlePath` | `String` | `main.jsbundle` | Path to JavaScript bundle file. |
| `bundle` | `Bundle` | `Bundle.main` | Bundle instance to lookup the JavaScript bundle resource. |
| `preferBundledBundleInDebug` | `Bool` | `false` | Prefer the embedded JavaScript bundle instead of Metro when the framework is built in Debug. |
| `bundleURLOverride` | `(() -> URL?)?` | `nil` | Dynamic bundle URL provider called on every bundle load. When set, overrides default behavior. |

---
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/docs/getting-started/expo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ struct IosApp: App {

init() {
ReactNativeBrownfield.shared.bundle = ReactNativeBundle
// Optional: use the packaged bundle even when the consumed framework is built in Debug.
// ReactNativeBrownfield.shared.preferBundledBundleInDebug = true
ReactNativeBrownfield.shared.startReactNative {
print("React Native has been loaded")
}
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/docs/getting-started/ios.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ When running in **Debug**, React Native Brownfield expects a JS dev server runni
npx react-native start
```

If you want to run a **Debug-built** framework without Metro, enable the bundled bundle explicitly before calling `startReactNative`:

```swift
ReactNativeBrownfield.shared.bundle = ReactNativeBundle
ReactNativeBrownfield.shared.preferBundledBundleInDebug = true
ReactNativeBrownfield.shared.startReactNative()
```

### Release Configuration

In **Release**, the JS bundle is loaded directly from the XCFramework - no dev server needed.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package com.callstack.nativebrownfieldnavigation

interface BrownfieldNavigationDelegate
interface BrownfieldNavigationDelegate {
fun navigateToSettings()
fun navigateToReferrals(userId: String)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.callstack.nativebrownfieldnavigation

import android.util.Log
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod

class NativeBrownfieldNavigationModule(
reactContext: ReactApplicationContext
) : NativeBrownfieldNavigationSpec(reactContext) {
@ReactMethod
override fun temporary() {
Log.d(NAME, "temporary")
override fun navigateToSettings() {
BrownfieldNavigationManager.getDelegate().navigateToSettings()
}

@ReactMethod
override fun navigateToReferrals(userId: String) {
BrownfieldNavigationManager.getDelegate().navigateToReferrals(userId)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation

@objc public protocol BrownfieldNavigationDelegate: AnyObject {

@objc func navigateToSettings()
@objc func navigateToReferrals(_ userId: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

@implementation NativeBrownfieldNavigation

- (void)temporary {
NSLog(@"temporary");
- (void)navigateToSettings {
[[[BrownfieldNavigationManager shared] getDelegate] navigateToSettings];
}

- (void)navigateToReferrals:(NSString *)userId {
[[[BrownfieldNavigationManager shared] getDelegate] navigateToReferrals:userId];
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TurboModuleRegistry, type TurboModule } from 'react-native';

export interface Spec extends TurboModule {
temporary(): void;
navigateToSettings(): void;
navigateToReferrals(userId: string): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
Expand Down
7 changes: 5 additions & 2 deletions packages/brownfield-navigation/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import NativeBrownfieldNavigation from './NativeBrownfieldNavigation';

const BrownfieldNavigation = {
temporary: () => {
NativeBrownfieldNavigation.temporary();
navigateToSettings: () => {
NativeBrownfieldNavigation.navigateToSettings();
},
navigateToReferrals: (userId: string) => {
NativeBrownfieldNavigation.navigateToReferrals(userId);
},
};

Expand Down
37 changes: 35 additions & 2 deletions packages/cli/src/brownfield/commands/packageIos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
import { stripFrameworkBinary } from '../utils/stripFrameworkBinary.js';
import { copyDebugBundleToSimulatorSlice } from '../utils/copyDebugBundleToSimulatorSlice.js';

export const packageIosCommand = curryOptions(
new Command('package:ios').description('Build iOS XCFramework'),
Expand Down Expand Up @@ -96,6 +97,40 @@ export const packageIosCommand = curryOptions(
platformConfig
);

const productsPath = path.join(options.buildFolder, 'Build', 'Products');
const frameworkName = options.scheme;

if (frameworkName) {
copyDebugBundleToSimulatorSlice({
productsPath,
configuration,
frameworkName,
});

if (configuration.includes('Debug')) {
await mergeFrameworks({
sourceDir: userConfig.project.ios.sourceDir,
frameworkPaths: [
path.join(
productsPath,
`${configuration}-iphoneos`,
`${frameworkName}.framework`
),
path.join(
productsPath,
`${configuration}-iphonesimulator`,
`${frameworkName}.framework`
),
],
outputPath: path.join(packageDir, `${frameworkName}.xcframework`),
});
}
} else if (configuration.includes('Debug')) {
logger.warn(
'Skipping Debug simulator JS bundle copy: scheme is required to locate the framework output'
);
}

const reactBrownfieldXcframeworkPath = path.join(
packageDir,
'ReactBrownfield.xcframework'
Expand All @@ -108,7 +143,6 @@ export const packageIosCommand = curryOptions(
}

if (hasBrownie) {
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
const brownieOutputPath = path.join(packageDir, 'Brownie.xcframework');

await mergeFrameworks({
Expand Down Expand Up @@ -141,7 +175,6 @@ export const packageIosCommand = curryOptions(
}

if (hasNavigation) {
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
const brownfieldNavigationOutputPath = path.join(packageDir, 'BrownfieldNavigation.xcframework');

await mergeFrameworks({
Expand Down
Loading
Loading