In a previous post I described our past experience with Javascript. I explained that we have been using the YUI Library since 2006 with mostly positive results. But it is 2015 and much has changed. We are looking for a replacement.
There are many libraries out there to choose from, with many of them focused on different, but overlapping, aspects of development:
In this post, I examine the key factors we have identified for deciding a framework in 2015, how these are very different than they were in 2006, and look at how well the various frameworks available now address these needs. Finally, I decide on a path to move forward on which I believe will provide the ideal foundation for web and other Javascript based projects for the next several years.
Many developers don't feel the need to put much time into picking a framework. Many already know jQuery; they like jQuery; and everyone else seems to be using it. And indeed, popularity is a positive factor that should go into the decision. But the base framework choice has many repercussions, so a full analysis should be done with a clear and quantified view of the benefits popularity offers. And those benefits must be weighed alongside many other important factors.
jQuery is currently in use in about 60%+ of the leading sites—a lead so strong that few developers could even name the #2 library. jQuery is essentially the de facto library to load when embarking upon a new web project (much respect to the jQuery author John Resig for this accomplishment!). Most javascript developers know how to use the library, and the number of "plugins" available is extensive.
These are some of the benefits of jQuery over other libraries that we can list without even considering the actual features of the framework itself. It would seem hard for any other library to compete.
But I feel there are some choices made by the jQuery authors, often due to the time period in which they were made, which makes jQuery a poor choice in 2015. As the framework name indicates, jQuery focused first on finding DOM elements on a web page. It has many functions to then inspect and change those elements. This focus is no longer relevant. In fact, in 2015, one has the same power of selection built in to every modern browser.
There are many other problems I could identify with jQuery, but rather than list specific jQuery cons, lets itemize the objectives we wish to achieve with a framework and consider how well the jQuery framework, or others, meets those objectives.
Some of the most important objectives that need to be considered are iterated below. The relative importance of these will vary by team and by project. But they are the major criteria which we are using to pick our framework.
I hope to make it clear that for any project larger than a simple website, features play a very small role, and that its much more about providing a foundation upon which a developer or team can build upon.
The architecture of a framework is very important. It strongly effects, and often even dictates how you must structure the apps that you write on top of it.
One of the pains of using YUI Library was that it was a large monolithic framework. YUI was written in modules, but to swap out one piece of functionality provided by YUI with a third party implementation was difficult at best. They were designed to be a tight-knit group.
jQuery is also quite monolithic in its approach. It is not written in modules - but delivered as one large module. Don't like jQuery's animation handling (and you'd be right not to)? Well, you get it anyway - and then you must monkey patch a better animation engine over it.
Given the diversity of applications being written for the web, and the variety of contexts they are deployed into - we are better served with a thinner and more interoperable base layer. The term framework already evokes a structure which encompasses your entire application, rather than simply providing a foundation upon which you can combine best-fit 3rd party modules to create the ideal surface upon which to build your apps.
Somewhat related to the basic architecture is the coding style. YUI used a very academic approach, while jQuery is nearly the opposite. By academic I mean YUI resembled a more classic Object Oriented style - using deep inheritance hierarchies and get/set object properties. jQuery, by contrast takes a very terse approach - even using the same method for different functionality based on the arguments.
I believe that neither quite got it right. Brevity is good and flatter hierarchies is preferred - but overloading functions is a bad idea. The ideal framework would find a better balance between these two extremes.
In 2006 when jQuery (and YUI) were first released, the browser was the only context these libraries were meant to be run in. jQuery is named after its primary original goal, which was to make it easier to find elements within a page (and then do something with them).
In 2015 javascript is being used in many other places outside the web browser: on the server, as a scripting language within other applications, for OS scripting, within database engines and as standalone user applications. In many of these non-browser contexts there is no need for DOM-related functionality.
So ideally, the best foundation core would be absent of browser-specific code. The browser related functionality would then be an optional extension (see section below on Loadable Modules). Furthermore, the browser module should take advantage of the state of browsers in 2015 and their mostly consistent, rich API. Older libraries like jQuery have difficulty transitioning to this approach due to the need for backward compatibility.
Complex web applications often contain a lot of asynchronicity. They are loading data over the wire, reacting to user events, firing animations once images load, etc. This juggling of events, callbacks and error handling quickly lead to what is referred to as Callback Hell. When you depend on many asynchronous activities in your application, the callback management challenges have even given rise to libraries to help, such as the async library.
Promises go a long way in making asynchronous code cleaner and less brittle. Promises are now available in nearly all the modern browsers and are a standard part of ES6. (Unfortunately, not available in IE11). But to maximize the benefits of promises, it helps if all asynchronous functions use them. This enables chaining as well as other strategies that group or schedule events and callbacks.
So for choosing a framework in 2015, the asynchronous functionality should be promise based.
Currently, jQuery offers a promise-like utility called Deferred . But this is not fully compatible with Promises and is therefor substantially less useful. YUI has a module for ES6 compatible promises, but these were not used in the core library anywhere.
Of course, there are many stand-alone implementations of Promises that can be added to your solutions. You can then "promise-ify" any functions you call that are using deferreds or callbacks. But this adds bulk and overhead to an already complex code path, which is what we are trying to avoid in the first place.
So to reiterate: Promises should be both included and used internally throughout any framework worthy of consideration in 2015.
This is a big topic that deserves its own article - but it is important to recognize that the architecture of the core framework plays a big role in how you break your project into modules and build your applications for deployment.
If you wish to break your application into many small modules (as you should), you will need to determine how you will build those modules into a deployable application.
jQuery doesn't have a standard concept of modules or a module loader - it is offered as one big function called jQuery (and aliased to $). [Note: jQuery does have a getScript() function to dynamically load and execute javascript, but getScript() has no concept of dependencies and isn't a full fledged module loader]
YUI Library was distributed as a bunch of smaller files which could be appended together by an external build system, or loaded dynamically as needed. One could also combine these techniques to load a core set of modules in a single request, and then load additional modules if and when needed dynamically. This was a very effective and efficient approach, which could be used in your own modules as well.
There are several standalone "module loaders" such as RequireJS which provide dynamic loading similar to YUI. And there are build-time solutions such as browserify which gather modules and their dependencies into a single file for distribution. These can be used with jQuery, or other frameworks, to dynamically load your own modules.
But in order for the framework itself to take advantage of dynamic module loading (i.e. its own modules), it has to provide one itself. The YUI Library got this part right (though given its overly-academic approach it became a challenge to identify which modules of the many available were required for a build).
Ideally, a framework should adopt a module format and offer a built-in loader to provide a dynamic load option for both the framework itself, and as an option for user modules. The built-in loader should be thin enough that if end users prefer an alternate approach for their own modules, they aren't burdened with two large competing module loaders.
So we haven't talked much about specific features. This has been more about architecture, philosophy and internals. The reason for this is that specific features are not nearly as important. I don't care if a framework provides a good charting feature, or calendar, or even animation. These needs are all served in various feature-specific libraries. Those library authors have increasingly rejected dependencies on jQuery or any other framework so that they will work in any environment.
This is why it is much more important to have a rock solid foundation that provides and encourages efficient and modular coding practices. This way you can build complex and highly dynamic applications that can make use of best-fit libraries for specific features.
Of course, the very commonly used features such as element selection, domready, ajax, etc. should be easy to include. I'm not suggesting that a developer should have to hunt around for every little need they have while building a solution.
But again, in 2015 many of these features are built-in. Check out vanilla-js.com for a humorous but informative look at built-in browser functionality as if it were a competing framework. (No matter what features you choose to include, the resulting "library" is 0 bytes!)
So unless you need to support very old browsers (I'm looking at you IE6), you might not need jQuery or any other framework for the basic features.
As the complexity of an application and its interface grows, so do the challenges of ensuring a consistent and updated state of your UI. Most developers eventually realize the benefits of keeping a clean separation of the business logic/data (model) and the user interface (view). This is generally referred to as MVC (Model-View-Controller) or an alternate (MV*).
Several high profile frameworks have risen to aid in building applications in this pattern. Google's AngularJS and Facebook's React are two of the most popular right now. These and other MV* frameworks are very much worth consideration - particularly if your "web site" behaves more like a "web application".
These can completely replace a browser library like jQuery, or they can sit atop it. Angular, for example, contains a subcomponent they call jqLite which is optionally included if the full "jQuery" is not found. (Lots of potential gotcha's there!) React discourages any direct DOM inspection or manipulation. And so while you can still load jQuery, little of it will be used.
This awkward mix of loading redundant and/or unusable code is indicative of a badly structured approach. Others clearly agree. Angular contains its own module loader - but using that for your web applications means learning a different module loader for your non-Angular sites. Likewise, if your jQuery/RequireJS site evolves into needing data binding, you face the choice to completely replace both libraries with Angular, or loading Angular on top of them and having lots of redundant code. This may be partly responsible for average web pages now clocking in at over 2MB!
A much better approach that supports projects of all sizes and evolves smoothly, is to have module loading be part of the core - but DOM manipulation is not. The DOM functions can be optionally loaded, or a data binding framework can be used. Neither case carries a redundant code penalty or growing pains as you swap out components.
To reiterate, data binding should be an optional enhancement, not a core framework. When it is added to an existing solution, there should be very little overlap or redundant functionality.
Now that we've identified what a core foundational Javascript library should look like, what options exist that meet this criteria?
Well, here's a list of popular frameworks (alphabetically) and a brief note about why they do not qualify:
I have searched fairly extensively for a foundation that meets our needs and have come up empty. I believe there are others that recommend this approach - Chris Love in this Code Magazine article describes a similar strategy which he calls "Micro Javascript"—dynamically piecing together best-fit single-purpose micro libraries for efficient, high-performance apps.
I think Chris and I are championing the same approach; but it is very valuable to adopt a module style and load a foundation that understands and can load these modules. As a developer or development team, you will still want to choose specific single-purpose modules that you know how to use, keep available on your server or CDN, and can pull in to any project as needed. You will also likely write many of these yourself.
Now you could use a fast, specification-compliant Promise library like Bluebird along with a popular module loader like RequireJS. That would give you a core foundation upon which you could then load any necessary single-purpose modules. I considered this option myself. But Bluebird is 72k minimized, and RequireJS is 15k minimized. That is 87k before you have loaded a single module! I would like to see these two functions provided in about 5k.
Of course, we could find smaller Promise implementations and smaller module loaders, but I chose these because they were very fast (bluebird) and provided enough features for complex sites (RequireJS). Things get prioritized in different ways for different teams - and apparently my priorities (small, fast, powerful, cleanly written, and well documented) is not as common as I'd like it to be.
So I have decided that the best option is to write this foundation myself along with help from the web development community (you!). This may sound more ambitious than it really is. At the beginning, it is just writing a Promise implementation to spec, and a powerful but efficient module loader. In fact, I've already written the Promise implementation—its called Zousan. It is 2k minimized (less than 1k gzipped!) and its the fastest Promise implementation I know of. So we're off to a good start!
I will be moving quite quickly on this. The Promise portion is done, and the module loader is near completion.
I will then identify the best single-purpose modules on Github, NPM, MicroJS, WWWhere, etc. to serve common needs. Where I can't find a module that meets my criteria, I will write it. My criteria is the same as listed on the Zousan doc:
We have quite a few modules written as extensions to the YUI Library (not open-sourced) and I will move those to work within our new foundation (and as stand-alone modules) - so there is lots of Javascript fun coming!
I invite everyone reading this to comment below about this plan and I hope you will follow as we build a practical and efficient foundation for Javascript projects in a 2015 mindset!