Frameworkless Web Development
Mišo Mirosavljević
Posted on March 1, 2019
TL;DR: It's never been easier to not use a framework for web development. If you don't want to read quite lengthy intro, jump straight to Frameworkless section to see how not to use framework for creating reusable component, or better put, how to create new HTML elements.
New. Shiny. Modern. Fresh. Recent. Hip. Fast. In.
You, as a human being, are attracted to new things and that can be seen in every part of your life. You probably have a box in your pocket, that you now use for everything, browsing the web, taking photos, taking notes, tracking this and that (someone is tracking you by using it, but that's another story), stay connected and at the same time become alienated from people, and sometimes, just sometimes you use it like first intended, for taking a phone calls. But wait, there is a new pocket box (name taken from honest cell phone commercial), just released, with a higher number in its name and it's worth like someone's monthly salary (or in some countries like someone's 4-5 salaries). You can't plug in a headphones, but hey, you get a notch on a screen. Wow, a notch! They sell you things and features that you don't need and didn't ask for and somehow they get you interested in their product by invent new stupid names that sound catchy. Names like Infinity Display. How stupid you should be to accept name like Infinity Display and actually take that as one of the benefits and selling points for new phones? You can clearly see the end of that display, there it is, on the edge of the phone and it doesn't go to the infinity in any way, well except maybe, in price.
You are willing to pay a lots of money in exchange for losing your privacy and time just to not feel fear of missing out of something that's new and popular. You are paying something that you can't customize even if it belongs to you. Well, you can put silicone case with ears on it and that is sort of customization, but you can't remove bloatware software that comes with it. How would you react if you buy a flat and you can't throw out a couch you are not using and couple of chairs, lamps and wardrobes? Yeah, there are maybe couple of benefits of new phones. But, don't you miss an old days when you didn't have to check every single notifications that pops in, take a photo of every meal you have, or get every news instantly?
You, as a human being, are getting more and more lazy and that's bad. Take for example cars. There was a time, you had to to drop what you carry in your hands and open the trunk manually, by hand. Not only that, there was a time when you had to take a key, put it in the ignition lock and start the car. Or even worse, you had to actually drive your car and be aware of your surroundings and traffic flow. But in the meantime, something changed and still is changing. Instead of putting a key into door lock, you had remote controlled keys. But you are lazy for pressing a button on a remote controlled key, so there is keyless entry now. Do you really need to wave your foot under the trunk to open it? Air suspensions, electronic parking brake, hill-start assist, start-stop system, lane assist, traffic sign cameras, blind spot sensors, adaptive cruise control, back-up cameras, 360 degree view, cameras and sensors for whatnot, driving assistance, collision mitigating system, self parking cars, self driving cars, etc. Did you ask for all those stuff because you are lazy or car manufacturers imposed on you all that and make you lazy?
Do you really need to give someone a few tens of thousands euros and more, every couple of years for a new car that you would use for getting from point A to point B? I know you can't compare let's say Mercedes-Benz and Dacia even they both use Renault's engines in their cars. But the point is that is it worth to give such amount of money for bunch of sensors that you didn't ask for in the first place. When did it become such pain for you to turn on and off windscreen wipers, so you need rain sensors? Yeah, there are some new stuff in cars, that are more or less helpful, but in the end it's harder, complex, more expensive to maintain and there is more and more stuff that could go wrong. Don't you miss the old days of driving a car and having control over it, instead of a computer? Or maybe you can't wait for self driving car, so you can stare at your pocket box while driving a car, well, not driving a car, better to say riding in a car?
Frameworks
Since you suffer from the shiny object syndrome, have FOMO and you are getting more and more lazy it's easy for some companies and people to convince you that you need something that you actually don't need. Under assumption that you as a developer are lazy and that you need something to ease the job, they created numerous frameworks and libraries telling you that you shouldn't spend one day without working with them. Developers are lazy as any other human being. I read that Bill Gates said something like: "I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it." And laziness is a good thing. But maybe there are different types of laziness. Maybe same person can be lazy and diligent. Developers are certainly not lazy when it comes to learning. They need to learn all those architectures, technologies, languages.
You should not reinvent the wheel, you should use proven frameworks and libraries for specific purpose, you should focus on business logic and not implementing things already implemented by someone else. That sentence is true and not true at the same time. You probably shouldn't write your implementation of some specific cryptography algorithm. But you should be able to write simple function to pad strings. Don't you remember how one developer broke Node, Babel and thousands of projects in 11 lines of JavaScript? I'm sure you have been in situation where you get a new project. In that project some cool HTTP library is used. Before using that library, you have have to invest time and effort to read through API, see some code examples, etc. You finish that project and start another one. Other project have some other HTTP library, that is popular in those days. What you do? You have to invest time and effort again to learn to do same stuff that you already know how to do just in a different way. Same thing is with frameworks.
Learning new, over engineered, ways to do same simple stuff, display data (in the most cases it's about delivering content) and take data from user using web browser. In my eyes that's constantly repeating yourself and reinventing the wheel. Learn X framework, its component, its router, its two-way binding, its template system, its way of doing HTTP calls. Then go to another project and learn how to do that using Y framework, then Z framework. And don't be fooled that by name Z framework it's not the last one, for sure. And then you find yourself thinking how to do simplest task, how to handle click event. Is it element.addEventListener('click', ...)
or maybe onclick
or ng-click
or (click)
or v-on:click
or @click
or onClick
? Are quotes after equals sign needed, is needed to have curly brackets or not, what is the argument value, function, function reference, string?
Sometimes, laziness and shiny object syndrome are driving you to more and more work. You are lazy to create some component or router, so you will invest your time and effort to learn and use whole framework because of that. But, there is a new framework that does same things different, but of course better. It's made in this company that tracks what people do on the Internet and sell those information to advertising companies, so it must be great. Just to be clear, I'm not saying that, one framework is bad just because specific company created it. What I want to say is that it was created in a profitable company, probably for specific use case that they had and that they can do whatever they want with it. They can stop working on it, they can make breaking changes between versions, etc., and you are in a way stuck.
Did you read any newsletter about front-end technologies lately? You get headlines like: 4 Awesome Things You Can Do with the Framework.js CLI, the-best-datatable: A Feature-Rich Data-Table Component for Framework2, some-content-loader: SVG-based 'Loading Placeholder' Component, This Awesome XFramework Component Is what You waited for your whole life. After all that you may ask yourself: "Am I doing it wrong here?"
I need a feature rich data table in my app but I'm using React instead of Angular. Can I use Angular component in React? Maybe there is a React component, but does it have all functionalities like Angular component. Did I made a mistake choosing React in the first place, should I rewrite my app using Angular? But Angular changed its code base and introduce new version that's not compatible with old versions. What if they do it again? What if React do something like that? I also need content loader. Can I integrate Vue component in application written in React? I see there is more and more posts about Vue, should I change my focus from React to Vue, and completely ignore Angular? But what about Backbone, Knockout, Ember, Aurelia, ...? Should I change to one of them? Clearly there is a big need for Specific Framework Engineers.
Nowadays, in all areas of your life you have this thing called The Paradox of Choice. Same is for software development. With all those choices of libraries, frameworks, toolkits you can never be truly satisfied with a chosen option. You will always think if other library is faster, is this old, is that better because there is more stars on GitHub, is that framework lighter for browser, etc.
You don't need a framework. You need a painting, not a frame.
— Klaus Kinski
When you first decide to use a frame you are limited by frame's boundaries (shape and size), properties and behavior. Those kind of limitations are sometimes good, e.g., when your kid wants a new toy, you should probably create boundary like price limit. But when you composing a song you probably shouldn't put boundary like number of chords.
When you are using framework you are boxed to think and act how its creators wants you to think. That might be fine to you, but you should think twice before choosing a such framework. Do you like every part of framework, what libraries it uses, how much resources should you spent learning it, are you ready to face a fact it might get dead or obsolete in couple of years, what if company make some breaking changes, are you ready for updating to a newer version of same framework every couple months, are you choosing framework based on your need or do you fit your needs based on framework you picked, are you choosing it just because everyone talks about it right now, are you ready to rewrite your app if needed?
Frameworks add some benefits, at first, but also add lots of complexities, and as, one of the best speakers today, in the industry, says Do not walk away from Complexity, Run. You might think at first that frameworks make you faster but that's not always needed for a win. Sometimes, for win you need a focus and persistence and not getting distracted in the middle of way like shown in Tortoise vs. Hare
To see that I'm not completely against frameworks I'm going to suggest you one. Vanilla JS, it's clean and easy to install framework and most lightweight framework available anywhere.
Frameworkless
In English, the suffix -less means "without". So, if someone is fearless it means it is brave, without fear. If something is serverless it means that there is no server, except that, in reality, there is a server and the word is used to describe an architectural concept. Frameworkless means without framework, in this case you should #UseThePlatform for web development.
Approachable, Versatile, Performant, Declarative, Component-Based, Learn Once - Write Anywhere, Speed & Performance, Incredible Tooling, Loved by Millions, Code Generation, Code Splitting, Less Code, Templates, CLIs, Animation. Those are some catchphrases that glorify some of the frameworks and libraries out there. Can you guess which framework uses which catchphrase? Sounds very similar and convincive, right?
Companies give high accuracy ratings to descriptions of theirs framework features that supposedly are tailored specifically to it, that are in fact vague and general enough to apply to a wide range of other frameworks.
— Paraphrase of the part of first sentence in article about Barnum Effect
All those front-end framework, libraries and toolkits that existed and will die have one thing in common. Guess what? JavaScript, evolving standard for front-end development. Frameworks and libraries can't do for you nothing that can't be done in plain old JavaScript, that's not supported in a browser or there is no polyfill for it. Most of mentioned catchphrases, if not all, are also applicable to pure JavaScript.
JavaScript is evolved to have Web Components. A universal way of creating new reusable HTML tags. And if done right you could use such new HTML tag anywhere you want, without external dependencies on specific framework. If you create let's say, in a Framework, component <a-menu></a-menu>
where exactly could you reuse it? Only in your application since you can't change items in a menu. But if you create it with some interface (to accept some attributes) like menu items then you could use it in other applications written in the same Framework. It's hard if not impossible to use component from one framework in another framework. But if you create it with web standards, e.g., like <a-menu><a-menu-item></a-menu-item></a-menu>
, same as how <select><option></option></select>
works, then you are capable of using such component with your specific menu items anywhere you like.
I said you could use new elements anywhere you want. Well, that's not entirely true, but it will be. You could use it only in browsers that's supporting Web Components standards (Shadow DOM, Custom elements, HTML template element and ES Modules) and for other browsers you could use polyfills to mimic standard implementation. When browsers implement support, and they will, you could simply remove polyfill, and you know there is nothing better than removing unnecessary stuff. In the end it will be in every browser natively and no framework will ever achieve that.
I won't go in detail of Web Components standards, you could find that easily on the web I will just give simple example of use. Instead of using HTML template element, I will create elements programmatically. So I end up using only Custom element spec for defining new HTML tag by extending existing HTMLElement
and creating Shadow DOM for encapsulation of style and markup in new Web Component.
Can you remember when is the last time you saw examples of front-end code that doesn't starts with npm install framework-with-bunch-of-dependencies-which-size-is-like-200MB
? Well, this doesn't, everything you need is in a browser already. You don't need CLI, you don't need package manager, or slightly faster package manager that you installed using previously installed package manager. You don't need numerous complex config files, you don't need any module bundler, build tool, preprocessor, compiler and whatnot. Only external dependency is Web Components polyfill loader and its simple script
element. It dynamically loads the minimum polyfill bundle, using feature detection, with purpose to enable this example to work in browsers that doesn't support standard implementation yet.
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-loader.js"></script>
If you wan to use component you just use it like any other HTML element. Write its name like a tag and define desired attribute values. Custom Web Component must include a dash in its name, so browser can differentiate it from existing elements. Since custom element can't be self-closing, you are required to write closing tag.
<cn-jokes headerText="Three random Chuck Norris jokes" count="3"></cn-jokes>
<cn-jokes></cn-jokes>
First things you should do, when creating Web Component, is to define it in windows.customElements
. In that way you let browser know about new element. Element is created by extending HTMLElement
which contains specific DOM API.
window.customElements.define('cn-jokes', class extends HTMLElement {})
Class should define constructor and in constructor call to super()
must be made in first line. It's not required for every component, but probably it's a good idea to instruct component to create Shadow DOM. By using Shadow DOM you get benefit of isolated and self-contained DOM and CSS. Elements and style in Shadow DOM can't be accessed by accident. For example if you have h1
element in your Shadow DOM and someone write custom CSS that change styles of h1
elements on a page, you can be safe that those changes won't affect styles in your component.
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.jokesUrl = 'https://api.icndb.com/jokes/'
this.shadowRoot.appendChild(this.createStyleTag())
this.refreshBtn = document.createElement('button')
this.refreshBtn.textContent = '\u21bb'
this.h1 = document.createElement('h1')
this.h1.textContent = this.getAttribute('headerText') || 'Random Chuck Norris joke'
this.header = document.createElement('header')
this.header.appendChild(this.h1)
this.header.appendChild(this.refreshBtn)
this.shadowRoot.appendChild(this.header)
}
You can think of constructor as lifecycle hook that is called when element is created and it's great place for creating structure of component. Other hooks are connectedCallback
and disconnectedCallback
. They are called every time element is inserted in DOM and removed from DOM, respectively. They are great place for fetching resources, attaching event listeners and doing clean up.
connectedCallback() {
this.refreshBtn.addEventListener('click', () => this.fetchAndDisplayResult())
this.fetchAndDisplayResult()
}
disconnectedCallback() {
this.refreshBtn.removeEventListener('click')
}
Attributes are used for sending data in custom element. Another useful callback (not shown in example) that can be used is attributeChangedCallback
. It's called whenever elements attribute, defined in static observedAttributes
, is updated, removed or added. If you want to send data from custom element you use CustomEvents
this.dispatchEvent(new CustomEvent('cn-jokes-load', {
bubbles: true,
composed: true,
detail: {
jokes: jokes
}
}))
Styling Web Components can be done in the same way like any other HTML element, using CSS. Outside styles for element will overwrite styles defined in shadow DOM. Styling internal elements, in Shadow DOM, from outside, can be done using CSS Variables that are defined in Web Component.
body {
font-family: Arial, Helvetica, sans-serif
}
cn-jokes {
--quote-font-size: 19px;
}
cn-jokes:nth-of-type(2) {
width: 50%;
--header-bg-color: #FF1744;
--quote-font-size: 12px;
}
Styling Shadow DOM inside element is done by CSS that is scoped to it.
createStyleTag() {
const styleTag = document.createElement('style')
styleTag.textContent = `
:host {
display: block;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
/* this is for browsers which does not support :host https://stackoverflow.com/questions/25468701/why-does-the-host-selector-only-work-in-chrome-with-platform-js#33475684 */
cn-jokes {
display: block;
-moz-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
-webkit-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
header {
align-items: center;
background-color: var(--header-bg-color, #00B8D4);
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 16px;
}
h1 {
color: white;
font-size: 18px;
}
button {
background: none;
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
height: 32px;
width: 32px;
}
button:hover {
background-color: rgba(158,158,158,.5)
}
button:disabled {
cursor: wait;
-webkit-animation: rotation 1s infinite linear;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(359deg);
}
}
blockquote {
font-size: var(--quote-font-size, 14px);
padding-bottom: 10px;
}
:focus {outline:none;}
::-moz-focus-inner {border:0;}
`
return styleTag
}
When creating your own component you are losing the need for CSS frameworks and libraries, you don't need let's say Bootstrap component and its CSS and JavaScript. When using a CSS Flexible Box Layout or CSS Grid Layout you don't need CSS framework for layouts. Also, since CSS styles are scoped in Shadow DOM, your selectors becomes much more simpler. You see in the styles I don't have any complex, nested selector with IDs, classes, etc.
When creating your own component you should stop and think if the new component is really needed. Do you really need a new button component that wraps button
which already can be styled as you wish.
You may say that it adds more semantic, but does it really? It's still button
with class
attribute. You should take advantage of existing elements. Why would you create your custom dialog when you could use dialog element? This is perfectly fine HTML.
<dialog>
<header>Dialog header</header>
<div>Dialog content</div>
<footer>Dialog footer</footer>
</dialog>
When creating your own component keep it clean and simple. I know HTML is computer readable and browsers will deal with it quite efficient but isn't it nice to see clean HTML without unnecessary markup. Take for example this code snippet
Do you really need li
as a container and than dv-driver-communications-flyout-item
as a container and then article
as a container and than div.article-content
as another container?
I know, it can get hard to create complicated components like data tables, but that stuff is complicated in any other framework also, hence you use component created by someone else. You could use existing Web Components found on https://www.webcomponents.org/. Sometimes you just need to add some dependencies.
It's your choice
When some company releases smartphone with ten cameras, someone will think: "That's the feature in a smartphone I've been waiting my whole life, I must buy it, plus I really need 32GB of RAM that new model has". When some company makes car with one more color choice in interior ambient lighting or one more mode in adjusting massage seats someone will think: "My three year old car is useless. I want to drive with that new shade of rose inside, while feeling one hundred massaging hands in my seat". Those types of people just wants to be first at everything and thats not bad thing at all. Companies live because of people who wants latest and greatest things.
When some company releases new framework there will be people willing to learn it and use it. Again, I'm not saying you shouldn't learn and use frameworks. Framework is good option in some cases. If you are small start-up, that needs some fast prototyping, you know that the application will last couple of months than you should probably use framework. But if you are company/person that creates stuff that last, doesn't change a lot then you should probably be in control of your work and minimize dependencies that could cause problems, even though it means you will create some kind of in-house framework.
After reading about Web Components you may say that Web Components are new shiny technology. And answer is probably yes, but it's standard technology. It won't be changed that often as other libraries and frameworks and there won't be breaking changes. You may say it would be boring to learn only one thing and stick to standards. Maybe, but boring is what get you to sleep at night. Boring is what get you more time with your family and friends, doing something other than learning every new framework. When using "old" standard technology, that's stable and proven, with known limitations and possibilities you don't have fear that something will break easily. You may say that it would be lots of code and that's true, but you are professional problem solver, you'll find a way to make it all and find a joy when you do it.
Posted on March 1, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.