-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Multi-bundle support (v2) #152
Comments
also cc @ide |
The |
cc @acoates-ms based on the conversation from RN.EU (ah, already in OP’s cc list). Thanks for trimming this down based on our conversation in Poland. Im on vacation this week but will take a look when I get back. Hopefully some of the people you cc’d have thoughts. Especially Microsoft on whether this is the same change they needed to make to make this possible. |
So we can use require("androidFilesPath/X.js") or require("iOSDocumentsPath/X.js") if the loadScript function is done? |
So I was looking through our code, and it doesn't actually look like we've made this change currently. We are calling loadApplicationScript multiple times as is. It looks like the logic in that function is setting up some globals etc in a relatively stateless way, so I'm curious what issues you hit with calling it multiple times. Having said that, it shouldn't redo that work when called multiple times, so we agree the with the proposed change. |
@zcgit those require statements would still be evaluated at bundle time. The change to core being done wont provide out of the box bundle splitting / loading for RN. But it makes changes to core that allow libraries on top to potentially provide that capability. |
@acoates-ms The idea to make this globals initialisation a one-time logic was added here to (like you said) prevent it from redoing the work and prevent any potential edge-cases, that we haven't spotted yet. |
@TheSavior @cpojer |
@zamotany, you and I followed up over DM a couple of days ago but forgot to follow up here as well. It seems like if this is really a small change and Microsoft agrees with that change, then going to a PR seems like a reasonable next step for us to take a closer look. 👍 |
Updated the description with Detailed plan |
Thanks for all the work you’ve put into this! I’ve taken a look and I think the changes to RN core make sense to me, and I think we're good to go with making the PR(s) - I just have a couple questions/suggestions: Questions:
Again, I don't mean to suggest that we shouldn't do this - just trying to understand the motivations. SuggestionsI only really have one change I’d propose, for #4 in your proposal:
In this proposal, both
What do you think? Splitting it upIt’d be great to split this up even further, if possible - obviously you’re more familiar with the code, so you’ll know better if this is feasible, but here’s how I'd go about splitting this up into separate commits/PRs:
TestingI know most of these classes probably don’t have any tests right now :( but if you’re up for it it would be really awesome to add them here - it would help make the PRs easier to review (so we can see how each of these is intended to be used) and it would also make it harder for us to break in the future (which is especially important for APIs that we don’t use at FB). |
@ejanzer Thanks for the feedback 👍 Like you said, one of the differences between proposed changes and what's currently in RN core is the ability to load from string, which itself is not a main motivation. We would like to be able to mix multiple bundle types together, for example it's currently not possible to have initial bundle as plan JS and additional as RAM bundles. Moreover we want to improve the API for bundle handling and make the API consumers concerned only about where to load the bundle from, instead of what the bundle actually is. As for the rest, I really like the suggestion you've proposed and we'll incorporate it. The reason why we haven't proposed it, is that we wanted to keep the amount of changes smaller. Given the much better way of splitting up the work you've proposed, I assume it won't be much of an issue now. We'll try to add tests for the untested code. If you have any suggestions how to test it, what's the best approach, I'll be more than happy to hear it. |
@ejanzer
We would greatly appreciate your feedback on them. Another think I've realised and wanted to discuss is that with current setup, we're not supporting a use case we have, which is to synchronously call native side from JS, to load required bundle, before continuing evolution of the current bundle. In out case we have a We figured out there are 2 viable ways to achieve that:
WDYT? Maybe you have some other idea? We would like to avoid having to deal with NDK in native module that will be open source, due to greater maintenance cost. |
So am I understanding correctly that you only need the first 2 PRs to support your use case? If that's true, then I don't think there's any reason to go ahead with the third one - splitting up and renaming I believe it should be possible to make a native module method synchronous today: (see: https://medium.com/@some_day_man/synchronous-returns-in-react-native-native-modules-453af33d5999). What you described in 1 is similar to TurboModules, which is coming soon, but not available yet. I'm not as familiar with |
@ejanzer Well, the problem is I cannot use Right now So to better illustrate the problem:
|
Thanks for all the work you’ve put into this! It's Awesome! So, I researched a lot about "multi-bundle support" in the last time and I didn't find any solution for this. In my case, I need to do "Super App" which contains mini-apps, bundles of mini-apps will store on the server-side. I thought about rendering mini-apps in WebView but I like native solution more |
@fredikey Sorry for late response. Yes, you can use that together with Haul. However, I would suggest to do it only for prototyping, since it's using old version and is not really maintained. |
First PR is here, the other two are on their way. |
…7844) Summary: This is the first of three PRs related to enabling multi-bundle support in React Native. More details, motivation and reasoning behind it can be found in RFC [here](react-native-community/discussions-and-proposals#152). Logic responsible for installing globals was pulled out from `loadApplicationScript` to `initializeRuntime` since it should be ran only once, what was left was renamed to `loadBundle`. It's based on dratwas work from [here](https://github.com/callstack/react-native/tree/feat/multibundle/split-load-application), but applied to current `master` to avoid rebasing 3-months old branch and issues that come with that. ## Changelog [Internal] [Changed] - split `loadApplicationScript` into `initializeRuntime` and `loadBundle` to enable multi-bundle support in the future Pull Request resolved: #27844 Test Plan: Initialized new RN app with CLI, set RN to build from source and verified the still app builds and runs OK using code from this branch. Reviewed By: rickhanlonii Differential Revision: D19888605 Pulled By: ejanzer fbshipit-source-id: 24ace48ffe8978796591fe7c6cf53a61b127cce6
…cebook#27844) Summary: This is the first of three PRs related to enabling multi-bundle support in React Native. More details, motivation and reasoning behind it can be found in RFC [here](react-native-community/discussions-and-proposals#152). Logic responsible for installing globals was pulled out from `loadApplicationScript` to `initializeRuntime` since it should be ran only once, what was left was renamed to `loadBundle`. It's based on dratwas work from [here](https://github.com/callstack/react-native/tree/feat/multibundle/split-load-application), but applied to current `master` to avoid rebasing 3-months old branch and issues that come with that. ## Changelog [Internal] [Changed] - split `loadApplicationScript` into `initializeRuntime` and `loadBundle` to enable multi-bundle support in the future Pull Request resolved: facebook#27844 Test Plan: Initialized new RN app with CLI, set RN to build from source and verified the still app builds and runs OK using code from this branch. Reviewed By: rickhanlonii Differential Revision: D19888605 Pulled By: ejanzer fbshipit-source-id: 24ace48ffe8978796591fe7c6cf53a61b127cce6
@simka Is there any progress on PRs? Multi-bundle support looks like a great feature. |
Would this approach work for something like plugins ? Say you deploy you main application that has a plugin architecture and then plugins can be downloaded from a remote server and "installed" into the app as a bundle ? that would be incredible for a use case we have at Mattermost. |
@enahum As long as the bundles will be run under a single app that should work. Alternatively you could run multiple React instances with some native code as a brownfield app. |
@enahum, yes, this would be possible. In fact, we have already prototyped a similar use-case for one of our existing clients. Feel free to reach out in a DM/email to follow up. |
@grabbou thanks.. I'll contact you by email within the rest of the week, I'm a bit caught up with something else |
@grabbou I will also contact you by email, it sounds really interesting. |
@grabbou can you please create a medium post about it? So that we can all read it |
First of all, thanks @zamotany and Callstack for making the proposal for loading multi bundle in React Native. We also faced the same challenge when we develop a solution to load multiple "mini-apps" in the same hosted app. Let ' say, we have these screens:
Each RN screens have a lot of features inside (e.g: Seller tab shows all products of sellers, their categories, their videos, their feeds, ...). So it is fair to call they are RN app instead of RN screens. So, we wanted to take another approach to solve this issue, and the multi bundle is the right answer for us.
The interesting part is how we load a new bundle?
This API is worked for both Android and iOS. Although this API is marked as an experiment, we still try to use it, and it turns out it works quite well.
So when we load a new RN app, we will create a new JSContext, and copy some global variables from dll context to new JSContext. Let's call the new JSContext is app context. Inside the app context, we will load the app bundle. This approach is very easy to implement, and we could implement it without modifying RN Core - (JSGlobalContextRef)cloneJSContext:(JSGlobalContextRef)ctx name:(NSString *)name {
// creat a new context
JSContextGroupRef vm = JSContextGetGroup(ctx);
JSGlobalContextRef newCtx = JSGlobalContextCreateInGroup(vm, nullptr);
JSGlobalContextSetName(newCtx, JSStringCreateWithUTF8CString([name UTF8String]));
// copy global object
JSObjectRef global = JSContextGetGlobalObject(ctx);
JSObjectRef newGlobal = JSContextGetGlobalObject(newCtx);
JSPropertyNameArrayRef names = JSObjectCopyPropertyNames(ctx, global);
for (size_t i = 0; i < JSPropertyNameArrayGetCount(names); i++) {
JSStringRef name = JSPropertyNameArrayGetNameAtIndex(names, i);
JSValueRef value = JSObjectGetProperty(ctx, global, name, nil);
JSObjectSetProperty(newCtx, newGlobal, name, value, kJSPropertyAttributeNone, nil);
}
JSPropertyNameArrayRelease(names);
return newCtx;
}
- (void)evaluateJavascriptWithContent:(NSString *)content sourceName:(NSString *)name {
JSStringRef sourceRef = JSStringCreateWithUTF8CString([content UTF8String]);
JSStringRef sourceURLRef = JSStringCreateWithUTF8CString([name UTF8String]);
JSEvaluateScript(_ctx, sourceRef, nil, sourceURLRef, 0, nil);
JSStringRelease(sourceRef);
if (sourceURLRef) {
JSStringRelease(sourceURLRef);
}
}
- (void)evalulateJavascriptWithPath:(NSString *)path sourceName:(NSString *)name {
NSString* content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
[self evaluateJavascriptWithContent:content sourceName:name];
} For JavascriptCore and V8 runtime, we figure out the solution to do it. But for Hermes, we still looking digging into Hermes to figure out how to do it. So if you guys know how to do it, please let me know. Thanks |
Introduction
This RFC is a 2nd attempt finding the best way to add support for multiple bundles to the React Native. The original discussion can be found here: #127
The Core of It
Why we need multi-bundle support?
The main reason why want to introduce multi-bundle support, is that there are some cases in which the single-bundle approach, even with Hermes bytecode bundles won't work. For example, in apps where some logic has to be loaded dynamically or where the bundle has to be fetched from the remote location. A concrete example would be a word processing application, when based on some criteria a bundle with relevant logic, analytics, AI would be downloaded and used to provide better experience when the app detects that the content the user is writing matches those criteria.
Another example, can be preloading and warming up the environment ahead of time - in case of interconnected application like a application with a dashboard, which also includes shortcut to a messaging app, upon receiving a new message, we can assume that the user will switch to the messaging app, so we could be able to spun up the app in the background and load a bundle with common dependencies to improve TTR/TTI.
But what about Hermes and bytecode bundles? As great as Hermes is, it's not always feasible or straight up possible to use. As of right now, Hermes is not available for iOS and it might not be used in some out of tree platforms.
Required changes
Based on discussion in #127, in this proposal, we'll try to minimise the amount of the code that has to be touched in the React Native core and off-load as much of the logic as possible to a 3rd party native module - this means that the only changes required will be on the native side, no JavaScript API will be modified or added. Also the refactoring part (handling bundle types using polymorphism) will not be included here.
Essentially, what we need, is to be able to call
loadScriptFromAssets
/loadScriptFromFile
andloadRAMBundleFromString
/loadRAMBundleFromFile
multiple times. This means that the initialisation part in theJSExecutor::loadApplicationScript
(for instance inReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
) will need to be either done is a separate function, that will be called only once or executed in a conditional block based on a boolean flag likeisInitialised
.The function
registerSegment
could be then removed in favour ofloadScriptFromX
functions.Discussion points
The main question is, if the proposed changes are something that FB/React Native team would agree to merge and is there anything that needs to be tweaked/improved.
CC: @matthargett @joeblynch @TheSavior @cpojer @acoates-ms
Detailed plan
Instance
fromCxxReact
exposesloadScriptFromString
method - it would create an instance ofBundle
usingBundle::fromString
and callloadBundle
.Instance
fromCxxReact
exposesloadScriptFromFile
method - it would create an instance ofBundle
usingBundle::fromFile
and callloadBundle
.CatalystInstance
fromReactAndroid
exposesjniLoadScriptFromAssets
method - it would create an instance ofBundle
usingandroid::Bundle::fromAssets
and callloadBundle
.Instance
exposesloadBundle(bundle, bundleId, loadSynchronously)
, which would store bundle inRAMBundleRegistry
if it's a RAM bundle, callloadApplication
orloadApplicationSync
for initial bundle; for additional bundles it would callinitializeBundle
onnativeToJsBridge
initializeBundle
onnativeToJsBridge
callsinitializeBundle
onJSExecutor
, which should takestartupScript
from the bundle and evaluate it (RAM bundle is already stored inRAMBundleRegistry
inInstance::loadBundle
fornativeRequire
).RAMBundleRegistry
will be stored inInstance
and passed down toJSExecutor
as shared pointer.Bundle
is a abstract class forStringBundle
,IndexedRAMBundle
andFileRAMBundle
.RAMBundleRegistry
(it's always a multibundle registry)setSourceURL
should be renamed toloadScriptFromRemoteDebugger
(optional)loadScriptFromString
/loadScriptFromFile
/jniLoadScriptFromAssets
the last argument is abundleId
with default value0
-0
for initial bundle.Instance::loadBundle
raises error if trying to overwrite already existing bundle.Files that would be touched:
RAMBundleRegistry
(h, cpp)Instance
(h, cpp)CatalystInstance
(h, cpp, java)NativeToJsBridge
(h, cpp)JSExecutor
(h, cpp)JSIExecutor
(h, cpp)ProxyExecutor
(h, cpp)RCTCxxBridge
Bundle
,StringBundle
,IndexedRAMBundle
,FileRAMBundle
(h, cpp, added)We plan to open 2 PRs together:
The text was updated successfully, but these errors were encountered: