Brains & Beards Show

BBS 15 - Juggling JS Bundles

Episode Summary

In this episode, we're talking about different ways you can distribute test builds of your app to other members of your team, from very simple setups to elaborate workflows for product teams in larger organizations.

Episode Notes

You can find us on https://brainsandbeards.com/

Key Moments:

Episode Transcription

Hello, wonderful listener. Today, you're listening to a new episode of the Brains and Views show, where Patrick and Wojtek discuss programming, building teams, workflows, and everything else that it takes to deliver great mobile applications.

 

In this episode, we're talking about different ways you can distribute test builds of your app to other members of your team, from very simple setups to elaborate workflows for product teams in larger organizations.

 

Enjoy.

 

Hi, Patrick. Hello. Great to have you here. Hello, hello. Before we jump into the nitty gritty of distributing test builds, I think there's some context you need to lay down some basics around React Native to explain. So we make sure that everybody in our audience is on the same page, right? Yes. Let's share some intro into this topic so everybody can follow this ideas and understand what this is about, because this is not something which you usually do, but we discover that you can do it and it can bring you a lot of benefits.

 

So let's start with a very, very tiny intro to React Native. So if you want to compare React Native to something, I would compare it to a puppet and then puppeteer. So you can think that React Native consists of some native parts, it's like a native shell, it's like a bunch of LEGO bricks,

 

it's native views, like basically empty boxes, which are covering all the elements which iOS and Android supports.

 

And you can, and this native boxes are written in Objective C, Swift and Kotlin. That's why when somebody says like, oh, React Native is not native, well, it's not really true. We do spend most of the time writing Naba languages, but we at the end, we're using native parts and native elements.

 

And actually in React Native, those empty boxes, which I told you about, they are written in those languages, in those native languages, like Objective C, Swift or Kotlin. And let's call this shell, or I compare this shell to a puppet. And then we have the puppeteer. And the puppeteer is a person who moves those boxes, those empty views and arrange them on the screen and give them the color and the nice look and tells them what to do when a user, you know, presses on them.

 

And the important thing is that this, this puppeteer doesn't create any new views. They basically tells the existing native views of the puppet, what to do. This is what to do and so on. That sounds like a business logic, right? So in, in React Native, this business logic, this puppeteer is written in JS and it's bundled in the nicely way as the one single bundle, which exists in your application when you release your app.

 

So just to summarize it, so everybody has it in just one sentence, you have a native skeleton and JavaScript bundle containing business logic. And this is what you have to remember for the rest of this podcast.

 

And interesting stuff is when your application matures and you don't, you know, do, you don't add a new libraries every, every day to the app. What, which you do when you, you start writing application, but on some point you mainly your main work is just changing the business logic. So I would say like on some point, like 95% of, of your application releases is just changing JavaScript and changing business logic, and you don't add any new native elements.

 

And this is like the overview I wanted to, to give you just so you can follow on us with the rest of the ideas.

 

Okay. Yeah. I, I have, I share the same experience. I think the older the application is, the less you end up running bundle exec pod install to update your native modules. And if you're not doing it or you're not writing your own modules, then it means everything that goes into your update is the pure JavaScript. So we have our, our business logic, locked up nicely in, inside of the JavaScript bundle, what opportunities does this fact open for us? So, so this started for us when we started that using code push. So again, a small introduction code push is a way to deliver changes, changes in your application, business logic means JavaScript to your users without going through the review of Apple on Android, because you kind of pushes your changes to very fast to the, your user forms.

 

And when you do the setup for the code push, because you, of course, you have to do some tiny changes, you realize that what they do is basically a change, slightly change of the one specific up delegate method.

 

And this is the method where you tell, you know, where to take this JavaScript bundle from, and then you may start to wonder like, okay, if code push can replace this JavaScript on the run with something completely different than it was before, then what other things you can do with it. And then it brings you, as you said, what, what kind of opportunities does it give it to us? And this is like kind of cool and definitely it's not end of our journey and not end of our ideas. And you're going to bring your own ideas to it. But you can see that you can very easily do the cool stuff. Obviously you can do the code push, right?

 

