@bm-fe/react-native-multi-bundle 1.0.0-beta.5 → 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
 
@@ -14,115 +18,275 @@ RCT_EXPORT_MODULE(ModuleLoader);
14
18
  // Bundle manifest file name
15
19
  static NSString *const BundleManifestFileName = @"bundle-manifest.json";
16
20
 
21
+ #pragma mark - Bundle Path Resolution
22
+
17
23
  /**
18
24
  * 获取 bundle 文件的完整路径
19
- * 查找顺序:
20
- * 1. CodePush 目录(如果可用)
21
- * 2. MainBundle 的 Bundles 目录
22
- * 3. MainBundle 根目录
23
- * 4. MainBundle 的 assets 目录
24
- * 5. Documents 目录
25
25
  */
26
26
  - (NSString *)getFullBundlePath:(NSString *)bundlePath {
27
- // bundlePath 格式可能是 "modules/home.jsbundle" 或 "home.jsbundle"
28
27
  NSString *fileName = [bundlePath lastPathComponent];
29
28
  NSString *relativePath = bundlePath;
30
-
31
- // 如果路径包含 "modules/",提取文件名
29
+
32
30
  if ([bundlePath containsString:@"modules/"]) {
33
31
  fileName = [bundlePath lastPathComponent];
34
32
  relativePath = [NSString stringWithFormat:@"modules/%@", fileName];
35
33
  }
36
-
37
- // 使用 NSMutableArray 来避免 nil 值问题
34
+
38
35
  NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
39
-
40
- // CodePush 路径(如果可用)
36
+
37
+ // CodePush 路径
41
38
  NSString *codePushPath = [self getCodePushBundlePath:bundlePath];
42
39
  if (codePushPath) {
43
40
  [searchPaths addObject:codePushPath];
44
41
  }
45
-
42
+
46
43
  // MainBundle/Bundles/modules/xxx.jsbundle
47
44
  NSString *path1 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:relativePath]];
48
- if (path1) {
49
- [searchPaths addObject:path1];
50
- }
51
-
52
- // MainBundle/Bundles/xxx.jsbundle (直接文件名)
45
+ if (path1) [searchPaths addObject:path1];
46
+
47
+ // MainBundle/Bundles/xxx.jsbundle
53
48
  NSString *path2 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:fileName]];
54
- if (path2) {
55
- [searchPaths addObject:path2];
56
- }
57
-
58
- // MainBundle 根目录下的完整路径
49
+ if (path2) [searchPaths addObject:path2];
50
+
51
+ // MainBundle 根目录
59
52
  NSString *path3 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:bundlePath];
60
- if (path3) {
61
- [searchPaths addObject:path3];
62
- }
63
-
64
- // MainBundle 根目录下的文件名
53
+ if (path3) [searchPaths addObject:path3];
54
+
65
55
  NSString *path4 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:fileName];
66
- if (path4) {
67
- [searchPaths addObject:path4];
68
- }
69
-
70
- // MainBundle/resourcePath 下的完整路径
56
+ if (path4) [searchPaths addObject:path4];
57
+
58
+ // resourcePath
71
59
  NSString *path5 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:bundlePath];
72
- if (path5) {
73
- [searchPaths addObject:path5];
74
- }
75
-
76
- // MainBundle/resourcePath 下的文件名
60
+ if (path5) [searchPaths addObject:path5];
61
+
77
62
  NSString *path6 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
78
- if (path6) {
79
- [searchPaths addObject:path6];
80
- }
81
-
63
+ if (path6) [searchPaths addObject:path6];
64
+
82
65
  // Documents 目录
83
66
  NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
84
67
  if (documentsPath) {
85
68
  NSString *path7 = [documentsPath stringByAppendingPathComponent:bundlePath];
86
- if (path7) {
87
- [searchPaths addObject:path7];
88
- }
69
+ if (path7) [searchPaths addObject:path7];
89
70
  }
90
-
71
+
91
72
  for (NSString *fullPath in searchPaths) {
92
73
  if (fullPath && [[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
93
74
  RCTLogInfo(@"[ModuleLoader] Found bundle at: %@", fullPath);
94
75
  return fullPath;
95
76
  }
96
77
  }
97
-
98
- // 记录所有搜索路径用于调试
99
- RCTLogWarn(@"[ModuleLoader] Bundle not found in any location: %@", bundlePath);
100
- RCTLogWarn(@"[ModuleLoader] Searched locations:");
78
+
79
+ RCTLogWarn(@"[ModuleLoader] Bundle not found: %@", bundlePath);
101
80
  for (NSString *path in searchPaths) {
102
- if (path) {
103
- RCTLogWarn(@"[ModuleLoader] - %@", path);
104
- }
81
+ if (path) RCTLogWarn(@"[ModuleLoader] - %@", path);
105
82
  }
106
-
83
+
107
84
  return nil;
108
85
  }
109
86
 
87
+ - (NSString *)getCodePushBundlePath:(NSString *)bundlePath {
88
+ return nil;
89
+ }
90
+
91
+ #pragma mark - Bridge Access
92
+
110
93
  /**
111
- * 获取 CodePush bundle 路径(如果可用)
94
+ * 获取当前的 RCTBridge(类似 Android 的 ReactContext)
112
95
  */
113
- - (NSString *)getCodePushBundlePath:(NSString *)bundlePath {
114
- // 这里可以集成 CodePush SDK 来获取路径
115
- // 暂时返回 nil,表示不使用 CodePush
96
+ - (RCTBridge *)getCurrentBridge {
97
+ // 方法 1: 使用模块自带的 bridge(最可靠)
98
+ if (self.bridge) {
99
+ RCTLogInfo(@"[ModuleLoader] Using self.bridge: %@", NSStringFromClass([self.bridge class]));
100
+ return self.bridge;
101
+ }
102
+
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 获取
111
+ UIApplication *app = [UIApplication sharedApplication];
112
+ id delegate = app.delegate;
113
+
114
+ if ([delegate respondsToSelector:@selector(bridge)]) {
115
+ RCTBridge *bridge = [delegate performSelector:@selector(bridge)];
116
+ if (bridge) {
117
+ RCTLogInfo(@"[ModuleLoader] Using AppDelegate.bridge: %@", NSStringFromClass([bridge class]));
118
+ return bridge;
119
+ }
120
+ }
121
+
122
+ // 方法 4: 通过 rootViewFactory
123
+ if ([delegate respondsToSelector:@selector(rootViewFactory)]) {
124
+ id factory = [delegate performSelector:@selector(rootViewFactory)];
125
+ if (factory && [factory respondsToSelector:@selector(bridge)]) {
126
+ RCTBridge *bridge = [factory performSelector:@selector(bridge)];
127
+ if (bridge) {
128
+ RCTLogInfo(@"[ModuleLoader] Using rootViewFactory.bridge: %@", NSStringFromClass([bridge class]));
129
+ return bridge;
130
+ }
131
+ }
132
+ }
133
+
134
+ RCTLogWarn(@"[ModuleLoader] Could not get bridge from any source");
116
135
  return nil;
117
136
  }
118
137
 
138
+ #pragma mark - Bundle Loading (JSI)
139
+
140
+ /**
141
+ * 通过 JSI Runtime 加载 bundle
142
+ * 这是 RN 新架构 (RCTBridgeProxy) 下加载子 bundle 的唯一方式
143
+ */
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
+ }
162
+
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);
186
+ }
187
+ }
188
+
189
+ if (!runtimePtr) {
190
+ if (outError) {
191
+ *outError = [NSError errorWithDomain:@"ModuleLoader"
192
+ code:1003
193
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not get JSI runtime from bridge"}];
194
+ }
195
+ return NO;
196
+ }
197
+
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;
210
+ }
211
+
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]}];
231
+ }
232
+ };
233
+
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;
256
+ }
257
+ }
258
+
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]}];
274
+ }
275
+ return NO;
276
+ }
277
+ }
278
+
279
+ if (evalError && outError) {
280
+ *outError = evalError;
281
+ }
282
+ return success;
283
+ }
284
+
285
+ #pragma mark - Main Load Method
286
+
119
287
  /**
120
288
  * 加载子 bundle
121
- *
122
- * @param bundleId 模块 ID(如 "home", "details", "settings")
123
- * @param bundlePath bundle 文件路径(如 "modules/home.jsbundle")
124
- * @param resolve 成功回调
125
- * @param reject 失败回调
289
+ * 使用 JSI Runtime 直接执行 JavaScript 代码
126
290
  */
