@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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// MainBundle 根目录下的文件名
|
|
53
|
+
if (path3) [searchPaths addObject:path3];
|
|
54
|
+
|
|
65
55
|
NSString *path4 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:fileName];
|
|
66
|
-
if (path4)
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
94
|
+
* 获取当前的 RCTBridge(类似 Android 的 ReactContext)
|
|
112
95
|
*/
|
|
113
|
-
- (
|
|
114
|
-
//
|
|
115
|
-
|
|
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
|
-
//
|
|
147
|
-
RCTBridge *bridge =
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
366
|
+
RCTLogInfo(@"[ModuleLoader] Found manifest at Bundles: %@", bundlesManifestPath);
|
|
347
367
|
resolve(bundlesManifestPath);
|
|
348
368
|
return;
|
|
349
369
|
}
|
|
350
|
-
|
|
351
|
-
// 2.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
481
|
-
|
|
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
|
|
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