In Part 1 of this series I talked about the current state of JavaScript frameworks and how in many ways JavaScript frameworks have become the new baseline for developing client centric Web applications or Single Page Applications. Due to the complexities involved in building complex client side applications using JavaScript and HTML, frameworks have just about become a necessity to effectively building any non-trivial application. Frameworks are a huge help with organizing code, providing easy ways to bind data from models rather than binding data to the DOM manually with imperative code, providing tons of helper functionality for operations most applications need like view and nested view routing, form validation, http request management and tons of other features that every application needs but isn’t immediately obvious as a bullet point feature.
Version 1 of the major frameworks came about later than I would have expected them to, as there was a lot of pent up need to address the missing pieces. When these frameworks finally did arrive on the scene and got to a reasonably stable point, public interest took off like wild fire and adoption rates have gone through the roof since then. It’s not surprising – for me personally using both Angular and React I’ve seen myself being able to build complex front ends in a fraction of the time that it took previously.
Trouble in V1 Paradise
As much as the first round of frameworks have improved life for JavaScript developers everywhere, there are lots of warts in the V1 frameworks. Most of these frameworks started out as very experimental projects that morphed into frameworks only after public interest inspired exploration of more general purpose functionality. Angular in particular evolved from a template HTML generator, into a what is now a full featured Angular 1 framework. Angular had its start in 2009 which seems like a lifetime ago now in terms of new Web capabilities and developer practices. With the exception of some of the newer frameworks like React, the story is similar with other frameworks that had an early start.
The result is that a number of frameworks have quite a bit of old code that wasn’t designed for the purposes we use them for today. Even beyond that when these frameworks were originally built, the complexity of applications we are actually building today wasn’t imagined in those early days. The end result is that frameworks have been refactored from the outside in, resulting in a number of inconsistencies, overly complex APIs and sometimes funky behavior that requires detailed inside knowledge of how the framework works to work around issues.
Since those early days, framework developers have done an admirable job of patching up the existing frameworks, keep them relatively easy to use as well as providing the features that we as developers need to get our job done. As I mentioned in Part 1, it’s amazing what these frameworks have provided us in terms of a leap over your typical jQuery based application we built before it, both in terms of functionality as well as productivity. For me personally the productivity gains in building front end applications has skyrocketed after starting to use a framework (Angular in particular) because it freed me from having to build my own system level components and try to figure out project structure. It's been a huge win for me.
But… looking over how frameworks are working there are many things that are suboptimal. Again speaking from my Angular particular view there are tons of rough edges. In Angular the built-in routing system is terrible even though UI-Router provides some relief for that it's still a very messy way to handle routing. Directives have to be near the top of the list for awkward APIs with its multi-overloaded structure and naming standards (if you can call it that). There are lots of quirks where binding can go terribly wrong if you bind circular references or when you need to do manual binding at the wrong time in the Digest cycle and you end up in an endless digest loop that eventually pops the execution stack. While these are all known and relatively minor issues that have workarounds, it shows the architectural choices from a bygone time catching up with frameworks that are trying to do so much more than they were originally designed for.
Starting Over with V2 Frameworks
The inclination to reimagine is very high in software development, and in the JavaScript world where major version cycles are measured in weeks instead of years this is especially true. But in the case of the existing JavaScript frameworks and their explosive growth and rate of change, it’s actually become quite clear that starting over and building for the current use cases that we’ve discovered and mostly standardized on for JavaScript framework based development is a good idea. Clear the slate so to say, and start over with focus on the performance and features that we are tasking those frameworks with today.
Not only are we dealing with new framework re-designs, at the same time we're also dealing with a bunch of new-ish technologies that are disrupting the way things have been done in the past. Among them are:
- Components not Applications
- JavaScript Changes
- Platform Changes
- Mobile Optimizations
I've been watching and playing around with Angular 2, Aurelia and Ember 2 for version 2 frameworks. Out of these three only Ember is shipping a production V2 version today while the others are still in alpha and beta state respectively and not looking like they are going to release anytime soon. This post mostly relates to these three frameworks since that's what I've looked at, but much of the content can be applied more broadly to other frameworks as well.
Components over Applications
One theme that all the frameworks are starting to embrace is that applications should be built out of smaller and simpler building blocks. So rather than building large modules or pages/controllers, the idea is to create self-contained smaller components that can be patched together like legos into a larger document. Components have their root in the fledging Web Components standard that seems to have stalled out due too many problems and inconsistencies, but the overall concept of self contained components is still something that each of these frameworks is paying lip service to. All the framework vendors claim some Web Components functionality but in reality it looks like the concept more than the actual implementation is what counts.
In Angular 2 for example, rather than having controllers and directives there will only be components. A component can take over the functionality of what directives and controllers used to do under a single component type. At a high level both do many of the same things - they both have models and binding code to deal with. Likewise services and factories are simply classes in Angular 2. The goal of much of the new functionality is to simplify and strip functionality down to its most essential features to limit the API surface of frameworks which results in simplification on many levels. Certainly angular 1 had a lot of duplicated concepts and in Angular 2 the object model will be much smaller and more concise which makes it easier to work with.
Besides the object model simplification, a component approach can have a profound impact on how you build applications. Rather than building large and complex controllers, you can break out pages into their individual components where sections of a form, or say a list live in their own components. By isolating each of these components and assigning their own logic and data to them they become easier to develop (less dependencies and smaller code base) as well as more easily testable. Angular, Aurelia and Ember all embrace this component style with nested customer HTML tags that describe the individual components in a page.
But having played with components over controller approach I have to admit I have to really force myself to think this way - it's hard to draw the line and not over fragment applications into a million little shards. There's a fine line between not enough and too much componentization. But the beauty of the model is that it's your choice. You can make your components as atomic or as monolithic as you choose.
Improved Databinding Syntax for Angular and Aurelia
Angular 2.0 is making a big break from Angular 1 with drastic changes to its markup syntax for data binding. In Angular you can bind any attribute, event or property through the markup interface using a funky CSS inspired syntax (# for objects/properties, [] for attributes and () for events) for example. While the syntax definitely is 'different', it does provide for a well thought out approach for data and event binding that is much more flexible than the old model. This model reduces the need for custom directives just to handle special bindings for each type of control or control attribute. This feature alone should cut down drastically the amount of directives needed in Angular.
Aurelia also has a beautiful, explicit data binding syntax model that uses descriptive prefixes like bind. or delegate. to actively describe the operations to perform for binding. It's very clear and descriptive although a little verbose. Like Angular Aurelia can bind to anything on an element which makes for more flexibility than V1 frameworks had. I'm a big fan of Aurelias binding and validation model. It's very clean and easy to work with in code.
The changes in these frameworks are very welcome as they provide much more flexibility than what was available in V1 frameworks. It also looks like performance of data binding will be much improved as a result of these changes.
JavaScript: On the Cusp of Big Change
The other major change that's happening in the V2 frameworks is that they are all focused on EcmaScript 6 (ES 2015) and beyond. With that change comes a whole slew of other technologies that are required in order to make it all work because even modern browsers cannot run ES6 natively today.
This means new technologies you have to learn and work with:
- EcmaScript 6 (ES 2015)
- Transpilers
- ES6 Module System and Loaders
- Build system technologies
ES6/ES 2015
Some of the highlight features of ES6 are:
The built-in module system is probably the most notable feature as it greatly simplifies the alphabet soup of module systems that are in use today. Having a standard module system defined at the language level should - in the future - provide for a more consistent experience across libraries instead of the messy multi-module support most libraries have to support today. ES6's module system doesn't support all the use cases of other module systems (there's no dynamic module loading support for example), so we may still need a supporting module system for that, but at least at the application level the interface will be more consistent.
Classes are the other big feature for ES6 although not everybody is a fan of classes in JavaScript, as it sidesteps some of the traditional functional nature that many love in JavaScript. Realistically classes just add to existing features so the purists can go on using their prototypes and functional classes, but I think classes add a necessary construct that is needed in a general purpose language like JavaScript. Dissenting purist voices aside, classes will end up being the most popular choice for creating a data structure and looking at how the V2 frameworks handle operations that's certainly validating this point. Function() based classes and Maps likely will be relegated to special use cases once ES6 takes hold broadly.
I might be biased coming from Object Oriented environments but to me classes make a lot more sense than the haphazard choices we've had in JavaScript thus far with their individually different behaviors. As a bonus classes finally maintain the scope of the .this pointer in method code, which is one of the most vexing problems that new JavaScript developers coming from OO often run into.
Another great feature are template strings which can be used to merge string and data content inline. This is great for code based output generation, but also useful for things like components which often need to be fully self contained and not ship with external HTML content. In this new word of components inline HTML may not be uncommon and template strings greatly facilitate embedding data into string content for rendering.
Promises are now included in ES6 as part of the base language which again provides a consistent baseline on which libraries can build. The built-in implementation doesn't support all the features a library like Q provides, but it provides the core implementation. Libraries can build ontop and provide the additional functionality. It's been frustrating to see different promise implementation that handle callbacks and error handling using differing syntax and by having a standard in the language at least we're bound to see standardization of the core syntax. All the new major frameworks are using the base ES6 promises and building extensions on top of it.
Arrow functions are like C# lambdas and make for less verbose function syntax. Not exactly a big feature but I have noticed that it does make for more readable code as it cuts down on verbosity for anonymous functions. Unlike standard anonymous functions, arrow functions also guarantee that the parent scope's .this pointer is captured, rather than the active context's which is important when executing inside of the context of a class.
Finally there are a bunch of new language features in ES6 - the let keyword allows truly local scope variables in addition to the sometimes tricky block scoping. There are tons of new features via method extensions on the base JavaScript objects. Arrays in particular gain a ton of new functionality. There's also support of yield syntax using Generators so you can build IEnumerable style functions, and an implementation of iterators using the new .keys() and .values() and .next() functions that allow iterating over the internal members and values of an array. .find() and .findIndex() make it easier to find elements. Many of these features have been available as part of third party libraries like underscore or lodash, but it's nice to have these common features available natively without a lib. It's a good idea to poke around in the list of new features in ES6 to see what other things you can use and might allow you to ditch an external library for. ES7 promises even more common language enhancements like async and await, object.observe() and more array and object extension functions which are useful for everyday scenarios.
None of the features are necessarily new - most you've been able to accomplish with third party libraries or other code workarounds. But having this stuff as part of the native JavaScript platform can reduce the amount of external libraries required and generally make functionality more consistent.
ES6 and (no) Browser Support
We can all agree that ES6 is nice and has many, many worthwhile enhancements. But the reality is that ES6 is not natively supported by any browser shipping today. Some browsers support some of the features, but no browser supports ES6 fully today. if you take a look at this chart, you'll see a lot of red and the highest feature coverage of any browser is FireFox with 71%. It's actually quite surprising that full support isn't here yet especially given that most browsers (with the exception of Internet Explorer) are now evergreen browsers that are automatically updated with new features and standards and the ES6 standard has been more or less finished for nearly a year (although fully ratified only a few months ago). Yet full native support for ES6 looks like it will be off by quite some time yet. My guess is we won't see a full native implementation until early next year and probably a few more months before even all evergreen browsers will be there.
But even when evergreen browsers become capable to run native ES6, there's still the issue of older browsers that won't go away. Internet Explorer versions before 11 and Edge will never get upgraded, and there are literally a billion old smartphones that have old browsers that also won't upgrade. The shift to ES6 with native browser support will take a long time before you can ensure that native ES code runs the way we expect ES5 code to run today.
ES6 requires Transplilation
Because this is all so much fun we had to invent a new word for running ES6 on browsers today: Transpilation. Transpilation is the process of converting JavaScript from one version to another - specifically from ES6/7 to ES5. For now, if you want to use ES6 the unfortunate truth is that you have to a transform ES6 code into ES5 somehow so that it can run on just about any browser. Transpilers take ES6 (and ES7) code and convert the code to ES5 code that can run in any browser. Tools like Babel and Traceur come both in build time and runtime libraries that convert ES6 code into ES5. The most common use case is to use a transpiler as part of the build process and statically 'compile' ES6 code into ES5 code, which is then loaded in the browser using a command line build tool or build tool like Gulp or Grunt.
There are a number of transpilers available today:
- Traceur
- Babel
- Typescript (still requires traceur or shim transpiler at the moment)
Traceur by Google is optimized for machine read code which essentially means it creates very terse and unreadable code. Babel is probably the most popular of the transpilers at the moment and creates reasonably readable ES5 code from ES6 or 7. The advantage of Babel is that it provides good debugging functionality and source mapping as well as smart exception handling that actually provides usable errors. Typescript is Microsoft's JavaScript enhancement language that provides new language features ontop of JavaScript and a type system for JavaScript. Typescript's features are mostly compatible with ES6 and ES7 syntax and provides additional features like variable and member typing, interfaces etc. Typescript comes natively with a compiler to turn typescript into JavaScript and there are options on the compiler to create either ES5 or ES6 code. Typescript is a super set of JavaScript, so plain JavaScript just works in Typescript. TypeScripts is shooting for full ES6 support, but it's not quite there yet. While Typescript can compile down to ES5 making it a transpiler of sorts, it still requires some additional module loader code to handle ES6 module loading completely.
Say Goodbye to Simplicity
Say what you will about the complexity of the V1 set of frameworks, one thing that is nice about them is you don't need much to get started. You can simply add a script reference to a page and you're off and running. The V2 frameworks are not going to be so easy to get started with.
With the ES6 recommendation of these new frameworks (Angular 2, Aurelia, Ember 2) that simplicity is gone as you *have to* use a build process in order to transpile your code before you can execute it. Theorhetically you don't have to use ES6 as the frameworks pay lip service to supporting ES5, but it's clear that this is not the optimal use case. The new frameworks are designed for ES6 and when you look at the examples you are also not likely to want to use ES5 as it's much more verbose to write code with the same functionality.
But the complexity goes beyond just the transpilation. To get the transpilers set up you need a bunch of additional tooling. Aurelia requires jspm which is quite nice and meshes really well ES6's module system by using the same naming convention as modules. Angular uses Typescripts pacakage manager and Ember uses its CLI based loader. Everywhere you look tooling is required to get things loaded compiled modified and rearranged. Welcome to the simple JavaScript lifestyle.
Going to the home page and reading the getting started guide for any of the frameworks reads like a tutorial for a whole product of earlier libraries with discussion of installing package managers, transpilers, and running build agents that compile your code as soon as it's changed. There are usually a page or two of command line tools you get to run before you ever get to write a single line of JavaScript or HTML code. The move to ES6 might provide cleaner code, but the downside is that there is a lot more complexity involved in actually starting and building an application.
To be fair once you understand the basic steps involved this process of setting up a simple build environment won't take very long, but if you are a new developer starting from scratch and staring at the first time tutorials I'm not sure that I would stick with it. My first impression would likely be: "Are you fucking serious? You call this easier?"
I think in the big picture it will be easier to use these new tools and as much as it seems painful at first, a build process for JavaScript is becoming a necessity even if you are using an older framework. The main thing is that the getting started for a complete newbie learning curve has gotten significantly more complex. And that is not a good thing.
Even for seasoned developers comfortable with V1 frameworks there will be a learning curve, at the very least picking up ES6 and new module loading syntax and the new fraemwork syntax, but also to work with a whole new set of tools to build your application.
Dedicated CLIs
To help alleviate some of this setup, build and configuration pain the new frameworks come with dedicated CLIs that help manage project creation, build process and running watchers. These tools are geared towards easing the repetitive and tedious tasks that you have to go through especially at project creation. This goes a long way to making the process appear simpler as long as it all works.
But when it doesn't, things can get ugly because at this point you have hundreds of files and packaged dependencies created and very little in the way of hints where things went wrong. I ran into this with both Aurelia and Ember starter projects and in both cases the problem ended up being out of date package references which took a bit of time and searching to correct.
Regardless, tooling is going to be a vital part of this lifestyle if there is any hope of getting the unwashed masses to use this stuff or even try it out for that matter. I think it's going to be an uphill battle to get people weaned off the simplicity of the V1 frameworks and get over the AYFS moment. :-)
There's lots of room to improve here. The V2 frameworks are either in Alpha, Beta or just Released so they'll improve and get more reliable with time. Let's also hope that the build tooling and dependency trees can be whittled down over time to reduce the complexity of what is required just to get an application off the ground.
Mobile Development
Another important aspect of V2 frameworks is additional focus on mobile development. This comes mostly in the form of optimizations to make sure that frameworks are optimized for mobile devices to use less power and perform adequately. Existing V1 frameworks are notoriously CPU hungry and a lot of focus is going into V2 versions to optimize performance.
All of the new frameworks are using new approaches to deal with view rendering and data binding performance by overhauling their view and binding engines. The React framework seems to have had a massive effect on other frameworks in spurring better performance for rendering large amounts of data, so much so that some frameworks like Ember are emulating some of the React engines features for their own rendering. React's main selling point is that its blistering fast in rendering data, so much so that it doesn't use traditional bindings but rather just redraws the UI as needed from memory rendered buffers. React's approach is more traditional in that it works like a view engine rather than a data binder with the view engine simply re-rendering the entire view rather than trying to update the DOM's existing state.
Angular and Aurelia on the other use data binding to update existing DOM nodes when data changes. In V2 new approaches are used to detect changes. MutationObservers and support for Object.observe() make it easier to detect changes on DOM elements and classes in a highly performant way and this translates into better overall data binding performance.
Additional mobile development features are geared to the animation features built into the frameworks, that are optimized for mobile deveices as much as possible. I haven't really checked this out, but there's quite a bit of hoopla around this aspect.
There are also efforts in making various frameworks to support multiple view engines that can be swapped out and actually render native controls in native applications. Again React started this with React Native which maps their JSX language markup to native controls. Telerik is also heavily pushing their NativeScript framework which is a more traditional component library approach to building applications, but using JavaScript.
Native development is definitely an important aspect. For me personally, the last 4 applications I've built have had major mobile components to them and while I prefer building Web based applications that work well on the Web, 2 of those apps needed to be wrapped up in Cordova to provide the functionality we needed. Native frameworks address this need. To me though it's not so much the development of the applications that is at issue though - it's the infrastructure and deployment/testing process that's the big issue. Building applications that can go easily between a Cordova solution and a native Web application require a bit of tweaking, and the entire process of rigging up and testing Cordova applications still feels very cumbersome. Again I think tooling in this space is going to potentially make this better and as far as it goes Telerik seems to have the right idea with their integrated Web and Visual Studio environments.
Mobile is definitely an interesting space - so much is changing and no matter what you use it seems by the time you get rolling with it something new and better shows up…
Two Steps forward One Step Back
It's easy to get wrapped up in the hype around the V2 frameworks as they are clearly bringing many improvements and cleaner object models to framework Web development. But we're getting hit with a large amount of change, not just from the frameworks themselves but also the underlying JavaScript and build system changes that are essentially forcing a complete reoboot on how you build front end applications with these new frameworks. We're gaining easier to use code and new framework features at the cost of additional infrastructure overhead.
It's ironic to see JavaScript move in this direction because simplicity was always one of the biggest selling points of JavaScript. In the past it's been "Just add a script tag to an HTML page and you're in business" - well that's no longer the case if you want to take advantage of all of these new features. And just as ironic is that JavaScript now needs what essentially amounts to a compilation system that can compile a bunch of modules into a concatenated and compacted executable file. This is starting to look a lot like a .NET or Java project - without all the nice IDE build tool features that don't exist for JavaScript today.
Regardless, it'll be interesting to see where JavaScript frameworks will end up once they are actually releasing. Currently the biggest pain is that it's hard to see when some of these frameworks will actually ship. I've been really enjoying playing around with Aurelia and some parts of it feel really solid while others are clearly still under construction and wobbly. Neither Aurelia nor Angular are ready for any sort production work yet, so it's hurry up and wait. I'm not about to jump in for real before the frameworks are ready for anything but just playing and getting familiar with some of the new concepts. Make no mistake there's a lot to learn and it will take time, so getting a headstart on ES6 especially is probably a good idea.
I'm taking my two steps forward cautiously. For production use I'm continuing on with Angular 1.x for the time being - after all the V1 frameworks work today. I'm checking out the new frameworks and building a few simple sample apps with them but that's as far as I'm willing to commit for right now. I get questions from customers regularly asking whether they should wait for V2 of this framework or that, and my advice always is - go with what is available today. Don't wait for the new and shiny. Even if it shipped tomorrow you'd need some time for learning, and it's probably a good idea to wait for the first few point releases and see what issues crop up.
The future starts tomorrow. In the meantime get stuff done today…
Other Posts you might also like