React Native Radio

RNR 310 - Nitro with Marc Rousavy

Episode Summary

Our hosts Robin Heinze and Mazen Chami chat with Marc Rousavy about Nitro Modules, a powerful new tool that outperforms both Turbo Modules and Expo Modules. They discuss how its object-oriented design simplifies native module development and enhances React Native’s performance!

Episode Notes

Our hosts Robin Heinze and Mazen Chami chat with Marc Rousavy about Nitro Modules, a powerful new tool that outperforms both Turbo Modules and Expo Modules. They discuss how its object-oriented design simplifies native module development and enhances React Native’s performance!

This episode is brought to you by Infinite Red! Infinite Red is an expert React Native consultancy located in the USA. With nearly a decade of React Native experience and deep roots in the React Native community (hosts of Chain React and the React Native Newsletter, core React Native contributors, creators of Ignite and Reactotron, and much, much more), Infinite Red is the best choice for helping you build and deploy your next React Native app.

Show Notes

Connect With Us!

Episode Transcription

Jed Bartausky:

Welcome back to another episode of the React Native Radio Podcast, episode 310 Nitro Modules with Mark Ru.

 

Mazen Chami:

Hey everyone, welcome to another episode of React Native Radio. We have a real cool episode lined up for you today. Robin and I have a cool guest. I'll get to our guest in a little bit. I'm Mazen Chami and I live in Durham, North Carolina with my wife and toddler. I'm a former pro soccer player and coach and a senior React native engineer here at Infinite Red. My co-host Robin Heinz. Robin is the director of engineering and Infinite Red. She's located west of Portland, Oregon with her husband and two kids and has specialized in React Native for the past seven years. How are you doing, Robin?

 

Robin Heinze:

I'm doing good. I'm just getting over Covid, so I'm glad that's over.

 

Mazen Chami:

Yeah, I hear you. I hear you. I'm still some long covid symptoms over here for sure. And our guest who needs no introduction is Marc Rousavy. Mark is an Austrian full stack developer who excels in mobile apps, low level languages, and GPU based code. He has a Keen ui, UX Design Sense and expertise in c plus plus Swift Kotlin and React Native as the creator and maintainer of popular open source libraries and CEO of High-end app development agency. Margie. He collaborates with high profile clients and occasionally tackles intriguing contracts himself. Mark, welcome to the show. How are you doing?

 

Marc Rousavy:

Hey, I'm good. Thanks for having me.

 

Mazen Chami:

Yeah, of course. We're always happy to have you on here. Before we get into the topic, let's hear from our sponsor. Infinite Red is a Premier React native consultancy, fully located remote in the United States. We're a team of 30 senior plus level React native developers and support staff and have been doing React Native for nearly a decade. If you're looking for React native expertise for your next project, hit us up at infinite.red/radio. Don't forget to mention that you heard us through the React Native Radio podcast. 

Alright, let's get into our topic. We're going to be talking about a topic called Nitro who if you follow Mark on Twitter X, whatever they call it these days, you'll know what we're talking about, but luckily we have Mark on here to give us a little more in depth thoughts on it. So Mark, before we get started with the show, I know you were on with React Native Radio 2 69 where we talked about Vision Camera V three, but tell us a little bit about yourself and how you got to where you're at.

 

Marc Rousavy:

That's an interesting question. I've always been really interested in app development and cameras and GPU based stuff, and it's always fun to play around with those things. So yeah, built stuff and then at some point I published stuff and then that's how a vision camera got created. And then same story about React native fm kv, and now also Nitro.

 

Mazen Chami:

That's awesome. Well, okay, what is

 

Marc Rousavy:

Maybe your

 

Mazen Chami:

Elevator pitch? What is

 

Marc Rousavy:

Nitro Nitro Modules? The fun thing is that this is the first time that I'm actually talking about this and I actually never gave an elevator pitch about Nitro modules before, so I don't even know how to, so this

 

Robin Heinze:

Is your jam, how to phrase it? You're going to workshop it. This is my chance. Live on React native debris.

 

Marc Rousavy:

Yeah,

 

Mazen Chami:

Exactly. We just walked into an elevator. Hey Mark,

 

Marc Rousavy:

What are you working on these days?