It's a no brainer. Also, apart from the code push, if you're using expo, you have those EIS updates or expo updates, like they have different vocabulary for it, but the idea is basically the same, right? Like you push a JavaScript update and you omit the app store completely. Yes. And then, so, okay, code push, we know we can do it and other things, but then you, what you can do is, for example, you can test changes of your merge request before merging them into the main branch. So you can, how would that work? You can imagine that, you know, like, like, for example, that's, that's how we do it. We, we build an integration or demo version every night, and with some scripts and so on. So every day, POs, designers, and who else want to test it tracking engineers, for example, they can have a look to the app and see what, how it looks like and how it will go in the next release cycle to the users. But they can as well take this application. They, they just download it and then go to settings and to replace the current JavaScript bundle with your code, which hasn't been merged yet. So basically the code is downloaded. The app restarts. And then you can see that's your feature works or you broke everything, which brings us to the cool thing is that, that you, you won't have this old pesky fixes in the main because you merge something and somebody validates it and it doesn't work, then you have to revert it or put a fix on it on another fix and you patch on the patch, it just mess for, for people who has to test it, but as well, you don't, you rarely work alone. So other people would branch out of this main, which is kind of broken state because you broke it and it's just you distribute this kind of work to everybody and everybody can hate you or whatever. So this is the cool thing where you can validate your work before.

 

Um, merging it for everybody else.

 

And just to compare it and alternative naive approach would be to on all of your feature branches that represent work that you'd like to verify before merging. You would have to run the build task that takes half an hour for per platform or 20 minutes, and then somebody being, well, a lot of times being a product person or a QA person, uh, would have to install a separate version. Like you do would have to look at the documentation and check out like, Oh, Hey, what's the, what's the build number for this particular feature branch and

 

remove the app they currently have installed and install a new one, but removing an app removes the data. So they would have to log in again and do a lot of things like juggling the belts to, to get basically the set to the same result where they can test things before they release. They would have to know exactly and they would have to wait and they would have probably do it on a loyal local machines, which we, we don't have to do it because we can use, um, some clusters of building machines and they can do it very fast. Yeah. And building on your own machine introduces the whole set, uh, range of problems where they have to keep Xcode updated and, and Gradle and Android studio, and is it broken because of your code or they build it wrongly, right? It just adds a lot of time, which is, uh, you can spare. So basically it saves you time and money, right? By some let's say technical investment. Like you have to invest a bit in the setup, but then the whole team is ripping their rewards, right? The bigger the team, the bigger the rewards. Yes. There is definitely some setup needed for this, but it's not as big as you may think, and it's pretty stable in the way that it doesn't break all the time.

 

It kind of works for, for longer period of the time. If you do it correctly, of course. Which is something you cannot say about the deployment that goes and pushes a new build to the stores and makes it available to testers because sometimes the, the, the Apple or Google servers are down and they don't accept new belts or somebody has to, or they asked the account owner to sign in and click accept the new terms of service. And those are things that are in third party infrastructure. And here you own the, this, the, the setup so you can easily fix any, any things. Yeah. It says, you know, the definitely there are some things which some teams will struggle more because they have less automations. We have at Brains and Beers, fairly big amount of DevOps for the mobile

 

expertise. And we can help you with that. If you struggling with, with this kind of things, if you want to spare, save some time and give your developers more meaningful, full, uh, uh, win for them more meaningful time for, for working instead of fighting with the builds and so on. So we, we can help you with that definitely. Um, but that's the, that's the, like, uh, the beginning, you know, like now you know, you have the native part and you separated a little bit that the, the bundles from, uh, each other and you can push one without the other or replace the JavaScript without changing the native parts. And then you can think about it. Okay. What else can we do? And one of the things is like, uh, and to end tests. And when I say and to end tests, I think about the detox on my astro, which are taking a lot of time to build even on AWS Mac machines. They, they, it takes a while when your app grows, the build times will grow and then, okay, you can throw more money on it, right? You can always throw more money on it and have several of those machines and perhaps they're near fine, but there is a other ways of scaling this. And one of the way of scaling is for end to end tests. Usually, as I said, the native parts doesn't change. So you can catch the native parts and just push, uh, JavaScript, uh, build updates and, and that's much faster. And then the other point of this is that you have to build the native parts on AWS machines or whatever you have. And they are very expensive. You're saying much faster. What time savings are we talking about? Like 30 seconds, minute, five minutes. In this case, the, I think the application, uh, where we set it up, uh, was building around 20 minutes for the native part and it, it comes in several flavors, so it's a multiples of the 20 minutes. If you would like to test, uh, um, I know for German market, for a Spanish market, and, uh, you have it configured different flavors of the app, then those builds multiply and the, I think 20 minutes for a native build, it sounds pretty typical, but it depends on the, on the, how many builds you run in parallel. It can go slower and then it's, it's not only how fast, but for how long are you blocking the hours to, to, to work on it? Because we are trying to do run end to end tests before the merging. So for how long you block the other pipelines before you can, uh, say that your ticket is finished. So, so it's, it's, uh, it's hard to just, uh, well, it's, you can quantify it how long it takes, but it's a little bit more than just a 20 minutes, which you save. And also, uh, it's always a different 20 minutes while you're working on the ticket and 20 minutes while it's deploying when you know your work is done. Because if you're still working on it and waiting for the build to make sure that you can close it, it's hard to switch to a new one because you don't know if you're going to come back to this one. And, uh, it's kind of like at this X KCD comic where you can, the only thing you can do is, uh, sort fighting in the office because your code is compiling and you need to know the answer, right? Like nobody likes to multitask. There is the saving, uh, because as I said, you have to do the native parts on the AWS machine in our case at least, uh, because we don't have our own host that make mini or whatever to do it.

 

