Symbiote.js VS LitElement

The motivation of developers of front-end libraries and frameworks can be different. And if you, being such a developer, want to work not “for the table” but with the expectation of some kind of recognition and benefit for the industry, you must clearly understand what exactly you are doing and why. If you want to save users a couple of kilobytes of traffic or a couple of milliseconds of response, it will be very difficult for you to prove to the world that it is worth choosing your solution for this. People will choose the size of the community, a rich ecosystem and a large vendor. Your set of arguments should be strong enough to attract attention. Now I will try to prove that given a solution such as LitElement from industry giant Google, it makes sense to look away Symbiote.js.

Why the comparison with LitElement?

LitElement is the undisputed leader among web component-based libraries. The fundamental difference between such libs and all others is that they use native browser component mechanics (modern DOM API) and not their own additional runtime. This allows you to save user resources and create more universal solutions. I have been following the development of the Web Components group of standards for a very long time, since their inception. And I don’t just monitor, but actively participate in its implementation. LitElement is a logical continuation of the Polymer project, which, in turn, began as an adaptation of a draft standard. A lot of water has passed under the bridge since then. From an experiment by enthusiasts, everything turned into a set of accepted standards, support for which has long been implemented in all modern browsers.

Like LitElement, Symbiote.js is also a library for creating web components, but with a focus on organizing their subsequent interaction.

Traction

Over the past year, the number of weekly npm package downloads for Symbiote.js, increased from 500 to 3000. That is, we see a 6-fold stable increase in popularity. If we take into account the fact that no one has been actively promoting Symbiote yet, then this is probably a good result. In any case, with the release of the 2nd major version of the library, much more effort is planned to be spent on promotion.

U LitElement, the growth ranged from 700,000 downloads to almost 1,400,000. The scale, of course, is incomparable. But the growth curve is much flatter and, it seems to me, reflects the growing popularity of web components in general. But a twofold increase in popularity is an excellent result on such a scale.

By the way, React, which is undoubtedly more popular at the moment, has seen virtually no growth at all.

License

Symbiote is licensed by MIT. LitElement has BSD-3-Clause. These licenses are quite close, but the BSD-3-Clause version additionally restricts the use of author and brand names. I found a good analysis of the nuances of these two types of licensing: here (https://opensource.stackexchange.com/questions/217/what-are-the-essential-differences-between-the-bsd-and-mit-licences)

In general, I don’t see much difference, and both options are acceptable to me personally.

Size

In terms of size, Symbiote.js wins, but not very significantly. ~ 5.13 KB (gzip) for Symbiote, versus ~ 6.85 KB (gzip) for LitElement. Both can rightly be classified as compact solutions.

In addition to the size of the library bundle itself, the overall growth in the size of the application with increasing complexity is of great importance. This parameter is more difficult to compare and I plan to do this in a separate publication, adding React, Vue and Svelte. A surprise awaits you.

Shadow DOM

I know that working with Shadow DOM causes difficulties for many developers. This is a very cool technology, but it can be poorly compatible with the usual practices of working with styles.

Apart from this, there are at least three approaches to Shadow DOM:

  1. When all application components have shadow markup and styles are added to the shadow root of each.

  2. When shadow areas are created only at certain levels of the DOM hierarchy, in order to ensure that these areas are isolated (complex widgets, microfrontends) and all internal parts are styled externally, in a common scope.

  3. When Shadow DOM is not used at all and all the classic techniques for working with CSS (general CSS per document) work.

By default, Symbiote does NOT use Shadow DOM. To enable shadow mode, you need an additional flag (renderShadow) or adding styles through the special shadowStyles interface. This allows you to avoid unnecessary performance overhead and significantly simplifies working with styles in the classic style, with the ability to use custom tag names as convenient selectors.

The rootStyles interface allows you to add styling rules to the top-level shadow root in a hybrid approach. This is an important feature of Symbiote, giving it special flexibility, for example, you can set isolated local styles and top-level context styles at the same time.

LitElement uses Shadow DOM for all components by default. This behavior can be disabled, but it does not have the function of defining an external top-level shadow root and adding styles to it.

Both Symbiote and LitElement can work with the new native browser interface adoptedStyleSheets, which is part of the CSSOM API.

Templates

Symbiote’s declarative templates are just HTML. This allows you to use HTML files to describe them, use loaders for direct import into your code, or animate a document previously generated on the server.

Symbiote.js, like LitElement, has a tag function (html) for use with template literals, but the result of its work is not a special object (as in lit), but HTML, in the form of a string. Thus, in Symbiote.js, the html function is simply a helper for a more convenient data binding syntax, the use of which is optional.

Also, Symbiote templates are abstract, they are not tied to the context of the component itself, and you can easily write them in separate files and even in separate packages so that, for example, you can use them to generate a document on the server without any additional tools or special “server components”.

In LitElement, to work with SSR you will have to use a separate package with its own dependencies, which, at the time of writing, has an experimental status.

Symbiote.js allows you to define and override (customize) templates in a shared document, outside of your JavaScript code.

The Symbiote – html helper function allows you to use regular string interpolation, which does not work with the tag function from the lit-html package, which is used in LitElement.

In LitElement, template rendering is tied to the component’s class context and to make it more abstract you’ll have to invent a separate wrapper.

To highlight the syntax of template literals, for Symbiote.js and LitElement, you can use the same extension for your IDE, it will work almost identically.

Rendering dynamic lists

To render lists, LitElement uses loops nested within template literals, which in turn nest item templates. Or, you can define external loops and insert the finished result into the main template. Lit also provides a special repeat directive, which is designed to efficiently render lists based on keys.

Symbiote.js, in its itemize API, uses simple itemize and item-tag directive attributes, which look more compact. And the element template can simply be nested HTML. For efficient updates, you can use source data update. The symbiote makes changes to the DOM as efficiently as possible, creating new elements and changing them only when necessary. It can work both with and without item keys, but under the hood it is the unkeyed rendering method, which is more productive.

For each list element, Symbiote.js creates its own instance of a component that implements its own local state, making this solution flexible and performant. You can also use a predefined web component that can be further optimized. The list elements, in this case, do not have to be Symbiote components; they can be any CustomElement instance.

The basic characteristic when evaluating the effectiveness of a list rendering approach is performance. To cover this topic in more detail, I will also have to write a separate article.

Dependencies

The Lit package has three main dependencies: lit-html, lit-element and @lit/reactive-element.

Symbiote.js has no external dependencies.

In modern realities, when all current versions of popular browsers have support for import maps, this point loses some weight, and nevertheless, the simpler the dependency tree of your project, the better, in terms of the capabilities of their management, auditing and overall system reliability.

Additional features

The basic capabilities of Symbiote and Lit are similar. This is a common component model based on the CustomElements standard, declarative templates, reactive data bindings, lifecycle callbacks…

But Symbiote has a number of additional features that make it easier to configure interactions between components in their natural environment – the DOM environment. For example, Symbiote can initialize properties from the values ​​of CSS variables. Thus, a cascading configuration distribution model is used, which can be delivered along with other customizations for your UI entities. You can easily define and override component properties for entire branches of the DOM tree, just as easily as you can style them. And you won’t need any additional libraries or functions for this, everything works on the standard DOM API and CSS.

In addition, Symbiote components can connect directly to the data contexts of top-level components in the hierarchy, as well as create common shared contexts (this works by analogy with the behavior of built-in radio inputs, which “learn” about the state of their colleagues by the name attribute) . Thus, you can implement a complex graph of connections between components without using any additional tools.

Examples

Here I will give two examples of code for components that are completely identical in functionality. The original example is taken from the official website of the Lit project:

import { LitElement, html, css } from 'lit';

export class MyElement extends LitElement {

  static properties = {
    greeting: {},
    planet: {},
  };

  static styles = css`
    :host {
      display: inline-block;
      padding: 10px;
      background: lightgray;
    }
    .planet {
      color: var(--planet-color, blue);
    }
  `;

  constructor() {
    super();
    this.greeting = 'Hello';
    this.planet="World";
  }

  render() {
    return html`
      <span 
        @click=${this.togglePlanet}>${this.greeting}
        <span class="planet">${this.planet}</span>
      </span>
    `;
  }

  togglePlanet() {
    this.planet = this.planet === 'World' ? 'Mars' : 'World';
  }
  
}

customElements.define('my-element', MyElement);

The same example, but written using Symbiote.js:

import { Symbiote, html, css } from '@symbiotejs/symbiote';

export class MyElement extends Symbiote {

  init$ = {
    greeting: 'Hello',
    planet: 'World',
    togglePlanet: () => {
      this.$.planet = this.$.planet === 'World' ? 'Mars' : 'World';
    },
  };

}

MyElement.shadowStyles = css`
  :host {
    display: inline-block;
    padding: 10px;
    background: lightgray;
  }
  .planet {
    color: var(--planet-color, blue);
  }
`;

MyElement.template = html`
  <span 
    ${{onclick: 'togglePlanet'}}>{{greeting}}
    <span class="planet">{{planet}}</span>
  </span>
`;

MyElement.reg('my-element');

As you can see, in the second example there are ten fewer lines. I will say right away that with Symbiote, the amount of boilerplate will be smaller in the vast majority of cases.

conclusions

It’s up to you to draw conclusions. The author of this article is himself a skeptic and is not a fan of pointless increases in entropy. All the differences and nuances described above were born from real practice in solving problems, often very non-trivial ones. It’s not a fact that you personally will encounter such problems, but if you do, the Symbiote will not let you down.

Like my previous article, I will end this one with a request, if possible, to mark the project with an asterisk on GitHub. This will add enthusiasm to developers.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *