@onekeyfe/react-native-background-thread 1.1.51 → 1.1.53

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.
@@ -108,6 +108,108 @@ class BackgroundThreadManager private constructor() {
108
108
  return null
109
109
  }
110
110
 
111
+ /**
112
+ * Creates a JSBundleLoader that loads two bundles sequentially from Android assets:
113
+ * first the common bundle (polyfills + shared modules), then the
114
+ * entry-specific bundle (entry-only modules + require(entryId)).
115
+ */
116
+ private fun createSequentialAssetBundleLoader(
117
+ appContext: android.content.Context,
118
+ commonAssetName: String,
119
+ entryAssetName: String
120
+ ): JSBundleLoader {
121
+ return object : JSBundleLoader() {
122
+ override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
123
+ val totalStart = System.nanoTime()
124
+
125
+ // Step 1: Load common bundle (polyfills + shared modules)
126
+ val commonStart = System.nanoTime()
127
+ delegate.loadScriptFromAssets(appContext.assets, "assets://$commonAssetName", false)
128
+ val commonMs = (System.nanoTime() - commonStart) / 1_000_000.0
129
+ BTLogger.info("[SplitBundle] common bundle loaded from assets in ${String.format("%.1f", commonMs)}ms: $commonAssetName")
130
+
131
+ // Step 2: Load entry-specific bundle
132
+ val entryStart = System.nanoTime()
133
+ delegate.loadScriptFromAssets(appContext.assets, "assets://$entryAssetName", false)
134
+ val entryMs = (System.nanoTime() - entryStart) / 1_000_000.0
135
+ BTLogger.info("[SplitBundle] entry bundle loaded from assets in ${String.format("%.1f", entryMs)}ms: $entryAssetName")
136
+
137
+ val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
138
+ BTLogger.info("[SplitBundle] sequential asset load total: ${String.format("%.1f", totalMs)}ms (common=${String.format("%.1f", commonMs)}ms + entry=${String.format("%.1f", entryMs)}ms)")
139
+
140
+ return "assets://$entryAssetName"
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Creates a JSBundleLoader that loads two bundles sequentially from local files:
147
+ * first the common bundle, then the entry-specific bundle.
148
+ */
149
+ private fun createSequentialFileBundleLoader(
150
+ commonPath: String,
151
+ entryPath: String,
152
+ entrySourceURL: String
153
+ ): JSBundleLoader {
154
+ return object : JSBundleLoader() {
155
+ override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
156
+ val totalStart = System.nanoTime()
157
+
158
+ // Step 1: Load common bundle (polyfills + shared modules)
159
+ val commonFile = File(commonPath)
160
+ if (!commonFile.exists()) {
161
+ BTLogger.error("Common bundle file does not exist: $commonPath")
162
+ throw RuntimeException("Common bundle file does not exist: $commonPath")
163
+ }
164
+ BTLogger.info("[SplitBundle] common bundle file: ${commonFile.length() / 1024}KB")
165
+ val commonStart = System.nanoTime()
166
+ delegate.loadScriptFromFile(commonFile.absolutePath, "common.bundle", false)
167
+ val commonMs = (System.nanoTime() - commonStart) / 1_000_000.0
168
+ BTLogger.info("[SplitBundle] common bundle loaded from file in ${String.format("%.1f", commonMs)}ms: $commonPath")
169
+
170
+ // Step 2: Load entry-specific bundle
171
+ val entryFile = File(entryPath)
172
+ if (!entryFile.exists()) {
173
+ BTLogger.error("Entry bundle file does not exist: $entryPath")
174
+ throw RuntimeException("Entry bundle file does not exist: $entryPath")
175
+ }
176
+ BTLogger.info("[SplitBundle] entry bundle file: ${entryFile.length() / 1024}KB")
177
+ val entryStart = System.nanoTime()
178
+ delegate.loadScriptFromFile(entryFile.absolutePath, entrySourceURL, false)
179
+ val entryMs = (System.nanoTime() - entryStart) / 1_000_000.0
180
+ BTLogger.info("[SplitBundle] entry bundle loaded from file in ${String.format("%.1f", entryMs)}ms: $entryPath")
181
+
182
+ val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
183
+ BTLogger.info("[SplitBundle] sequential file load total: ${String.format("%.1f", totalMs)}ms (common=${String.format("%.1f", commonMs)}ms + entry=${String.format("%.1f", entryMs)}ms)")
184
+
185
+ return entrySourceURL
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Check if common.bundle exists in the Android assets directory.
192
+ */
193
+ private fun hasCommonBundleInAssets(appContext: android.content.Context): Boolean {
194
+ return try {
195
+ appContext.assets.open("common.bundle").close()
196
+ true
197
+ } catch (e: Exception) {
198
+ false
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Resolve the common bundle path for OTA (file-based) loading.
204
+ * Looks for common.bundle in the same directory as the entry bundle.
205
+ */
206
+ private fun resolveCommonBundlePath(entryBundlePath: String): String? {
207
+ val entryFile = File(entryBundlePath)
208
+ val parentDir = entryFile.parentFile ?: return null
209
+ val commonFile = File(parentDir, "common.bundle")
210
+ return if (commonFile.exists()) commonFile.absolutePath else null
211
+ }
212
+
111
213
  private fun createDownloadedBundleLoader(appContext: android.content.Context, entryURL: String): JSBundleLoader {
112
214
  return object : JSBundleLoader() {
113
215
  override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
@@ -149,7 +251,8 @@ class BackgroundThreadManager private constructor() {
149
251
  BTLogger.warn("Background runner already started")
150
252
  return
151
253
  }
152
- BTLogger.info("Starting background runner with entryURL: $entryURL")
254
+ val bgStartTime = System.nanoTime()
255
+ BTLogger.info("[SplitBundle] background runner starting with entryURL: $entryURL")
153
256
 
154
257
  val appContext = context.applicationContext
155
258
  val packages =
@@ -163,10 +266,43 @@ class BackgroundThreadManager private constructor() {
163
266
  val localBundlePath = resolveLocalBundlePath(entryURL)
164
267
  val bundleLoader =
165
268
  when {
269
+ // Debug mode: remote URL — use single bundle (Metro dev server)
166
270
  isRemoteBundleUrl(entryURL) -> createDownloadedBundleLoader(appContext, entryURL)
167
- localBundlePath != null -> createLocalFileBundleLoader(localBundlePath, entryURL)
168
- entryURL.startsWith("assets://") -> JSBundleLoader.createAssetLoader(appContext, entryURL, true)
169
- else -> JSBundleLoader.createAssetLoader(appContext, "assets://$entryURL", true)
271
+
272
+ // OTA / local file path — try sequential loading with common bundle
273
+ localBundlePath != null -> {
274
+ val commonPath = resolveCommonBundlePath(localBundlePath)
275
+ if (commonPath != null) {
276
+ BTLogger.info("Using sequential file bundle loader: common=$commonPath, entry=$localBundlePath")
277
+ createSequentialFileBundleLoader(commonPath, localBundlePath, entryURL)
278
+ } else {
279
+ BTLogger.info("No common bundle found for OTA path, using single bundle: $localBundlePath")
280
+ createLocalFileBundleLoader(localBundlePath, entryURL)
281
+ }
282
+ }
283
+
284
+ // Assets-based loading — try sequential loading with common.bundle in assets
285
+ entryURL.startsWith("assets://") -> {
286
+ val entryAssetName = entryURL.removePrefix("assets://")
287
+ if (hasCommonBundleInAssets(appContext)) {
288
+ BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryAssetName")
289
+ createSequentialAssetBundleLoader(appContext, "common.bundle", entryAssetName)
290
+ } else {
291
+ BTLogger.info("No common.bundle in assets, using single bundle: $entryURL")
292
+ JSBundleLoader.createAssetLoader(appContext, entryURL, true)
293
+ }
294
+ }
295
+
296
+ // Bare filename (e.g. "background.bundle") — treat as asset
297
+ else -> {
298
+ if (hasCommonBundleInAssets(appContext)) {
299
+ BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryURL")
300
+ createSequentialAssetBundleLoader(appContext, "common.bundle", entryURL)
301
+ } else {
302
+ BTLogger.info("No common.bundle in assets, using single bundle: assets://$entryURL")
303
+ JSBundleLoader.createAssetLoader(appContext, "assets://$entryURL", true)
304
+ }
305
+ }
170
306
  }
171
307
 
172
308
  val delegate = DefaultReactHostDelegate(
@@ -191,7 +327,8 @@ class BackgroundThreadManager private constructor() {
191
327
 
192
328
  host.addReactInstanceEventListener(object : ReactInstanceEventListener {
193
329
  override fun onReactContextInitialized(context: ReactContext) {
194
- BTLogger.info("Background ReactContext initialized")
330
+ val initMs = (System.nanoTime() - bgStartTime) / 1_000_000.0
331
+ BTLogger.info("[SplitBundle] background ReactContext initialized in ${String.format("%.1f", initMs)}ms")
195
332
  context.runOnJSQueueThread {
196
333
  try {
197
334
  val ptr = context.javaScriptContextHolder?.get() ?: 0L
@@ -170,6 +170,8 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
170
170
 
171
171
  - (NSURL *)bundleURL
172
172
  {
173
+ // When _jsBundleSource is set (dev mode or explicit override), use it as-is.
174
+ // This is a single full bundle (not split), so DON'T use common+entry strategy.
173
175
  if (!_jsBundleSource.empty()) {
174
176
  NSString *jsBundleSourceNS = [NSString stringWithUTF8String:_jsBundleSource.c_str()];
175
177
  NSURL *resolvedURL = resolveBundleSourceURL(jsBundleSourceNS);
@@ -180,11 +182,19 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
180
182
  [BTLogger warn:[NSString stringWithFormat:@"Unable to resolve custom jsBundleSource=%@", jsBundleSourceNS]];
181
183
  }
182
184
 
183
- NSURL *defaultBundleURL = resolveMainBundleResourceURL(@"background.bundle");
184
- if (defaultBundleURL) {
185
- return defaultBundleURL;
185
+ // Default: load common bundle (shared polyfills + modules).
186
+ // The background entry bundle is loaded later in hostDidStart:.
187
+ NSURL *commonURL = resolveMainBundleResourceURL(@"common.jsbundle");
188
+ if (commonURL) {
189
+ return commonURL;
186
190
  }
187
- return [[NSBundle mainBundle] URLForResource:@"background" withExtension:@"bundle"];
191
+ return [[NSBundle mainBundle] URLForResource:@"common" withExtension:@"jsbundle"];
192
+ }
193
+
194
+ - (NSString *)resolveBackgroundEntryBundlePath
195
+ {
196
+ NSURL *url = resolveMainBundleResourceURL(@"background.bundle");
197
+ return url.path;
188
198
  }
189
199
 
190
200
  - (void)hostDidStart:(RCTHost *)host
@@ -203,6 +213,28 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
203
213
  return;
204
214
  }
205
215
 
216
+ // When _jsBundleSource is set, the bundle loaded in bundleURL was already
217
+ // a full single bundle (dev mode / explicit override), so skip entry loading.
218
+ BOOL isSplitBundle = _jsBundleSource.empty();
219
+
220
+ // Read the background entry bundle data before entering the executor block
221
+ // (only needed in split-bundle mode).
222
+ NSData *bgBundleData = nil;
223
+ NSString *bgBundleSourceURL = nil;
224
+ if (isSplitBundle) {
225
+ NSString *bgBundlePath = [self resolveBackgroundEntryBundlePath];
226
+ if (bgBundlePath) {
227
+ bgBundleData = [NSData dataWithContentsOfFile:bgBundlePath];
228
+ bgBundleSourceURL = bgBundlePath.lastPathComponent ?: @"background.bundle";
229
+ [BTLogger info:[NSString stringWithFormat:@"Background entry bundle loaded from %@ (%lu bytes)",
230
+ bgBundlePath, (unsigned long)bgBundleData.length]];
231
+ } else {
232
+ [BTLogger warn:@"Background entry bundle not found, __setupBackgroundRPCHandler may not be defined"];
233
+ }
234
+ }
235
+
236
+ CFAbsoluteTime bgStartTime = CFAbsoluteTimeGetCurrent();
237
+
206
238
  [_rctInstance callFunctionOnBufferedRuntimeExecutor:[=](jsi::Runtime &runtime) {
207
239
  [self setupErrorHandler:runtime];
208
240
 
@@ -218,6 +250,22 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
218
250
  };
219
251
  SharedRPC::install(runtime, std::move(bgExecutor), "background");
220
252
  [BTLogger info:@"SharedStore and SharedRPC installed in background runtime"];
253
+
254
+ // In split-bundle mode, evaluate the background entry bundle now.
255
+ // This must happen BEFORE invokeOptionalGlobalFunction since the entry
256
+ // bundle defines __setupBackgroundRPCHandler.
257
+ if (isSplitBundle && bgBundleData && bgBundleData.length > 0) {
258
+ CFAbsoluteTime bgEvalStart = CFAbsoluteTimeGetCurrent();
259
+ auto buffer = std::make_shared<jsi::StringBuffer>(
260
+ std::string(static_cast<const char *>(bgBundleData.bytes), bgBundleData.length));
261
+ runtime.evaluateJavaScript(std::move(buffer), [bgBundleSourceURL UTF8String]);
262
+ double bgEvalMs = (CFAbsoluteTimeGetCurrent() - bgEvalStart) * 1000.0;
263
+ [BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg entry evaluated in %.1fms", bgEvalMs]];
264
+ }
265
+
266
+ double bgTotalMs = (CFAbsoluteTimeGetCurrent() - bgStartTime) * 1000.0;
267
+ [BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg hostDidStart total setup in %.1fms", bgTotalMs]];
268
+
221
269
  invokeOptionalGlobalFunction(runtime, "__setupBackgroundRPCHandler");
222
270
  }];
223
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-background-thread",
3
- "version": "1.1.51",
3
+ "version": "1.1.53",
4
4
  "description": "react-native-background-thread",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",