@bm-fe/react-native-multi-bundle 1.0.0-beta.0 → 1.0.0-beta.2

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,449 @@
1
+ package com.bitmart.exchange.module.loader
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
+ import com.facebook.react.bridge.ReactMethod
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.JSBundleLoader
10
+ import com.facebook.react.module.annotations.ReactModule
11
+ import kotlinx.coroutines.CoroutineScope
12
+ import kotlinx.coroutines.Dispatchers
13
+ import kotlinx.coroutines.SupervisorJob
14
+ import kotlinx.coroutines.launch
15
+ import kotlinx.coroutines.withContext
16
+ import java.io.BufferedReader
17
+ import java.io.File
18
+ import java.io.FileOutputStream
19
+ import java.io.FileReader
20
+ import java.io.IOException
21
+ import java.io.InputStream
22
+ import java.io.InputStreamReader
23
+
24
+ /**
25
+ * 简单的 Bundle 加载模块
26
+ * 用于在同一个 JS 运行时中动态加载子 bundle
27
+ *
28
+ * 新架构下通过反射获取 ReactInstance,然后调用 loadJSBundle 方法
29
+ * 注意:ReactInstance 是 internal 类,必须完全通过反射操作
30
+ */
31
+ @ReactModule(name = ModuleLoaderModule.NAME)
32
+ class ModuleLoaderModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
33
+
34
+ companion object {
35
+ const val NAME = "ModuleLoader"
36
+ private const val TAG = "ModuleLoaderModule"
37
+
38
+ // 缓存反射获取的类和方法,避免重复反射
39
+ private var reactHostImplClass: Class<*>? = null
40
+ private var reactInstanceClass: Class<*>? = null
41
+
42
+ init {
43
+ try {
44
+ reactHostImplClass = Class.forName("com.facebook.react.runtime.ReactHostImpl")
45
+ reactInstanceClass = Class.forName("com.facebook.react.runtime.ReactInstance")
46
+ } catch (e: ClassNotFoundException) {
47
+ Log.w(TAG, "New architecture classes not found, will use old architecture")
48
+ }
49
+ }
50
+ }
51
+
52
+ // 记录已加载的 bundle,避免重复加载
53
+ private val loadedBundles = mutableSetOf<String>()
54
+
55
+ // 协程作用域,用于异步操作
56
+ private val moduleScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
57
+
58
+ override fun getName(): String = NAME
59
+
60
+ /**
61
+ * 加载子 bundle
62
+ *
63
+ * @param bundleId 模块 ID(如 "home", "details", "settings")
64
+ * @param bundlePath bundle 文件路径(如 "modules/home.bundle")
65
+ * @param promise 加载结果回调
66
+ */
67
+ @ReactMethod
68
+ fun loadBusinessBundle(bundleId: String, bundlePath: String, promise: Promise) {
69
+ Log.d(TAG, "loadBusinessBundle: bundleId=$bundleId, bundlePath=$bundlePath")
70
+
71
+ // 已经加载过的 bundle,直接返回成功
72
+ if (loadedBundles.contains(bundleId)) {
73
+ Log.d(TAG, "Bundle already loaded: $bundleId")
74
+ val result = Arguments.createMap().apply {
75
+ putBoolean("success", true)
76
+ }
77
+ promise.resolve(result)
78
+ return
79
+ }
80
+
81
+ try {
82
+ // 获取 ReactApplication
83
+ val application = reactContext.applicationContext as? android.app.Application
84
+ val reactApplication = application as? com.facebook.react.ReactApplication
85
+
86
+ if (reactApplication == null) {
87
+ Log.e(TAG, "Application is not a ReactApplication")
88
+ val result = Arguments.createMap().apply {
89
+ putBoolean("success", false)
90
+ putString("errorMessage", "Application is not a ReactApplication")
91
+ }
92
+ promise.resolve(result)
93
+ return
94
+ }
95
+
96
+ val reactHost = reactApplication.reactHost
97
+
98
+ // 构建 assets 路径
99
+ val assetUrl = "assets://$bundlePath"
100
+ Log.d(TAG, "Loading bundle from: $assetUrl")
101
+
102
+ // 创建 JSBundleLoader
103
+ val bundleLoader = JSBundleLoader.createAssetLoader(
104
+ reactContext,
105
+ assetUrl,
106
+ false // 异步加载
107
+ )
108
+
109
+ // 检查是否是新架构(ReactHostImpl)
110
+ val hostImplClass = reactHostImplClass
111
+ if (reactHost != null && hostImplClass != null && hostImplClass.isInstance(reactHost)) {
112
+ loadBundleViaReflection(reactHost, bundleLoader, bundleId, promise)
113
+ } else {
114
+ // 旧架构回退方案:使用 CatalystInstance
115
+ Log.d(TAG, "Falling back to CatalystInstance (old architecture)")
116
+ loadBundleWithCatalystInstance(bundleId, bundlePath, promise)
117
+ }
118
+
119
+ } catch (e: Exception) {
120
+ Log.e(TAG, "Failed to load bundle: $bundleId", e)
121
+ val result = Arguments.createMap().apply {
122
+ putBoolean("success", false)
123
+ putString("errorMessage", e.message ?: "Unknown error")
124
+ }
125
+ promise.resolve(result)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * 新架构方案:完全通过反射获取 ReactInstance 并加载 bundle
131
+ */
132
+ private fun loadBundleViaReflection(
133
+ reactHost: Any,
134
+ bundleLoader: JSBundleLoader,
135
+ bundleId: String,
136
+ promise: Promise
137
+ ) {
138
+ try {
139
+ val hostImplClass = reactHostImplClass ?: throw Exception("ReactHostImpl class not found")
140
+ val instanceClass = reactInstanceClass ?: throw Exception("ReactInstance class not found")
141
+
142
+ // 通过反射获取 private 的 reactInstance 字段
143
+ val reactInstanceField = hostImplClass.getDeclaredField("reactInstance")
144
+ reactInstanceField.isAccessible = true
145
+ val reactInstance = reactInstanceField.get(reactHost)
146
+
147
+ if (reactInstance == null) {
148
+ Log.e(TAG, "ReactInstance is null")
149
+ val result = Arguments.createMap().apply {
150
+ putBoolean("success", false)
151
+ putString("errorMessage", "ReactInstance not available")
152
+ }
153
+ promise.resolve(result)
154
+ return
155
+ }
156
+
157
+ // 通过反射调用 ReactInstance.loadJSBundle 方法
158
+ val loadJSBundleMethod = instanceClass.getMethod("loadJSBundle", JSBundleLoader::class.java)
159
+ loadJSBundleMethod.invoke(reactInstance, bundleLoader)
160
+
161
+ // 标记为已加载
162
+ loadedBundles.add(bundleId)
163
+ Log.d(TAG, "Bundle loaded successfully via ReactInstance: $bundleId")
164
+
165
+ val result = Arguments.createMap().apply {
166
+ putBoolean("success", true)
167
+ }
168
+ promise.resolve(result)
169
+
170
+ } catch (e: Exception) {
171
+ Log.e(TAG, "Failed to load bundle via ReactInstance: $bundleId", e)
172
+ val result = Arguments.createMap().apply {
173
+ putBoolean("success", false)
174
+ putString("errorMessage", e.message ?: "Unknown error")
175
+ }
176
+ promise.resolve(result)
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 旧架构回退方案:使用 CatalystInstance 加载 bundle
182
+ */
183
+ @Suppress("DEPRECATION")
184
+ private fun loadBundleWithCatalystInstance(bundleId: String, bundlePath: String, promise: Promise) {
185
+ try {
186
+ val catalystInstance = reactContext.catalystInstance
187
+ if (catalystInstance == null) {
188
+ Log.e(TAG, "CatalystInstance is null")
189
+ val result = Arguments.createMap().apply {
190
+ putBoolean("success", false)
191
+ putString("errorMessage", "CatalystInstance not available")
192
+ }
193
+ promise.resolve(result)
194
+ return
195
+ }
196
+
197
+ val assetPath = "assets://$bundlePath"
198
+ catalystInstance.loadScriptFromAssets(
199
+ reactContext.assets,
200
+ assetPath,
201
+ false
202
+ )
203
+
204
+ loadedBundles.add(bundleId)
205
+ Log.d(TAG, "Bundle loaded successfully via CatalystInstance: $bundleId")
206
+
207
+ val result = Arguments.createMap().apply {
208
+ putBoolean("success", true)
209
+ }
210
+ promise.resolve(result)
211
+ } catch (e: Exception) {
212
+ Log.e(TAG, "Failed to load bundle via CatalystInstance: $bundleId", e)
213
+ val result = Arguments.createMap().apply {
214
+ putBoolean("success", false)
215
+ putString("errorMessage", e.message ?: "Unknown error")
216
+ }
217
+ promise.resolve(result)
218
+ }
219
+ }
220
+
221
+ /**
222
+ * 检查 bundle 是否已加载
223
+ */
224
+ @ReactMethod
225
+ fun isBundleLoaded(bundleId: String, promise: Promise) {
226
+ promise.resolve(loadedBundles.contains(bundleId))
227
+ }
228
+
229
+ /**
230
+ * 获取已加载的 bundle 列表
231
+ */
232
+ @ReactMethod
233
+ fun getLoadedBundles(promise: Promise) {
234
+ val array = Arguments.createArray()
235
+ loadedBundles.forEach { array.pushString(it) }
236
+ promise.resolve(array)
237
+ }
238
+
239
+ // ==================== Bundle Manifest Support ====================
240
+
241
+ /**
242
+ * 获取当前 bundle manifest 文件路径
243
+ * 优先从 assets-base 目录获取,如果不存在则返回 null
244
+ */
245
+ @ReactMethod
246
+ fun getCurrentBundleManifest(promise: Promise) {
247
+ try {
248
+
249
+ // 确保 CodePush 目录已初始化
250
+ ensureCodePushDirectoryInitialized()
251
+
252
+ // 从 assets-base 目录获取 manifest
253
+ val codePushDir = "${reactContext.filesDir.absolutePath}/CodePush"
254
+ val assetsBaseManifestPath = "$codePushDir/assets-base/bundle-manifest.json"
255
+ val assetsBaseManifestFile = File(assetsBaseManifestPath)
256
+
257
+ if (assetsBaseManifestFile.exists()) {
258
+ promise.resolve(assetsBaseManifestPath)
259
+ return
260
+ }
261
+
262
+ // 如果都不存在,返回 null
263
+ promise.resolve(null)
264
+ } catch (e: Exception) {
265
+ Log.e(TAG, "Failed to get current bundle manifest: ${e.message}")
266
+ promise.reject("MANIFEST_ERROR", "Failed to get manifest path", e)
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 获取当前 bundle manifest 文件内容
272
+ * 使用协程在 IO 线程读取文件内容
273
+ */
274
+ @ReactMethod
275
+ fun getCurrentBundleManifestContent(promise: Promise) {
276
+ moduleScope.launch {
277
+ try {
278
+ val manifestContent = withContext(Dispatchers.IO) {
279
+ // 确保 CodePush 目录已初始化
280
+ ensureCodePushDirectoryInitialized()
281
+
282
+ // 从 assets-base 目录读取 manifest
283
+ val codePushDir = "${reactContext.filesDir.absolutePath}/CodePush"
284
+ val assetsBaseManifestPath = "$codePushDir/assets-base/bundle-manifest.json"
285
+ val assetsBaseManifestFile = File(assetsBaseManifestPath)
286
+
287
+ if (assetsBaseManifestFile.exists()) {
288
+ val content = readFileContent(assetsBaseManifestFile)
289
+ if (content != null) {
290
+ Log.d(TAG, "✓ Read bundle manifest from assets-base directory")
291
+ return@withContext content
292
+ }
293
+ }
294
+
295
+ // 如果都不存在,返回 null
296
+ null
297
+ }
298
+ promise.resolve(manifestContent)
299
+ } catch (e: Exception) {
300
+ Log.e(TAG, "Failed to get bundle manifest content: ${e.message}")
301
+ promise.reject("MANIFEST_CONTENT_ERROR", "Failed to read manifest content", e)
302
+ }
303
+ }
304
+ }
305
+
306
+ /**
307
+ * 确保 CodePush 目录已从 assets 初始化
308
+ */
309
+ private fun ensureCodePushDirectoryInitialized() {
310
+ try {
311
+ val codePushDir = "${reactContext.filesDir.absolutePath}/CodePush"
312
+ val assetsBaseDir = "$codePushDir/assets-base"
313
+
314
+ // 检查 assets-base 目录是否有 bundle-manifest.json 和 modules
315
+ val manifestFile = File("$assetsBaseDir/bundle-manifest.json")
316
+ val modulesDir = File("$assetsBaseDir/modules")
317
+
318
+ // 如果任一不存在,从 assets 复制
319
+ if (!manifestFile.exists() || !modulesDir.exists()) {
320
+ Log.d(TAG, "Initializing CodePush directory from assets...")
321
+ copyAssetsToCodePushDirectory(codePushDir)
322
+ }
323
+ } catch (e: Exception) {
324
+ Log.e(TAG, "Failed to initialize CodePush directory: ${e.message}")
325
+ }
326
+ }
327
+
328
+ /**
329
+ * 从 assets 复制 bundle-manifest.json 和 modules 目录到 CodePush 目录
330
+ */
331
+ private fun copyAssetsToCodePushDirectory(codePushDir: String) {
332
+ try {
333
+ // 创建 assets-base 目录
334
+ val defaultPackageDir = "$codePushDir/assets-base"
335
+ val defaultPackageDirFile = File(defaultPackageDir)
336
+ if (!defaultPackageDirFile.exists()) {
337
+ defaultPackageDirFile.mkdirs()
338
+ }
339
+
340
+ // 1. 复制 bundle-manifest.json
341
+ try {
342
+ val manifestAssetPath = "bundle-manifest.json"
343
+ val manifestInput = reactContext.assets.open(manifestAssetPath)
344
+ val manifestOutput = File("$defaultPackageDir/bundle-manifest.json")
345
+ copyInputStreamToFile(manifestInput, manifestOutput)
346
+ Log.d(TAG, "✓ Copied bundle-manifest.json to CodePush directory")
347
+ } catch (e: IOException) {
348
+ Log.w(TAG, "Warning: Could not copy bundle-manifest.json: ${e.message}")
349
+ }
350
+
351
+ // 2. 复制 modules 目录
352
+ try {
353
+ val modulesAssetPath = "modules"
354
+ val modulesOutputDir = File("$defaultPackageDir/modules")
355
+ if (!modulesOutputDir.exists()) {
356
+ modulesOutputDir.mkdirs()
357
+ }
358
+
359
+ copyAssetFolder(modulesAssetPath, modulesOutputDir.absolutePath)
360
+ Log.d(TAG, "✓ Copied modules directory to CodePush directory")
361
+ } catch (e: IOException) {
362
+ Log.w(TAG, "Warning: Could not copy modules directory: ${e.message}")
363
+ }
364
+
365
+ } catch (e: Exception) {
366
+ Log.e(TAG, "Failed to copy assets to CodePush directory: ${e.message}")
367
+ }
368
+ }
369
+
370
+ /**
371
+ * 递归复制 asset 文件夹
372
+ */
373
+ @Throws(IOException::class)
374
+ private fun copyAssetFolder(assetPath: String, targetPath: String) {
375
+ val assets = reactContext.assets.list(assetPath)
376
+ if (assets.isNullOrEmpty()) {
377
+ // 这是一个文件,不是目录
378
+ val input = reactContext.assets.open(assetPath)
379
+ val output = File(targetPath)
380
+ copyInputStreamToFile(input, output)
381
+ } else {
382
+ // 这是一个目录
383
+ val dir = File(targetPath)
384
+ if (!dir.exists()) {
385
+ dir.mkdirs()
386
+ }
387
+
388
+ for (asset in assets) {
389
+ val subAssetPath = "$assetPath/$asset"
390
+ val subTargetPath = "$targetPath/$asset"
391
+ copyAssetFolder(subAssetPath, subTargetPath)
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * 将 InputStream 复制到文件
398
+ */
399
+ @Throws(IOException::class)
400
+ private fun copyInputStreamToFile(input: InputStream, output: File) {
401
+ var outputStream: FileOutputStream? = null
402
+ try {
403
+ outputStream = FileOutputStream(output)
404
+ val buffer = ByteArray(4096)
405
+ var bytesRead: Int
406
+ while (input.read(buffer).also { bytesRead = it } != -1) {
407
+ outputStream.write(buffer, 0, bytesRead)
408
+ }
409
+ } finally {
410
+ try {
411
+ input.close()
412
+ } catch (e: IOException) {
413
+ // Ignore
414
+ }
415
+ try {
416
+ outputStream?.close()
417
+ } catch (e: IOException) {
418
+ // Ignore
419
+ }
420
+ }
421
+ }
422
+
423
+ /**
424
+ * 读取文件内容
425
+ */
426
+ private fun readFileContent(file: File): String? {
427
+ var reader: BufferedReader? = null
428
+ return try {
429
+ reader = BufferedReader(FileReader(file))
430
+ val content = StringBuilder()
431
+ var line: String?
432
+ while (reader.readLine().also { line = it } != null) {
433
+ content.append(line).append("\n")
434
+ }
435
+ content.toString()
436
+ } catch (e: IOException) {
437
+ Log.e(TAG, "Failed to read file: ${file.absolutePath} - ${e.message}")
438
+ null
439
+ } finally {
440
+ try {
441
+ reader?.close()
442
+ } catch (e: IOException) {
443
+ // Ignore
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+
@@ -0,0 +1,24 @@
1
+ package com.bitmart.exchange.module.loader
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ /**
9
+ * ModuleLoader ReactPackage
10
+ * 注册 ModuleLoaderModule 到 React Native
11
+ */
12
+ class ModuleLoaderPackage : ReactPackage {
13
+ @Suppress("DEPRECATION")
14
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
15
+ return listOf(ModuleLoaderModule(reactContext))
16
+ }
17
+
18
+ @Suppress("DEPRECATION")
19
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
20
+ return emptyList()
21
+ }
22
+ }
23
+
24
+
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.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "React Native 多 Bundle 系统 - 支持模块按需加载和独立更新",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -20,6 +20,8 @@
20
20
  "src",
21
21
  "scripts",
22
22
  "templates",
23
+ "android/moduleloader",
24
+ "react-native.config.js",
23
25
  "README.md",
24
26
  "INTEGRATION.md",
25
27
  "LICENSE"
@@ -34,10 +36,12 @@
34
36
  "version:patch": "standard-version --release-as patch",
35
37
  "version:minor": "standard-version --release-as minor",
36
38
  "version:major": "standard-version --release-as major",
37
- "version:beta": "npm version prerelease --preid=beta --no-git-tag-version",
39
+ "version:beta": "npm version prerelease --preid=beta --no-git-tag-version --no-scripts",
40
+ "prepublishOnly": "rm -rf android/moduleloader/build",
38
41
  "publish:beta": "npm publish --tag beta",
39
42
  "release:beta": "npm run version:beta && npm run publish:beta",
40
- "release": "npm run version && npm publish"
43
+ "release": "npm run version && npm publish",
44
+ "android": "react-native run-android"
41
45
  },
42
46
  "peerDependencies": {
43
47
  "react": ">=18.0.0",
@@ -46,6 +50,8 @@
46
50
  "dependencies": {
47
51
  "@react-navigation/native": "^7.1.21",
48
52
  "@react-navigation/native-stack": "^7.6.4",
53
+ "react": "19.1.1",
54
+ "react-native": "0.82.1",
49
55
  "react-native-fs": "^2.20.0",
50
56
  "react-native-safe-area-context": "^5.6.2",
51
57
  "react-native-screens": "^4.18.0"
@@ -59,6 +65,7 @@
59
65
  "@react-native-community/cli-platform-ios": "20.0.2",
60
66
  "@react-native/babel-preset": "0.82.1",
61
67
  "@react-native/eslint-config": "0.82.1",
68
+ "@react-native/gradle-plugin": "0.82.1",
62
69
  "@react-native/metro-config": "0.82.1",
63
70
  "@react-native/typescript-config": "0.82.1",
64
71
  "@rnx-kit/babel-preset-metro-react-native": "^3.0.0",
@@ -73,6 +80,7 @@
73
80
  "patch-package": "^8.0.1",
74
81
  "postinstall-postinstall": "^2.1.0",
75
82
  "prettier": "3.6.2",
83
+ "react-native": "^0.82.1",
76
84
  "react-test-renderer": "^19.1.1",
77
85
  "standard-version": "^9.5.0",
78
86
  "typescript": "5.9.3"
@@ -0,0 +1,23 @@
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: null, // iOS 暂未实现
20
+ },
21
+ },
22
+ };
23
+
@@ -13,17 +13,41 @@
13
13
  * - --env: 'development' | 'staging' | 'production' (默认: 'production')
14
14
  *
15
15
  * 环境变量:
16
- * - PROJECT_ROOT: 项目根目录(可选,默认从脚本位置推断)
16
+ * - PROJECT_ROOT: 项目根目录(可选,默认从当前工作目录向上查找包含 multi-bundle.config.json 的目录)
17
17
  */
18
18
 
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
  const { execSync } = require('child_process');
22
22
 
23
- // 支持从任意项目目录运行
24
- // 如果作为 npm 包使用,PROJECT_ROOT 应该是调用脚本的项目根目录
25
- // 如果直接运行,PROJECT_ROOT 是脚本所在目录的父目录
26
- const PROJECT_ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
23
+ /**
24
+ * 查找项目根目录
25
+ * 从当前工作目录向上查找,直到找到包含 multi-bundle.config.json 的目录
26
+ * 如果找不到,则使用当前工作目录(通常打包命令在项目根目录执行)
27
+ */
28
+ function findProjectRoot() {
29
+ // 如果通过环境变量指定,直接使用
30
+ if (process.env.PROJECT_ROOT) {
31
+ return process.env.PROJECT_ROOT;
32
+ }
33
+
34
+ // 从当前工作目录开始向上查找
35
+ let currentDir = process.cwd();
36
+ const root = path.parse(currentDir).root;
37
+
38
+ while (currentDir !== root) {
39
+ const configFile = path.join(currentDir, 'multi-bundle.config.json');
40
+ if (fs.existsSync(configFile)) {
41
+ return currentDir;
42
+ }
43
+ currentDir = path.dirname(currentDir);
44
+ }
45
+
46
+ // 如果找不到配置文件,使用当前工作目录(通常打包命令在项目根目录执行)
47
+ return process.cwd();
48
+ }
49
+
50
+ const PROJECT_ROOT = findProjectRoot();
27
51
  const CONFIG_FILE = path.join(PROJECT_ROOT, 'multi-bundle.config.json');
28
52
  const OUTPUT_DIR = path.join(PROJECT_ROOT, 'build/bundles');
29
53
 
@@ -61,9 +61,31 @@ function convertNativeManifestToBundleManifest(nativeManifest: any): BundleManif
61
61
  */
62
62
  async function getCurrentBundleManifest(): Promise<BundleManifest> {
63
63
  // 生产环境:从 Native 模块获取 manifest
64
- if (!__DEV__) {
65
- try {
66
- const nativeManifest = await NativeModules.CodePush.getCurrentBundleManifestContent();
64
+ // if (!__DEV__) {
65
+ // try {
66
+ // const nativeManifest = await NativeModules.ModuleLoader.getCurrentBundleManifestContent();
67
+ // if (nativeManifest) {
68
+ // return convertNativeManifestToBundleManifest(nativeManifest);
69
+ // } else {
70
+ // // Native 模块返回 null 或 undefined
71
+ // throw new Error(
72
+ // '[LocalBundleManager] Native module returned null manifest. ' +
73
+ // 'Please ensure bundle-manifest.json exists in the app bundle.'
74
+ // );
75
+ // }
76
+ // } catch (error) {
77
+ // console.error(
78
+ // `[LocalBundleManager] Failed to get manifest from Native: ${error}`
79
+ // );
80
+ // // 生产环境无法获取 manifest 时抛出异常
81
+ // throw new Error(
82
+ // `[LocalBundleManager] Failed to get manifest from Native module: ${error}`
83
+ // );
84
+ // }
85
+ // }
86
+ console.log("ReactNativeJS","nativeManifest getCurrentBundleManifestContent")
87
+ const nativeManifest = await NativeModules.ModuleLoader.getCurrentBundleManifestContent();
88
+ console.log("ReactNativeJS","nativeManifest "+nativeManifest)
67
89
  if (nativeManifest) {
68
90
  return convertNativeManifestToBundleManifest(nativeManifest);
69
91
  } else {
@@ -73,16 +95,6 @@ async function getCurrentBundleManifest(): Promise<BundleManifest> {
73
95
  'Please ensure bundle-manifest.json exists in the app bundle.'
74
96
  );
75
97
  }
76
- } catch (error) {
77
- console.error(
78
- `[LocalBundleManager] Failed to get manifest from Native: ${error}`
79
- );
80
- // 生产环境无法获取 manifest 时抛出异常
81
- throw new Error(
82
- `[LocalBundleManager] Failed to get manifest from Native module: ${error}`
83
- );
84
- }
85
- }
86
98
 
87
99
  // 开发环境:从开发服务器获取 manifest
88
100
  const config = getGlobalConfig();