I'd like to make a point for the separation of concerns in web development.
HTML is a language that's been created for structuring and linking documents. CSS is for styling said documents. JS provides additional functionality. The three languages are heavily specialized to their intended use. They're also really good in what they're supposed to be doing.
But where's light there's also shadow. Straying away from their paths means you'd have to accept (or even mitigate) some disadvantages.
Let's take a look at some of those.
Depending on Resources
A classic request chain would go as follows:
I've inserted a green line to mark the First Contentful Paint. Also, ignore the huge numbers. I had this on a slow 3G connection.
The document comes with the initial request. It references CSS and JS files, so they start loading as soon as the document is parsed.
Your styles are render blocking, so they are parsed next. Your JS comes in last place. The document is already visible at this point, because we deferred it.
Our request chain goes from least to most complex.
HTML is pretty robust. Even if you mess up your code, your browser will probably still be able to display your content somehow. While it's easy to mess up your CSS, it's hard to mess it up so badly that your document becomes completely garbled. You can quickly implement some severe accessibility issues, though.
Javascript is arguably the most complex language out of the three. It's also the most fragile one. Mess up your main thread and the whole script stops working.
Whenever a resource after the document fails to load or parse properly, you still retain the core functionality of your page: displaying content. Everything else just adds things to make the experience of your site a bit nicer. That's the principle of Progressive Enhancement.
Rendering everything in Javascript
There's a trend to break that principle by just rendering everything in Javascript. Angular and Vue are basically JS-in-HTML and React is HTML-in-JS. All of the big three frameworks are mixing the separation of concerns between Structure and Functionality by depending on the latter to render the former.
What the browser gets from the webserver is basically an empty document pointing to a large Javascript bundle. And that's what it's going to stay until the bundle has been loaded and processed. Until then, not only valuable time passes, you also add the risk of your site not loading at all. Remember how fragile JS is?
Until your page is rendered, the browser has to process the initial document, the framework bundle and whatever you did in that framework. That's a lot of stuff that doesn't really add anything for the user. It's just boilerplate code. Here's a diagram of what a Vue app need to do to show content:
Your bundle may also fail to load due to a flaky internet connection or a critical error in the script. HTML is forgiving in that manner and will still display whatever it can. JS not so much.
Too much Makeup on the Markup
We can also do it the other way around and inline everything into our document. That would reduce the request chain, but at the cost of bloating up your html and binding style directly to the DOM.
Suppose you have an Atomic CSS framework in use and write utility classes on your elements (My example uses tailwindcss).
<article class="mx-auto sm:my-8 md:my-12 lg:my-16 sm:p-8 sm:p-12 lg:p-16 bg-white">
<header class="text-l lg:text-xl text-darkgrey">
<h1>Lorem Ipsum</h1>
</header>
<div class="text-s lg:text-m text-black">
<p>Dolor sit amet, consectetur...</p>
</div>
</article>
You've bound your style information directly to your DOM. Now, suppose you want to add a dark theme, a printable layout, or personalized styles. Do you add even more classes? Do you accept that text-black
doesn't mean black text in an inverted theme anymore? By writing style information directly into the DOM you lose the flexibility you gained by separating their concerns.
Also, your HTML is bloated with repeating class names now. Worse yet, those class names only describe what they do, not why.
Complexity
You can mitigate all of the aforementioned issues. You can use SSR to deliver a functioning Document with JS frameworks. You can use Mixins to bundle atomic CSS classes into semantic classes and keep your HTML clean. But all of that is adding complexity to your project. You'll have to keep your workarounds in mind while developing. Each one is creating mental overhead and possibly slowing down your build process. I'd argue that our current web development landscape is already complex enough.
When you're building just a small blog or a fairly static product page, why use frameworks that were meant for huge, complicated web apps in the first place? You can save yourself from working around their drawbacks by simply not using them at all, if you don't really need their functionality. Just ask yourself if you really need reactive programming for your project. Or if your CSS becomes so huge that it's impossible to maintain in semantic modules. If not, maybe just try some old fashioned DOM manipulation and BEM.
Before you start choosing the right technology for your project, take a step back, take a look at the problem you're trying to solve and ask yourself if you really need the increased complexity that this fancy framework is giving you. Remember that in the end, your users don't care if you used React or jQuery.
Buts! π
Everyone uses those frameworks, so they must be The Solution(tm)!
If it was the solution to all our web development problems, there would be no reason to write this. Even if JS-first paradigms are hot right now, they're not free from drawbacks.
I have that super awesome SPA that relies on complex state processing and routing and...
Sure, go ahead and use frameworks where they're fit. Just be sure to have their shortcomings in mind.
I have that really edgy edge case where I have to write inline styles
Yeah, they exist. I know. I don't want to forbid anyone to write inline styles or utility classes or anything, I just want you to be conscious about it.
My boss told me to use React.
I don't like your boss. Show them this article.
Homework
Here's a little exercise in mindfulness: Every once in a while: Write a small website. Don't use a framework, keep your build process as simple and straight forward as possible. Maybe don't use a build process at all?
You'd be surprised how refreshing that change in perspective is, especially if you're stuck in a big clunky web app at work.
Top comments (7)
Thank you for the article! In a time when React and the like are accepted so readily, this is a refreshing change of perspective.
I think the big problem is that there is a tension between separation of concerns, as you defined it, and componentization. Using HTML, CSS and JavaScript as they were originally meant to is nice, but so is having reusable components.
That, and the other issues you raised, is the reason I like Svelte so much. First, it lets you write actual HTML and actual CSS at the same time as having components, with all the perks this brings. It is the best of both worlds. And, since it compiles itself away, you are shipping just the JavaScript you actually need. The end result the user receives is basically what you would've written in vanilla JS, except you get there much more easily.
I don't have any experience with Svelte, but everyone I spoke to about it was amazed. Definitely have to give it a look.
Nonetheless I'd argue that you still have to carefully evaluate what "only the js you actually need" is. That holds true even for vanilla.
Sadly the development industry is mostly driven by fads.
There are many technologies. The old ones worked well, but have lost favor, so now there's new ones. Its also big business to get people to use your stuff.. So things change, even when they don't need to.
But there is real risk going against the fads and using things that are not yet (or ever) in style, or have lost favor:
All these factors may lead to a re-write, which can often be a death sentence for the project.
I've seen many great technologies come and go.. because developers decided to go with something else that excited them. Even when the new thing is much less capable and much less tested.
Nice, if we don't have to target 98% of browsers, as
Don't know how I missed this four years ago, but better late than never.
This is an excellent article.
It's not really about "separation of concerns". We can choose whatever "concerns" we want and split the code in different ways. Whether that makes sense or not depends on the circumstance. The arguments around SoC are like arguing how many angels can dance on the head of a pin. Who cares?
What is important is performance. The ability of the devs to understand the code and write it quickly with few or no bugs and little tech debt. The ability of the user to understand the interface quickly and get things done. The ability of the company to make the product profitable.
And the key to all three of these β assuming that you're making the right product for the right people with the right features β is cognitive footprint.
Cognitive footprint is simply the cognitive load over the lifetime of the product. The code has a cognitive footprint. The interface has one, too.
The argument for separating the HTML, CSS, and JS is that doing so greatly reduces cognitive load. If we work with a component architecture and we keep components small and simple (but composable), then we're never dealing with more than a few lines of code at a time.
Throw in good naming and a few design principles (such as grouping like things, proper use of whitespace including indentation, etc.), and the code becomes as easy to read as a nursery rhyme.
You can put all your code β HTML, CSS, and JS β in a single file if that works for you. But using a programming language to generate all your HTML and CSS just makes it very difficult to grok what's happening. This, BTW, is the great feature of JSX: it looks like the HTML it produces.
I don't believe that this argument is a matter of opinion or preference. Cognitive load is measurable. We should be able to measure which approach results in the least load, hence effort.
My money is on keeping HTML, CSS, and JS/TS separate.
I talk about this quite a bit on the Craft Code site.
Meanwhile the boundaries between websites and web apps is getting blurry, and application frameworks start to target smaller and smaller units of functionality.
Which is a good thing. I'm not even complaining about heavy framework usage in itself. I'm about how the heaviest frameworks are used in even the smallest projects.