But we do have Linux machines in the office. So what we can do is then again, you can build the native parts on the expensive AWS machine and just have one instead of a couple of them and build the JS bundles and all the unit tests and everything else on the Linux machines, which you can just basically scale and scale, uh, in your office, uh, space. Every of those benefits opportunities are really helpful.

 

And, um, probably you would have other ideas how, how to use it, but it's not like, I'm not talking about here, like, Oh, we can do it. It's just cool for the hack weeks or whatever. Now this is like something which we really use and which really brings benefits to our customers and saves time and frustration for developers. A very underrated aspect of automation is how much developer frustration you're, you're saving because that's, uh, even if something is simple to do, what it has four steps, then just thinking about those four steps to do is is tiring when you would rather just run one script. Uh, I'm a huge believer in taking like that. We, we, as developers in React Native, we have enough to worry about from third parties or from business logic that we need to implement to, uh, to have the free space to, to live for worrying about your own tooling. That's as well. Like you said, uh, the gist does those four commands perhaps for you are clear. Um, but, uh, many of our customers clients, they start, um, with React Native and they sometimes brings the web developers into, uh, as a support. So they, or that they teach the web developers how to write React Native to, to, to, uh, I don't know, uh, scale up, let's say, uh, up development. And for those people, this four commands can be complicated or, you know, like they can don't understand, uh, which one should be run first. And especially if you see errors popping out from those commands, what's then. So having a good DevOps where you can save those newcomers to the React Native world, uh, some, some headaches, uh, saves the headaches for them, but saves as well the support where basically your senior developers would need to be a sub on support all the time. Um, yeah. To, to, to the newcomers, uh, learned enough to, to care for themselves. Yeah. Also probably that's something that brings a lot of people to expo. The expo automated for them as a paid service. And it might make sense to, if you're a small team and you have a small project, then it makes more sense to pay expo than handle it yourself. But, uh, I think the bigger your team is and the bigger the money we're talking about, then it, uh, steers towards, uh, building your own infrastructure so that you can keep the cost low. Like for example, if you end up needing to rent like three a Mac Mini, so on the AWS all the time, then maybe it makes sense to just buy them and put them in the office. All right. Like if you have, uh, enough, uh, operations, uh, knowledge in the team to, to keep them updated and secure. Oh, to be honest on AWS, you have to do the same. You have to all the time log in with the string sharing and instead of doing everything on CLI, you have to. Click some buttons and accept some license agreement. So you don't get the same, I think benefits when you, which you get from other machines, which you get from AWS.

 

So the Mac Mini is still a lot of it doesn't really matter if it comes from you or from AWS, you will still have a lot of maintenance work to do. Well, a lot. It's okay. Let's don't put adjectives in front of everything, but this is some work. No, there is definitely this work that you have to plan for. Okay. I have some more targeted questions. You're talking about sharing the native belt, uh, for between that docs runs and, uh, juggling JS bundles for, for testing for validating issues. Where do you host them? Where do you, how, where do you cash those, uh, those bundles both for JS and for the native code? Uh, so we were working with the client who is heavily invested in, in AWS. So they are cached there. So as free, right? Uh, yes. As the beginning, I thought you, we could download them that we could upload them to code push and download them from there, but it's not that easy. Like you cannot, well, you can use the code for many things, but they don't,

 

they, you cannot hack them in the way that you can use them for branching. Like in the way that you have once the code push, um, content for, for every branch you have, you could do it, but I fast AWS is kinda working nice. So basically any kind of storage, remote storage is fine. Like you could put it on Dropbox if you wanted. And if you're okay with it, if you don't need like something like a VPN access. Sure. Um, because we were talking about, of course, about development. So it has to be kind of secured from third party access. That's, uh, we have some access, which is you need a basically VPN to be able to, to run the application and download those content. But if you are like less critical, then yeah, you can host it wherever you want.

 

