DEV Community

Sam Thorogood
Sam Thorogood

Posted on • Updated on

Modern Web Components

I'm the lead on Santa Tracker. Yes, I know it's June right now—pretty much as far from the holidays as you can get. 💼

I want to talk about Web Components. A quick refresher: these are Custom Elements which might use Shadow DOM, allowing elements of your own name that have their own CSS, styling and DOM contained within them:

<div>
  <my-custom-element></my-custom-element>
  <p>Mixed with regular HTML!</p>
</div>
Enter fullscreen mode Exit fullscreen mode

Polymer Away 👋

One of the reasons we're updating Santa's core UI to remove the Polymer Web Component library is because Polymer is sticky. Polymer only really works when all the other elements it interacts with are also Polymer: anything it touches also needs to work the same way.

This isn't extensible and doesn't give us room to move in the future. Sites like WebComponents.org, released at the height of Google's evangelism for Polymer, proclaim #UseThePlatform, but I suspect the majority of elements there are sticky in this same way.

Smooth Elements 😎

One of the main reasons we're rewriting the core UI of Santa Tracker using lit-element is because unlike Polymer, Lit is not sticky. It's just a helper library that can be used interchangeably with any other element on your page. 🤝

So in doing our rewrite of Santa Tracker, we've found that many elements just don't need to inherit from anything aside the built-in HTMLElement class, because they're only simple building blocks. We call these 'vanilla' elements. 🍨

Lit aside, there's a huge variety of small or large Web Component libraries out there that act as helpers. My good IRL friend Trey writes SkateJS, and just searching the #webcomponents tag on dev.to reveals a bunch of candidates too. 🔎

Of course, you probably shouldn't ship several different libraries: that's just sensible, to save bytes and not overly complicate your code. But if you use Lit one day, but rewrite using Skate on another (with a smattering of vanilla too), you can safely have those libraries co-exist during a migration so your site is never unusable. 🤗

An Example 🔥

For completeness, let's show off what an element looks like in Lit:

class SimpleGreeting extends LitElement {
  static get properties() {
    return { name: { type: String } };
  }

  constructor() {
    super();
    this.name = 'World';
  }

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}
customElements.define('simple-greeting', SimpleGreeting);
Enter fullscreen mode Exit fullscreen mode

Easy, right? SkateJS has a similar, easy, getting started sample. 🛹

Vanilla Example 🍦

And what a simple element might look like without any libraries, using just the platform:

class SantaChoiceElement extends HTMLElement {
  constructor() {
    super();

    const template = Object.assign(document.createElement('template'), {
      innerHTML: `
<style>/* CSS here */</style>
<div class="wrap">
  <!-- more HTML -->
</div>
`,
    });

    // polyfill for CSS in Shadow DOM
    if (self.ShadyCSS) {
      self.ShadyCSS.prepareTemplate(template, 'santa-choice');
    }
    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(document.importNode(template.content, true));
  }
}
customElements.define('santa-choice', SantaChoiceElement);
Enter fullscreen mode Exit fullscreen mode

And this code is only as complex as it looks (with the polyfill for Shady CSS) for the ~10% of users who don't support Shadow DOM. Lit is including this "for free". 🆓

As an aside; <santa-choice> is an element I'm really proud of that drives the chooser thing at the bottom of Elf Maker 🧝. I'd like to write how it works some day soon.

Thanks!

I hope this has enlightened you a bit about WCs (Web Components). For me, the absolute insightful moment was when I realised that the benefit of using Lit, or other libraries, was that it was not all-in: they play nicely with any other part of the ecosystem and you can use as little or as much of it as you like. 👍

16 👋

Oldest comments (17)

Collapse
 
bennypowers profile image
Benny Powers 🇮🇱🇨🇦

Great examples!

(s/HtmlElement/HTMLElement/g)

Collapse
 
samthor profile image
Sam Thorogood

whoops. Thanks 😄

Collapse
 
