@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.
- package/ios/ModuleLoader/ModuleLoaderModule.mm +155 -141
- package/package.json +1 -1
|
@@ -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:
|
|
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)
|
|
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)
|
|
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
|
|
138
|
+
#pragma mark - Bundle Loading (JSI)
|
|
129
139
|
|
|
130
140
|
/**
|
|
131
|
-
*
|
|
141
|
+
* 通过 JSI Runtime 加载 bundle
|
|
142
|
+
* 这是 RN 新架构 (RCTBridgeProxy) 下加载子 bundle 的唯一方式
|
|
132
143
|
*/
|
|
133
|
-
- (BOOL)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
// 方法
|
|
228
|
-
if (
|
|
229
|
-
RCTLogInfo(@"[ModuleLoader]
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
304
|
-
|
|
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
|
}
|