-
From my understanding, the aim for both mechanisms is to enable the main application thread to synchronously call JS code from native code and vice versa. What are the fundamental differences in both mechanisms? Is one more performant than the other? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 11 replies
-
I'll summarise what I know about the React Native approach just to get everyone on the same page. For the NativeScript side of things, I'll write up my working understanding, but any experts are welcome to correct details! Marshalling in React NativeThe old way: a relatively simple JSON bridgeIn React Native, the old way of communicating was via a JSON bridge. The native side would accept JSON messages from the JS side, and vice versa, and you could set up each side to react to those messages however you'd like. The limitation being that only JSON-serialisable data types could be sent over the bridge. The new way: direct marshalling via JSIThe new way is via the JavaScript Interface (JSI). For each different JS engine (JSC, V8, Hermes, QuickJS, and more), Meta or Microsoft will likely have implemented an interface (likely using C++) that tells the JS engine how to handle native data types (HostObjects) internally. Here's the one for JSC, for example. Then, from each different platform (iOS, Android, etc.), one can use the appropriate language (typically Obj-C++ for iOS, and Java with JNI for Android) to call upon React Native's generic JSI APIs. Those generic JSI API calls then hand off to the JS engine-specific implementation to handle the native data types. So React Native JSI allows a user to manually set up individual bindings to APIs of interest, by writing some native code for each platform. Codegen simplifies this process by having you write out the shape of the API you want in TypeScript and then filling in all the interfaces for you, but you'd still have to write the implementation. The main friction is that there is no official documentation on either Codegen, JSI, or Turbomodules yet. On that note, Turbomodules is, to my understanding, just JSI written by a particular convention to avoid as much as possible rewriting the same thing for each different target platform. By having this conventional shape, it may be that it can benefit from being loaded in a different order to JSI, but I'm not sure (just something I heard on Twitter). As I said, lack of documentation makes it all a bit mysterious for now. NativeScriptEDIT: After writing this section, I did a deep-dive to see NativeScript's JS->native bindings work at the low level, which you can read here. I'll keep the below high-level explanation as I originally wrote it, however. This section is all just to my understanding. I defer to anyone more expert in this! Upon building your app, NativeScript updates its metadata bindings so that essentially every API in your app with a public header gets an equivalent JavaScript binding generated for it (and equivalent TypeScript types can also be generated). There are some pros and cons to this. UpsidesYou can access all native APIs directly using JS. To reiterate, in React Native JSI, you'd manually write some bindings in Obj-C++ and Java (assuming just an iOS and Android project) for a small number of APIs of interest and then from JS you could call those bindings. In NativeScript, all bindings for the entire platform's SDK and your userland native code are already established and ready to go, and again, come with equivalent TypeScript typings. This means that, in NativeScript, you don't have to wrestle with native code files, pointer ownership, garbage collection, writing TypeScript types (though yes, not a huge concern once Codegen becomes documented) and whatnot. You can write a whole app just using TS/JS files; if there are any gaps in the NativeScript Core APIs, you can handle it in userland rather than having to fork NativeScript Core to add the new APIs. Of course, if you do ever want to write any native code directly using a native language like Obj-C, then you can still make native modules for that just fine in NativeScript. DownsidesLoading in metadata to bind all platform APIs will have some implications for app size and startup time, but I don't know how much (to be clear, it's an acceptable level, though naturally the weight is non-zero). Fortunately, it is possible to filter down the bound metadata to just the SDKs of interest using metadata filtering. Another downside of accessing native APIs entirely from JavaScript is that you may miss some native IDE assistance. A good amount of this is mitigated (e.g. ConclusionI suspect that the speed of data marshalling is comparable between React Native JSI and NativeScript. So I feel the main difference between the two is whether the bindings need to be written manually (React Native) or are generated automatically (NativeScript). |
Beta Was this translation helpful? Give feedback.
I'll summarise what I know about the React Native approach just to get everyone on the same page. For the NativeScript side of things, I'll write up my working understanding, but any experts are welcome to correct details!
Marshalling in React Native
The old way: a relatively simple JSON bridge
In React Native, the old way of communicating was via a JSON bridge. The native side would accept JSON messages from the JS side, and vice versa, and you could set up each side to react to those messages however you'd like. The limitation being that only JSON-serialisable data types could be sent over the bridge.
The new way: direct marshalling via JSI
The new way is via the JavaScript Interface (J…