I'm working on insanely fast c plus plus Swift or Kotlin modules that are bridged to JavaScript using Nitro, and I'm building the framework for that, the foundation to build a bridge essentially. It is funny because everybody knows what Turbo modules are and everybody knows what native modules are, and then also everybody knows what expo modules are, but I don't think anyone ever expected there to be a new library or a new way of doing things anytime soon. I mean, expo was the answer to or expo modules was the answer to not having Swift or to terrible modules being too complicated. So that's why Expo modules got built and it did a very good job at that. So expo modules is really cool. The syntax is amazing. It's very easy to use, but for me, I just always had, so there's some limitations in term modules and expo modules that you can only work around with if you just use a bare GSI layer.

So the JavaScript interface, if you just use the whole library from c plus plus and if you do everything yourself, then you can build it. But turbo modules and expert modules just don't provide APIs for some edge cases. One of those edge cases is vision cameras frame processing feature, which is something that I talked about in a previous podcast here. So the way frame processors work is I have a Swift component, which is just a normal native module or a native view component, and then to actually send the frame over to JavaScript as efficiently as possible. Remember, the frame is like a 10 megabyte buffer and we cannot copy it over the bridge or not even with turbo modules. So we needed a way to expose a complex c plus plus type, or in this case a SWIFT type to JavaScript and provide some APIs around it, like getting the frames width and frames height. And we cannot just wrap it in an interface because an interface is just like an normal JSON type. So this is the main reason I built Nitro modules. And then there's many other benefits of Nitro modules that I'm excited to show you guys and talk about. And one of those is also the improved performance, which was not the main goal with Nitro modules. I didn't want to just build something faster, it was more about limitations.

 

Mazen Chami:

So we'll get to the speed part, but one part that I really like about Nitro is you're using Swift. I just did the workshop at Chain React and it was mainly around turbo modules. It was great, but every single step of the way I kept getting questions, when can we use Swift?

Why aren't we using Swift? Why are we still an objective C? Why are we writing this one line of C plus plus to tell objective C, to compile down to C plus plus and all that kind of stuff. And I got to show the class Swift and c plus plus side by side and Swift is just a more modern language, I guess is the best way to put it. And it's much cleaner and easier to read just like looking at the code versus objective C. So I'm excited that we finally have access to Swift. I wonder if Turbo Modules will get there at some point, but not at the point of this recording.

 

Marc Rousavy:

Yeah, that's also the cool thing about Nitro because I built it mainly for vision camera and vision camera is just a SWIFT library, and Nitro Modules actually doesn't even go through objective C at all because conventionally you would go through objective C as an objective C bridging layer and then call into SWIFT using the at objective C annotations and then the NS number types and the NS error and all those kinds of things. But Nitro Modules doesn't do any of that. Nitro modules actually uses the new Swift, what is it like five point 10, something like that. It was just released a few months ago. The new Swift c plus plus interrupt, which is like a direct layer between Swift and c plus plus. So you can use some SCD types like SCD, string, SCD, optional. You can expose any non templated type to Swift and directly call it, which is simpler and much, much faster. Insane speeds. It's insane. You don't have to go through objectives here at all. That's awesome.

 

Robin Heinze:

You've talked about the advantages of Nigel modules over two modules and why you built this and what problems it's solving, but because Turbo modules are sort of like meta's thing, they're blessed by meta, which is obviously React native is meta. Are there any disadvantages to going with something that's not Meta's thing, if that makes sense?

 

Marc Rousavy:

Yeah, of course. I mean, I would prefer to have Nitro inside reactive core or at least the concepts that I built, but there's a few problems around that. And I mean Turbo modules are like the objective C and Java Turbo modules don't create any new groundbreaking new API design changes because that would break backwards compatibility.

So that's one reason why turbo modules aren't the same thing as Nitro modules now. And also the reason why we can't bring Nitro modules into rec Native Core as of today, I also don't think Facebook wants or Meta wants to have a second option for building native components. They have to modules, they don't want to introduce a second option, and we can't really make natural modules backwards compatible right now. There's a few reasons for that, but I think the main one being that it just has a strong requirement on IV 0.75 or 74, and I'm not sure if Meta really wants to ship that right now. And also all existing modules need to be rewritten in Nitro modules if they want to use the new APIs. And the last point, as far as I know, meta doesn't use Swift internally, so that's a blocker. I'm not sure. I think I heard it somewhere, but yeah.

 