127
291
  RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
128
292
  bundlePath:(NSString *)bundlePath
@@ -130,191 +294,54 @@ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
130
294
  rejecter:(RCTPromiseRejectBlock)reject)
131
295
  {
132
296
  RCTLogInfo(@"[ModuleLoader] loadBusinessBundle: bundleId=%@, bundlePath=%@", bundleId, bundlePath);
133
-
134
- // 获取完整路径
297
+
135
298
  NSString *fullPath = [self getFullBundlePath:bundlePath];
136
299
  if (!fullPath) {
137
- resolve(@{
138
- @"success": @NO,
139
- @"errorMessage": @"BUNDLE_PATH_NOT_FOUND"
140
- });
300
+ resolve(@{@"success": @NO, @"errorMessage": @"BUNDLE_PATH_NOT_FOUND"});
141
301
  return;
142
302
  }
143
-
144
- // 确保在主线程执行
303
+
145
304
  dispatch_async(dispatch_get_main_queue(), ^{
146
- // 尝试多种方式获取 bridge
147
- RCTBridge *bridge = nil;
148
-
149
- // 方法 1: 从 AppDelegate 获取(最可靠的方式)
150
- UIApplication *app = [UIApplication sharedApplication];
151
- id delegate = app.delegate;
152
- if (delegate && [delegate isKindOfClass:NSClassFromString(@"RCTAppDelegate")]) {
153
- // 尝试获取 bridge 属性
154
- if ([delegate respondsToSelector:@selector(bridge)]) {
155
- bridge = [delegate performSelector:@selector(bridge)];
156
- }
157
- // 尝试获取 reactNativeHost,然后获取 bridge
158
- if (!bridge && [delegate respondsToSelector:@selector(reactNativeHost)]) {
159
- id reactNativeHost = [delegate performSelector:@selector(reactNativeHost)];
160
- if (reactNativeHost && [reactNativeHost respondsToSelector:@selector(bridge)]) {
161
- bridge = [reactNativeHost performSelector:@selector(bridge)];
162
- }
163
- }
164
- }
165
-
166
- // 方法 2: 使用 self.bridge(如果可用,通过协议方法)
305
+ // 获取 bridge
306
+ RCTBridge *bridge = [self getCurrentBridge];
167
307
  if (!bridge) {
168
- // RCTBridgeModule 协议应该提供 bridge 属性
169
- // 但需要通过 valueForKey 或直接访问
170
- @try {
171
- bridge = [self valueForKey:@"bridge"];
172
- } @catch (NSException *e) {
173
- RCTLogWarn(@"[ModuleLoader] Could not access bridge via valueForKey: %@", e.reason);
174
- }
175
- }
176
-
177
- if (!bridge) {
178
- RCTLogError(@"[ModuleLoader] Bridge not available - cannot load bundle");
179
- resolve(@{
180
- @"success": @NO,
181
- @"errorMessage": @"BRIDGE_NOT_AVAILABLE"
182
- });
308
+ RCTLogError(@"[ModuleLoader] Bridge not available");
309
+ resolve(@{@"success": @NO, @"errorMessage": @"BRIDGE_NOT_AVAILABLE"});
183
310
  return;
184
311
  }
185
-
186
- // 读取 bundle 内容
187
- NSError *error = nil;
188
- NSString *bundleContent = [NSString stringWithContentsOfFile:fullPath
189
- encoding:NSUTF8StringEncoding
190
- error:&error];
191
- if (error || !bundleContent) {
192
- RCTLogError(@"[ModuleLoader] Failed to read bundle file: %@", error.localizedDescription);
193
- resolve(@{
194
- @"success": @NO,
195
- @"errorMessage": [NSString stringWithFormat:@"READ_ERROR: %@", error.localizedDescription ?: @"Unknown error"]
196
- });
197
- return;
198
- }
199
-
200
- // 使用 CatalystInstance 执行脚本
201
- // 注意:这是 React Native 的内部 API,但这是动态加载 bundle 的标准方式
202
- @try {
203
- // 获取 CatalystInstance
204
- id catalystInstance = nil;
205
-
206
- // 尝试从 bridge 获取 batchedBridge(旧架构)
207
- if ([bridge respondsToSelector:@selector(batchedBridge)]) {
208
- catalystInstance = [bridge performSelector:@selector(batchedBridge)];
209
- }
210
-
211
- // 如果失败,尝试直接获取 _catalystInstance(私有属性)
212
- if (!catalystInstance) {
213
- @try {
214
- catalystInstance = [bridge valueForKey:@"_catalystInstance"];
215
- } @catch (NSException *e) {
216
- RCTLogWarn(@"[ModuleLoader] Could not access _catalystInstance: %@", e.reason);
217
- }
218
- }
219
-
220
- if (catalystInstance) {
221
- // 方法 1: 尝试 loadScriptFromString:sourceURL:
222
- SEL loadScriptSelector = NSSelectorFromString(@"loadScriptFromString:sourceURL:");
223
- if ([catalystInstance respondsToSelector:loadScriptSelector]) {
224
- NSURL *sourceURL = [NSURL fileURLWithPath:fullPath];
225
- #pragma clang diagnostic push
226
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
227
- [catalystInstance performSelector:loadScriptSelector withObject:bundleContent withObject:sourceURL];
228
- #pragma clang diagnostic pop
229
-
230
- RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully via loadScriptFromString: %@", bundleId);
231
- resolve(@{@"success": @YES});
232
- return;
233
- }
234
-
235
- // 方法 2: 尝试 executeSourceCode:sync:
236
- SEL executeSelector = NSSelectorFromString(@"executeSourceCode:sync:");
237
- if ([catalystInstance respondsToSelector:executeSelector]) {
238
- #pragma clang diagnostic push
239
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
240
- [catalystInstance performSelector:executeSelector withObject:bundleContent withObject:@NO];
241
- #pragma clang diagnostic pop
242
-
243
- RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully via executeSourceCode: %@", bundleId);
244
- resolve(@{@"success": @YES});
245
- return;
246
- }
247
-
248
- // 方法 3: 尝试 loadScriptFromFile:withSourceURL:
249
- SEL loadFileSelector = NSSelectorFromString(@"loadScriptFromFile:withSourceURL:");
250
- if ([catalystInstance respondsToSelector:loadFileSelector]) {
251
- NSURL *sourceURL = [NSURL fileURLWithPath:fullPath];
252
- #pragma clang diagnostic push
253
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
254
- [catalystInstance performSelector:loadFileSelector withObject:fullPath withObject:sourceURL];
255
- #pragma clang diagnostic pop
256
-
257
- RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully via loadScriptFromFile: %@", bundleId);
258
- resolve(@{@"success": @YES});
259
- return;
260
- }
261
-
262
- RCTLogError(@"[ModuleLoader] CatalystInstance found but no suitable method available");
263
- } else {
264
- RCTLogError(@"[ModuleLoader] CatalystInstance not found");
265
- }
266
-
267
- // 如果所有方法都失败,在开发环境下返回成功(用于测试)
268
- #if DEBUG
269
- RCTLogWarn(@"[ModuleLoader] Using debug fallback - bundle may not execute");
312
+
313
+ RCTLogInfo(@"[ModuleLoader] Bridge class: %@", NSStringFromClass([bridge class]));
314
+
315
+ // 使用 JSI Runtime 加载脚本
316
+ NSURL *sourceURL = [NSURL fileURLWithPath:fullPath];
317
+ NSError *loadError = nil;
318
+
319
+ BOOL success = [self loadScriptViaJSI:fullPath sourceURL:sourceURL bridge:bridge error:&loadError];
320
+
321
+ if (success) {
322
+ RCTLogInfo(@"[ModuleLoader] ✅ Bundle loaded successfully: %@", bundleId);
270
323
  resolve(@{@"success": @YES});
271
- #else
272
- resolve(@{
273
- @"success": @NO,
274
- @"errorMessage": @"CATALYST_INSTANCE_METHOD_NOT_FOUND"
275
- });
276
- #endif
277
-
278
- } @catch (NSException *exception) {
279
- RCTLogError(@"[ModuleLoader] Exception while loading bundle: %@", exception.reason);
280
- resolve(@{
281
- @"success": @NO,
282
- @"errorMessage": [NSString stringWithFormat:@"EXCEPTION: %@", exception.reason ?: @"Unknown exception"]
283
- });
324
+ } else {
325
+ RCTLogError(@"[ModuleLoader] ❌ Failed to load bundle: %@", loadError.localizedDescription);
326
+ resolve(@{@"success": @NO, @"errorMessage": loadError.localizedDescription ?: @"LOAD_FAILED"});
284
327
  }
285
328
  });
286
329
  }
287
330
 
288
331
  #pragma mark - Bundle Manifest Methods
289
332
 
290
- /**
291
- * 获取 Application Support 目录路径
292
- */
293
- + (NSString *)getApplicationSupportDirectory
294
- {
295
- NSString *applicationSupportDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
296
- return applicationSupportDirectory;
333
+ + (NSString *)getApplicationSupportDirectory {
334
+ return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
297
335
  }
298
336
 
299
- /**
300
- * 获取 bundle assets 路径
301
- */
302
- + (NSString *)bundleAssetsPath
303
- {
304
- NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
305
- return [resourcePath stringByAppendingPathComponent:@"assets"];
337
+ + (NSString *)bundleAssetsPath {
338
+ return [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"assets"];
306
339
  }
307
340
 
308
- /**
309
- * 读取文件内容
310
- */
311
- - (NSString *)readFileContent:(NSString *)filePath
312
- {
341
+ - (NSString *)readFileContent:(NSString *)filePath {
313
342
  @try {
314
343
  NSError *error;
315
- NSString *content = [NSString stringWithContentsOfFile:filePath
316
- encoding:NSUTF8StringEncoding
317
- error:&error];
344
+ NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
318
345
  if (error) {
319
346
  RCTLogWarn(@"[ModuleLoader] Failed to read file: %@ - %@", filePath, error.localizedDescription);
320
347
  return nil;
@@ -326,78 +353,62 @@ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
326
353
  }
327
354
  }
328
355
 
329
- /**
330
- * 获取当前 bundle manifest 文件路径
331
- * 查找顺序:
332
- * 1. Bundles 目录
333
- * 2. Documents 目录
334
- * 3. MainBundle assets 目录
335
- */
336
356
  RCT_EXPORT_METHOD(getCurrentBundleManifest:(RCTPromiseResolveBlock)resolve
337
357
  rejecter:(RCTPromiseRejectBlock)reject)
338
358
  {
339
359
  @try {
340
360
  NSFileManager *fileManager = [NSFileManager defaultManager];
341
-
342
- // 1. 先尝试从 MainBundle/Bundles 目录获取
361
+
362
+ // 1. MainBundle/Bundles 目录
343
363
  NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
344
364
  NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
345
365
  if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
346
- RCTLogInfo(@"[ModuleLoader] Found bundle manifest at Bundles: %@", bundlesManifestPath);
366
+ RCTLogInfo(@"[ModuleLoader] Found manifest at Bundles: %@", bundlesManifestPath);
347
367
  resolve(bundlesManifestPath);
348
368
  return;
349
369
  }
350
-
351
- // 2. 尝试从 Documents 目录获取
370
+
371
+ // 2. Documents 目录
352
372
  NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
353
373
  if (documentsPath) {
354
374
  NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
355
375
  if ([fileManager fileExistsAtPath:documentsManifestPath]) {
356
- RCTLogInfo(@"[ModuleLoader] Found bundle manifest at Documents: %@", documentsManifestPath);
357
376
  resolve(documentsManifestPath);
358
377
  return;
359
378
  }
360
379
  }
361
-
362
- // 3. 尝试从 Application Support 目录获取
380
+
381
+ // 3. Application Support 目录
363
382
  NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
364
383
  NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
365
384
  if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
366
- RCTLogInfo(@"[ModuleLoader] Found bundle manifest at Application Support: %@", appSupportManifestPath);
367
385
  resolve(appSupportManifestPath);
368
386
  return;
369
387
  }
370
-
371
- // 4. 尝试从 MainBundle assets 目录获取
388
+
389
+ // 4. MainBundle assets 目录
372
390
  NSString *assetsPath = [[self class] bundleAssetsPath];
373
391
  NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
374
392
  if ([fileManager fileExistsAtPath:assetsManifestPath]) {
375
- RCTLogInfo(@"[ModuleLoader] Found bundle manifest at assets: %@", assetsManifestPath);
376
393
  resolve(assetsManifestPath);
377
394
  return;
378
395
  }
379
-
380
- // 5. 尝试从 MainBundle 根目录获取
396
+
397
+ // 5. MainBundle 根目录
381
398
  NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
382
399
  if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
383
- RCTLogInfo(@"[ModuleLoader] Found bundle manifest at MainBundle: %@", mainBundleManifestPath);
384
400
  resolve(mainBundleManifestPath);
385
401
  return;
386
402
  }
387
-
388
- // 未找到 manifest 文件
389
- RCTLogWarn(@"[ModuleLoader] Bundle manifest not found in any location");
403
+
404
+ RCTLogWarn(@"[ModuleLoader] Bundle manifest not found");
390
405
  resolve(nil);
391
406
  } @catch (NSException *exception) {
392
- RCTLogError(@"[ModuleLoader] Failed to get current bundle manifest: %@", exception.reason);
407
+ RCTLogError(@"[ModuleLoader] Failed to get manifest: %@", exception.reason);
393
408
  reject(@"MANIFEST_ERROR", @"Failed to get manifest path", nil);
394
409
  }
395
410
  }
396
411
 
397
- /**
398
- * 获取当前 bundle manifest 文件内容
399
- * 查找顺序与 getCurrentBundleManifest 相同
400
- */
401
412
  RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolve
402
413
  rejecter:(RCTPromiseRejectBlock)reject)
403
414
  {
@@ -405,85 +416,68 @@ RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolv
405
416
  @try {
406
417
  NSFileManager *fileManager = [NSFileManager defaultManager];
407
418
  NSString *manifestContent = nil;
408
-
409
- // 1. 先尝试从 MainBundle/Bundles 目录获取
419
+
420
+ // 1. MainBundle/Bundles 目录
410
421
  NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
411
422
  NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
412
423
  if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
413
424
  manifestContent = [self readFileContent:bundlesManifestPath];
414
425
  if (manifestContent) {
415
- RCTLogInfo(@"[ModuleLoader] Read bundle manifest from Bundles");
416
- dispatch_async(dispatch_get_main_queue(), ^{
417
- resolve(manifestContent);
418
- });
426
+ RCTLogInfo(@"[ModuleLoader] Read manifest from Bundles");
427
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
419
428
  return;
420
429
  }
421
430
  }
422
-
423
- // 2. 尝试从 Documents 目录获取
431
+
432
+ // 2. Documents 目录
424
433
  NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
425
434
  if (documentsPath) {
426
435
  NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
427
436
  if ([fileManager fileExistsAtPath:documentsManifestPath]) {
428
437
  manifestContent = [self readFileContent:documentsManifestPath];
429
438
  if (manifestContent) {
430
- RCTLogInfo(@"[ModuleLoader] Read bundle manifest from Documents");
431
- dispatch_async(dispatch_get_main_queue(), ^{
432
- resolve(manifestContent);
433
- });
439
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
434
440
  return;
435
441
  }
436
442
  }
437
443
  }
438
-
439
- // 3. 尝试从 Application Support 目录获取
444
+
445
+ // 3. Application Support 目录
440
446
  NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
441
447
  NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
442
448
  if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
443
449
  manifestContent = [self readFileContent:appSupportManifestPath];
444
450
  if (manifestContent) {
445
- RCTLogInfo(@"[ModuleLoader] Read bundle manifest from Application Support");
446
- dispatch_async(dispatch_get_main_queue(), ^{
447
- resolve(manifestContent);
448
- });
451
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
449
452
  return;
450
453
  }
451
454
  }
452
-
453
- // 4. 尝试从 MainBundle assets 目录获取
455
+
456
+ // 4. MainBundle assets 目录
454
457
  NSString *assetsPath = [[self class] bundleAssetsPath];
455
458
  NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
456
459
  if ([fileManager fileExistsAtPath:assetsManifestPath]) {
457
460
  manifestContent = [self readFileContent:assetsManifestPath];
458
461
  if (manifestContent) {
459
- RCTLogInfo(@"[ModuleLoader] Read bundle manifest from assets");
460
- dispatch_async(dispatch_get_main_queue(), ^{
461
- resolve(manifestContent);
462
- });
462
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
463
463
  return;
464
464
  }
465
465
  }
466
-
467
- // 5. 尝试从 MainBundle 根目录获取
466
+
467
+ // 5. MainBundle 根目录
468
468
  NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
469
469
  if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
470
470
  manifestContent = [self readFileContent:mainBundleManifestPath];
471
471
  if (manifestContent) {
472
- RCTLogInfo(@"[ModuleLoader] Read bundle manifest from MainBundle");
473
- dispatch_async(dispatch_get_main_queue(), ^{
474
- resolve(manifestContent);
475
- });
472
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
476
473
  return;
477
474
  }
478
475
  }
479
-
480
- // 未找到 manifest 文件
481
- RCTLogWarn(@"[ModuleLoader] Bundle manifest content not found in any location");
482
- dispatch_async(dispatch_get_main_queue(), ^{
483
- resolve(nil);
484
- });
476
+
477
+ RCTLogWarn(@"[ModuleLoader] Bundle manifest content not found");
478
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(nil); });
485
479
  } @catch (NSException *exception) {
486
- RCTLogError(@"[ModuleLoader] Failed to get bundle manifest content: %@", exception.reason);
480
+ RCTLogError(@"[ModuleLoader] Failed to get manifest content: %@", exception.reason);
487
481
  dispatch_async(dispatch_get_main_queue(), ^{
488
482
  reject(@"MANIFEST_CONTENT_ERROR", @"Failed to read manifest content", nil);
489
483
  });
@@ -492,4 +486,3 @@ RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolv
492
486
  }
493
487
 
494
488
  @end
495
-
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.5",
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",
@@ -342,3 +342,4 @@ function preloadModule(moduleId: string): Promise<void>
342
342
  - [多 Bundle 架构设计](../docs/React%20Native%20多%20bundle%20技术方案.md)
343
343
 
344
344
 
345
+