Web Components: a future-proof structure

Matteo Pietro Dazzi
gft-engineering
Published in
6 min readJan 31, 2020

--

Photo by Matt Artz on Unsplash

Before starting…

I would like to introduce the basic definitions in order to make you more comfortable with the rest of the article:

  • Web Components: is a suite of web technologies that allows to create reusable components.
  • Custom elements: web standard that allows you to register a new HTML tag that will encapsulate some functionality.
  • Shadow DOM: is an API defined by the W3C standard and used to encapsulate inside an element the style and the structure of the Web Component. You can think of shadow DOM as a scoped subtree inside an element.
  • HTML Templates: the HTML Content Template (<template>) element is a mechanism for holding HTML that is not to be rendered immediately when a page is loaded, but may be instantiated subsequently during runtime using JavaScript.

Why should I use it?

This question deserves a simple answer: because it is a web standard.

As opposed to the UI Components that you can create with Angular, React or Vue, the web standard defines a contract that must be respected by every browser that wants to be considered W3C Compliant. It means that your components will always be future-proof, regardless of the way that you implement them or the framework that you want/don’t want to use.

Web Components are supported natively in every major browser.

So…what is the purpose of this article?

The aim of this paper is to suggest a structure for your Web Components that is based on Separation of Concerns; in particular we will see how to separate: model, business logic, style and template in order to permit the development of a single UI Component, in parallel, between several people.

Ok, I believe you! Where do we start?

First of all you should make an important decision about how to create your components: vanilla or with a library?

Vanilla

With vanilla components, you directly use the functions provided by JavaScript language with one (big?) side effect: to define a new component you have to write a lot of boilerplate code.

Libraries

On the other side, libraries help you to reduce it and keep your logic cleaner. Also, each library has a rendering technique in order to render, at change of attributes or properties, only the interested component part. I suggest you a couple of them:

  • LitElement: made by the Polymer Team and supported by Google.
  • Stencil: made by the Ionic team.

Once you’ve chosen the one that fits your needs, keep in mind that the proposed structure does not depend on this decision, even if the example is made with LitElement.

Let’s start!

NOTE: all the code will be available on my GitHub profile. To execute it, you need only to run:

npm install
npm start

I’ll split it into several branches, one for each step that we make.

First implementation

In this case study, I would like to make a Web Component for a toggle. For the reason of time I’ll take and modify the toggle provided here by W3Schools. You can see the result here; let’s analyse what has been done:

  • created a new npm project.
  • configured the dependencies and devDependencies in the package.json.
  • configured the rollup module bundler in three different ways:
  1. live for local development.
  2. dev for distribution without compressions.
  3. prod for production-ready component.

But…I want you to focus on the component source code, where we can distinguish four different blocks:

  • The properties and events definition:
Properties and events definition
  • The styles implementation:
Styles implementation
  • The template implementation:
Template implementation
  • The business logic implementation:
Business logic implementation

As you can see, we have too many lines of code for a single Typescript file, because we are inserting blocks of different kind in a single place.

We must refactor this!

Solution #1: split everything into three different files

You can find the first solution here; as you can see, the general structure of the project wasn’t changed. What’s new in this solution is the content of the toggle-switch folder: instead of a single file, everything is splitted in 3 different sources:

Can you tell me the problems of this solution? “It’s horrible!” is a valid answer 😄

In my opinion, we still have:

  • definitions and business logic in the same file.
  • 2 Typescript classes made with the wrong purpose (CSS and HTML).
  • public access modifier, to keep the methods and fields accessible in the template class.
  • the ToggleSwitch class that “route” the calls of the styles() and render() methods to the classes that implements them.

Solution #2: improve it again!

With the new solution provided, we fixed three issues using a simple technique: the inheritance. Thanks to the new file toggle-switch-base.ts, we are now able to split definitions from business logic and create the proper inheritance chain.

To better understand this solution, here is a picture that represents the inheritance cascade:

Inheritance cascade of the second solution

In this case, we achieved the “1 element for each block” goal, but to make it possible we had changed the entry point of each rollup configuration: from ToggleSwitch class to ToggleSwitchTemplate class.

Why did we do it? Because the ToggleSwitchTemplate class is the one in which every definition and implementation converge. It’s not always that easy to reach this situation: as the number of the classes increases, you have a more complex cascade to maintain and sometimes it can be hard to find the proper inheritance chain; the final result can be a total mess.

Also, remember that the problem “2 Typescript’s classes made with the wrong purpose (CSS and HTML)” is still here.

The rest of the project wasn’t changed.

Solution #3: the final stage

For the final stage, we will mix all the previous solutions with the addition of a fundamental piece: a proper rollup configuration and plugins that can be used to bundle together Typescript source files, HTML templates and SCSS stylesheets.

Take a look at the master branch, where you’ll find the last solution that we’re going to discuss.

First of all, we have to find the right plugins and include them in the package.json; I suggest:

After that, we have to edit all the rollup configurations in order to include the plugins and make them work.

Now, we are ready to include templates and styles as external files!

In the src folder, we added:

From the previous solution, we changed:

  • toggle-switch-base.ts: this class extends LitElement and provides the properties and events definition. Here we also place boilerplate code as:
Definition of tag name
  • toggle-switch.ts: that inherit all the fields from ToggleSwitchBase class, implements the business logic and the render() method.

NOTE: in this method we pass to the template module the html function used for rendering and the current instance of the class in order to do data bindings.

Conclusions

To sum up, with the latest refactor we divided our component into four blocks of the right kind, each of which can be implemented by separate developers without any strong or complex inheritance chain to follow; for example we can create a team with:

  • a developer that define the properties, events and business logic
  • a designer that create the template and styles

I want to leave you with one question to make you think: with this structure, is it possible to create a Web Component generator, using a definition language as YML?

FAQ

  • Q: Why did you choose Rollup as bundler?

A: Because I think it is simpler to configure than Webpack.

  • Q: Why you preferred LitElement?

Because I observed that its build phase is more customisable than Stencil.

--

--