Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
  • Overview
  • Transcript

2.3 Challenges of Rolling Your Own

In this lesson, I’ll share some of the challenges my team faced when building Milojs for the Daily Mail. I’ll explain some of the creative solutions we found to technical problems—returning a function from a constructor, using eval and doT.js templates to generate access methods—and some techniques we used to identify performance problems. I’ll also tell you about some of the interpersonal issues we faced, and steps that we took to mitigate them.

Related Links

2.3 Challenges of Rolling Your Own

Milo models are probably my favorite part of the mother framework as you may have seen in a previous video and they have a lot of features to make them easy to use very easy to access deeply nested data and even better. You can subscribe to deeply nested data changes including using star syntax and interpellation. When you use a modern model to access a property inside the model you actually get returned a model path. This is an object that behaves exactly like a model which means you can subscribe to changes from that point in the model now or even past that model path to a sub controller to delegate responsibility for that subset of data. So just how did we make the modern model? Well the first thing you'll notice is that when you call new milo model, you get returned, not only an instance of milo model but that thing that is returned is actually a function. Returning a function from a constructor that is also an instance of that constructor is actually not very straightforward in JavaScript. To take a look at it, we're going to have to open up milo-core. Milo-core is the part of milo which is essentially isomorphic and can run on both the server and the client and includes models messengers and also the Milo minder which enables us to deeply bind models together. If we have a look at the model code to return a function from a constructor. You can see first we have to create that function and return it like so that part seems pretty straightforward but what's more is that we have to set the model proto to the model prototype. The underscore underscore proto property is usually best avoided but is still the only way to change the prototype property of an object instance. And once we've done that we also need to change the proto property of the Model prototype to Model__ proto. You'll see that the Model path that gets returned when you actually invoke a Model as a function Is using the model path which is just the path method of model. So at the end of the day, it is just a syntax sugar but it allows our models to be used in a more elegant way. The other challenge around Milo models was how we actually implement this deep access. And how we then implement setters and getters of these properties that are passed to strings. The first thing we thought of was to actually interpret the string, pass the string and essentially use those properties to access each level of the model data. This would have however been quite inefficient in terms of performance, passing strings every single time you want to access some data. It's not gonna be as performance as getting same methods that are precompiled and are able to access the data directly like that. To get around this we have to do something rather unique and something that most jobs for developers will tell you never to do. We had to use a val, most of the model code is actually written as .template files. dot is maybe the fastest templating engine out there. It's very simple, 120 lines of code and it allowed us to create all the methods of a model or model path, in such a way that they can be pre-compiled for each access path. Basically means that when you call a user, and then pass this string in the background, Milo is secretly going about and creating, a delete method, a getter method, a setter and a splice method, creating all these methods for that particular access path in the model. It takes a little bit of time although not that much. But once it's done it's as if we have getter and setter methods that are specifically written for this particular path into the model. So if you look at the getter, it's quite simple. It treats m as the model and then for every part of that path, it simply appending that part of the path to the model object. So this as it iterate over this loop is essentially going to create return m. Address.street. For the setter, it's a lot more complicated, because we want to also fire off messages about the change that occurred. And this is what makes Milo models very powerful, because rather than having a polling method looking for changes in the model object is a push methodology. As changes happen they get posted to the messenger and whoever is listening to it gets the message. And of course none of this would be possible if we just followed the old mantra of eval is evil. There's no reason not to use eval for this code here. Eval is no longer as slow as it used to be and is definitely fast enough, given that we can precompiled these model paths and it essentially becomes part of the bootstrap code rather than the runtime code. And the other thing is there's absolutely nothing unsafe about eval in this code. It is our own code essentially and there's no way that it can be used as an exploit. One of the other big problems we faced early on was performance. Milo trades in bootstrapping time for run time performance. So while Milo models operate quite fast the bootstrapping of Milo models and components was taking far too long. In order to investigate these issues, we used the chrome developer tools CPU Profiler. Here's an example that I've actually been debugging from our main application. I've noticed that things in the application have been taking longer. So I started recording a new CPU profile, and then I went to open a new article and when it was finished loading I stopped it and what you get back is this profile here. This is basically a recording of the time spent in all the different functions throughout your application and you can view it in many different ways. So here we can see the idle time spent so this is time where the CPU wasn't doing a thing was 14 seconds is obviously includes the time between when I started recording and actually opened up the article. But if we take a bottom up approach you can see that almost four seconds was spent in a function called on options change. And this is something I have to investigate separately. It's actually not related to Milo but the application that we built using Milo. But the initial performance problems that we did find were related to Milo messengers. The Milo messenger really is the backbone of Milojs. Just about every object in Milo gets one of these messengers attached. And the messenger is implemented as a mixin,so all the methods of the messenger, such as on, off, once, and post message, these are all proxy directly onto the object that is the host of the messenger. This makes it very easy to post messages around the application and we also have a global messenger called Milo Mail. So given that everything gets a messenger, we noticed that on bootstrapping application and loading up an article we were creating around 3,000 different messenger instances. So it was an obvious place to make some performance gains. The main problem that we identified was that the mixing functionality was actually creating new instances of functions for every method that it was mixing into the host object. This is obviously very inefficient and we were able to get a significant performance increased by simply reusing proper named functions. But actually, the first performance optimization that we identified was much simpler. In Milo, we use a library called check. It's been adapted from Meteor.js as check library but basically what it does is runtime type checking for JavaScript. So we use it to type check our arguments in all of our main call library functions and methods. So in this case where checking that the host object matches one of either object or function. And it's wrapped in optional, which means that it could also be undefined. And that would also be acceptable. Here, we're matching the proxy methods passed to a mix-in is either an array of strings or a map of strings. And once again, that is also optional. Now, this check library. It's quite powerful in that it can be used to essentially check deeply nested objects and to make sure that they match a very specific structure. And because of this deep nested checking, the check library can be quite slow. We managed to gain a 30% speed increase just by disabling it in production. The type checking is most important during the development phase anyway. Finally, I wanted to talk briefly about the interpersonal challenges that we faced enrolling our own framework. Building our a framework from scratch is not to everybody's taste and we had a situation with one developer placed on the team who was particularly against the idea. He was a very good Angular developer and became frustrated when he was unable to do everything that he was able to do in Angular with my Milo JS. One way we had of addressing the problem was to allow him to do a Spike project of a small part of the application in Angular. Another thing we did was to develop an Angular facet for Milo components. This would be a facet that would allow you to inject Angular code into a milo component thereby allowing us to code certain parts of the application and angular without being locked into doing the whole thing that way. Another important part of the conflict resolution, I think, was in the team retrospective that we did. For anyone who doesn't know, a retrospective is a structured meeting that's used to try to communicate and resolve conflicts. It involves talking about things you think are being done well or you think are not being done well within the team. And it's specifically designed to get people to air their feelings. The one big thing I learned from the whole process was not to wait until there's something wrong to have a team retrospective, it's a very good tool that can be used on a fortnightly basis to get issues out in the open before they become real problems. In the end, we progressed with Milo functionality fast enough that we did not need AngularJS to fill in any gaps. The guy himself who is for using Angular, even admitted that he would never have used Angular to try to build the interactive canvas. Of articles for the mile on line CMS and I think that was another important thing finding a base level which we could all agree and to build up from there. The final challenge that really comes to mind is the challenge of onboarding new members to the team when you're dealing with a custom framework. Now at the end of the day, any significantly complex application there's always going to be a lot of stuff for new starters to pick up. So I don't see this problem as being particularly relating to rolling your own framework. Any application is gonna have it's own patents that you've developed over time, its own idiosyncrasies. And so I think the following advice can benefit just about any complex project. On the first things I did. Was to start building little tutorials about Milo js. In the examples folder of the Milo repository we have a tutorials folder and that has tutorials on binding which is all about the basics of Milo components the data facet which shows how Milo access its dom data. The messengers tutorial which is all about the various things you can do with messengers. Models which we've spoken about at length so far and a minded tutorial which is kind of putting it all together in regards to data flow within the application. I tried to make these tutorials as simple and as easy to pick up as possible. All you need is a text editor and a browser to be able to open up the tutorial and have a look. The tutorial text, and the actual tutorial code itself, is all included within the one file. And I tried to write it in a sequential fashion so that while you're reading through the HTML file you're also reading through the tutorial. We also built a couple of examples of to-do applications. No framework is complete until it can at least make to-do application. And so we showed various approaches. One where we create custom Milo component classes and another simpler one where we're just using out of the box components and facets without any custom classes. And then we have a controller file that brings it all together. As you can see the Milo to do app is about 50 lines of JS. We also have a lot of documentation in the code which we then convert to proper HTML documentation using Mr. Dock which is an NPM module it goes through and passes all the special comment blocks and turns it into code documentation in a more familiar website format. We've also endeavored to write a few articles about Milo JS. Which I can direct new starters to, such as this one on Tuts+. And we've talked about Milo at some developer meet ups.

Back to the top