Robin Heinze:

So could you see realistically, could you see when the new architecture is broadly adopted and people have basically moved on and aren't using the old architecture anymore, can you kind of see Nitro modules being the standard that most people use even though it's not included with React Native Core?

 

Marc Rousavy:

Yeah, I can definitely see React native Nitro being a very good alternative to Turbo or expo modules. And I just noticed that I didn't even answer your question before, so I'm going to do that now. My bad. Yes, there are disadvantages. The main disadvantage right now is that you have to build the library as a library author from scratch again in Nitro modules.

So I mean, that's only the bindings, right? So for example, for vision camera, I don't have to write the whole camera stuff again. I just have to write the bindings to JavaScript again, which is take photo, start recording, all those kinds of external bindings, which is not a lot of effort. But for some people, and especially in vision camera, because I have this whole c plus plus layer where I use some fancy tricks to get the frame exposed to JavaScript, I need to rewrite the whole thing with Nitro, which is going to be much less code, much faster, much easier to maintain, but it's still going to be a rewrite. And second, I mean every user of a library that is built with Nitro needs to install nitro modules. I don't know if that's a big problem, but it's an extra step.

 

Robin Heinze:

It need to install it. It is a thing. So you've said on Twitter, people are asking you a lot about it, and you said it's object oriented, which speaks to my brain a Ruby Dev at heart. Can you talk about how this is different from the normal ways of writing native modules and why you chose to go sort of object oriented in your approach?

 

Marc Rousavy:

So previously in turbo modules, native modules, way back in the day, everything was just very simple. So you had one method exposed, two JavaScripts, which is an objective C, which you could call, and that's it, right? So Facebook or Meta just decided to keep this layer very simple and then do more stuff on the native side or more stuff on the JavaScript side. But communication in between is expensive anyways, and it was just kept at a minimum. So traditionally, and still today in a turbo module, everything, every module under quotes is just a single in your app, which has static methods. It's not a literal static method on the native side, but it feels like a static method on JavaScript, which means you can't really do stuff like passing around instances of some complex type. That's what object oriented programming is about. You have essentially deep down, you have in C for example, in the C language, you have struct with states and methods, which is essentially what an object is.

And you cannot really represent that in internal modules, right? So for example, one very good example I always talk about is the UI image complex object on iOS, which just holds image data. So you have either in-memory image, it might just be a large bite array, it might also be backed with a GPU texture, maybe it's in system image, whatever it is. It's just a handle you can use to access image data, pass it around, set it on an image component, read back, modify in memory, and all those kinds of things. And you can't really represent that in a turbo module because turbo modules have a fixed type of, or a number of types they support, which is like a number a string, an array, a Boolean, and what is it, a dictionary as well. And then I guess promise and callback. But you can't really create a complex type per se.

And this is what Nitro modules are all about. Every native type you write, which is declared as a type script interface, xray represents an instant able type on the native side. So you can create instances of whatever type you create. For example, if I write an image component, everything is called a hybrid object. That's how natural modules work. So if I create an image hybrid object, then it would be hybrid image on a native side, and I can create it with a UI image, with a CG image, whatever it is. And I have instances of this type, which I can pass around without copying any bites in memory. So this is what object oriented means in this concept. And having first cast support for that is really awesome because imagine a situation where your camera library of choice, hopefully vision camera is built with Nitro,

 

Robin Heinze:

Always

 

Marc Rousavy:

Your image component library of choice is built with Nitro and your image manipulation library of choice is built with Nitro. Imagine if you return an image from vision camera, pass it to your image manipulation library, make it gray scale, crop it, make it bigger, whatever, all of those in memory, and then pass it to your image component. You don't have to do any file writing, which is what you have to do now.

 

Robin Heinze:

Oh my gosh, yeah, yeah. You'd have to write it, write it to the disc and read it from the dis.

 

Marc Rousavy:

