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.