When I started programming there was a notion that front-end development is easy. At the time it was little more than arranging HTML elements, styling them, and making AJAX calls. jQuery and Bootstrap were everyone’s default tech stack.
If you look at today’s landscape it’s hard to believe that jQuery’s dominant days were less than a decade ago. In ten years, front-end reached a level of complexity that requires companies to have architects, working on application structure, data flow, and tooling.
Design systems went from the esoteric projects of big corps to a common occurrence in most companies with established UI development practices. From stitching JS files together with Grunt, we arrived at the dawn of micro-frontends with Module Federation.
But front-end development has never been harder. Here’s why.
No Control Over the Environment
To understand some of the challenges faced in front-end development we need to compare it to back-end work. Nowadays, microservices are usually deployed in containers. The developer gets to pick the environment and the software running in it.
As a front-end developer, you have zero control over the environment your code is running in. Supporting different browsers, devices and optimizing for bad connection speed is an exclusive burden for people working on UI.
At the FT, we had 150 people who opened the website on a fridge, probably running an obscure browser with non-standard dimensions.
The nature of the web makes backward compatibility a necessity. A non-trivial part of each front-end developer’s time is devoted exactly to that - making sure every browser API used is poly filled and the latest generation features are transpiled.
Despite all the tools we have in that regard, there’s always this one browser that makes a layout appear broken. It used to be Internet Explorer in the past, now it’s Safari.
But it’s not just the support of new features that’s problematic. Browsers that run different engines may diverge in the implementation of fundamental language features. For example, Chrome uses quick sort while Firefox uses merge sort.
The end result is the same but your code executes in a different way between browsers.
I am yet to be bitten by something like this but I’m sure that day is not too far away.
I know my kung fu when it comes to front-end but I still get lost when it comes to tooling. Between linters, test runners, bundlers, and TypeScript I can never remember the right combination of tools and configuration that makes for a successful build.
Take ES modules, for example, an attempt to unify how modules are imported in the browser and in Node. For people who don’t have the context about Node’s development, it seems counterintuitive to have two competing module resolution patterns in the same language.
But despite the great initiative, Node’s implementation has requirements around the import paths which TypeScript doesn’t support directly. It’s a puzzle that (hopefully) has a single working combination between all the tools and abstractions that you use.
It’s not a rare practice to abandon a tooling-related initiative and wait for a resolution to a problem.
I can make sense of it all because I saw the ecosystem develop. But a newcomer who’s just finished a course on front-end development will have little understanding of why we need the additional transpilers, plugins, and small hacks to get everything working.
Compare this to Go, where the formating and build tools are baked into the language and you use them to compile production code just the same. No need for anything besides the tools that come with the language.
The tools you use for local hobby development are the same for complex distributed systems running in production. And most importantly, they are most likely going to remain the same between companies and products.
No Best Practices & Design Patterns
But I consider the fact that it gives you multiple tools to achieve the same result to be a serious flaw. I can tell five different ways to loop over a collection, not counting recursion. The standard library gets bloated with functions aiming to do in one line what you usually do in three.
When I worked with Go for a while I noticed how there’s an idiomatic, established way to express common logic. Functionality is not added as a way to make things easier but to solve a problem.
But that aside, the abundance of techniques makes it hard for newcomers to understand what’s the “right way” of achieving something. No, the answer that it’s up to them doesn’t really cut it because a person without experience doesn’t know how to make a trade-off between two techniques.
There comes a time when you cross the threshold of comfort and you can develop a piece of functionality or a feature. So you start looking for established patterns, you don’t want to reinvent the wheel or fall into common problems.
But it’s exceptionally hard to find information about how to structure a codebase and advice on building applications.
The practices you find are usually limited to a particular technology and the common design patterns don’t have much application here. You won’t have to use a Strategy, a Repository, or a Singleton. This is valid not only for beginners but for experienced engineers drawn to the front-end.
Most often, best practices are learned through trial and error and passed down by knowledgeable engineers like ancient martial art techniques.
Between Programming & Design
Something that I noticed in my work with Go was how rarely I had to actually run my code. Once I got the compiler to agree with me and I wrote a few tests, I was confident that my code worked correctly.
But in front-end development, getting your code to compile is only half the job because there are no automated tests for a pleasant UI.
You can get your code to run but the visual result may still be incorrect. This often leads to situations when you have to make amends to a seemingly good implementation just to get around the specifics of the browser or the positioning of the elements on the screen.
But still, a good UI developer needs to be able to deliver a pixel-perfect implementation that can be extended and maintained. It’s a challenge to organize the styling of a complex page because components are not isolated in the UI. A change in a single component can send the entire page in disarray.
The Price of Progress
Not that long ago front-end developers were web designers with some coding knowledge. Now some of the best engineers I’ve ever met are working in this field.
The lack of best practices that is confusing for beginners works great for experienced engineers who can structure their applications in a way best fit for their domain. I’ve worked with React since 2016 and I’m yet to see two codebases that are built the same way.
And that’s a sign that the ecosystem is maturing. Not yet, but we’ll get there.