jordanaustin profile image
Jordan Austin • Edited

Thanks for posting this. At my company we've recently migrated away from the all-in nature of Polymer and moved to LitElement where needed. It's amazing and even easier then Polymer because it's even closer to standard Web Components.

I think blog posts like this are really great because it shows people how easy Web Components can be and hopefully gets people thinking about using them more and maybe making their life easier with a small helper library like Skatejs, Lit, or hyperHTML 👏

Collapse
 
ryansmith profile image
Ryan Smith

I love the idea of using built-in components that can be used anywhere but when trying to dive deeper into web components I found there to be a bit of friction in using it to create an app.

There seemed to be a number of different ways to approach using web components:

  • Vanilla web components
    • The issue here seems like there are missing features or difficult to use features in vanilla, which I believe is the reason Lit exists. Vanilla isn't as "batteries included" as I would have liked.
  • Use a wrapper (LitElement)
    • I like LitElement/LitHTML's simplicity and syntax. I think the thing missing here is how to assemble this into an app with modern features such as routing, offline support, bundling, etc. There wasn't a clear direction on the next steps to take. A "Create Lit App" or "Lit CLI" to get up and running quickly with a more minimal working app that has a lot of the setup abstracted away would be ideal.
    • I know there are repos like PWA Starter Kit and OpenWC, but I felt that these had too much included. If I were to build a simple site, I would be removing or not using a good portion of either of these.
    • Tooling wasn't quite there. I know it is just JavaScript, but in using these you have to hunt for tools that will have proper syntax highlighting, formatting, and linting. Those tools seemed to not exist, had conflicts with other tools, or were difficult to get working properly.
  • Use a compiler (SkateJS, Stencil)
    • While these look like awesome tools, they felt wrong to me. One of my reasons looking into web components was to be framework-agnostic and to future-proof my skills, so while these tools are "not frameworks", they lock you into a specific library. It isn't #UseThePlatform, it is #CompileForThePlatform.

I want to like web components, but there are some things holding me back. I stopped pursuing them for now because it seemed like using a framework such as Vue was easier to be productive with and a better choice for me.

I'm open to any opposing viewpoints. Any suggestions for someone like me?

Collapse
 
uppercod profile image
Matias Trujillo

I invite you to try Atomico, it's simpler than the exposed libraries,eg:

Example.

Atomico 3kB is based on virtual-dom, HoCs and hooks.

It has a small router and deferred charges(dinamic import), eg:

Example

I hope I have covered the essentials. start simple npm init @atomico

Collapse
 
josefjezek profile image
Josef Ježek • Edited

We love Web Components, not custom apis. ;-)

github.com/w3c/webcomponents

Thread Thread
 
uppercod profile image
Matias Trujillo

No thanks, WC api is unfriendly and complex, current class-based implementations generate too much repetitive code and tie tightly to this.

instead, with my proposal you can better separate the logic from the view and avoid things like this.

microsoft code.

class /**any libraries based on classes, eg LitElement */{
    anyUpdate(changedProps) {
        if (changedProps.get('_showMenu') === false) {
          // get popup bounds
          const popup = this.renderRoot.querySelector('.popup');
          if (popup && popup.animate) {
            this._popupRect = popup.getBoundingClientRect();

            // invert variables
            const deltaX = this._loginButtonRect.left - this._popupRect.left;
            const deltaY = this._loginButtonRect.top - this._popupRect.top;
            const deltaW = this._loginButtonRect.width / this._popupRect.width;
            const deltaH = this._loginButtonRect.height / this._popupRect.height;

            // play back
            popup.animate(
              [
                {
                  transformOrigin: 'top left',
                  transform: `
                  translate(${deltaX}px, ${deltaY}px)
                  scale(${deltaW}, ${deltaH})
                `,
                  backgroundColor: `#eaeaea`
                },
                {
                  transformOrigin: 'top left',
                  transform: 'none',
                  backgroundColor: `white`
                }
              ],
              {
                duration: 100,
                easing: 'ease-in-out',
                fill: 'both'
              }
            );
          }
        }
      }
}

