This article is all about WebComponents, a new HTML specification for a component-based application architecture on the web. We’ll first take a brief look at the history of web development and then explore what a component-based architecture actually means.
We’ll explore how the WebComponent specification describes a new way for browsers to implement this architecture without needing framework support, and then ways in which existing frameworks can leverage this support to provide an enhanced experience for developers. Along the way we’ll take a detailed look at the actual specification as well as current support by some actual frameworks.
Finally, we’ll see what still needs to be done, as well as some speculation about the future of this emerging standard.
The first websites were simply hosted on servers and consisted of entirely static content. Pages were written in HTML and contained other resources such as images which were referenced within this HTML. Pages linked to each other via hyperlinks; when a link was clicked, the browser would fetch the referenced page and then replace the existing page, then load all referenced resources as necessary for this new page.
As users and developers demanded more from websites, server-side rendering technologies were created. Initially the Common Gateway Interface (CGI) was used for this purpose. CGI is a way of running executables where the standard input is the HTTP request body and anything written to standard output becomes the HTTP response body. This allowed CGI executables to be written in any language, however languages such as Perl prevailed. To circumvent some of the limitations of CGI, other languages and associated web server interfaces were developed, such as PHP. All of these technologies allowed pages to become dynamic, with content built by the server during each request rather than being static.
But why stop there? Why not have a “application” that runs entirely in a single page in the browser? All communications would be done via Ajax technologies allowing for a much smoother user experience. This architecture is known as a Single-Page Application (SPA) and is the current state of the art in web app technology.
As well as SPAs, web app developers are also embracing new approaches to managing application state and architecture. For state management, Flux is gaining popularity as a simpler alternative to Model-View-Controller (MVC) for web apps. For architecture, the component-based architecture is rapidly gaining traction, and is the main focus of this article.
A component-based architecture uses the concept of a “component” as the building block of an application. A component is a reusable element that should have one well-defined role. This is nothing new, for example HTML is full of components: headers, paragraphs, line breaks, images, etc. What is new however — at least for the web — is the idea of allowing developers to create their own components. For example, we could create a
UserAvatar component which is responsible for displaying a user’s profile photo, their name, and providing a link to their profile page. This component should encapsulate all of the necessary mark-up and styles for displaying this content as well as events for interacting with is, such as clicking a link within it. Once defined, this component can then be used anywhere throughout the app.
Components should also be able to contain child components, just like most regular HTML elements, in order to create a component hierarchy. Using a Flux architecture, data is passed down from parent to child via component attributes (sometimes known as “props”) and events should bubble back up when triggered.
It should be no surprise that the most popular frameworks currently provide this component model. Currently, three of the most popular frameworks are Angular, React.js and Vue.js. It’s important to note however that these frameworks offer, some to lesser or greater extents, more functionality over just the component model.
A recent trend amongst frameworks has been the move from imperative to declarative APIs. But what does this mean exactly? Consider the task of adding up a list of numbers. An imperative program would define precisely how this operation should be performed, for example: –
At the end of this process, “sum” contains the sum of all values in the list. A declarative API on the other hand provides abstract ways of performing the same operation, such as something like this: –
The conceptual difference here is that with an imperative API we need to instruct the computer how to perform a task, and with a declarative API we only need to tell it what to do. With a declarative approach, we delegate the how responsibility to the library itself, trusting that it knows a good way of instructing the computer. This has a few advantages: –
Most modern web frameworks use the declarative approach. A good example of this shift is the move from jQuery — which itself uses an imperative API — to a more modern framework such as React.js.
Now we’re all up to speed, let’s introduce WebComponents.
As mentioned above, each of the component-based frameworks are reinventing the wheel when it comes to the component model. While each framework offers something different on top of this, they all implement the basic component model in largely the same way. This is not such a good thing as improvements in one will not directly affect another, and of course there’s a lot of duplication of effort between them.
Due to this separation of implementations, a “component” is not really a singular concept; a React.js component is very different from an Angular component for example in its implementation. This means that a component written in React could not easily be added to an Angular application as Angular has no method of creating and using this React component itself.
The WebComponent custom element specification therefore takes this concept of a component and removes it from the framework, instead placing the implementation within the browser. This has a number of advantages: –
Another important benefit comes from improved semantics.
When discussing semantics on the web, we usually refer to how the meaning of information can be portrayed by the markup. For example, imagine a list of films. Currently this list might be contained within an OL element (ordered list), with each film inhabiting an LI (list item) element. So far so good. Within each LI, the film’s title, production year, director name and summary need to be displayed. We might choose to display the title in a header element (H1, H2, etc.), the director’s name and production year in SPANs, and the summary in a P (paragraph) element.
Combined with relevant CSS styles, this list of movies can be made to look good to users. However, the semantics of this list are not as clear as they could be. Imagine that we want to take this list of films and analyse it, perhaps adding the items to a database. The list itself is clearly defined in the markup (OL with LI children) but after this things can become a little more complicated. The film title is marked fairly well by the header tag, but the rest of the information is not so well defined. The page designer decided to place the director’s name and production year in two SPAN elements, however there’s nothing about those elements that tells us specifically what the text within means semantically. For example, a SPAN containing “1983” could be a year, but could also be a rating of some kind or a year representing a different time (e.g. home video release date). The director’s name could be interpreted as the lead actor’s name for example.
It’s common to attach classes to elements for styling and sometimes semantic purposes. For example, the SPAN containing the director’s name could have the class “director-name”. This certainly helps, however this is not always the case; sometimes classes are only used for styling such as “smaller-bold”, which does not provide any semantic meaning.
Now consider the same list using custom elements. The OL could be replaced by a tag called “film-list”. The LI could be “film”. The title could be “film-title”, and so on. When viewing this page, users would see no difference, however when analysed the semantics are a lot clearer. The information contained within each custom elements is now given a much greater meaning simply by the use of these custom elements.
It’s important to note that frameworks generally remove this semantic information as part of the processing of the page. While custom elements can be defined in the framework, the rendering pipeline reduces these custom elements back to standard HTML elements, meaning that semantic information is lost.
The main WebComponent specification is split into a number of smaller specifications, all of which need to be implemented by a browser in order for it to claim full compatibility.
Browser support is improving all the time and as WebComponents are becoming a web standard we can expect support to improve with each new version. For browsers that don’t yet support one or more of the specifications, polyfills exist which allow them to use WebComponents, albeit with reduced performance.
The custom elements specification is the real focus of this article as it is the specification that allows developers to define custom HTML elements without the need of a framework.
HTMLElement base class) which allows a developer to specify how the element should be rendered and how it should behave, based on input attributes and events. Once defined, those elements can then be used like any other regular HTML element within the rest of the application. The definitions can of course then be utilised in other applications too, as well as in applications that use different frameworks. As an example of this, check out WebComponents.org for a library of components that are ready to use.
Shadow DOM is an important specification that allows custom elements to exist. The regular DOM (Document Object Model) is created by the browser when displaying a page and is a hierarchical structure containing all of the page elements. The Shadow DOM encapsulates a custom element’s internal element structure within the element, allowing a component to separate its own DOM from the rest of the document. This becomes important for re-usability and so that components can look and work correctly regardless of their containing page, so that event handlers, styles and other DOM attributes do not seep through when not wanted.
One of the benefits of Shadow DOM is the encapsulation of a component’s styles. As mentioned above, existing frameworks employ a range of workarounds in order to implement this encapsulation in their own components, however Shadow DOM is implemented directly by the browser and is therefore more efficient causes workarounds to become superfluous.
The problem currently comes from an inherent property of CSS, namely the ‘C’ in ‘CSS’: “cascading”. Styles defined in CSS, by design, cascade down to child components. A DIV element with a “color” attribute of “green” causes contained text to be displayed in green. This style then cascades to any other element contained within the DIV, unless that element explicitly overrides the “color” attribute. This is of course desirable in most cases and is what gives CSS its power, however for custom elements we sometimes do not want page styles to affect the component styles. For example, a custom element might want to inherit the parent “color” attribute, but not the padding and margin which would prevent the custom element from being displayed correctly.
The WebComponents specification also introduces the
slot elements to HTML. Both of these new elements allow re-use of markup by building a template which can then be used within a custom element.
A template is a container which can contain any other elements and which can be given a specific ID. These templates can then be inserted into a custom element by ID, and the browser will automatically insert all inner elements automatically. This allows common markup to be reused easily without duplication.
A template on its own can only contain static content; to display dynamic content the
slot element is required. Slots can be inserted into a template and are referenced by name and can also contain default content. After defining a
slot element within a template, elements can be inserted into that slot (known as slotting an element) by inserting the element to be slotted inside the custom element itself and setting the element’s
slot attribute to the name of the desired slot.
For example, imagine that a custom element called
my-component has a slot defined with the name
username. The slot can be filled with a child element like so: –
When rendering the above element, the browser would replace the
slot element in the template with the
Comparing this to React for example, this is very similar to the
For more details of templates and slots, see this MDN article.
While WebComponents can be used without any library support currently, there are reasons why the use of a WebComponents library might make sense: –
A few libraries currently exist for using WebComponents, two of which will be discussed here. For more details, check out the Libraries page on WebComponents.org.
Polymer.js is probably the most well-known WebComponents library and is built by Google.
The implementation of custom elements in Polymer.js uses
LitElement, a new base class for declaring custom elements. This new class provides a declarative API on top of the default
HTMLElement which is provided by the browser, and is a sub-class thereof. So instead of declaring a custom element as a sub-class of
HTMLElement, declaring it as a sub-class of
LitElement instead provides these new features. LitElement also uses
lit-html to provide HTML templates. While LitElement is part of Polymer.js, the class follows the WebComponent standard meaning that LitElements can be used with other frameworks, just as standard HTMLElement-based elements, or without the rest of Polymer.js.
Polmer.js also provides a set of polyfills to make sure that web applications written using Polymer.js can run on browsers without full WebComponents support.
Like Polymer.js, Stencil presents the developer with a declarative API on top of the native WebComponents imperative API. Stencil on the other hand is a compiler which takes components written using this new API and compiles them to native custom elements, which can then be used with any other framework. This means that Stencil.js only requires a very small runtime component, which includes a dynamic loader for polyfills based on the host browser.
Web application frameworks can still be used if necessary, and indeed frameworks such as Angular.js offer more than is encapsulated by the WebComponents specification. However, re-use of components between frameworks means that less work will have to be re-implemented for each, and frameworks might even get smaller as they will only need to provide additional functionality as necessary.
Existing frameworks might also decide to integrate WebComponents directly instead of implementing their own versions of templating, the component model, etc. Such an example is Angular Elements. Some libraries have stated that their component model is sufficiently different from custom elements meaning that they will continue to use their own implementation, however they will still allow interoperability (React.js). Please check custom-elements-everywhere.com for up-to-date details on framework support for custom elements.
The WebComponents specification is an exciting addition to the HTML standards, and one that will surely see increasing use within the growing area of web and progressive web applications.