About the author
- Albert Skibinski is a freelance full-stack developer en co-founder at Jafix.
- I write about web development, long bike rides and food!
I was exploring current front-end frameworks and libraries to get a better understanding where we stand and how we got here. I was especially interested in the current capabilities of native web components (in vanilla JS) using web standards and how they compare to these frameworks and libraries. I decided to write up all my notes into this post, maybe they prove useful for somebody.
First, some terminology.
Let me know what you think!
Imperative versus declarative
These are general programming paradigms but in the context of front-end development, a declarative representation of UI has become a standard approach for front-end web development. This offers a way of writing templates/views with data binding and separating the control flow. It's also described as creating UI as a function of state.
Imperative on the other hand is the sequential way of taking steps to complete a task. We all know imperative using basic vanilla JS or jQuery for DOM manipulation. When you are talking about views, you are talking about templates and their engines. And there are several flavours.
These are template engines like mustache which uses innerHTML
and are the least efficient but they used to be popular because DOM manipulation was slow in older browsers. And innerHTML
also has security concerns. But the concept is DOM independent, which could be seen as a pro, especially if we think about server-side rendering (SSR). By the way: Twig is also a string template engine, but in PHP.
Examples which use these are Angular, Alpine, and Vue (Vue only when using DOM templates). In all cases, the templates are parsed which can cause issues. For instance, (Vue developers warn about DOM templates) because they can be unpredictable: the browser renders the markup into a DOM template which is then used by the framework and it might disagree on some things. It's also impossible to do SSR since we have no browser server-side.
JSX is not a template or engine. It's declarative syntax that’s used to express the virtual DOM after it is parsed by babel into JavaScript objects. While JSX may look like HTML templates, it's not. It's not even React-specific (Stencil uses it too) and is used to create components that share markup and logic. This is a better fit for component-based architecture which React started.
Not a template but a technique to effectively update the DOM. The best explanation I found on the virtual DOM is actually a Svelte blog post which explains how React uses this to diff the DOM against the VDOM and make the necessary changes.
Architecture
All three major frameworks (Angular, React, Vue) have converged to the component model and an MVVM (Model-View-ViewModel) architecture.
Note: Depending on the implementation, you might only have a VVM architecture, for example, if you have a simple React site which pulls data directly into the component, without using a data layer like GraphQL.
Components, Custom Elements, and Web Components
The three major frameworks use Components but none of them use web components by default. What do we mean by native web components (or just web components)? Native web components use three main technologies. Although I've seen ES module specification being mentioned as a fourth.
You can just create Custom Elements without shadow DOM and template/slots; it's optional. In fact, many developers just use Custom Elements because the Shadow DOM has challenges of its own (specifically with styling and SSR, more on that below).
The smart people behind Polymer have created two libraries: lit-html
which makes creating declarative templates – UI as function of data – easier (like the frameworks do) but with minimal overhead. And lit-element
which essentially is a base class making it easier to create Web Components. Lit-html feels like JSX but internally it leverages tagged template literals to very efficiently update the DOM. Lit element creates Custom Elements with shadow DOM by default. So it seems like a nice way to create web standard components with data binding, minimal overhead, and a lot of flexibility. In this repo are two simple examples of a lit-html and lit-element.
Another tool is Stencil from the smart people of Ionic. It's not really a framework nor a library but a compiler which creates web standard web components or entire PWAs. It uses JSX and TypeScript and compiles to native JavaScript. This makes it perfect for integration with other frameworks or tools like Storybook, although you can also use Stencil as a component library on its own. Here is my Stencil playground repo.
Svelte isn't really a tool to create Web Components but rather an alternative with a different approach compared to the existing frameworks. I still think it's worth mentioning because it does have some similarities with Stencil as they are both compilers and compile into native JavaScript components which can be used by third parties (or standalone). But Svelte explicitly does not use a virtual DOM. It instead compiles to imperative code that updates the DOM, which may be faster. By default, Svelte does not create Custom Elements but it is possible with an option. Svelte has TypeScript and better support for testing on the roadmap.
What problems are we solving anyway?
So why might we prefer native web components over components provided by the frameworks? Interoperability and reusability using standards. Stencil clearly aims for being the tool to create web components which can be used in various frameworks without any dependencies.
Other issues
SSR and Shadow DOM
You can't declaratively create web components with shadow DOM without JavaScript: it doesn't exist until we customElements.define()
.
Accessibility
Web components should be built with accessibility in mind, just like any other HTML.
Phew
After having spent (probably too much time) exploring Web Components, various frameworks, tools and libraries and upcoming proposals, I feel web components have come a long way since I first played with them in Polymer 1.0 back in 2015.