So yeah, you either have to write it to a file and then return the file path. And then if you want to make it gray scale, pass it to your image manipulation library, load it again from the file, write it again to the file. It's a lot of file io. Or you use Buffer, which is a semi implemented API on rec native because it uses a global ID lookup and the buffer is not really accessible in JavaScript. I think it's called Buffer. Yeah. But yeah, it's very inefficient nowadays. So with Nitro modules it would be more efficient because you can actually pass around instances of the image in memory. And the same goes for databases. Rec native MMKV is one of those instances that has state, native state. So MMKV would be a perfect hybrid object image would be a perfect hybrid object. And essentially everything could be a hybrid object. Every native module is a hybrid object, even if it's just a single, you then just have one instance of the hybrid object.

 

Robin Heinze:

Wow, that's really cool. Like a completely new way of approaching all this stuff. And your example about using it for the whole image life cycle in an app is really exciting.

 

Mazen Chami:

Yeah, that to me is just mind blowing because you don't have to manually write the JSI bindings anymore, build it, and like you said, step 1, 2, 3, 4, 5, 6, all that kind of goes away and that's great. And you posted a tweet eventually got to this whole talk about, you don't have to write the manual JSI bindings anymore, and I just want to talk about the numbers real quick. You have a function for add numbers and you did it in expert module, turbo module and nitro module. Nitro module was 59 times faster than expo modules and 15 times faster than turbo modules. That's insane to me. I think just lemme put that in milliseconds for our listener. Expo module took 435 milliseconds.

 

Marc Rousavy:

Maybe to add some context, we called the function I think 100,000 times, right?

 

Mazen Chami:

Yeah, a hundred thousand times. Yes. Sorry, functional called a hundred thousand times. That's actually very good context

 

Robin Heinze:

Matter. It would be a long time for calling it once.

 

Mazen Chami:

Yeah, I was going to say, let's go back a little bit. We called add numbers a hundred thousand times. That's a lot. Expo modules took 435 milliseconds. Turbo module took 116 milliseconds. Okay, you're like, all right, cool. I'm going to use Turbo Modules moving forward. But then Nitro comes in to the chat and says, alright, I'm going to do this in 7.3 second milliseconds. That's incredible. That's like a huge number change that's mind blowing to me and great job. I'm kind of speechless here and I'm almost reading from thank you script. Amazing work on that. I want to go into more of the generation part of Nitro and how it does its generation. I think for our listeners that are familiar with Turbo modules and Code Gen and the whole JSI, new architecture, I think Meta likes to call the new architecture framework with Nitro, you have a cool generator called Nitrogen. I love that name. Thank you. It generates c plus plus.

 

Robin Heinze:

I just put that together. There

 

Mazen Chami:

You go. So this nitrogen code generator generates c plus plus from TypeScript, then a Swift file eventually. Collin, you said eventually Collin, is that because you're not at the Android stage, or was that just you have Android so far?

 

Marc Rousavy:

That's why we moved the podcast recording today one hour back, because I just finished probably 70% of Android already. So I'm almost done with Android as well. Yeah, there listeners, you heard it here.

 

Mazen Chami:

Mark does not sleep anyways. So nitrogen also auto generates many c plus plus Swift bindings that would otherwise be a lot of effort in your native Swift app. So we talked about that. Can you talk about nitrogen in aspects of it that we haven't talked about yet, how these bindings work and talk to each other? Right?

 

Marc Rousavy:

So yeah, the whole architecture is pretty cool actually to talk about nitrogen. So let's go through it from the step, the first step that you write as a library author as a user of Nitro modules to actually the last step of what gets executed by the GS runtime. So the beginning is you create your Nitro module, which is just a tive library, install tive Nitro module core or nitro modules or whatever it's called, and then also install nitrogen as a code generator. So that's step zero, right? Step one is you write your native components like a specification similar to how Cogen works. You create a TypeScript file and nitrogen is all about TypeScript, so there's no flow integration or anything. You create your TypeScript file, it has to have a Nitro TS suffix, and then in the Nitro file you export or you don't even need to export it, you just create your interfaces, which extend hybrid object. And yeah.

 

Mazen Chami:

So for context, for our listeners, this is the equivalent of a spec TSX file that you would do with Turbo, with turbo modules,

 

Marc Rousavy:

Correct? Yeah,

 

Mazen Chami:

Your Nitro Ts file.

 

Marc Rousavy:

Yeah, I'm not sure if turbo modules require spec, do they

 

Mazen Chami:

