The unnecessary complexity on the Front end
I'm close to 16 years of working as a Front-end Developer now. I’ve had the privilege of being involved in a wide range of projects. However, I’ve noticed that web development is especially harder than it should be in these days.
I think the reason for this is due to the expansion of the digital market, the acceleration of startups, the increase in competition, and the high demand for developers. It seems like we need to be able to grow quickly in multiple areas to keep up with the demand.
But something happened in the way… We are missing the pragmatism.
With the Influencer era we're living in, those complexities are not being exposed, instead, they are getting widely disseminated, I think this is due to the Demarcation Dispute, Influencers are trying to sell content, and if they are not in the hype they lose the audience.
Maybe you’re not convinced that we’re having a hard time developing UI applications on Front-end yet, that might be related to the time on which you’ve been introduced into the web development timeline.
For the most part of my life as a Software Engineer, I have been the mentor of many developers. One of my main concerns was to teach about the platform, and the fundamentals, and show the simpler way to do things, but for a long time, this "simpler way of doing things" has been getting more and more hard to do and hard to sell for newcomers.
A simple case… well…not that simple.
I was watching a YouTube channel and this influencer was teaching how to use a Custom Hook for Context API instead of useContext.
The code is something like this:
import React, { createContext, useContext, useState } from 'react'
type ThemeContextProviderProps = {
children: React.ReactNode
}
type Theme = 'dark' | 'light'
type ThemeContext = {
theme : Theme
setTheme: React.Dispatch<React.SetStateAction<Theme>>
}
export const ThemeContext = createContext<ThemeContext | null>(null)
export default function ThemeContextProvider({
children
}: ThemeContextProviderProps) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider
value={{
theme,
setTheme
}}>
{children}
</ThemeContext.Provider>
)
}
export function useThemeContext() {
const context = useContext(ThemeContext)
if( !context ) {
throw new Error(
"useThemeContext must be used within a ThemeContextProvider"
)
}
return context
}
export function myComponent() {
const { theme, setTheme } = useThemeContext()
return <div>Something...</div>
}
I know what you're thinking. Yes, that's just an example, and often people use a simple case to illustrate a problem.
But people won't have that in mind when they're learning.
There are a lot of things going on in this code. First of all, the inherited complexity of JSX which in my opinion is a thing to worry about in the complexity subject. But along with that, there's also Context API, Hooks, and types with Generics inception ( see that setTheme type )…
In case you didn't notice, that's just a theme toggling feature, that you could achieve the exact same thing with just few lines of code.
const setTheme = (themeName = 'light') => document.body.setAttribute('data-theme', themeName)
setTheme('dark')
console.log(document.body.dataset.theme)
"You are not getting the whole picture, applications are more complex today, and this vanilla code doesn't scale".
This is the type of argument that I often hear when I expose those complexities using a more concise code as a contrast.
I have worked on several applications in my life, and many of them were really really large projects. Looking back, the most painful issues in these projects wouldn't be avoided using the concepts in the React version above. Many of the project issues weren't at the code or application level in the first place.
The cases where we actually had room for improvement in code, were in fact related to the architectural level, bad decisions or misinterpretation of requirements, or the requirements changed a lot through the years, and code was not refactored at the same pace.
To make such a toggle class approach work well to change the style of your application at the component or application level has to be well designed, It doesn't matter which framework do you use, it will be a nightmare if you don't make it right.
Scalability also means that in many cases you have to design your application to work with other companies, with other teams, with different technologies and stacks, because that's real life, especially in larger projects.
That Reat code above will not age well, even with the best practices in Typescript ( I hope you have someone in the team who knows them ).
The use of Typescript as a solution to scalability is delusional. In order to solve type errors that annoy you, you have to add 10 other subjects that are deeper and more hard to understand in your environment and you're not quite aware of them in the very early stages when you're building your POCs alone.
The arguing and the facts…
I had several discussions with other Senior Front-end Engineers through these years about the over-engineering subject around the Javascript Front-end Ecosystem. It felt like they were looking at me as a person who was extremely resistant to change. From my perspective, that was not the case for the majority of the subjects discussed.
The thing is that I used to prioritize simplicity. I just consider some ideas more elegant than others.
For me, as senior developers we're missing the ability to be more critical with some ideas, and we are repeatedly making the same mistakes over and over again…
There are some clues that actually help me to understand if somehow what I'm advocating was making any sense, and that's the fact that many of those ideas that I argued against are now somehow forgotten or giving someone headaches in a legacy system:
- Flux, Redux, Redux-Saga over-engineering
- The use of classes in React and nonsense methods such
shouldComponentUpdate
,componentDidMount
etc … instead of using javascript Functions ( Douglas Crockford advice ). Now we have Hooks, which brings other concerns, especiallyuseEffect
. - SPA instead of Static Site Generation for the majority of the websites we have today
- RXJS… For those that are not using Angular, this library is not even a thing…
- CSS in JS, is being a stumbling block due to incompatibility with some isomorphic meta frameworks…
The Ugly Side of Frameworks
We are in a place where in many cases your front-end dictates your back-end stack. The libraries/frameworks are overlapping Front and Back-end borders and concerns, each one trying to be a silver bullet.
- They are getting so bloated that they are harder than if you were using something straightforward as close to vanilla code.
- You have to use hacks like “use client” in order to know if you're in a back-end or front-end environment.
- You have to remember to not use functions like
useState
inside if statements. - You can't use
async/await
in your effect function because the framework's bad ideas got in the javascript way… - You have to know about Memoization techniques, which is a Computing Science subject that I learned in Computer Science classes. It's very useful, but for framework designers from my perspective, they happen to solve performance issues, but the framework uses high performance to sell itself in the first place ( Blazing Fast ).
- The number of redundant solutions: Next, Remix, Nuxt, Solid Start, Qwik City, Svelte Kit, Angular Universal…
- Animations abstractions that are framework-specific, so you have to learn all over again if you choose another Javascript Framework.
More importantly… they are driving newcomer devs to think that all those things are web intrinsic problems, and they are not. Those Frameworks are heading them toward hyped complex solutions instead of being pragmatic and straightforward.
I understand that Frameworks are a nice way to ramp up fast, especially when you're in a small project, if you are an indie developer, or if you are in a startup with a few devs that need to be efficient with less headcount. I believe these are the best scenarios to use them.
The thing that bothers me most is when I see people advocating in favor of the use of frameworks in larger projects because that's the best way if you want to scale.
Well... I guess that might be true for the back end... But I don't think it's the same for the front end.
If the company is big enough to have Seniors, Mid-level, and Junior developers, the bloated Fullstack Frameworks are less needed.
So… what then?
Maybe the way is to get as simple as possible in the front-end land. Maybe it's time to step back and understand that there are a lot of great solutions available out there that help us to address some problems we had in the past. Many things got updated and the platform is consistently improving...
We got several updates and improvements in the language, browsers, DOM APIs, and Tooling ( Vscode helps a lot with code analysis and inferences ), Bundlers are getting very mature, fast, and easy to use…
You don't need to couple Design Systems to the Framework.
Bootstrap already teaches us how to create an agnostic design system that will work with any framework. tailwind and vanilla-extract are good ways to build the design system for your company.
Components Library does not have to be attached to the framework. Make it Agnostic, e.g: https://bootswatch.com/cosmo/
The site above is an example of how to build a page with components that have HTML examples of how to use it. Yes! You have to adapt to your framework when you are making a web page or app, but the time spent with CSS is saved, and most of the time CSS is in fact what we spend most of the time in development.
In order to "Save time by reusing code" devs are often choosing to couple with your favorite framework and now, you have to deal with compatibility between different versions between projects, and you're closing the ability to test other frameworks for some cases.
Use lightweight Javascript libraries
Browsers are getting faster, Developers are getting smarter. There are very great ideas out there that can actually improve the Developer Experience when developing Web Applications today, without locking you in the complex world of Framework and its ecosystem.
In Javascript vanilla land, there are fantastic options to solve all sorts of problems in UI, like Infinite Scroll, Sliders, Charts, Animations, etc... That's the Ecosystem we should actually put our efforts on.
In the Application context, there are some Component libraries that can actually interoperate with Vanilla Js libraries and can handle local state management and efficient dom updates, like Alpine, Stencil, Lit, etc.
For Global State Management search for those that are actually agnostic, and can store your use cases that can be tested standalone without any framework coupling, like Zustand for example, which actually has a vanilla option. I'm pretty sure that there are a ton of them out there.
There's also a way to solve this kind of problem with some sort of Publish/Subscribe pattern you can use for your application, It's a nice way to decouple your system with a few lines of code and is relatively easy to understand.
Advice for the Young at Heart
Just try to look for more agnostic solutions, give it a try on Astro and prioritize the "Architecture" of your application over the Framework. The best advice I can give around this subject is the one I learned reading some ideas from Uncle Bob, which is to push your implementation specifics, like Component Libraries, to the most external abstraction layers of your application, that's a good way to make your system to age well.
A potential change of some UI framework in your system should be always considered, It might take a while to do the migration, but it shouldn't be that hard.
The Architecture, Design systems, Utilities Functions and Helpers, Services/Repositories, Use Cases, Global State Management, Testing, and other Application abstraction layers are the most relevant parts of the project, they can be decoupled from any framework.
Your UI Library should be able to be plugged in with those parts easily. And if you work that way, the process of defining your Library shouldn't be so definitive and hard to do.
That's all I got, I've kind of proved to myself some points empirically, I have worked with a component library I've written myself for around 5 years now, and there wasn't any case where it wasn't sufficient enough to address the UI challenges I had, no matter how hard the system was.
Check it out to see more in practice what I'm talking about. Feel free to give constructive feedback, and let me know if you know any other nice and lite component libraries out there, or what are you doing to get free of all these complexities imposed by the framework industry.
Cheers.