From React to Web Components with Lit-HTML

The journey started when updating my portfolio site from react 16 to 18. After half an hour everything worked except for all the SVG imports. Two hours later I got frustrated and began to think utopia, as in minimising dependencies with web components…

Choices, choices

Native web component vs a utils library. Build system or not, typescript vs one Les dependency….. in the end I chose for: Lit-html, typescript and Vite for the build system.

My expectations/hopes are that in a couple years lit-html can be removed, typescript will remain and that I may need to look for another build system.

Choices in Lit-HTML

I made the choice to follow lit-html and declare the styles in the components with a single parent css file for the variables and baselines.
It is possible to render the components in the Light-dom with the cost of leaking CSS scope. With that downside in mind and that SEO seems too crawl shadow-dom I chose the latter.

The process

The process to convert a react component to a lit-html component was actually rather straightforward. Copy paste jsx, change props to @property, and change className for class. Especially className was so familiair for me that I forgot al lot. Web components with Lit-HTML have a lot in common with React before hooks. For example binding the this scope, to event handlers when using events.

this._handleChange = this._handleChange.bind(this);

Skill list in React

import React from 'react';
import { FiCheck } from 'react-icons/fi';

const SkillList = ({
    listData
})=> {
    const listItems = listData.map((item, index) => {
        return (
            <li className="SkilllListItem" key={index}>
                <FiCheck/>
                <span className="SkillListItemLabel">{item}</span>
            </li>
        )
      });

  return (
    <ul className="SkilllList">
      {listItems}
    </ul>
  )
}

export default SkillList;

Skill list in Lit-HTML

import { LitElement, html } from "lit";
import { map } from "lit/directives/map.js";
import { customElement, property } from "lit/decorators.js";

@customElement("portfolio-skills")
export class PortfolioSkills extends LitElement {
  @property()
  skills: string[] = [];

  protected render() {
    return html`
      <ul class="SkilllList">
        ${map(
          this.skills,
          (skill) => html`
            <li class="SkilllListItem">
              <svg
                stroke="currentColor"
                fill="none"
                stroke-width="2"
                viewBox="0 0 24 24"
                stroke-linecap="round"
                stroke-linejoin="round"
                height="1em"
                width="1em"
              >
                <polyline points="20 6 9 17 4 12"></polyline>
              </svg>
              <span class="SkillListItemLabel">${skill}</span>
            </li>
          `
        )}
      </ul>
    `;
  }
}

The webcomponent can than be used in html like. The . before skill makes it clear for lit-html that it is a property and not a default attribute .

<portfolio-skills 
    .skills="['React', 'HTML', 'Web Components']"
></portfolio-skill>

As can be seen above the resemblance between React and Lit-HTML is striking. I think Lit-HTML is a bit more verbose, hence that is a price I’m willing to pay since is (more) native, thus more robust in the long term.

Testing

Testing is workable, it is easy to test snapshots, properties, events and such. However the tooling (@open-wc/testing) feel a bit less mature than in the React ecosystem.

However when combined with reliable external libraries like sinon and other test helpers it should be possible to achieve the same coverage

Test example

import { html, fixture, expect } from "@open-wc/testing";

import { PortfolioSkills } from "../components/portfolio-grid/portfolio-skills";

describe("PortfolioSkills", () => {
  it("should display a list of 3 skills", async () => {
    const el: PortfolioSkills = await fixture(
      html`
        <portfolio-skills
          .skills="${["react", "Java", "Springboot"]}"
        ></portfolio-skills>
      `
    );

    expect(el).to.exist;
    expect(el.skills).to.have.length(3);
    expect(el.skills[2]).to.contain("Springboot");
  });
});

Thoughts

After decades in working in the frontend business I would be more than happy if web components became the defacto standard. I am aware of the fact that this could prevent fast innovation, since it will be locked into browser. Nevertheless, I think it the end it will help the industry especially for new developers if the tooling moved more slowly and durable.
learning frameworks becomes more easily if you have had to learn a few. The last few years I became a full stack Java developer with a focus on the frontend, and I came to appreciate the slower pace and the robust ecosystem of Java. Web components could be the start of the same robust and durable eco system for frontend.

The current code is still dependent on Lit-HTLML, TypeScript , both seem reliable enough to be there is a couple of years. Still for simple projects it would be nice to not depend on dependencies besides browsers.

Circular menu webcomponent

I have dabbled, some 4 years ago with webcomponents, now seems to be the time to update my knowledge about it. With the circular menu webcomponent it will be possible to wrap regular links in the custom webcomponent tags, which will than be transformed to a circular menu on menu button click.

DEMO: https://www.wonderolie.nl/filebox/circular-menu/

CODE: https://github.com/jeroenoliemans/circular-menu.git

example of circular menus
the component distributes the menu items on a semi-circle

Research direction

I know bits about shadow-html and such, but for SEO perspective I want the links to be visible as child elements somehow. lets research first.

Goal to reach

The minimum component which should be able to wrap a regular list with link elements. These links will then be displayed as circles distributed along a circular path of 180 degrees. I should be optional to place the menu on the right side of the page. The component should have public methods to close and open the menu.

This is the minimal desired feature set of the component.

Experience creating the component

I really liked working with webcomponents and I think it is the way forward for the web. Just w3c and no more third party frameworks. During the coding hours I had to make lots of decisions. Mostly concerned the styling, because each instance of the menu should have its own styling and positioning. Currently I found this much easier to do with JavaScript. I tried scoping withing the stylesheet but that did not work as intended for me.

To create a webcomponent you need the extend the class with a DOM-element. My first instinct was to use the HTMLUListElement , but that did not seemed to work yet. Therefore I set on the base class HTMLElement

class CircularMenu extends HTMLElement {
    constructor() {
        super();
        
        this.shadow =  this.attachShadow( { mode: 'open' } );
        this.activeClass = 'menu-is-active';

For the connectedCallback a webcomponent life-cycle method it is important to understand that the dom part which contains the custom element should be available before being called. Hence the webcomponent script should be included just before the closing body tag.

The same as with React, webcomponents should be simple and expose methods to be used by the main the application. These public methods can only be called when the component is ready, as you can see in the inline script

var onload = function() {  
            if (!window.HTMLImports) {
            document.dispatchEvent(
                new CustomEvent('WebComponentsReady', {bubbles: true}));
            }
        };

        document.addEventListener( 'WebComponentsReady', () => {
            document.querySelector('circular-menu').openMenu();
        });

The menu items gets inserted into the slot, these can then be styled with a pseudo selector in CSS as shown below

::slotted(li) {
            background-color: tomato;
            box-shadow: 0px 0px 5px #666;
        }

        ::slotted(li:hover) {
            background-color: coral;
            box-shadow: 0px 0px 10px #666;
        }

My Thoughts

In my opinion webcomponents are the best way to make future web with durable knowledge. The API’s are now being developed for a long time an have had time to ripe and are now mature.

Tough pure webcomponents are not ready for prime time usage yet when you need to support IE11 and older versions of IOS. Which is actually to bad. I think that it would be ideal if the whole web was to be written with W3C standard code. Things will be working for a long time.

My Utopian dystopia would happen when W3C will manage to come up with a standard way to bundle and optimize assets. Then all web developers will be able to use 100% of there time to create.