Sessions is temporarily moving to YouTube, check out all our new videos here.

FrintJS - Building Modular Web Applications with React and RxJS

Fahad Ibnay Heylaal speaking at Front-End London in July, 2017
130Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

FrintJS is a modular JavaScript framework, consisting of multiple packages. It embraces reactive programming with RxJS, and has official support for ReactJS for rendering.


Transcript


Okay, before I start, I really would like to apologise because I'm gonna go really, really fast. It's quite a lot of stuff to cover in like 20 minutes and I really want to be respectful to the organisers because 20 minutes is the hard deadline. And okay, so now that is out of the way. So today we are gonna talk about FrintJS. It's, I'm sorry, but yet another framework and it's something we built at Travix, that's where I work at in Amsterdam. And it's a very open unit for opinionated framework and we built it because we really needed something like that and it does what it does so that solves our problems. And after one year of development I think we have come to a stage where I feel it's generic enough so that anyone else can even benefit from it. So that's why I'm here. And before I go there to the framework, I'll just describe a bit about the company. It's called Travix and it's a global online travel company. So in plain English words we just sell flight tickets. And we have multiple brands worldwide. We are already operating in 40 plus countries and we are pretty large company comparatively, like 500 plus employees. And also our engineering is mostly based in Amsterdam, but we also have like engineering teams in London and some other cities. And also we are strong believer in Open Source like majority of our stack is built on Open Source projects and we also try to give back to the community as much as possible. So, okay, what is Frint? It is an OpenStack Source project, but if I have to sum it up in one single sentence, I think I would have to say that it's a modular JavaScript framework. It allows us to build your applications in a very, very modular way. And when I say modular, it does not even have to mean that okay, this is just for the browser, this is just for the server or even for command line apps. It can be used for anything where you can run JavaScript. And the way it is developed is it's a monorepo. It uses Lerna for managing it. And we have like a bunch of packages. I think currently it's sitting at eight. And everything is doing its own thing and whenever we see like okay, something is growing too big, we just try to isolate it and extract it out into a separate package so that it's like reusable enough for everyone. And we have very specific needs which I'll explain soon. But it's built primarily for the needs of Travix as a company. But again, it's like generic enough to be used anywhere right now and we really try to leverage the benefits of RxJS and React and combine it together so that we can get the most out of them. And also like we already instated production. It's stable since version one release and we are very strict on adding features to it, so we don't want to bloat it with too many features that we never use. So if we need something it's there, otherwise it's never there. So we are very strict about that. And yeah. So these are the current packages that we have in the monorepo. So the first one is the frint package itself. So this is the core ones. This allows us to build apps. I will explain what apps are. So this is like the building block of everything with frint. And then we have frint-react that binds or react with frint apps. And then there's frint-react-server for server side rendering of frint apps. Then there's frint-store. This is like Redux with actions and reducers but built with RxJS so you can instead of like listening to it as the method, we can just stream the state. And there's frint-model for object-like data structures. This is also reactive. And then we have frint-test-utils which is like almost sort of functions that we use for unit testing and setting up our environment. And since we are like we are already using it in production. So we have like this strict requirements so if we remove some feature in this quarter, in the next quarter it will be like deprecated and the quarter after that it will be like fully gone. So all the developers, we have like 40 plus developers simultaneously coding every single day. So they have like at least three months' window to migrate to the next version. So that control really puts us in a good position that we can predict it. Like what to work on and stuff. So that's frint-compat, and then there's frint-cli. This is the command line tool for frint and it itself is built with frint just like I said, like you can use frint anywhere. And so you can actually compose your local environment with multiple command line apps. So they can come from separate packages. So we have this Frint RC file so you can like extend it over time. And that one got cut off. We have two new packages coming up. I think by next week, because we are almost done with the routing service. So two packages. One is the frint-router, which is gonna stream the history API with RxJS and then there is frint-router-react which is like all the React components similar to the APIs in React Router. And the last one here you can't see it here though. It's called frint-data. This is still under consideration. We haven't started building it yet. So it's like a reactive model, collections-based data structures, so we can have like embedding data structures. So person model, address model can be embedded and you can construct it on the fly and stream it. So these are the packages for now, but the main question I think would be why another framework? Like why couldn't we just use something that's already out there? Like it would have saved us so much time. It's a bit vague, modularization, but this is the main word that actually we wanted to solve. So we are a travel company and our flow in the website is like this. You go to the home page, you search Amsterdam to Vienna. You see lots of search results of the flights. You select something then you have a passenger's form and you submit your details and then you go to an extras page where you see extra products like hotel, insurance, car rentals and these sort of things. And since it's such a big monolith, like it's really huge, like I think more than 1000 components right now, things were getting really out of sync. And also like because master branches like continuously we are circuiting large and deploys are getting longer and longer every single day. And we just wanted to extract this monolith into like smaller chunks. And we really tried hard to come up with some solutions but nothing else worked until we came up with this approach with frint. And also another thing is a business requirement. So it's not really fully out yet, but when I was mentioning about car rentals, insurance and hotels. So imagine Nick is selling cars, for example, and car rentals. And Steve is selling insurance. And there's another person, Dominic, who wants to sell insurance too. But when we launch our website in let's say cheaptickets.nl, in the Dutch market, we only want to load one insurance provider, not two. So since there are two separate apps, we can decide, okay, we only want to load one of them, and we also choose Nick's car app at the same time. And they can be swapped. So I don't like this car app, I can just disable it. I just don't need to load a script. Like that's as simple as it is. So we really split everything out into like individual apps, so all single apps are like their own bundles. And I can just load them on the browser and everything just composes during the run time. But then again, the concept comes like, why again we need Frint? Because internal things can be trained over time whenever we make a shift in some other way. But external things, when they're like other companies who are not part of Travix, when they build something for our platform, we have to actually give them something to conform to, because if we, let's say, use this most popular framework out there, but they have a breaking change in the next month, and we are really screwed because we need to support backups competitively at all times. And so this gave us a sense of control within the company. So that's why we built it, and also it's Open Source and well maintained. Documentation is out, so everything that we write is read by our internal teams and also externals in the same way. So there is no, what's that word? Priority of any sort. So everyone is treated the same. And the other part is code splitting, which I will show you in the next diagram, yeah. So when I was explaining everything is an app in Frint. So there are two kinds of apps. One is a root app that renders directly to the dom, of course if it's connected to React. And then everything else are like child apps. And so in this example you can see like there are four different JS files. One is vendors.js so this file would have all these third party libraries like RxJS, Lo-Dash, all the Frint packages, and et cetera. And then there's a root.js. This is a root app. So let's say this is the passenger's page or the extras page, that's more easier to understand. And so root app is defining the whole page. So this is a container for the whole page itself. And then in the root app, in the React level, we can say that, okay, in this area on the right side I want to call it a region and I want to give it a name, called sidebar. And so when other apps like app-1.js and app-2.js loads they'll say okay, app one wants to go to the sidebar app two may want to go to the footer, for example. So what will happen in the browser is these regions are like always listening for incoming apps. So if there is something that says, okay, I am for sidebar, it just takes it and puts it in the sidebar. So that's how the code splitting is working from the apps point of view. Another question is why not just use React directly with some existing solutions or AngularJS? I mentioned that we have a big monolith for the front end, and it's already using React, and we love React, really it's so nice to have JS and everything, but with React, like you have to like set a lot of things yourself. Like you have to like build your own thing. And AngularJS is like it's full-featured own framework and it was never an option for us to just dump the whole monolith and start from scratch because we have to like maintain our websites every single day. So if it's down we lose money. So we had to build something progressively and so we kept everything in the monolith as it is and we started like building this region concept and code splitting on top of it and we still haven't fully 100% wrapped everything with Frint but all the check out pages like passenger, search results and all these pages are already in Frint. So yeah. And the concepts. So I mentioned that everything is an app. So the two kinds of apps are like root apps, and everything is a child app. Those get registered to the root app. And the dependencies, the stuff that you put inside apps are called providers. And this name is taken directly from the concept of AngularJS. So to start you can just npm instal frint and this is how we create an app. So you import the createApp function from the frint package, then you create an app class. And the required key is the name, so you give it a name. It should be unique at all times. And then you have a list of providers. So if this one is more like how Angular did with their injector API, but this is all in ES5, so you don't need any extra features on that script or anything else to just use the dependency injection. And right now here it's like a very plain example, so you just have a foo provider and the value is foo value here. So it's a bit more smart, it can also do like dependency resolving and other stuff like classes, factories and all those things. So these are all supported right now. So what happens when you instantiate your app, you can get the value of foo but just calling app.get foo. So that's the base of app. Like it has a name and a list of providers. That's as minimal as it gets. And but what happens to like React? Like how does React come into view? Like if everything is an app, like how do you even render it and stuff? We create components, we get React into the system, and it's just a plain React component. Nothing fancy about it. It's a stateless component even. And it just returns to Hello World! But how does it get into the app? As a provider. So this is a reserve name for the provider, so we say okay, we have an app, and the name of the provider is called component and the value is the React component that you just created somewhere. And again, you instantiate it, but still it doesn't render, because it doesn't do anything yet. So then comes the rendering part. And for this you need react-dom of course. This is a peer dependency and the main package is frint-react. So this is what connects Frint apps to React. So just react-dom, you have a render function and instead of like passing the component to the render function, we pass the app instance that we have for the root app, and then we pass a dom node it dismounts there. That's as simple as it gets. So we saw creating apps, have your React component and then mounted to dom node. But now comes like more power from our RxJS and this is where we have the concepts of observed components. So we need RxJS of course and so it's like a bit of a shift in the mindset because we had a bit of trouble to actually communicate in thinking like streams. So we had some diagrams, I just copied them all here. So imagine you have a component and it receives, it expects an interval prop. So first the value is one, then it's two, then it's three. It just keeps on. So think of this changes as a stream. So it just flows. So that's what streaming props is. So if you think like this, this is RxJS, it has, it's streaming something. The props are coming to the React component, it receives it and renders it and then if there's some events they can go back to the RxJS handlers. So when it comes to dispatching events you can think a bit more complicated way. So if you have subjects the input value is coming from subject, let's say and there's a handle change even coming from the React component that emits a next in the subject itself, in RxJS. And updates the value. And when the value is updated, a new set of stream is coming back to the component. But how does it actually tie in together with Frint? Like why am I even talking about this? So imagine this base component that we had before. It was just saying Hello World!, but we also want to show the app's name and app's name is not known by the component. It should come from the app itself. It doesn't matter what app it is because component doesn't need to know about this. So we need to connect the app to our component. We do that with the observed high order component that comes from the frint-react package. So here it's a very simple example. We have React, Observable from RxJS and Observe HOC from frint-react and we have this base MyComponent that accepts app naming the props. And here what we are doing is, we are calling observe and it accepts a function which receives the app, which is in the context. And from this app we can prepare a list of an object that is props and just stream the value of props to the component. In this case it just emits only once, but imagine you have an interval like just like in the diagram that we saw before. So it could be like this. So you have an interval that's happening every 100 milliseconds and on every 100 millisecond we are streaming new props object that is interval to the value this So this will be the prop subject in your component and it will keep updating every 100 milliseconds. And once you have app it also means that you have access to all the providers just to have .get whatever then you can like compose all those chains together and just return one single stream of props. But Rx is pretty difficult for newcomers, so we also have a helper function called streamProps which is also shaped with frint-react so you can compose all your props one by one in a chained manner. So you just call streamProps, then you do .set, the prop key name and the value on the right. Then you just keep on setting stuff. And then you just do a final .get and that just returns the combined stream merging all the props. Then comes regions. I don't know if I'm over the time already. So regions are areas that are defining our components, where other child apps can mount themselves on. So how does it work? So this is the root app, the green part. And the green part decide, okay, I want to have a region that is the grey part. So in code it looks like this. You have your component, you're importing the region component from the frint-react package and you have it embedded in your original component somewhere and you just give it a name. So from your root apps perspective, your responsibility is over. You just define the name of the region and that's it. But how does it, how do child apps even come there? Like how do they get loaded and rendered? So we discussed before, like root apps get mounted on the dom directly and child apps are registered to root apps. This is how it's done. So after this is like what we have already seen. So this is the part where we register the child apps. And they can even come from a separate bundle altogether as long as you have access to the variable, the global variable of the root app. So we came up with a pattern that there is only one single, global variable in our entire application, and that is window.app. So here we registered the app, my child app, and pass a list of options where you have the regions key and area of region names. So whenever this gets loaded, the child app is shown in the region sidebar. As simple as that. And this is cascaded providers concepts, so if you have something on the root app, if you want that to be accessed by the child apps too, you just do cascade equal to true. So that. And this is a bit complex, so I'll skip that, sorry. And you can actually try it out with our frint-cli. So just create an empty directory and do an npm instal -g frint-cli and this kitchensink is the example that we have that has like almost all the features that we came up with 'til now. So you can just try it out locally with just npm instal and npm start. And it will just fire up a server. So state management is done with the frint-store, so this is like Redux but we did it with RxJS where it can be like subscribed to by an observable. So state is actually a stream here. And it's the same concept with the actions and reducers. You can still combine reducers into a single root reducer and stuff. And you can find more on the docs. So you just put frint-store and the way you set it is store is actually a provider here, so you give it a name store. It can be anything, of course is up to you. And you will use factory pattern here, because we want to like generate it when the app is instantiated. So we create the store class first with the root reducer and then we just return the in stance of it. And then we can do store .get state and get the state stream and just pass it by observe hire component. And you can also like share state between apps. So ChildA can listen to ChildB as long as you know the ChildB's name. So this is like ChildA.getAppOnceAvailable because it's not possible that all the scripts are loaded the same time. Sometime some apps may not even exist. And they may be loaded after like five seconds, for example. So once you get it then you can like get chained with multiple operators with RxJS and then get the store off the other app and then map it to a props compatible object that you can like pass it to your component. And regions can also pass data. Also regions can be repeated, so it's not like there's only one instance of the region. So imagine search results as a list. So each search result item has it's own region and we want some app to be mounted there. So that's like scoped by the individual item, so that's also possible with frint. So those are multi-instance apps because they are mounted in like a region repeatedly. There is an example already with documentation. And also server side rendering is very easy with this. We have frint-react-server, so we just get the app, we instantiate it. So the good thing is we can override providers when we instantiate stuff. So it means that if you are doing something really browser-specific you can mount API and just replace it providing the server side by keeping the API the same, the interface same. So that's completely possible with this. And then you get the HTML output and just ship it to the browser on the first load. And yep. What's next? Router is coming, hopefully next week, because we are almost done, we are just adding the unit test right now. And also frint-intl is something that we are considering for localization needs and these have not started yet, but router is definitely coming next week. And you can contribute in many ways, like I think the first thing that would be nice is like getting some feedback, because we did the things that we did so far based on our own needs, but we would like to hear things from like some more different point of view. So that may help the project even grow a bit bigger and better, even for us and everyone. And yeah, these are some useful links on the docs repository, issues and yeah, that's it.