Second question. It's all like unicorns and rainbows. If you only have JavaScript changes, right? The native bundle stays the same. What do you do when you have native changes? Is there something, do you have to go back to the old way or do you have a way of handling it? Because we, there's a different strategies. If you're talking about merge request and so on. Yeah, it's going to be different for detox. It's going to be different for, for merge requests. Because for example, I believe like mine naive understanding is that the merge requests flow is not going to work. If you have mess much between the native changes between the two branches. Right. Yes, exactly. So, so what, so perhaps if you don't know, uh, what Vojtek is saying is that if you have native changes and you try to, so basically native changes means that you have new empty boxes, right? A new views, something which, uh, hasn't been in your application before. And then you upload a new JavaScript bundle, which comes from the branch where you already have those native boxes, new ones. So you have the old application and the new JavaScript code telling to move those boxes, which doesn't exist. That will obviously crash. That won't work. So in that case, uh, you have to build another new native shell. Basically you, you cannot skip it. You can change a lot of things like some images and so on, but not everything is a part of the JavaScript bundle. Okay. So in this case, like, uh, just loading this particular bundle would crush your app and you would have to verify this. That would be a bad user experience.

 

So what we can do, what we programmed is we detect which bundle would apply for which version and you would basically, you wouldn't be so where you go in the settings and you have a list of bundles, you can download, there wouldn't be the bundle which would crush your app. So we went extra step to, to make the testers and other users life nicer. Do you detect it automatically or manually through something like version numbers, like semantic versioning? So we changed the version numbers based on native changes. Basically it's a short story. Uh, it's, it's based on hashing of the dependencies, native dependencies. If the hash changes, then we know we have to change the version by, by a specific way, and then we can compare the version numbers and see which bundle we are applies. So of course they, as I said, there is some setup, but it's a fairly robust setup and which you can, uh, for now, it's always an edge case. There's always an edge case to be discovered. Okay. Uh, what about the native changes? See if you want to run the detox files, detox tests, then you have to trigger the build of the whole shell as well. When you run the detox test, you will check whether you have a bundle for this particular set of dependencies. And if you don't trigger a bolt and wait for it to finish and if you have your user.

 

Yes. So basically we do this building of the shell every night. So it's not like, um, so that is anyway. Fairly recent version of the shell, which you can be used.

 

It's works. Final question.

 

How do you juggle those bundles? Like for, I mean, for the flow where the user can go to the special secret menu and, uh, and change the bundles. Uh, do you use code push for that? Or is there something that's built-in, you react native that you can do to like load the remote JavaScript? How, how, how did you implement that? We've wrote it in the, in the native parts and yeah, it's, it's goes and checks. Uh, we don't use the code push for this because code push is kinda, they want to have the new JavaScript bundle from their servers. So we have our own native code to download, uh, download the, uh, dependency. I mean the bundle basically story in the right place. And then we trigger the restart of the app and, uh, the app pulls in the new bundle. Of course they are. It's not like on the diff if else. And what I mean is like, uh, there are many, uh, things you have to consider when you restart up, restart the app, which bundle to take, like for example, uh, sometimes you test code push locally as well. Right. So you would know that you shouldn't take the code push version, but take the one from the branch and so on. So there is, uh, some, some logic inside of this, uh, method which decides which bundle to load after the app results.

 

I think that this flow is pretty clear to, well, at least to me, I hope it's also clear to our listeners. I think it should be fine because I was, I'm not involved at the moment in the project that, uh, Patrick, uh, is sharing the, the setup for, so I came here with a fresh mind and, uh, I learned a lot. And I hope you, you old it. So thank you, Patrick, for sharing that with us. Thank you for listening. Yeah. Thank you for listening to the episode. Uh, please subscribe if you haven't yet. And if you like our show, consider sharing it with your friends. I will find notes for this episode, uh, like for any other on our page, podcast.brainstonbeards.com where you can as well leave us feedback or suggest a topic for a future episode. We would be very happy to hear back from you and stay safe and curious. Till the next one. Thank you for listening to the episode. Please subscribe if you haven't yet. And if you like our show, consider sharing it with your friends. You will find notes to this episode on our page, brainstonbeards.com slash podcast, where you can as well leave us feedback or suggest a topic for the future episodes, we would be very happy hearing back from you. Stay safe and curious. Till the next one.