@bm-fe/react-native-multi-bundle 1.0.0-beta.6 → 1.0.0-beta.9

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.
@@ -3,6 +3,10 @@
3
3
  #import <React/RCTBridge.h>
4
4
  #import <React/RCTUtils.h>
5
5
  #import <React/RCTLog.h>
6
+ #import <objc/runtime.h>
7
+ #import <jsi/jsi.h>
8
+
9
+ using namespace facebook;
6
10
 
7
11
  @implementation ModuleLoaderModule
8
12
 
@@ -87,176 +91,202 @@ static NSString *const BundleManifestFileName = @"bundle-manifest.json";
87
91
  #pragma mark - Bridge Access
88
92
 
89
93
  /**
90
- * 获取当前的 RCTBridge
94
+ * 获取当前的 RCTBridge(类似 Android 的 ReactContext)
91
95
  */
92
96
  - (RCTBridge *)getCurrentBridge {
93
- // 方法 1: 使用模块自带的 bridge
97
+ // 方法 1: 使用模块自带的 bridge(最可靠)
94
98
  if (self.bridge) {
99
+ RCTLogInfo(@"[ModuleLoader] Using self.bridge: %@", NSStringFromClass([self.bridge class]));
95
100
  return self.bridge;
96
101
  }
97
102
 
98
- // 方法 2: AppDelegate 获取
103
+ // 方法 2: 使用静态方法获取当前 bridge
104
+ RCTBridge *currentBridge = [RCTBridge currentBridge];
105
+ if (currentBridge) {
106
+ RCTLogInfo(@"[ModuleLoader] Using [RCTBridge currentBridge]: %@", NSStringFromClass([currentBridge class]));
107
+ return currentBridge;
108
+ }
109
+
110
+ // 方法 3: 从 AppDelegate 获取
99
111
  UIApplication *app = [UIApplication sharedApplication];
100
112
  id delegate = app.delegate;
101
113
 
102
114
  if ([delegate respondsToSelector:@selector(bridge)]) {
103
115
  RCTBridge *bridge = [delegate performSelector:@selector(bridge)];
104
- if (bridge) return bridge;
105
- }
106
-
107
- // 方法 3: 通过 reactNativeHost
108
- if ([delegate respondsToSelector:@selector(reactNativeHost)]) {
109
- id reactNativeHost = [delegate performSelector:@selector(reactNativeHost)];
110
- if (reactNativeHost && [reactNativeHost respondsToSelector:@selector(bridge)]) {
111
- RCTBridge *bridge = [reactNativeHost performSelector:@selector(bridge)];
112
- if (bridge) return bridge;
116
+ if (bridge) {
117
+ RCTLogInfo(@"[ModuleLoader] Using AppDelegate.bridge: %@", NSStringFromClass([bridge class]));
118
+ return bridge;
113
119
  }
114
120
  }
115
121
 
116
- // 方法 4: 通过 rootViewFactory (新架构)
122
+ // 方法 4: 通过 rootViewFactory
117
123
  if ([delegate respondsToSelector:@selector(rootViewFactory)]) {
118
124
  id factory = [delegate performSelector:@selector(rootViewFactory)];
119
125
  if (factory && [factory respondsToSelector:@selector(bridge)]) {
120
126
  RCTBridge *bridge = [factory performSelector:@selector(bridge)];
121
- if (bridge) return bridge;
127
+ if (bridge) {
128
+ RCTLogInfo(@"[ModuleLoader] Using rootViewFactory.bridge: %@", NSStringFromClass([bridge class]));
129
+ return bridge;
130
+ }
122
131
  }
123
132
  }
124
133
 
134
+ RCTLogWarn(@"[ModuleLoader] Could not get bridge from any source");
125
135
  return nil;
126
136
  }
127
137
 
128
- #pragma mark - Bundle Loading Methods
138
+ #pragma mark - Bundle Loading (JSI)
129
139
 
130
140
  /**
131
- * 尝试使用各种方法加载 bundle
141
+ * 通过 JSI Runtime 加载 bundle
142
+ * 这是 RN 新架构 (RCTBridgeProxy) 下加载子 bundle 的唯一方式
132
143
  */
133
- - (BOOL)tryLoadBundle:(NSString *)bundleContent
134
- sourceURL:(NSURL *)sourceURL
135
- bridge:(RCTBridge *)bridge
136
- error:(NSError **)outError {
144
+ - (BOOL)loadScriptViaJSI:(NSString *)filePath
145
+ sourceURL:(NSURL *)sourceURL
146
+ bridge:(id)bridge
147
+ error:(NSError **)outError {
148
+
149
+ RCTLogInfo(@"[ModuleLoader] Loading script via JSI Runtime");
150
+
151
+ // 读取 bundle 数据
152
+ NSError *readError = nil;
153
+ NSData *bundleData = [NSData dataWithContentsOfFile:filePath options:0 error:&readError];
154
+ if (readError || !bundleData) {
155
+ if (outError) {
156
+ *outError = readError ?: [NSError errorWithDomain:@"ModuleLoader"
157
+ code:1001
158
+ userInfo:@{NSLocalizedDescriptionKey: @"Failed to read bundle file"}];
159
+ }
160
+ return NO;
161
+ }
137
162
 
138
- // 获取 batchedBridge(在新旧架构中都可能存在)
139
- id targetBridge = bridge;
140
- @try {
141
- if ([bridge respondsToSelector:@selector(batchedBridge)]) {
142
- id batchedBridge = [bridge performSelector:@selector(batchedBridge)];
143
- if (batchedBridge) {
144
- targetBridge = batchedBridge;
145
- RCTLogInfo(@"[ModuleLoader] Using batchedBridge");
146
- }
163
+ RCTLogInfo(@"[ModuleLoader] Bundle data loaded: %lu bytes", (unsigned long)bundleData.length);
164
+
165
+ // 获取 JSI runtime
166
+ void *runtimePtr = nil;
167
+
168
+ // 方法 1: 使用 runtime 方法
169
+ SEL runtimeSel = NSSelectorFromString(@"runtime");
170
+ if ([bridge respondsToSelector:runtimeSel]) {
171
+ NSMethodSignature *sig = [bridge methodSignatureForSelector:runtimeSel];
172
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
173
+ [invocation setTarget:bridge];
174
+ [invocation setSelector:runtimeSel];
175
+ [invocation invoke];
176
+ [invocation getReturnValue:&runtimePtr];
177
+ RCTLogInfo(@"[ModuleLoader] Got runtime via runtime method: %p", runtimePtr);
178
+ }
179
+
180
+ // 方法 2: 直接访问 _runtime ivar
181
+ if (!runtimePtr) {
182
+ Ivar runtimeIvar = class_getInstanceVariable([bridge class], "_runtime");
183
+ if (runtimeIvar) {
184
+ runtimePtr = (__bridge void *)object_getIvar(bridge, runtimeIvar);
185
+ RCTLogInfo(@"[ModuleLoader] Got runtime via _runtime ivar: %p", runtimePtr);
147
186
  }
148
- } @catch (NSException *e) {
149
- RCTLogWarn(@"[ModuleLoader] Could not get batchedBridge: %@", e.reason);
150
187
  }
151
188
 
152
- // 方法 1: 尝试 executeSourceCode:withSourceURL:onComplete: (新架构可能支持)
153
- SEL executeWithCompleteSel = NSSelectorFromString(@"executeSourceCode:withSourceURL:onComplete:");
154
- if ([targetBridge respondsToSelector:executeWithCompleteSel]) {
155
- @try {
156
- RCTLogInfo(@"[ModuleLoader] Trying executeSourceCode:withSourceURL:onComplete:");
157
- NSData *sourceData = [bundleContent dataUsingEncoding:NSUTF8StringEncoding];
158
-
159
- __block BOOL completed = NO;
160
- __block NSError *execError = nil;
161
-
162
- void (^onComplete)(NSError *) = ^(NSError *error) {
163
- execError = error;
164
- completed = YES;
165
- };
166
-
167
- NSMethodSignature *sig = [targetBridge methodSignatureForSelector:executeWithCompleteSel];
168
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
169
- [invocation setTarget:targetBridge];
170
- [invocation setSelector:executeWithCompleteSel];
171
- [invocation setArgument:&sourceData atIndex:2];
172
- [invocation setArgument:&sourceURL atIndex:3];
173
- [invocation setArgument:&onComplete atIndex:4];
174
- [invocation invoke];
175
-
176
- RCTLogInfo(@"[ModuleLoader] Bundle loaded via executeSourceCode:withSourceURL:onComplete:");
177
- return YES;
178
- } @catch (NSException *e) {
179
- RCTLogWarn(@"[ModuleLoader] executeSourceCode:withSourceURL:onComplete: failed: %@", e.reason);
189
+ if (!runtimePtr) {
190
+ if (outError) {
191
+ *outError = [NSError errorWithDomain:@"ModuleLoader"
192
+ code:1003
193
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not get JSI runtime from bridge"}];
180
194
  }
195
+ return NO;
181
196
  }
182
197
 
183
- // 方法 2: 尝试通过 CatalystInstance(旧架构)
184
- id catalystInstance = nil;
185
- @try {
186
- catalystInstance = [targetBridge valueForKey:@"_catalystInstance"];
187
- } @catch (NSException *e) {
188
- // 继续尝试其他方法
198
+ // 转换为 jsi::Runtime
199
+ jsi::Runtime *runtime = static_cast<jsi::Runtime *>(runtimePtr);
200
+
201
+ // 转换 NSData 为字符串
202
+ NSString *scriptString = [[NSString alloc] initWithData:bundleData encoding:NSUTF8StringEncoding];
203
+ if (!scriptString) {
204
+ if (outError) {
205
+ *outError = [NSError errorWithDomain:@"ModuleLoader"
206
+ code:1004
207
+ userInfo:@{NSLocalizedDescriptionKey: @"Failed to convert bundle data to string"}];
208
+ }
209
+ return NO;
189
210
  }
190
211
 
191
- if (catalystInstance) {
192
- RCTLogInfo(@"[ModuleLoader] Found CatalystInstance, trying legacy methods");
193
-
194
- // 方法 2a: loadScriptFromString:sourceURL:
195
- SEL loadScriptSel = NSSelectorFromString(@"loadScriptFromString:sourceURL:");
196
- if ([catalystInstance respondsToSelector:loadScriptSel]) {
197
- @try {
198
- #pragma clang diagnostic push
199
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
200
- [catalystInstance performSelector:loadScriptSel withObject:bundleContent withObject:sourceURL];
201
- #pragma clang diagnostic pop
202
- RCTLogInfo(@"[ModuleLoader] Bundle loaded via loadScriptFromString");
203
- return YES;
204
- } @catch (NSException *e) {
205
- RCTLogWarn(@"[ModuleLoader] loadScriptFromString failed: %@", e.reason);
206
- }
212
+ std::string script = std::string([scriptString UTF8String]);
213
+ std::string sourceURLStr = std::string([[sourceURL absoluteString] UTF8String]);
214
+
215
+ // 尝试获取 dispatchToJSThread 或使用 invokeAsync
216
+ __block BOOL success = NO;
217
+ __block NSError *evalError = nil;
218
+
219
+ // 创建执行 block
220
+ void (^executeBlock)(void) = ^{
221
+ @try {
222
+ auto buffer = std::make_shared<jsi::StringBuffer>(script);
223
+ runtime->evaluateJavaScript(buffer, sourceURLStr);
224
+ RCTLogInfo(@"[ModuleLoader] ✅ Bundle executed via JSI evaluateJavaScript");
225
+ success = YES;
226
+ } @catch (NSException *e) {
227
+ RCTLogError(@"[ModuleLoader] JSI evaluateJavaScript failed: %@", e.reason);
228
+ evalError = [NSError errorWithDomain:@"ModuleLoader"
229
+ code:1005
230
+ userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"JSI evaluation failed: %@", e.reason]}];
207
231
  }
232
+ };
208
233
 
209
- // 方法 2b: executeSourceCode:sync:
210
- SEL executeSel = NSSelectorFromString(@"executeSourceCode:sync:");
211
- if ([catalystInstance respondsToSelector:executeSel]) {
212
- @try {
213
- #pragma clang diagnostic push
214
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
215
- [catalystInstance performSelector:executeSel withObject:bundleContent withObject:@NO];
216
- #pragma clang diagnostic pop
217
- RCTLogInfo(@"[ModuleLoader] Bundle loaded via executeSourceCode:sync:");
218
- return YES;
219
- } @catch (NSException *e) {
220
- RCTLogWarn(@"[ModuleLoader] executeSourceCode:sync: failed: %@", e.reason);
221
- }
234
+ // 方法 1: 使用 invokeAsync (RCTBridge 的标准方法)
235
+ SEL invokeAsyncSel = NSSelectorFromString(@"invokeAsync:");
236
+ if ([bridge respondsToSelector:invokeAsyncSel]) {
237
+ RCTLogInfo(@"[ModuleLoader] Using invokeAsync: to dispatch to JS thread");
238
+
239
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
240
+
241
+ void (^wrappedBlock)(void) = ^{
242
+ executeBlock();
243
+ dispatch_semaphore_signal(semaphore);
244
+ };
245
+
246
+ #pragma clang diagnostic push
247
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
248
+ [bridge performSelector:invokeAsyncSel withObject:wrappedBlock];
249
+ #pragma clang diagnostic pop
250
+
251
+ // 等待执行完成 (最多 10 秒)
252
+ dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
253
+
254
+ if (success) {
255
+ return YES;
222
256
  }
223
- } else {
224
- RCTLogInfo(@"[ModuleLoader] CatalystInstance not available (likely new architecture)");
225
257
  }
226
258
 
227
- // 方法 3: 尝试使用 RCTCxxBridge 的方法
228
- if ([NSStringFromClass([targetBridge class]) containsString:@"Cxx"]) {
229
- RCTLogInfo(@"[ModuleLoader] Detected RCTCxxBridge, trying Cxx-specific methods");
230
-
231
- SEL runJSBundleSel = NSSelectorFromString(@"runJSBundle:");
232
- if ([targetBridge respondsToSelector:runJSBundleSel]) {
233
- @try {
234
- #pragma clang diagnostic push
235
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
236
- [targetBridge performSelector:runJSBundleSel withObject:sourceURL];
237
- #pragma clang diagnostic pop
238
- RCTLogInfo(@"[ModuleLoader] Bundle loaded via runJSBundle:");
239
- return YES;
240
- } @catch (NSException *e) {
241
- RCTLogWarn(@"[ModuleLoader] runJSBundle: failed: %@", e.reason);
259
+ // 方法 2: 直接执行 (如果已经在 JS 线程或可以同步执行)
260
+ if (!success) {
261
+ RCTLogInfo(@"[ModuleLoader] Executing directly on current thread");
262
+
263
+ @try {
264
+ auto buffer = std::make_shared<jsi::StringBuffer>(script);
265
+ runtime->evaluateJavaScript(buffer, sourceURLStr);
266
+ RCTLogInfo(@"[ModuleLoader] Bundle executed via direct JSI evaluateJavaScript");
267
+ return YES;
268
+ } @catch (NSException *e) {
269
+ RCTLogError(@"[ModuleLoader] Direct JSI evaluateJavaScript failed: %@", e.reason);
270
+ if (outError) {
271
+ *outError = [NSError errorWithDomain:@"ModuleLoader"
272
+ code:1005
273
+ userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"JSI evaluation failed: %@", e.reason]}];
242
274
  }
275
+ return NO;
243
276
  }
244
277
  }
245
278
 
246
- // 所有方法都失败
247
- if (outError) {
248
- *outError = [NSError errorWithDomain:@"ModuleLoader"
249
- code:1001
250
- userInfo:@{NSLocalizedDescriptionKey: @"No suitable bundle loading method found"}];
279
+ if (evalError && outError) {
280
+ *outError = evalError;
251
281
  }
252
-
253
- return NO;
282
+ return success;
254
283
  }
255
284
 
256
285
  #pragma mark - Main Load Method
257
286
 
258
287
  /**
259
288
  * 加载子 bundle
289
+ * 使用 JSI Runtime 直接执行 JavaScript 代码
260
290
  */
261
291
  RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
262
292
  bundlePath:(NSString *)bundlePath
@@ -272,44 +302,28 @@ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
272
302
  }
273
303
 
274
304
  dispatch_async(dispatch_get_main_queue(), ^{
305
+ // 获取 bridge
275
306
  RCTBridge *bridge = [self getCurrentBridge];
276
-
277
307
  if (!bridge) {
278
308
  RCTLogError(@"[ModuleLoader] Bridge not available");
279
309
  resolve(@{@"success": @NO, @"errorMessage": @"BRIDGE_NOT_AVAILABLE"});
280
310
  return;
281
311
  }
282
312
 
283
- // 读取 bundle 内容
284
- NSError *readError = nil;
285
- NSString *bundleContent = [NSString stringWithContentsOfFile:fullPath
286
- encoding:NSUTF8StringEncoding
287
- error:&readError];
288
- if (readError || !bundleContent) {
289
- RCTLogError(@"[ModuleLoader] Failed to read bundle: %@", readError.localizedDescription);
290
- resolve(@{@"success": @NO, @"errorMessage": [NSString stringWithFormat:@"READ_ERROR: %@", readError.localizedDescription ?: @"Unknown"]});
291
- return;
292
- }
313
+ RCTLogInfo(@"[ModuleLoader] Bridge class: %@", NSStringFromClass([bridge class]));
293
314
 
315
+ // 使用 JSI Runtime 加载脚本
294
316
  NSURL *sourceURL = [NSURL fileURLWithPath:fullPath];
295
317
  NSError *loadError = nil;
296
318
 
297
- BOOL success = [self tryLoadBundle:bundleContent sourceURL:sourceURL bridge:bridge error:&loadError];
319
+ BOOL success = [self loadScriptViaJSI:fullPath sourceURL:sourceURL bridge:bridge error:&loadError];
298
320
 
299
321
  if (success) {
300
- RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully: %@", bundleId);
322
+ RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully: %@", bundleId);
301
323
  resolve(@{@"success": @YES});
302
324
  } else {
303
- // DEBUG 模式下返回成功(因为模块可能已经在主 bundle 中)
304
- #if DEBUG
305
- RCTLogWarn(@"[ModuleLoader] Native loading failed, but continuing (DEBUG mode)");
306
- RCTLogWarn(@"[ModuleLoader] Note: In new architecture, dynamic bundle loading has limitations");
307
- RCTLogWarn(@"[ModuleLoader] The module may need to be included in the main bundle");
308
- resolve(@{@"success": @YES, @"warning": @"NATIVE_LOADING_FAILED_DEBUG_FALLBACK"});
309
- #else
310
- RCTLogError(@"[ModuleLoader] Failed to load bundle: %@", loadError.localizedDescription);
311
- resolve(@{@"success": @NO, @"errorMessage": loadError.localizedDescription ?: @"UNKNOWN_ERROR"});
312
- #endif
325
+ RCTLogError(@"[ModuleLoader] Failed to load bundle: %@", loadError.localizedDescription);
326
+ resolve(@{@"success": @NO, @"errorMessage": loadError.localizedDescription ?: @"LOAD_FAILED"});
313
327
  }
314
328
  });
315
329
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bm-fe/react-native-multi-bundle",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.9",
4
4
  "description": "React Native 多 Bundle 系统 - 支持模块按需加载和独立更新",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",