@bm-fe/react-native-multi-bundle 1.0.0-beta.1 → 1.0.0-beta.10

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.
@@ -0,0 +1,488 @@
1
+ #import "ModuleLoaderModule.h"
2
+ #import <React/RCTBridge+Private.h>
3
+ #import <React/RCTBridge.h>
4
+ #import <React/RCTUtils.h>
5
+ #import <React/RCTLog.h>
6
+ #import <objc/runtime.h>
7
+ #import <jsi/jsi.h>
8
+
9
+ using namespace facebook;
10
+
11
+ @implementation ModuleLoaderModule
12
+
13
+ RCT_EXPORT_MODULE(ModuleLoader);
14
+
15
+ // 合成 bridge 属性(RCTBridgeModule 协议要求)
16
+ @synthesize bridge = _bridge;
17
+
18
+ // Bundle manifest file name
19
+ static NSString *const BundleManifestFileName = @"bundle-manifest.json";
20
+
21
+ #pragma mark - Bundle Path Resolution
22
+
23
+ /**
24
+ * 获取 bundle 文件的完整路径
25
+ */
26
+ - (NSString *)getFullBundlePath:(NSString *)bundlePath {
27
+ NSString *fileName = [bundlePath lastPathComponent];
28
+ NSString *relativePath = bundlePath;
29
+
30
+ if ([bundlePath containsString:@"modules/"]) {
31
+ fileName = [bundlePath lastPathComponent];
32
+ relativePath = [NSString stringWithFormat:@"modules/%@", fileName];
33
+ }
34
+
35
+ NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
36
+
37
+ // CodePush 路径
38
+ NSString *codePushPath = [self getCodePushBundlePath:bundlePath];
39
+ if (codePushPath) {
40
+ [searchPaths addObject:codePushPath];
41
+ }
42
+
43
+ // MainBundle/Bundles/modules/xxx.jsbundle
44
+ NSString *path1 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:relativePath]];
45
+ if (path1) [searchPaths addObject:path1];
46
+
47
+ // MainBundle/Bundles/xxx.jsbundle
48
+ NSString *path2 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[@"Bundles/" stringByAppendingString:fileName]];
49
+ if (path2) [searchPaths addObject:path2];
50
+
51
+ // MainBundle 根目录
52
+ NSString *path3 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:bundlePath];
53
+ if (path3) [searchPaths addObject:path3];
54
+
55
+ NSString *path4 = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:fileName];
56
+ if (path4) [searchPaths addObject:path4];
57
+
58
+ // resourcePath
59
+ NSString *path5 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:bundlePath];
60
+ if (path5) [searchPaths addObject:path5];
61
+
62
+ NSString *path6 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
63
+ if (path6) [searchPaths addObject:path6];
64
+
65
+ // Documents 目录
66
+ NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
67
+ if (documentsPath) {
68
+ NSString *path7 = [documentsPath stringByAppendingPathComponent:bundlePath];
69
+ if (path7) [searchPaths addObject:path7];
70
+ }
71
+
72
+ for (NSString *fullPath in searchPaths) {
73
+ if (fullPath && [[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
74
+ RCTLogInfo(@"[ModuleLoader] Found bundle at: %@", fullPath);
75
+ return fullPath;
76
+ }
77
+ }
78
+
79
+ RCTLogWarn(@"[ModuleLoader] Bundle not found: %@", bundlePath);
80
+ for (NSString *path in searchPaths) {
81
+ if (path) RCTLogWarn(@"[ModuleLoader] - %@", path);
82
+ }
83
+
84
+ return nil;
85
+ }
86
+
87
+ - (NSString *)getCodePushBundlePath:(NSString *)bundlePath {
88
+ return nil;
89
+ }
90
+
91
+ #pragma mark - Bridge Access
92
+
93
+ /**
94
+ * 获取当前的 RCTBridge(类似 Android 的 ReactContext)
95
+ */
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");
135
+ return nil;
136
+ }
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
+
287
+ /**
288
+ * 加载子 bundle
289
+ * 使用 JSI Runtime 直接执行 JavaScript 代码
290
+ */
291
+ RCT_EXPORT_METHOD(loadBusinessBundle:(NSString *)bundleId
292
+ bundlePath:(NSString *)bundlePath
293
+ resolver:(RCTPromiseResolveBlock)resolve
294
+ rejecter:(RCTPromiseRejectBlock)reject)
295
+ {
296
+ RCTLogInfo(@"[ModuleLoader] loadBusinessBundle: bundleId=%@, bundlePath=%@", bundleId, bundlePath);
297
+
298
+ NSString *fullPath = [self getFullBundlePath:bundlePath];
299
+ if (!fullPath) {
300
+ resolve(@{@"success": @NO, @"errorMessage": @"BUNDLE_PATH_NOT_FOUND"});
301
+ return;
302
+ }
303
+
304
+ dispatch_async(dispatch_get_main_queue(), ^{
305
+ // 获取 bridge
306
+ RCTBridge *bridge = [self getCurrentBridge];
307
+ if (!bridge) {
308
+ RCTLogError(@"[ModuleLoader] Bridge not available");
309
+ resolve(@{@"success": @NO, @"errorMessage": @"BRIDGE_NOT_AVAILABLE"});
310
+ return;
311
+ }
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);
323
+ resolve(@{@"success": @YES});
324
+ } else {
325
+ RCTLogError(@"[ModuleLoader] ❌ Failed to load bundle: %@", loadError.localizedDescription);
326
+ resolve(@{@"success": @NO, @"errorMessage": loadError.localizedDescription ?: @"LOAD_FAILED"});
327
+ }
328
+ });
329
+ }
330
+
331
+ #pragma mark - Bundle Manifest Methods
332
+
333
+ + (NSString *)getApplicationSupportDirectory {
334
+ return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
335
+ }
336
+
337
+ + (NSString *)bundleAssetsPath {
338
+ return [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"assets"];
339
+ }
340
+
341
+ - (NSString *)readFileContent:(NSString *)filePath {
342
+ @try {
343
+ NSError *error;
344
+ NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
345
+ if (error) {
346
+ RCTLogWarn(@"[ModuleLoader] Failed to read file: %@ - %@", filePath, error.localizedDescription);
347
+ return nil;
348
+ }
349
+ return content;
350
+ } @catch (NSException *exception) {
351
+ RCTLogWarn(@"[ModuleLoader] Failed to read file: %@ - %@", filePath, exception.reason);
352
+ return nil;
353
+ }
354
+ }
355
+
356
+ RCT_EXPORT_METHOD(getCurrentBundleManifest:(RCTPromiseResolveBlock)resolve
357
+ rejecter:(RCTPromiseRejectBlock)reject)
358
+ {
359
+ @try {
360
+ NSFileManager *fileManager = [NSFileManager defaultManager];
361
+
362
+ // 1. MainBundle/Bundles 目录
363
+ NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
364
+ NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
365
+ if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
366
+ RCTLogInfo(@"[ModuleLoader] Found manifest at Bundles: %@", bundlesManifestPath);
367
+ resolve(bundlesManifestPath);
368
+ return;
369
+ }
370
+
371
+ // 2. Documents 目录
372
+ NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
373
+ if (documentsPath) {
374
+ NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
375
+ if ([fileManager fileExistsAtPath:documentsManifestPath]) {
376
+ resolve(documentsManifestPath);
377
+ return;
378
+ }
379
+ }
380
+
381
+ // 3. Application Support 目录
382
+ NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
383
+ NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
384
+ if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
385
+ resolve(appSupportManifestPath);
386
+ return;
387
+ }
388
+
389
+ // 4. MainBundle assets 目录
390
+ NSString *assetsPath = [[self class] bundleAssetsPath];
391
+ NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
392
+ if ([fileManager fileExistsAtPath:assetsManifestPath]) {
393
+ resolve(assetsManifestPath);
394
+ return;
395
+ }
396
+
397
+ // 5. MainBundle 根目录
398
+ NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
399
+ if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
400
+ resolve(mainBundleManifestPath);
401
+ return;
402
+ }
403
+
404
+ RCTLogWarn(@"[ModuleLoader] Bundle manifest not found");
405
+ resolve(nil);
406
+ } @catch (NSException *exception) {
407
+ RCTLogError(@"[ModuleLoader] Failed to get manifest: %@", exception.reason);
408
+ reject(@"MANIFEST_ERROR", @"Failed to get manifest path", nil);
409
+ }
410
+ }
411
+
412
+ RCT_EXPORT_METHOD(getCurrentBundleManifestContent:(RCTPromiseResolveBlock)resolve
413
+ rejecter:(RCTPromiseRejectBlock)reject)
414
+ {
415
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
416
+ @try {
417
+ NSFileManager *fileManager = [NSFileManager defaultManager];
418
+ NSString *manifestContent = nil;
419
+
420
+ // 1. MainBundle/Bundles 目录
421
+ NSString *bundlesPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Bundles"];
422
+ NSString *bundlesManifestPath = [bundlesPath stringByAppendingPathComponent:BundleManifestFileName];
423
+ if ([fileManager fileExistsAtPath:bundlesManifestPath]) {
424
+ manifestContent = [self readFileContent:bundlesManifestPath];
425
+ if (manifestContent) {
426
+ RCTLogInfo(@"[ModuleLoader] Read manifest from Bundles");
427
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
428
+ return;
429
+ }
430
+ }
431
+
432
+ // 2. Documents 目录
433
+ NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
434
+ if (documentsPath) {
435
+ NSString *documentsManifestPath = [documentsPath stringByAppendingPathComponent:BundleManifestFileName];
436
+ if ([fileManager fileExistsAtPath:documentsManifestPath]) {
437
+ manifestContent = [self readFileContent:documentsManifestPath];
438
+ if (manifestContent) {
439
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
440
+ return;
441
+ }
442
+ }
443
+ }
444
+
445
+ // 3. Application Support 目录
446
+ NSString *appSupportPath = [[self class] getApplicationSupportDirectory];
447
+ NSString *appSupportManifestPath = [appSupportPath stringByAppendingPathComponent:BundleManifestFileName];
448
+ if ([fileManager fileExistsAtPath:appSupportManifestPath]) {
449
+ manifestContent = [self readFileContent:appSupportManifestPath];
450
+ if (manifestContent) {
451
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
452
+ return;
453
+ }
454
+ }
455
+
456
+ // 4. MainBundle assets 目录
457
+ NSString *assetsPath = [[self class] bundleAssetsPath];
458
+ NSString *assetsManifestPath = [assetsPath stringByAppendingPathComponent:BundleManifestFileName];
459
+ if ([fileManager fileExistsAtPath:assetsManifestPath]) {
460
+ manifestContent = [self readFileContent:assetsManifestPath];
461
+ if (manifestContent) {
462
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
463
+ return;
464
+ }
465
+ }
466
+
467
+ // 5. MainBundle 根目录
468
+ NSString *mainBundleManifestPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:BundleManifestFileName];
469
+ if ([fileManager fileExistsAtPath:mainBundleManifestPath]) {
470
+ manifestContent = [self readFileContent:mainBundleManifestPath];
471
+ if (manifestContent) {
472
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(manifestContent); });
473
+ return;
474
+ }
475
+ }
476
+
477
+ RCTLogWarn(@"[ModuleLoader] Bundle manifest content not found");
478
+ dispatch_async(dispatch_get_main_queue(), ^{ resolve(nil); });
479
+ } @catch (NSException *exception) {
480
+ RCTLogError(@"[ModuleLoader] Failed to get manifest content: %@", exception.reason);
481
+ dispatch_async(dispatch_get_main_queue(), ^{
482
+ reject(@"MANIFEST_CONTENT_ERROR", @"Failed to read manifest content", nil);
483
+ });
484
+ }
485
+ });
486
+ }
487
+
488
+ @end
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.1",
3
+ "version": "1.0.0-beta.10",
4
4
  "description": "React Native 多 Bundle 系统 - 支持模块按需加载和独立更新",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -20,9 +20,16 @@
20
20
  "src",
21
21
  "scripts",
22
22
  "templates",
23
+ "android/moduleloader/src",
24
+ "android/moduleloader/*.gradle",
25
+ "android/moduleloader/*.pro",
26
+ "ios/ModuleLoader",
27
+ "react-native.config.js",
28
+ "react-native-multi-bundle.podspec",
23
29
  "README.md",
24
30
  "INTEGRATION.md",
25
- "LICENSE"
31
+ "LICENSE",
32
+ "!android/moduleloader/build"
26
33
  ],
27
34
  "bin": {
28
35
  "multi-bundle-build": "./scripts/build-multi-bundle.js"
@@ -35,50 +42,69 @@
35
42
  "version:minor": "standard-version --release-as minor",
36
43
  "version:major": "standard-version --release-as major",
37
44
  "version:beta": "npm version prerelease --preid=beta --no-git-tag-version --no-scripts",
45
+ "prepublishOnly": "rm -rf android/moduleloader/build",
38
46
  "publish:beta": "npm publish --tag beta",
39
47
  "release:beta": "npm run version:beta && npm run publish:beta",
40
- "release": "npm run version && npm publish"
48
+ "release": "npm run version && npm publish",
49
+ "android": "react-native run-android"
41
50
  },
42
51
  "peerDependencies": {
43
52
  "react": ">=18.0.0",
44
- "react-native": ">=0.70.0"
53
+ "react-native": ">=0.70.0",
54
+ "@react-navigation/native": ">=6.0.0",
55
+ "@react-navigation/native-stack": ">=6.0.0",
56
+ "react-native-fs": ">=2.0.0",
57
+ "react-native-safe-area-context": ">=4.0.0",
58
+ "react-native-screens": ">=3.0.0"
45
59
  },