Not dot spec? It's like the suffix is usually spec tsx, it's in a spec folder. Spec TXTs.

 

Marc Rousavy:

I mean, essentially I can also parse every TypeScript file because the hard requirement is just that it has to be an interface which extends hybrid object, but I just thought it would be cleaner to have a Nitro TS extension. There's no reason behind it. Also, I have to let pars less files I guess. So we have our NIRA Ts file, let's call it image. Let's go with image component. We have image nitro ts inside the TypeScript file, we export an interface called image, and image has to extend hybrid object. And then hybrid object has a generic parameter in which you can just configure the languages you want to use. So by default it will generate c plus plus for both platforms, which is just a shared c plus plus layer. The shared c plus plus layer always gets generated, even if you bridge to Swift or to Kotlin, it's just one c plus plus abstract class, which represents the image from TypeScript in c plus plus.

You can also, and here's the really cool part, if you generate one part in Swift or in Kotlin or whatever you want in any language, you can still use it from c plus plus as if it would be just a normal c plus plus class because essentially everything's derived from that base c plus plus class. So this is one of the use cases we needed for reacted filament where we had just three platform specific files, and the rest would all be cross platform c plus plus. We can now use those three platform specific files, which have a SWIFT or cutline implementation directly from c plus plus, and it's just a virtual method, so you can just call it any other c plus plus method. So basic inheritance, even as a library author, you can work with inheritance here. Okay, let's take a step back here.

We want to create our image hybrid object inside the image interface. In TypeScript, we have properties, so it's either a property or a method. You can add properties and method and methods. And a property can also be read only, in which case it only has a getter and not a setter. The image hybrid object will get generated on c plus plus. And if we also specify the target for iOS, for example, in the hybrid object extent clause in TypeScript, you can just specify iOS crawl like Swift, and then Android Kotlin for example. Then it will additionally also create the bindings for Kotlin and Swift. This is the cool thing about the swift interrupt. We can bridge directly to Swift, meaning we can use c plus plus types directly in Swift. It sounds amazing, but it's actually really, really complicated because we need to specialize all templates.

And if you're a c plus plus developer, you know what that means? You have to basically every generic on the quotes in c plus plus needs to be flattened out to not be a generic anymore because generics cannot be used in Swift. And because templates are a c plus plus only thing. So we need to generate a lot of additional binding code to bridge from C plus plus to swift, but nitrogen handles all of that. So for example, for let's say an optional with generic parameters of string, for example, an optional string would have to be flattened out, and it's called optional underscore STD string in swift. So there's a lot of additional code that needs to be generated, but it's all just to flat note the template. There's no overhead of this bridging at all. It's just direct c plus plus calls. But yeah, anyways, so you generated your specs, and the only thing you need to do is implement the interfaces on the native side.

So in swift it's called protocol. You just implement a protocol, and I try to keep it very similar to how it works in TypeScript. So for example, in Swift, we can use properties with getters and setters, which are bridged to c plus plus as get something and set something just normal functions because c plus plus does not have properties. So in Swift, you can use properties, which looks exactly like in TypeScript, you use the normal, the standards, swift types. So for example, you also use a dictionary or something like that. And then also it supports a ton of different standards, swift and cotton types. So for example, for a number, we get a double for bull and a bull for string. A string, just the normal types that we all love and know. And then also there's arrays, as you know. But the cool thing is Nitro also supports variants.

So for example, the pipe operator in TypeScript, so it's either a number or a string. Nitrogen also supports two pole. So fixed length arrays, which are even size checked when passing them between JS and native dictionary. Typed dictionaries actually with a fixed type optionals promises, obviously everybody uses promises. And then also a ray buffer and functions or callbacks actually, which are treated. There's no events anymore. In Nitro, you don't have events or completion or success or failure listeners. Everything is just either a callback, which you can hold onto or a synchronous function, and you can pass as many callbacks as you want. It's just like a JavaScript object. So events would be implemented by just creating a native list of functions and then just adding listeners through Nitro, so there's no event emitters anymore. And then, yeah, array buffers and just other hybrid objects. So that's the whole object oriented part. You can pass a image to an image or return an image from an image. You can pass it around inside Nitro modules. And it's all built on the interface level. So you can have different implementations of image as well. If you have a UI image, which uses UI image on iOS or CG image, which is the core graphic image implementation. So GPU based image, but you can pass it around as just any image. So that's a object oriented part of this as well.

 

