Why JS frameworks fail to last
Everyone in the JS community, or anybody watching it, have noticed a weird thing for the last few years: the rise and fall of countless frameworks.
And there’s a bad thing behind it that nobody seems to have pointed out yet. So the whole purpose of this article is to try to figure out what’s going on.
I’ll try to be the most objective as possible.
P.S. : I only talk about frontend here, no backend js!
So, I think anyone will agree when I say the current situation is completely insane and not sustainable.
Right now, you can’t be sure if the framework you’ve picked will still fit when your app will grow in size, or if the next release won’t break it. Anyone serious enough about his code should be concerned by this.
The hard question is why in hell can’t we reach a stage of stability?
A first answer may be the community mentality. I’ve noticed a touch of arrogance in the sense that as the language is unique compared to any other, it should not consider what everyone else has learned. Consequently, the community try to rethink everything on its own, and it does it poorly (in my opinion).
Why this feeling of uniqueness is pretty simple to answer, the js is the only language available in the browser context and it’s one that evolved a lot in the last couple of years. It opened a whole new platform to build websites/apps, attracting a lot of new developers.
And as the platform is still in expansion, more people are joining in; the more comes, the more we talk about it. And I suppose it ends up where anyone in this bubble when looking around see another one inside this same bubble, hence the feeling you’re in the only one.
Somehow this led to the hipster syndrome.
Anyone try to invent something new, and everyone around fall in the hype.
But guess what?! This new thing you’re trying to build/solve, chances are it’s already been solved elsewhere!
Same rules apply to everyone
Software engineering isn’t new, and lot of problems related to code structure has been solved. You know, with this little thing we call design patterns!
It’s not because your language is kinda new, those couldn’t be applied to you.
To be honest, I felt great when I heard AngularJS brought a dependency injection mechanism. I thought: great, we’re finally on the right track. (Then they fucked it up with Angular 2, but I’ll come back on this point later)
This single pattern has been introduced 45 years ago!
It’s one of the first principle you learn about software engineering. And as far as I can tell, every framework (except js ones) is built around it.
So before being taken seriously at replacing existing pattern, implement existing ones correctly, and only then we can discuss your new ideas.
Hint: a good starting point at implementing something serious is to look at what’s done in another language (ideally one close to your domain), and be a great artist about it: steal the ideas!
The point of a framework
It’s now time to explain whats is a framework. The overall definition could be resumed to:
A set of tools working together to help you build a solution to a problem, while keeping your codebase structured around a set of principles that can still apply no matter the size of your solution.
The first key point to get here is the notion of a set of tools. The thing to understand is that in js we mostly think about frameworks as a big swiss knife that help us do a lot of things. It should not!
The notion is to have a bunch of libraries that work together, but each should be first designed to work as standalone. Each part of a framework need to respond to a key problem, and respond in a way that can be applied to any context.
Let’s take a simple example: a router. The idea is simple, each time the url change you want to trigger something in your code, for a set of defined routes. Generic problem that can be answered in a small library. But right now, can you use the router of Ember or Angular without the rest of the framework? From what I saw the answer is no.
And this should be applicable to every part of a framework.
If when you look at a framework, you can’t determine when a feature stops and another one starts, that’s a first indicator that something’s wrong.
But of course in a model that each part should work as standalone, you need some glue to wire them together. But in the end this small layer is what really represent the framework.
The good thing with this approach is that you’re no longer tied to a framework but to a set of tools. So if at some point you realize that a component no longer feet your needs, you can more easily replace it. Or if from the start you don’t like the framework’s organisation but really want to use some solutions, well you can! (by building your own glue layer)
Which leads me to the second key part of the definition: the code structure.
If a vendor starts by telling you: this is how you build a controller, how to build a view, etc… They are missing an essential part of their job. The documentation should first tell you: this is how we intend you to structure your code because it will help you easily find what your looking, add new code, etc…
The second most important question you should have in mind when looking for a framework is: ok, it answers my needs in order to solve A, B, C, etc… but how can I be sure that I can add features to my solution in an easy way while keeping the global structure as clean as the first project keystroke?
If you can’t answer this, think twice before using this framework.
The shot you’re about to make is intended for the long run, not the middle one and especially not the short one. But you may respond: well we don’t know what’s coming up next? so why not settle with what we have now and will change if necessary?
My answer would be simple: this is weakness! Weakness induced by industry’s pressure to make a choice quickly.
Once again, software engineering isn’t new, we have plenty of history on how to do things. That’s why you need to aim to a set of principles (or patterns if you prefer) that are not bound to any technology in particular.
So remember: composability over swiss knife, principles over technology.
Latest shiny tech is not THE answer
This section follows up a bit of the composability explain before.
I noticed a weird trend lately on how to build your apps. One that revolves around the notion: hey guys, we have a great new tech, let’s focus on that specific point and do some, not so great, things around to build our app!
Those are good technologies but it answers only one problem!
First, let’s take a look at custom elements. We all know the div soup to build some great UI; but the problem was styling leaking, js snippets having side effects (due to selectors matching what we didn’t have thought of). And the answer was the custom elements, where the goal is to wrap a little brick of your UI (i.e.: a chat box, a profile card, and so on).
Then the idea was perverted. As each brick is self contained, why not build bigger bricks based on smaller ones?
But, you see, the problem is custom elements are here to built UI not contain functional logic!
At first it may not seem that horrible to do it this way. And if your codebase doesn’t grow, it may be fine. But to point back to principles, there’s one in software called separation of concerns. The idea is simple, a component will do a simple job with a closed perimeter; if it needs data not directly available, it will not look for it but will be provided to it. With this concept, the UI only handles UI stuff, by using custom elements to do it in a more stable way. But the UI will not organise things, a higher level (which is completely separated from the UI) will handle this task and will provide to the UI what it needs to do its job.
Second example, the virtual DOM.
Facebook gave us a nice tool here which only updates the parts of the real DOM that needs to be updated based on a new set of data by diffing a virtual and the real DOM.
As is, this tech is fine; but they brought React where you mix your view with the model layer. The why is that in complex applications you end up in complex tree of layers requiring data, and if you want to update part of this tree the higher level have to know about the needs of deeper levels (which leads to unwanted coupling). So their answer is that a level in this tree should know how to retrieve its data.
First problem: it breaks separation of concerns!
Second problem: well it’s not a problem but an indicator, it means that the overall system terribly lacks structure through principles.
I see you coming with your question: how the hell do I keep separation of concerns and that part of the UI without higher levels knowing whats needed?
Honestly the answer is very simple! The UI is structured in many layers, an action occurs on a deep layer, the event bubbles up and is catched by any listener in the tree. This last one dispatch an event saying: hey, the user wants to do this, do anybody knows how to respond to this? You’ve done your job well and there’s a listener of that event who understand this interaction, fetch some data (via another component) and then update that small layer the event originated from.
I know, too much code to do a simple thing, right? But that my friends, like it or not, is what’s needed to keep a codebase decoupled and scalable.
The last example I want to point out is to explain why I said Angular 2 fucked its dependency injection.
They leverage ES6 module mechanism associated to annotations.
The module loader is really great as you can only import in your module the classes you need for yours to work.
They had the idea to use an annotation at the level of your class to specify the other classes (that you imported with the module loader) you want to inject in yours. Then when you request an instance of your class via the DIC, it looks for this annotation and automatically inject the dependencies.
First problem: a class should import others because it knows it will use them in order to work, but a class should not be aware of how objects will be injected.
A few examples to explain why this approach is wrong are simple ones. Say you have a class that needs to persist data, so you want to inject an object dedicated to the storage. But if you want to do things correctly, your class should not be aware of the specific storage chosen, so normally you would import a class acting as an interface (as there’s no real interface in js ☹). But the thing is that you simply can’t do this with their DIC, because it uses the class imported to build an instance of it. Your only choice here is to explicitly specify which implementation you want.
Another feature I use a lot with a DIC, is to tag my services. A great example how this is helpful is with a storage engine. Imagine a library that propose a common interface to store objects and with by default 2 persisters (say localstorage and IndexedDB). This library let you choose which persister you want via configuration.
Tags are a good feat here for the persisters. At the DIC level, you could say: look at all the services tagged as storage.persister and inject them in the global storage mechanism.
Now if you want to add a new persister, you implement the persister interface, create a service and tag it. And tada, magic happened!
And if you don’t want to load the storage mechanism with the DIC, you can still inject manually your persister; because it induced the developer to build its library in an open way.
And as I’m sure you guessed, you can’t do this with the new Angular’s DIC.
By coupling too tightly components, it encourages you to build your code the same way. And you soon end up stuck because you can’t figure out how to extend/modify your code.
The real problem
To sum up, currently the majority of frameworks fall into a common pitfall. They think their goal is to help you build a user interface with the less code possible. And nobody can deny they put a lot of thinking into it.
It is the root of all the problems. If you focus on only one feature, you’ll end up with a strong concept on one side, and the other side a ton of things you don’t really know how to integrate. This is when you start writing weird stuff to try to still do all the things a framework is expected to do.
No matter how strong the concept is in helping you build great UI for your users, don’t forget that what you’re searching for is a concept helping you build great code!
And you’ll never reach this point if you keep choosing this kind of frameworks.
So, if the one you want to pick checks one of the following items, please abstain.
- each component can’t be used outside the framework
- the documentation doesn’t start by explaining the patterns ruling the framework
- bragging mainly about one feature (especially if it’s UI)
I understand it may be hard not to choose but I can guarantee you’ll regret it at some point if you do.
With great power comes great responsibility, right? So before rolling out your experiments to the public, make sure it will work in the long run; or at least insist deeply in the fact that it’s an experiment and casualties may arise.
But it’s also up to every team to discern what’s best for their projects. Cut the politics, do not rush to a solution because it has a big name on it and everyone else jumped in. Weight every point, does the solution seem right (meaning good patterns)? does it feet your needs? will it scale?
And remember that the answer for the best solution may be:
If you agree with what’s written here (or even if you don’t), have a look at this repository. I wrote this last year, with all the principles I praise here in mind. And as I said, I didn’t invent all of this, I stole the ideas from Symfony2.
It’s intended to help me build the frontend of my pet projects. You can play with it but I don’t advise using it in a work environment yet.