46
- "dependencies": {
47
- "@react-navigation/native": "^7.1.21",
48
- "@react-navigation/native-stack": "^7.6.4",
49
- "react-native-fs": "^2.20.0",
50
- "react-native-safe-area-context": "^5.6.2",
51
- "react-native-screens": "^4.18.0"
60
+ "peerDependenciesMeta": {
61
+ "@react-navigation/native": {
62
+ "optional": true
63
+ },
64
+ "@react-navigation/native-stack": {
65
+ "optional": true
66
+ },
67
+ "react-native-fs": {
68
+ "optional": true
69
+ },
70
+ "react-native-safe-area-context": {
71
+ "optional": true
72
+ },
73
+ "react-native-screens": {
74
+ "optional": true
75
+ }
52
76
  },
77
+ "dependencies": {},
53
78
  "devDependencies": {
54
- "@babel/core": "^7.28.5",
55
- "@babel/preset-env": "^7.28.5",
56
- "@babel/runtime": "^7.28.4",
57
- "@react-native-community/cli": "20.0.2",
58
- "@react-native-community/cli-platform-android": "20.0.2",
59
- "@react-native-community/cli-platform-ios": "20.0.2",
60
- "@react-native/babel-preset": "0.82.1",
61
- "@react-native/eslint-config": "0.82.1",
62
- "@react-native/metro-config": "0.82.1",
63
- "@react-native/typescript-config": "0.82.1",
64
- "@rnx-kit/babel-preset-metro-react-native": "^3.0.0",
79
+ "@babel/core": "7.25.2",
80
+ "@babel/preset-env": "7.25.2",
81
+ "@babel/runtime": "7.25.0",
82
+ "@react-native-community/cli": "18.0.0",
83
+ "@react-native-community/cli-platform-android": "18.0.0",
84
+ "@react-native-community/cli-platform-ios": "18.0.0",
85
+ "@react-native/babel-preset": "0.79.0",
86
+ "@react-native/eslint-config": "0.79.0",
87
+ "@react-native/gradle-plugin": "0.79.0",
88
+ "@react-native/metro-config": "0.79.0",
89
+ "@react-native/typescript-config": "0.79.0",
65
90
  "@testing-library/react-native": "^13.3.3",
66
- "@types/jest": "^30.0.0",
67
- "@types/react": "^19.2.6",
68
- "@types/react-test-renderer": "^19.1.0",
69
- "babel-jest": "^30.2.0",
91
+ "@types/jest": "29.5.13",
92
+ "@types/react": "19.0.0",
93
+ "@types/react-test-renderer": "19.0.0",
70
94
  "babel-plugin-transform-inline-environment-variables": "^0.4.4",
71
- "eslint": "^9.39.1",
72
- "jest": "^30.2.0",
73
- "patch-package": "^8.0.1",
95
+ "eslint": "8.22.0",
96
+ "jest": "29.7.0",
97
+ "patch-package": "8.0.0",
74
98
  "postinstall-postinstall": "^2.1.0",
75
- "prettier": "3.6.2",
76
- "react-test-renderer": "^19.1.1",
99
+ "prettier": "3.2.5",
100
+ "react": "19.0.0",
101
+ "react-native": "0.79.5",
102
+ "react-test-renderer": "19.0.0",
77
103
  "standard-version": "^9.5.0",
78
- "typescript": "5.9.3"
104
+ "typescript": "5.5.4"
79
105
  },
80
106
  "engines": {
81
- "node": ">=18"
107
+ "node": ">=18.0.0 <22.0.0"
82
108
  },
83
109
  "publishConfig": {
84
110
  "access": "public"
@@ -0,0 +1,29 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-multi-bundle"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.description = <<-DESC
10
+ React Native 多 Bundle 系统 - 支持模块按需加载和独立更新。
11
+ 提供 iOS 原生模块 ModuleLoader,用于在同一个 JS 运行时中动态加载子 bundle。
12
+ DESC
13
+ s.homepage = "https://github.com/AntonyMei/react-native-multiple-bundle-demo"
14
+ s.license = { :type => package["license"], :file => "LICENSE" }
15
+ s.author = package["author"]
16
+ s.platforms = { :ios => "13.0" }
17
+ s.source = { :git => "https://github.com/AntonyMei/react-native-multiple-bundle-demo.git", :tag => "v#{s.version}" }
18
+
19
+ s.source_files = "ios/ModuleLoader/**/*.{h,m,mm}"
20
+
21
+ # 编译选项
22
+ s.pod_target_xcconfig = {
23
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
24
+ "DEFINES_MODULE" => "YES"
25
+ }
26
+
27
+ # 依赖 React Native Core
28
+ s.dependency "React-Core"
29
+ end
@@ -0,0 +1,26 @@
1
+ /**
2
+ * React Native 配置文件
3
+ * 用于配置原生模块的自动链接
4
+ *
5
+ * 当此包被安装到其他 RN 项目时,React Native CLI 会自动读取此配置
6
+ * 并将 ModuleLoaderPackage 自动注册到 PackageList 中
7
+ */
8
+
9
+ module.exports = {
10
+ // 配置原生模块自动链接
11
+ dependency: {
12
+ platforms: {
13
+ android: {
14
+ sourceDir: './android/moduleloader',
15
+ packageImportPath: 'import com.bitmart.exchange.module.loader.ModuleLoaderPackage;',
16
+ packageInstance: 'new ModuleLoaderPackage()',
17
+ buildTypes: ['debug', 'release'],
18
+ },
19
+ ios: {
20
+ // iOS 使用 podspec 自动链接
21
+ podspecPath: './react-native-multi-bundle.podspec',
22
+ },
23
+ },
24
+ },
25
+ };
26
+