At last year’s Fronteers Conference, I had numerous conversations about the merits of using client-side frameworks for virtual DOMs, declarative templating and routing, versus a classic, vanilla approach to building websites. I had always been a vanilla-fan, but built a recent project in a framework, liked that too, and, well… I think they aren’t the polar opposites that they used to be in my head!
This post has three parts: in the first, I look at what I like about “the web standards stance” or a “vanilla approach”. In the second, I share what I liked when I used a JavaScript component framework. In the last part, I look at whether these two approaches are actually different: maybe I assumed a false dichotomy?
The web standards stance
As I see it, Fronteers Conference has traditionally been very web standards focused. We had no talks about Sass until years after it came about. We did hear about new CSS specs years before they were in browsers. Standards over abstractions. Progressive enhancement and accessibility have been recurring themes from the very first event. I’m not saying any of these things are superior over others, just that learning about them influenced me and many other regulars in our thinking about the web.
Personally, I have until recently managed to avoid JavaScript frameworks in projects. Did I miss out? Maybe. I did my front-end work in teams with a rather traditional stack. Often, they used Node to turn abstractions of HTML, CSS and JavaScript into browser-ready code. But the browser would not be sent much JavaScript and there would be no virtual DOMs.
It’s applying what’s in web standards directly, without a framework that wraps them. I’ll call this the ‘vanilla’ approach. It is basically a very minimal one, with no or few dependencies. Hand-written solutions for just the problems at hand. Some call it ‘not invented here syndrome’, which has a negative connotation, but there are also downsides to whatever the opposite of ‘not invented here’ is (packages can go bad, and what about christmas effects in production?).
I believe going vanilla is a totally valid approach to making sites. Large companies do it. Like GitHub, they even open sourced their components, but also Netflix, Marks and Spencers and others. Going vanilla is controversial, too, I found. When Sara Soueidan tweeted:
It’s really depressing that most useful tools these day are made for React projects and/or require React knowlegde to set up and use. This locks out many of us who are not using React for everything & who still prefer the vanilla route for their projects.
she got various people tell her she should learn React, some very unfriendly. But vanilla is a fine choice too, and more cross compatible, as her point proves: a vanilla component or library could work across multiple ecosystems or outside any ecosystem. A vanilla approach assumes the web itself as its ecosystem or platform.
It’s not just easier cross compatibility though. I have seen in real world projects, including some for very large organisations, that these traditional, vanilla stacks let us have fast, accessible and maintainable sites. By default.
- Fast, because (JavaScript) code you don’t ship to the user, is code they don’t have to download and their browsers don’t have to parse. See also the performance.now() keynote of Steve Souders, “grandfather of web performance”, and Alex Russel’s piece on ‘ambush by JS’: Can you afford it?
- Accessible, because the web is accessible by default. Because web standards are standards, they have the biggest chance of being the thing that makers of assistive technologies adopt to base their tech on. Using a
select
rather than a library that customises one and makes it inaccessible in the process (happens), increases the chance of the component to work for more people - Maintainable, or I should say, maintainable for potentially a longer time, because if you depend on less things, there is less chance your project stops working because of a third factor.
I’m not saying you can’t have these things if you use frameworks. As I said, from real world projects, I learned that traditional, minimal dependency stacks have these benefits. I should note here, you can also get these benefits if you use a framework: giving users more speed and accessibility is also the goal of some frameworks, and there are lots of people in the framework world that are very effective at improving such aspects through framework code.
(Yup, I find accessibility and speed, i.e. aspects that users experience, the most important criteria for judging my tools)
Having said all this, I tried a framework and I loved it! Probably not a surprise, as almost the whole world seems hooked to frameworks.
I tried a framework
In the last few months I built my very first framework-based front-end, in Vue.js. I complemented it with a router, a store and a GraphQL library, in order to have, respectively, multiple (virtual) pages, globally shared data and a smart way to load new data in my templates.
Those of you who work with front-end frameworks will likely nod along to the above description. Others may feel confuses and/or start laughing. Multiple pages, global scope and loading data can be done in web-default stacks with vanilla code. In way fewer lines. Multiple pages by literally creating pages and links between them, global scope by putting stuff in the window object (scoped to your app if you like), loading data with plain old XHR or fetch()
. Why let users download lots of kilobytes of framework code, if the web already has the functionality?
Well, let’s start with things I learned to love. If you have worked with front-end frameworks, you probably want to skip ahead.
Routers
Routers are pretty useful abstractions of which types of pages exists and what data they can have. It took me a lot of uneasy feelings to replace the plain old <a>
’s in my site with instances of Vue Router’s <router-link>
component.
For those unfamiliar with what this means: in an <a>
you use the href
attribute to say where the link goes. A <router-link>
points to a predefined named route
, and accepts in its to
attribute objects for settings that you invent, as well as query parameters.
So if you’d write an <a>
to go to a page with parameters:
<a href="/search?query=css&category=tutorials">
CSS
</a>
you can instead define it as a <router-link>
, a special component that comes with the Vue Router library:
<router-link :to="{
name: 'SearchResult',
query: {
query: 'css',
category: 'tutorials'
}
}"/>CSS</router-link>
(note this is a difference in the template only, the DOM and accessibility trees will only ever see something like the first)
Either is pretty readable. The router link is more abstract, if you don’t construct the URL yourself, you may make less mistakes and it may be easier for others what’s happening. This is not a client-side framework advantage, of course, you can find similar concepts on the server side, like in Flask routing. Routing is as old as the web itself.
The major advantage isn’t readability, it is that the client-side router ties in with the state your website is in. Because it has a fuller picture, it can make useful decisions like to only reload parts of the page. If you link to the same type of page, it may just update the difference in content. This does come with some focus and scroll management challenges, but that’s for another post.
Real components
Ask five people on a web team what a component is, and you may end up with wildly different definitions. Even in our world where most web teams made components central to their way of working, we struggle to mean the same by the word.
As a front-end developer, I’ve long thought of components as a blob of markup, with optional corresponding style and/or script, all living in the same folder. They would have a shared classname on the root element, which would be unique to instances of that component. Just by being in the same folder, they’d be a component for anyone working with the code. They exist because the team agrees not to clash names.
In declarative JS frameworks like Vue and React, components are defined as components. You define each component in the syntax the framework provides. This is arguably more rigid and meaningful than the ‘being in a same folder’ form of defining components I described above. In the case of Vue’s ‘single file components’, the JS knows of a component its name and possible properties (and their types). For associated scripts, it knows the component’s methods, life cycle hooks (a fancy word for: scripts to execute before or after component markup exists in DOM) and event handlers. For styling, it also knows the component’s associated CSS.
Reactivity
All template languages offer some form of variables:
<h1></h1>
The text inside the h1
becomes whatever header
resolves to. When there is reactivity, variables aren’t resolved once, but will update when they need to. For example, if a component is expanded
, the value of expanded
can update from true
to false
, at a programmatically determined time. Following whatever logic your component has for expanding. In online banking, the value for totalBalance
could be reactive and change on some event that is triggered by money coming into the bank account. The credit card logo URL could be reactive and change from mastercard.svg
to visa.svg
based on what credit card number is being typed in. That sort of thing.
Easy to get started
Both Vue and React have a command line tool that creates a project, with lots of stuff built in (create-react-app
and create-vue-app
). This scared me at first, as I was afraid it would by default put stuff into bundles I would send to my users. It turns out a lot of what create-*-app
setups offer is build and testing tools. My least favourite part of making websites is configuring tooling, a ‘just works’ default is good enough for my needs.
Also, wow, there is some very good documentation for these frameworks, that explain just enough to get started, but also let you dive much deeper if you want or need to. It’s all very welcoming and nice.
Thankfully, I also had great colleagues at work and peers at Fronteers Slack that I could direct questions to, have code reviewed by, et cetera. If you want to get started with a framework, I do recommend joining a Slack group for the framework or your area.
What I liked less
Backwards compatibility
Web standards are extremely backwards compatible. Very rarely do HTML tags get deprecated (hi <blink>
!), almost never do DOM methods or ECMAScript features get taken out. This makes sites built with vanilla code future proof. You will likely have to do security updates to old sites, but it is unlikely you’ll have to write your HTML, because someone decided fieldset
s are no longer supported.
If you’re using a framework, the features you rely on are more likely to disappear. When Vue just came out, one would loop through an object like this:
<ul id="tags">
<li v-repeat="tags">
</li>
</ul>
(example from Vue 0.11)
Then v-for
was introduced. My point is, this sometimes happens. Frameworks come up with better paradigms (see also Hooks and Suspense in React). When your site wants to update to a new version, your team needs to rewrite and know what to rewrite. All of Twitter is like “I’ve rewritten this project X to use Hooks”, but this is not trivial, even less so if it is the complicated codebase of your large corporate client.
There are lots of organisations out there using Angular 1 in projects, and I see them struggle to find developers willing to do the work. They’ve since moved on to newer frameworks. The orgs are stuck with outdated paradigms that are incompatible with the now. For freelance front-end developers in The Netherlands, recruitment calls for a certain bank that went all-in on Angular may sound familiar?
I feel this is a real difference between ‘just’ using web standards and buying into a framework. There is also legacy vanilla code, of course. But if that is, as I described in the introduction, very minimal, few dependency code, maintaining it is consequently less involved.
Different origins
I love the origin story of the web, and the idea that it was further developed at an independent consortium and groups like WHATWG. The origins of the big component frameworks are big corporations like Google (Angular) and Facebook (React). Corporations that are more for profit than for humans. Even-though the projects themselves are open source and have volunteer communities full of great humans.
A false dichotomy?
As I described above, there are lots of things to like about using modern JavaScript frameworks to build websites, and a couple of things to moan about. But let’s look now at how that approach differs from ‘just’ using web standards and vanilla code. Let’s look at how it differs from the approach of which I said it gives us good things like accessibility and performance by default. If there is a difference, because maybe frameworks versus web standards is a false dichotomy?
De-facto standards
Various frameworks from the past have inspired web standards. jQuery’s selector engine Sizzle inspired querySelector
/querySelectorAll
. The modern component frameworks seem to diverge towards standard ways of working, both in syntax patterns and in broader concepts, like routing and reactivity. They are not in specs, but they could inspire new web standards. As far as I’m aware, nobody is standardising concepts like reactivity, but there for example this proposal for declarative routing in Service Workers, and among the new proposals in ECMAScript there are many ideas inspired by how people write framework code, like optional chaining. Still, epic standard battles to make features happen are epic.
Framework-browser collaboration
Framework engineers and browser makers collaborate, said React engineer Andrew Clark:
Our team has a regular weekly meeting with Chrome engineers. Sometimes we meet more than once! We’re collaborating on display locking, main thread scheduling, resource prioritization, and more. Great relationship, much credit to @stubbornella and @shubhie
Chrome PM Nicole Sullivan confirmed this:
My favorite part of my job is collaboration with frameworks. The web platform engineers are beyond excited about the tight feedback loops we’ve developed and are developing. We even added getting framework feedback to our intent to implement process.
I don’t really know of such a direct collaboration between framework authors and the Firefox browser. Or other browsers. Such collaborations are quite different from web standards efforts, but I guess they can lead to improved performance and accessibility for large amounts of users. Less “open” improvements, but improvements.
Lots of vanilla code in frameworks components
If I’m comparing vanilla to frameworks, I should also say that within the framework wrapper, I’ve always ended up using vanilla knowledge. Not only is it still important to know which HTML elements there are and what they mean, because framework-based sites yield accessibility trees just like regular sites do. It’s essential to have a deep understanding of all things JavaScript (for instance, everything on Chris Fernandi’s JS roadmap, because the component frameworks offer just that: a framework to get started. Within that, it’s your choice if you want to solve your problems with your own custom solutions, or use libraries, plug-ins, etc Or, of course, and this is common, do either when it makes sense.
Progressive enhancement
Progressive enhancement and client-side frameworks do seem to be hard to marry, because by default they do their work in the browser. There are plenty of ways to make servers do the initial render, but if we are honest this is not trivial. Regardless of where we render, we can and likely should apply a progressive enhancement way of thinking to a framework-based project. To stretch what progressive enhancement means very far: make no assumptions. Not about your user’s devices, not about their permissions, not about their content blockers, not about any other of their choices.
For example:
- if you ultimately want to plot on a map the cars nearest to a user’s location, you could display a list of car locations initially. When users have location abilities, their browser’s support exposing those through the location API and they have granted your site permissions, then try and do the plotting.
- in a project I worked on, we generated identicons on the client. The process involved the Web Crypto API, to convert strings into hashed strings. In browsers that don’t support Web Crypto, we fell back to a hardcoded hashed string. Browsers that got the full thing showed avatars based on people’s username, so they were unique for each user, browsers all showed non-unique avatars based on the string ‘user’.
- you want to lay out a component of your site using CSS Grid Layout. To the 10-20% of browsers that don’t support this spec, you serve a simple fallback, so that all the content is usable, readable and looks not broken.
Conclusion
I love vanilla code, and things that are invented here. Thoughtful developers that solve a problem at hand. I’ve sometimes felt worried that working with front-end frameworks like React or Vue would be at odds with that mental model. I feel I was probably wrong there, there is a lot of grey between vanilla and frameworks, and people do all sorts of cool things on either side to get to solutions that are accessible and performant. This is great. I am glad I tried a framework and found its features were extremely helpful in creating a consistent interface for my users. My hope is though, that I won’t forget about vanilla. It’s perfectly valid to build a website with no or few dependencies.
Comments, likes & shares
No webmentions about this post yet! (Or I've broken my implementation)