@bm-fe/react-native-multi-bundle 1.0.0-beta.5 → 1.0.0-beta.6
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.
|
@@ -14,115 +14,249 @@ RCT_EXPORT_MODULE(ModuleLoader);
|
|
|
14
14
|
// Bundle manifest file name
|
|
15
15
|
static NSString *const BundleManifestFileName = @"bundle-manifest.json";
|
|
16
16
|
|
|
17
|
+
#pragma mark - Bundle Path Resolution
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* 获取 bundle 文件的完整路径
|
|
19
|
-
* 查找顺序:
|
|
20
|
-
* 1. CodePush 目录(如果可用)
|
|
21
|
-
* 2. MainBundle 的 Bundles 目录
|
|
22
|
-
* 3. MainBundle 根目录
|
|
23
|
-
* 4. MainBundle 的 assets 目录
|
|
24
|
-
* 5. Documents 目录
|
|
25
21
|
*/
|
|
26
22
|
- (NSString *)getFullBundlePath:(NSString *)bundlePath {
|
|
27
|
-
// bundlePath 格式可能是 "modules/home.jsbundle" 或 "home.jsbundle"
|
|
28
23
|
NSString *fileName = [bundlePath lastPathComponent];
|
|
29
24
|
NSString *relativePath = bundlePath;
|
|
30
|
-
|
|
31
|
-
// 如果路径包含 "modules/",提取文件名
|
|
25
|
+
|
|
32
26
|
if ([bundlePath containsString:@"modules/"]) {
|
|
33
27
|
fileName = [bundlePath lastPathComponent];
|
|
34
28
|
relativePath = [NSString stringWithFormat:@"modules/%@", fileName];
|
|
35
29
|
}
|
|
36
|
-
|
|
37
|
-
// 使用 NSMutableArray 来避免 nil 值问题
|
|
30
|
+
|
|
38
31
|
NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
|
|
39
|
-
|
|
40
|
-
// CodePush
|
|
32
|
+
|
|
33
|
+
// CodePush 路径
|
|
41
34
|
NSString *codePushPath = [self getCodePushBundlePath:bundlePath];
|
|
42
35
|
if (codePushPath) {
|
|
43
36
|
[searchPaths addObject:codePushPath];
|
|
44
37
|
}
|
|
45
|
-
|
|
38
|
+
|
|
46
39
|
// MainBundle/Bundles/modules/xxx.jsbundle
|
|
47
40
|
NSString *path1 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:relativePath]];
|
|
48
|
-
if (path1)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// MainBundle/Bundles/xxx.jsbundle (直接文件名)
|
|
41
|
+
if (path1) [searchPaths addObject:path1];
|
|
42
|
+
|
|
43
|
+
// MainBundle/Bundles/xxx.jsbundle
|
|
53
44
|
NSString *path2 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:fileName]];
|
|
54
|
-
if (path2)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// MainBundle 根目录下的完整路径
|
|
45
|
+
if (path2) [searchPaths addObject:path2];
|
|
46
|
+
|
|
47
|
+
// MainBundle 根目录
|
|
59
48
|
NSString *path3 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:bundlePath];
|
|
60
|
-
if (path3)
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// MainBundle 根目录下的文件名
|
|
49
|
+
if (path3) [searchPaths addObject:path3];
|
|
50
|
+
|
|
65
51
|
NSString *path4 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:fileName];
|
|
66
|
-
if (path4)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// MainBundle/resourcePath 下的完整路径
|
|
52
|
+
if (path4) [searchPaths addObject:path4];
|
|
53
|
+
|
|
54
|
+
// resourcePath
|
|
71
55
|
NSString *path5 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:bundlePath];
|
|
72
|
-
if (path5)
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// MainBundle/resourcePath 下的文件名
|
|
56
|
+
if (path5) [searchPaths addObject:path5];
|
|
57
|
+
|
|
77
58
|
NSString *path6 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
|
|
78
|
-
if (path6)
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
59
|
+
if (path6) [searchPaths addObject:path6];
|
|
60
|
+
|
|
82
61
|
// Documents 目录
|
|
83
62
|
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
84
63
|
if (documentsPath) {
|
|
85
64
|
NSString *path7 = [documentsPath stringByAppendingPathComponent:bundlePath];
|
|
86
|
-
if (path7)
|
|
87
|
-
[searchPaths addObject:path7];
|
|
88
|
-
}
|
|
65
|
+
if (path7) [searchPaths addObject:path7];
|
|
89
66
|
}
|
|
90
|
-
|
|
67
|
+
|
|
91
68
|
for (NSString *fullPath in searchPaths) {
|
|
92
69
|
if (fullPath && [[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
|
|
93
70
|
RCTLogInfo(@"[ModuleLoader] Found bundle at: %@", fullPath);
|
|
94
71
|
return fullPath;
|
|
95
72
|
}
|
|
96
73
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
RCTLogWarn(@"[ModuleLoader] Bundle not found in any location: %@", bundlePath);
|
|
100
|
-
RCTLogWarn(@"[ModuleLoader] Searched locations:");
|
|
74
|
+
|
|
75
|
+
RCTLogWarn(@"[ModuleLoader] Bundle not found: %@", bundlePath);
|
|
101
76
|
for (NSString *path in searchPaths) {
|
|
102
|
-
if (path)
|
|
103
|
-
RCTLogWarn(@"[ModuleLoader] - %@", path);
|
|
104
|
-
}
|
|
77
|
+
if (path) RCTLogWarn(@"[ModuleLoader] - %@", path);
|
|
105
78
|
}
|
|
106
|
-
|
|
79
|
+
|
|
107
80
|
return nil;
|
|
108
81
|
}
|
|
109
82
|
|
|
83
|
+
- (NSString *)getCodePushBundlePath:(NSString *)bundlePath {
|
|
84
|
+
return nil;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#pragma mark - Bridge Access
|
|
88
|
+
|
|
110
89
|
/**
|
|
111
|
-
*
|
|
90
|
+
* 获取当前的 RCTBridge
|
|
112
91
|
*/
|
|
113
|
-
- (
|
|
114
|
-
//
|
|
115
|
-
|
|
92
|
+
- (RCTBridge *)getCurrentBridge {
|
|
93
|
+
// 方法 1: 使用模块自带的 bridge
|
|
94
|
+
if (self.bridge) {
|
|
95
|
+
return self.bridge;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 方法 2: 从 AppDelegate 获取
|
|
99
|
+
UIApplication *app = [UIApplication sharedApplication];
|
|
100
|
+
id delegate = app.delegate;
|
|
101
|
+
|
|
102
|
+
if ([delegate respondsToSelector:@selector(bridge)]) {
|
|
103
|
+
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;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 方法 4: 通过 rootViewFactory (新架构)
|
|
117
|
+
if ([delegate respondsToSelector:@selector(rootViewFactory)]) {
|
|
118
|
+
id factory = [delegate performSelector:@selector(rootViewFactory)];
|
|
119
|
+
if (factory && [factory respondsToSelector:@selector(bridge)]) {
|
|
120
|
+
RCTBridge *bridge = [factory performSelector:@selector(bridge)];
|
|
121
|
+
if (bridge) return bridge;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
return nil;
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
#pragma mark - Bundle Loading Methods
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 尝试使用各种方法加载 bundle
|
|
132
|
+
*/
|
|
133
|
+
- (BOOL)tryLoadBundle:(NSString *)bundleContent
|
|
134
|
+
sourceURL:(NSURL *)sourceURL
|
|
135
|
+
bridge:(RCTBridge *)bridge
|
|
136
|
+
error:(NSError **)outError {
|
|
137
|
+
|
|
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
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} @catch (NSException *e) {
|
|
149
|
+
RCTLogWarn(@"[ModuleLoader] Could not get batchedBridge: %@", e.reason);
|
|
150
|
+
}
|
|
151
|
+
|
|
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);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 方法 2: 尝试通过 CatalystInstance(旧架构)
|
|
184
|
+
id catalystInstance = nil;
|
|
185
|
+
@try {
|
|
186
|
+
catalystInstance = [targetBridge valueForKey:@"_catalystInstance"];
|
|
187
|
+
} @catch (NSException *e) {
|
|
188
|
+
// 继续尝试其他方法
|
|
189
|
+
}
|
|
190
|
+
|
|
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
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
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
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
RCTLogInfo(@"[ModuleLoader] CatalystInstance not available (likely new architecture)");
|
|
225
|
+
}
|
|
226
|
+
|
|
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);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 所有方法都失败
|
|
247
|
+
if (outError) {
|
|
248
|
+
*outError = [NSError errorWithDomain:@"ModuleLoader"
|
|
249
|
+
code:1001
|
|
250
|
+
userInfo:@{NSLocalizedDescriptionKey: @"No suitable bundle loading method found"}];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return NO;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#pragma mark - Main Load Method
|
|
257
|
+
|
|
119
258
|
/**
|
|
120
259
|
* 加载子 bundle
|
|
121
|
-
*
|
|
122
|
-
* @param bundleId 模块 ID(如 "home", "details", "settings")
|
|
123
|
-
* @param bundlePath bundle 文件路径(如 "modules/home.jsbundle")
|
|
124
|
-
* @param resolve 成功回调
|
|
125
|
-
* @param reject 失败回调
|
|
126
260
|
*/
|
|
127
261
|
RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
|
|
128
262
|
bundlePath:(NSString *)bundlePath
|
|
@@ -130,191 +264,70 @@ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
|
|
|
130
264
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
131
265
|
{
|
|
132
266
|
RCTLogInfo(@"[ModuleLoader] loadBusinessBundle: bundleId=%@, bundlePath=%@", bundleId, bundlePath);
|
|
133
|
-
|
|
134
|
-
// 获取完整路径
|
|
267
|
+
|
|
135
268
|
NSString *fullPath = [self getFullBundlePath:bundlePath];
|
|
136
269
|
if (!fullPath) {
|
|
137
|
-
resolve(@{
|
|
138
|
-
@"success": @NO,
|
|
139
|
-
@"errorMessage": @"BUNDLE_PATH_NOT_FOUND"
|
|
140
|
-
});
|
|
270
|
+
resolve(@{@"success": @NO, @"errorMessage": @"BUNDLE_PATH_NOT_FOUND"});
|
|
141
271
|
return;
|
|
142
272
|
}
|
|
143
|
-
|
|
144
|
-
// 确保在主线程执行
|
|
273
|
+
|
|
145
274
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
146
|
-
|
|
147
|
-
|
|
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(如果可用,通过协议方法)
|
|
167
|
-
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
|
-
|
|
275
|
+
RCTBridge *bridge = [self getCurrentBridge];
|
|
276
|
+
|
|
177
277
|
if (!bridge) {
|
|
178
|
-
RCTLogError(@"[ModuleLoader] Bridge not available
|
|
179
|
-
resolve(@{
|
|
180
|
-
@"success": @NO,
|
|
181
|
-
@"errorMessage": @"BRIDGE_NOT_AVAILABLE"
|
|
182
|
-
});
|
|
278
|
+
RCTLogError(@"[ModuleLoader] Bridge not available");
|
|
279
|
+
resolve(@{@"success": @NO, @"errorMessage": @"BRIDGE_NOT_AVAILABLE"});
|
|
183
280
|
return;
|
|
184
281
|
}
|
|
185
|
-
|
|
282
|
+
|
|
186
283
|
// 读取 bundle 内容
|
|
187
|
-
NSError *
|
|
284
|
+
NSError *readError = nil;
|
|
188
285
|
NSString *bundleContent = [NSString stringWithContentsOfFile:fullPath
|
|
189
286
|
encoding:NSUTF8StringEncoding
|
|
190
|
-
error:&
|
|
191
|
-
if (
|
|
192
|
-
RCTLogError(@"[ModuleLoader] Failed to read bundle
|
|
193
|
-
resolve(@{
|
|
194
|
-
@"success": @NO,
|
|
195
|
-
@"errorMessage": [NSString stringWithFormat:@"READ_ERROR: %@", error.localizedDescription ?: @"Unknown error"]
|
|
196
|
-
});
|
|
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"]});
|
|
197
291
|
return;
|
|
198
292
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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");
|
|
293
|
+
|
|
294
|
+
NSURL *sourceURL = [NSURL fileURLWithPath:fullPath];
|
|
295
|
+
NSError *loadError = nil;
|
|
296
|
+
|
|
297
|
+
BOOL success = [self tryLoadBundle:bundleContent sourceURL:sourceURL bridge:bridge error:&loadError];
|
|
298
|
+
|
|
299
|
+
if (success) {
|
|
300
|
+
RCTLogInfo(@"[ModuleLoader] Bundle loaded successfully: %@", bundleId);
|
|
270
301
|
resolve(@{@"success": @YES});
|
|
302
|
+
} 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"});
|
|
271
309
|
#else
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
@"errorMessage": @"CATALYST_INSTANCE_METHOD_NOT_FOUND"
|
|
275
|
-
});
|
|
310
|
+
RCTLogError(@"[ModuleLoader] Failed to load bundle: %@", loadError.localizedDescription);
|
|
311
|
+
resolve(@{@"success": @NO, @"errorMessage": loadError.localizedDescription ?: @"UNKNOWN_ERROR"});
|
|
276
312
|
#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
|
-
});
|
|
284
313
|
}
|
|
285
314
|
});
|
|
286
315
|
}
|
|
287
316
|
|
|
288
317
|
#pragma mark - Bundle Manifest Methods
|
|
289
318
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
*/
|
|
293
|
-
+ (NSString *)getApplicationSupportDirectory
|
|
294
|
-
{
|
|
295
|
-
NSString *applicationSupportDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
|
296
|
-
return applicationSupportDirectory;
|
|
319
|
+
+ (NSString *)getApplicationSupportDirectory {
|
|
320
|
+
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
|
297
321
|
}
|
|
298
322
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
*/
|
|
302
|
-
+ (NSString *)bundleAssetsPath
|
|
303
|
-
{
|
|
304
|
-
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
|
|
305
|
-
return [resourcePath stringByAppendingPathComponent:@"assets"];
|
|
323
|
+
+ (NSString *)bundleAssetsPath {
|
|
324
|
+
return [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"assets"];
|
|
306
325
|
}
|
|
307
326
|
|
|
308
|
-
|
|
309
|
-
* 读取文件内容
|
|
310
|
-
*/
|
|
311
|
-
- (NSString *)readFileContent:(NSString *)filePath
|
|
312
|
-
{
|
|
327
|
+
- (NSString *)readFileContent:(NSString *)filePath {
|
|
313
328
|
@try {
|
|
314
329
|
NSError *error;
|
|
315
|
-
NSString *content = [NSString stringWithContentsOfFile:filePath
|
|
316
|
-
encoding:NSUTF8StringEncoding
|
|
317
|
-
error:&error];
|
|
330
|
+
NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
|
|
318
331
|
if (error) {
|
|
319
332
|
RCTLogWarn(@"[ModuleLoader] Failed to read file: %@ - %@", filePath, error.localizedDescription);
|
|
320
333
|
return nil;
|
|
@@ -326,78 +339,62 @@ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
|
|
|
326
339
|
}
|
|
327
340
|
}
|
|
328
341
|
|
|
329
|
-
/**
|
|
330
|
-
* 获取当前 bundle manifest 文件路径
|
|
331
|
-
* 查找顺序:
|
|
332
|
-
* 1. Bundles 目录
|
|
333
|
-
* 2. Documents 目录
|
|
334
|
-
* 3. MainBundle assets 目录
|
|
335
|
-
*/
|
|
336
342
|
RCT_EXPORT_METHOD(getCurrentBundleManifest:(RCTPromiseResolveBlock)resolve
|
|
337
343
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
338
344
|
{
|
|
339
345
|
@try {
|
|
340
346
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
341
|
-
|
|
342
|
-
// 1.
|
|
347
|
+
|
|
348
|
+
// 1. MainBundle/Bundles 目录
|
|
343
349
|
NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
|
|
344
350
|
NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
345
351
|
if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
|
|
346
|
-
RCTLogInfo(@"[ModuleLoader] Found
|
|
352
|
+
RCTLogInfo(@"[ModuleLoader] Found manifest at Bundles: %@", bundlesManifestPath);
|
|
347
353
|
resolve(bundlesManifestPath);
|
|
348
354
|
return;
|
|
349
355
|
}
|
|
350
|
-
|
|
351
|
-
// 2.
|
|
356
|
+
|
|
357
|
+
// 2. Documents 目录
|
|
352
358
|
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
353
359
|
if (documentsPath) {
|
|
354
360
|
NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
355
361
|
if ([fileManager fileExistsAtPath:documentsManifestPath]) {
|
|
356
|
-
RCTLogInfo(@"[ModuleLoader] Found bundle manifest at Documents: %@", documentsManifestPath);
|
|
357
362
|
resolve(documentsManifestPath);
|
|
358
363
|
return;
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
|
-
|
|
362
|
-
// 3.
|
|
366
|
+
|
|
367
|
+
// 3. Application Support 目录
|
|
363
368
|
NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
|
|
364
369
|
NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
365
370
|
if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
|
|
366
|
-
RCTLogInfo(@"[ModuleLoader] Found bundle manifest at Application Support: %@", appSupportManifestPath);
|
|
367
371
|
resolve(appSupportManifestPath);
|
|
368
372
|
return;
|
|
369
373
|
}
|
|
370
|
-
|
|
371
|
-
// 4.
|
|
374
|
+
|
|
375
|
+
// 4. MainBundle assets 目录
|
|
372
376
|
NSString *assetsPath = [[self class] bundleAssetsPath];
|
|
373
377
|
NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
374
378
|
if ([fileManager fileExistsAtPath:assetsManifestPath]) {
|
|
375
|
-
RCTLogInfo(@"[ModuleLoader] Found bundle manifest at assets: %@", assetsManifestPath);
|
|
376
379
|
resolve(assetsManifestPath);
|
|
377
380
|
return;
|
|
378
381
|
}
|
|
379
|
-
|
|
380
|
-
// 5.
|
|
382
|
+
|
|
383
|
+
// 5. MainBundle 根目录
|
|
381
384
|
NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
|
|
382
385
|
if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
|
|
383
|
-
RCTLogInfo(@"[ModuleLoader] Found bundle manifest at MainBundle: %@", mainBundleManifestPath);
|
|
384
386
|
resolve(mainBundleManifestPath);
|
|
385
387
|
return;
|
|
386
388
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
RCTLogWarn(@"[ModuleLoader] Bundle manifest not found in any location");
|
|
389
|
+
|
|
390
|
+
RCTLogWarn(@"[ModuleLoader] Bundle manifest not found");
|
|
390
391
|
resolve(nil);
|
|
391
392
|
} @catch (NSException *exception) {
|
|
392
|
-
RCTLogError(@"[ModuleLoader] Failed to get
|
|
393
|
+
RCTLogError(@"[ModuleLoader] Failed to get manifest: %@", exception.reason);
|
|
393
394
|
reject(@"MANIFEST_ERROR", @"Failed to get manifest path", nil);
|
|
394
395
|
}
|
|
395
396
|
}
|
|
396
397
|
|
|
397
|
-
/**
|
|
398
|
-
* 获取当前 bundle manifest 文件内容
|
|
399
|
-
* 查找顺序与 getCurrentBundleManifest 相同
|
|
400
|
-
*/
|
|
401
398
|
RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolve
|
|
402
399
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
403
400
|
{
|
|
@@ -405,85 +402,68 @@ RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolv
|
|
|
405
402
|
@try {
|
|
406
403
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
407
404
|
NSString *manifestContent = nil;
|
|
408
|
-
|
|
409
|
-
// 1.
|
|
405
|
+
|
|
406
|
+
// 1. MainBundle/Bundles 目录
|
|
410
407
|
NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
|
|
411
408
|
NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
412
409
|
if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
|
|
413
410
|
manifestContent = [self readFileContent:bundlesManifestPath];
|
|
414
411
|
if (manifestContent) {
|
|
415
|
-
RCTLogInfo(@"[ModuleLoader] Read
|
|
416
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
417
|
-
resolve(manifestContent);
|
|
418
|
-
});
|
|
412
|
+
RCTLogInfo(@"[ModuleLoader] Read manifest from Bundles");
|
|
413
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
|
|
419
414
|
return;
|
|
420
415
|
}
|
|
421
416
|
}
|
|
422
|
-
|
|
423
|
-
// 2.
|
|
417
|
+
|
|
418
|
+
// 2. Documents 目录
|
|
424
419
|
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
425
420
|
if (documentsPath) {
|
|
426
421
|
NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
427
422
|
if ([fileManager fileExistsAtPath:documentsManifestPath]) {
|
|
428
423
|
manifestContent = [self readFileContent:documentsManifestPath];
|
|
429
424
|
if (manifestContent) {
|
|
430
|
-
|
|
431
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
432
|
-
resolve(manifestContent);
|
|
433
|
-
});
|
|
425
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
|
|
434
426
|
return;
|
|
435
427
|
}
|
|
436
428
|
}
|
|
437
429
|
}
|
|
438
|
-
|
|
439
|
-
// 3.
|
|
430
|
+
|
|
431
|
+
// 3. Application Support 目录
|
|
440
432
|
NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
|
|
441
433
|
NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
442
434
|
if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
|
|
443
435
|
manifestContent = [self readFileContent:appSupportManifestPath];
|
|
444
436
|
if (manifestContent) {
|
|
445
|
-
|
|
446
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
447
|
-
resolve(manifestContent);
|
|
448
|
-
});
|
|
437
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
|
|
449
438
|
return;
|
|
450
439
|
}
|
|
451
440
|
}
|
|
452
|
-
|
|
453
|
-
// 4.
|
|
441
|
+
|
|
442
|
+
// 4. MainBundle assets 目录
|
|
454
443
|
NSString *assetsPath = [[self class] bundleAssetsPath];
|
|
455
444
|
NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
|
|
456
445
|
if ([fileManager fileExistsAtPath:assetsManifestPath]) {
|
|
457
446
|
manifestContent = [self readFileContent:assetsManifestPath];
|
|
458
447
|
if (manifestContent) {
|
|
459
|
-
|
|
460
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
461
|
-
resolve(manifestContent);
|
|
462
|
-
});
|
|
448
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
|
|
463
449
|
return;
|
|
464
450
|
}
|
|
465
451
|
}
|
|
466
|
-
|
|
467
|
-
// 5.
|
|
452
|
+
|
|
453
|
+
// 5. MainBundle 根目录
|
|
468
454
|
NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
|
|
469
455
|
if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
|
|
470
456
|
manifestContent = [self readFileContent:mainBundleManifestPath];
|
|
471
457
|
if (manifestContent) {
|
|
472
|
-
|
|
473
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
474
|
-
resolve(manifestContent);
|
|
475
|
-
});
|
|
458
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
|
|
476
459
|
return;
|
|
477
460
|
}
|
|
478
461
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
483
|
-
resolve(nil);
|
|
484
|
-
});
|
|
462
|
+
|
|
463
|
+
RCTLogWarn(@"[ModuleLoader] Bundle manifest content not found");
|
|
464
|
+
dispatch_async(dispatch_get_main_queue(), ^{ resolve(nil); });
|
|
485
465
|
} @catch (NSException *exception) {
|
|
486
|
-
RCTLogError(@"[ModuleLoader] Failed to get
|
|
466
|
+
RCTLogError(@"[ModuleLoader] Failed to get manifest content: %@", exception.reason);
|
|
487
467
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
488
468
|
reject(@"MANIFEST_CONTENT_ERROR", @"Failed to read manifest content", nil);
|
|
489
469
|
});
|
|
@@ -492,4 +472,3 @@ RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolv
|
|
|
492
472
|
}
|
|
493
473
|
|
|
494
474
|
@end
|
|
495
|
-
|
package/package.json
CHANGED