Mazen Chami:

That's awesome. Yeah. I'm going to put in the show notes, your tweet that has the full list of all these c plus plus JS types mapping. So that'll be available to our listeners. Perfect.

 

Robin Heinze:

We're unfortunately getting to the end of the time that we have, but this has all been incredible information. I want to wrap up talking to our audience briefly, who a lot of them are probably app developers, maybe some library authors, I'm not sure. But can you talk a bit about what app developers should expect to need to do in order to get the benefits of Nitro? Primarily? I think your audience is library authors, but if I'm a regular app developer, what do I need to do to leverage these speeds? Is it really just waiting until my favorite libraries are using Nitro and then installing them? Or is there anything the app developers should know about Nitro and using Nitro?

 

Marc Rousavy:

So the goal of Nitro is to make library development easier and faster for everyone. So my long-term goal is to make just the entire library ecosystem in native faster and to provide better libraries. And I already have a lot of popular libraries on board with the idea. So that's yaku from Native Uni styles, that's OV from Rick Native video, a lot of popular libraries as well as my libraries, vision camera and kv filament of those things. So app developers will get those performance gains for free. And also if they want to build their own native modules, it should be really, really simple to do so with Nitro because they can just run nitrogen themselves on a native app and then just add, write their native components and just register them manually in their app delegate, and that's it. So it's very, very easy to write a library with Nitro, and if you want to do so yourself, then it's all open source. There's going to be documentation and everything.

 

Robin Heinze:

Yeah, it sounds like you're really, really focused on developer experience and making things as pain-free as possible, so that's great.

 

Marc Rousavy:

Exactly.

 

Robin Heinze:

When will Nitro be ready and available to the public? It is possible it will be by the time this is released. So

 

Marc Rousavy:

Yeah, so as of today when we're recording this, that's August 20th. The c plus plus code base of Nitro is already completely finished. So everything is already running in production in rec native filament, which uses a early version of Ative Nitro, and the Swift implementation is also pretty much finished. Jacob is actually experimenting with that right now for magnetic uni styles, and it seems to work very, very good. There's only two components which are not bridge to Swift yet, but I consider them low priority for now. And then Kotlin is also 70% finished. So I think the next steps would be for next week to battle test the whole thing, and then maybe we can have Nitro modules in two weeks.

 

Robin Heinze:

Nice. So that will probably mean that Nitro will be out by the time you're listening to this, and we'll make sure everything you need to know is in the show notes so you can try it out yourself.

 

Mazen Chami:

Mark, thank you very much for coming on and talking to us about Nitro, and I'm really excited to see where this goes and how Library Maintainers can leverage these incredible speeds and continue to grow the React native ecosystem and continue to grow React native in general.

 

Robin Heinze:

Every time you make a React Native app faster, you help the community and you help it sort of gain ground against the naysayers.

 

Mazen Chami:

Thank you so much for all your hard work, mark. Thank you. So as we're wrapping up, Robin, do you have a mom joke for us?

 

Robin Heinze:

I do. I had a lot to choose from this time. We've had lots of activity in the Jokes Channel and Slack, but this is my favorite. What do you call, reluctant potato?

 

Mazen Chami:

What?

 

Robin Heinze:

AOR is so dumb, but it's so funny.

 

Marc Rousavy:

It is. So you have a dedicated joke channel.

 

Robin Heinze:

We do have a joke channel. Yeah,

 

Marc Rousavy:

We do. Damn, now I'm jealous.

 

Mazen Chami:

Create one for us, mark, and we'll put some jokes in there for you. Then you're Discord. Awesome. Well thank you both and to our listeners. See you all next time. Bye.

 

Jed Bartausky:

As always, thanks to our editor, Todd Werth, our assistant editor, this guy Jed Bartausky, our marketing and episode release coordinator, Justin Huskey and our guest coordinator, Mazen Chami. Our producers and hosts are Jamon Holmgren, Robin Heinze and Mazen Chami. Thanks to our sponsor, infinite Red. Check us out at infinite.red/radio. A special thanks to all of you listening today. Make sure to subscribe to React Native Radio on all the major podcasting platforms.