The right is part of the code that shows how inelegant it can be to solve a state problem using classes.

Indifferent to eg, if I believe that sometimes you need solutions like Atomico if your WC is simple

Collapse
 
samthor profile image
Sam Thorogood

WCs are just another platform primitive. I don't think they're intended to be part of something called "Create Lit App". I appreciate that this might be what you want. For all of the criticism of say, the thousands of dependencies needed by Create React App, I appreciate that it's an easy way to start.

If anything, WCs lend themselves to the idea of modern ES6 bundling. I'm not necessarily endorsing the Pika package manager, but it has an interesting writeup on its vision. This is effectively the "no-build" system- you just import './element-name.js, and use <element-name> inside your code.

That <element-name> you've created can now be used inside any modern "create foo" or "starter kit". It's just important to remember that WCs aren't really targeting that high level on their own.

I hope that helps.

Collapse
 
ryansmith profile image
Ryan Smith

Good point on the dependencies, I was definitely shocked when I npm installed lit-element and saw one folder in node_modules. There is definitely a lot of developer convenience in those types of up-and-running tools, but there is a cost passed on to the user. I have heard of Pika before and I like their vision. It brings me back to the old days before all of the "necessary" tooling.

I think it makes sense from a viewpoint of web components being reusable things and not part of a larger library or ecosystem. I think for me it is deciding really how I want to use them since they are basic building blocks. I could write LitElement components and create a basic router (or pull in an existing library) but there may not be a roadmap to follow and some hurdles to jump over. Or I could use a library I like such as Vue and pull in web components, I'm sure there is some documentation out there but there also may be some things to figure out. Those components are then freed from any one library and can be used in my next Vue app, React app, or [next big library] app, which seems like a good deal.

Thread Thread
 
samthor profile image
Sam Thorogood

I think this is sort of the vision, although I don't want to speak for those frameworks (I suspect they're generally on the no-WC-bandwagon). But again, it doesn't matter, because now you've just created a better HTML element that slots in nicely—it's no difference from a complex built-in.

Collapse
 
abraham profile image
Abraham Williams

PWA Starter Kit does have multiple templates so you can choose versions with less stuff included by default.

Collapse
 
simevidas profile image
Šime Vidas

I’m not sure why anyone would want to write vanilla code. The available standard API just isn’t enough (and probably never will be).

Collapse
 
samthor profile image
Sam Thorogood

Whether you choose to or not, the option is there because it's part of the platform. It doesn't hurt you, either in bytes or complexity, to do so.

Collapse
 
simevidas profile image
Šime Vidas

But the standard API is more complicated to use, is it not? Just rendering a static template requires you to call a bunch of APIs. I don’t even know if I’m supposed to try to memorize those patterns or just copy-paste the boilerplate code whenever I’m creating a new component.

It’s much more complicated than say writing a template literal inside a render function as with Lit. That’s why I said, I’m not sure why anyone would use (just) the standard API.

Collapse
 
uppercod profile image
Matias Trujillo

I'm a fan of the web components, I would like to know your appreciation of a simpler approach to creation, based on JSX, HoC, virtual-dom and Hooks, 3kB.

https://github.com/atomicojs/atomico.

Atomico is a personal project made with love

Collapse
 
abraham profile image
Abraham Williams

You mention that Polymer is "sticky" and likes to only work with other Polymer elements. Since Polymer 3 is built on LitElement, shouldn't it have the same base level of interoperability as LitElement? Or do you see Polymer 3 elements that somehow have functionality regressions over the base LitElement?

Collapse
 
samthor profile image
Sam Thorogood
  • Polymer 3 is a mostly mechanical migration of 2 to use ES modules.

  • Polymer 2/3 both use the class-based method of inheriting from a base element, but they use Polymer-isms (such as the notify stuff)

There's no real relation to Lit, aside some of the same authors.