Introducing CWCO — The Only Web Components Solution You Need

Featured image for “Introducing CWCO — The Only Web Components Solution You Need”

A context-full web component library meant to simplify the way you work with native web component APIs in the browser or server.

CWCO (pronounced Cuoco) is a context-full web component library meant to simplify the way you work with native web component APIs in the browser or server. It is built on top of native browser APIs and JavaScript and it only requires you to know JavaScript, HTML, and CSS to start working.

Plug and play

Unlike other web libraries out there, it does not need to be built to work. This gives it great advantages like:

  • It can be mixed with other libraries like React, Angular, Vue, or Svelte, even other web component libraries like Lit and Stencil;
  • Simply import it in the browser and start using it;
  • Combined with native browser API and JavaScript you have everything you need to build an app in a very small package with a fast rendering capability.
  • Only bundle your code! CWCO is less than 10k when minified.
<script src="https://unpkg.com/cwco/dist/cwco.min.js"></script>

What makes it attractive?

It has a relatively small learning curve because it does not introduce any additional syntax. It relies on web standards and how HTML, CSS, and JavaScript already work to do everything.

  • Event and Data Binding in HTML and CSS;
  • Simple Web Component API and better developer experience;
  • Truly Reactive Template;
  • Lightweight package;
  • Fast rendering;
  • Build view directly in HTML file;
  • Built-in Context;
  • Powerful built-in Directives(including the ability to create your own);
  • Client and Server Side Rendering;

It also stays close to the native web components APIs by being OOP and using native options in a new way.

// instead of this
class MyButton extends HTMLElement {
  // code goes here
}
customElement.define('my-button', MyButton);
// you do this
class MyButton extends WebComponent {
  // code goes here
}
MyButton.register()

Build once, use everywhere!

The main advantage of it all is the fact that it can be used with anything. That means you can create a UI library and then use it with React, Angular, Svelte, Vue, or whatever other libraries you do your web app development with.

No need to maintain multiple versions of the same library. For development, maintaining a single code base is much easier.

HTML and CSS Data Binding

CWCO does one-way data binding. That means that you use events to detect when the element changes, and use curly braces(square brackets for CSS) to set values in elements.

The button example below has four observed attributes which are used in the template and CSS and when updated will automatically update the DOM elements and the style that affects them.

class MyButton extends WebComponent {
   static observedAttributes = ['type', 'label', 'bg', 'color'];
   // set default values for attributes
   type = 'button';
   bg = '#222';
   color = '#fff';

   template = `
      <button
        type="{type}">
        <slot>{label}</slot>
      </button>
   `;

   stylesheet = `
      <style>
        button {
          padding: 5px 10px;
          background: [bg];
          color: [color];
          border: none;
          border-radius: 3px;
        }
      </style>
   `;
}
MyButton.register();

You can use this component like so:

<my-button bg="#222" color="#fff">click me</my-button>

You can see it in action in this codepen example.

Event Handling

HTML has native event handling attributes which are strongly advised against and this library uses it in a much better way. You still use it like you normally would but they are all changed into event listeners and removed from the element when it is added to the DOM.

<my-button onclick="handleClick($event)"></my-button>
...
handleClick(event) {
  event.preventDefault();
  this.dispatchEvent(new Event('click))
}

These functions can be called with any data and the way you pass the event to the function is by using the $event variable much like Angular.

You can also use curly braces to write the handler code right into the attribute value body which must be used for simple things only.

<my-button
   onclick="{this.dispatchEvent(new Event('click))}">
</my-button>

Built-in Context

One of the best features of it all is its built-in context data support. All elements are context-full whether a component or not.

Context data is one that all elements inside the element that declares it can access which makes it perfect for theme providers, localization, global app data, user information, etc.

class ThemeProvider extends ContextProviderComponent {
  static initialContext = {
    theme: {
      colors: {
        primary: 'purple',
        secondary: '#222',
        cta: '#900',
        light: '#f2f2f2',
        dark: '#111',
      },
    }
  }
}

One specific component you can use is the ContextProviderComponent which has a very specific way to handle templates, but any component can declare its *initialContext*.

<theme-provider>
    <todo-app>
        <button
            type="button"
            attr.style="
              color: {theme.colors.light};
              background: {theme.colors.primary}, true">
          click me
        </button>
    </todo-app>
</theme-provider>

Templating over rendering

This library follows the native HTML concept of the template and again, stays very close to web standards. The template is used to define the body of the component but it is only used once in the beginning when building the component body.

Some other libraries use the “render” concept where a render function is called every time there is a change to recalculate the DOM and changing where there needs to be changed.

// index.html
<template id="my-button">
   <style>
     button {
        padding: 5px 10px;
        background: [bg];
        color: [color];
        border: none;
        border-radius: 3px;
     }
   </style>
   <button
     type="{type}">
     <slot>{label}</slot>
   </button>
</template>
// app.js
class MyButton extends WebComponent {
   static observedAttributes = ['type', 'label', 'bg', 'color'];

   templateId = 'my-button';
}
MyButton.register();

Once the template is parsed into DOM elements, every data change will be done directly on the DOM. No JSX! No virtual DOM! No DOM recalculations. It will only change the elements related to the data that changed by doing a shallow data comparison.

Reactive Data

One great thing about this library is that it reacts to deep data changes by leveraging the power of proxy. One even better detail about it is that it is just normal data manipulation that you do inside a class.

class PushList extends WebComponent {
  list = [];

  template = `
    <ul>
      <li repeat="list">{$item}</li>
    </ul>
    <my-button
       onclick="addItem()"
       bg="#222"
       color="#fff">add item</my-button>
  `;

  addItem() {
    this.list.push(
      window.prompt(
        'Enter item name',
        'Item ' + Math.floor(Math.random() * 100)
      )
    )
  }
}
PushList.register();

This means that you will trigger a DOM update when any public property changes either through re-assignment or updating the value deeply.

In general, this library recommends that if the data is not used in the template, it should be private to avoid unecessary DOM inspections.

Also to know, the DOM updates whether you update the element from inside or outside the class.

const btn = new MyButton();
document.body.appendChild(btn);
btn.bg = '#900';

Lifecycles

The lifecycles still mimic the native web components but are renamed and adapted to the internal calculations done inside web components.

  • onMount: called when the element is added to the DOM and its template is done being parsed. Unlike connectedCallback it does not get called after attribute changes. You can verify if the element is mounted by reading the mounted property. Re-adding elements to the DOM will also trigger this callback but will not get the template re-parsed.
  • onUpdate: called when the component data is changed and the DOM elements got updated with the data changed. Can only be started being called after the onMount is called. Similar to the attributeChangedCallback, it gets called with the name of the property, its old and new value, including the context change.
  • onDestroy: called when the component element is removed from the DOM. Works just like disconnectedCallback lifecycle.
  • onAdoption: called when the component element is added to a different document. Works just like adoptedCallback lifecycle.
  • onError: called every time something goes wrong inside the component. Can be used to create a global error handler. You can even call it yourself to report your caught errors.

Of course, you can also use the constructor to do things when the element gets instantiated way before it gets mounted but doing things in the constructor may happen even if the element never makes it to the DOM.

Directives

The concept of the directive is built-in into HTML. For example, [draggable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable) and contenteditable are a perfect example of global attributes that when added to an element tag it gives it extra capabilities. That is exactly what directives are.

Directives fit into the HTML category of global attributes. With CWCO you can create these global attributes and even override native HTML ones since they don’t get rendered in the DOM but work in the background for the element that it is attached to.

By default, CWCO comes with four built-in directives:

  • if: will conditionally add or remove an element to the DOM based on the value truthness. learn more.
  • repeat: will repeat the element any given number of times or based on iterable object entries. learn more.
  • attr: will conditionally add or remove an attribute from an element tag based on a value truthness. learn more.
  • ref: will collect a reference of the dom that it is attached to. If the same name is used for multiple tags, it will collect an array of references. learn more.

You may also create your own set of directives to do things you like by following the custom directive doc which uses the Directive class which works very similar to components with a much simpler logic.

class Wrapper extends Directive {
  parseValue(value, prop) {
    return `["${value}", "${prop}"]`;
  }

  render([value, prop], {element}) {
    const wrapperNode = document.createElement(value);

    if(prop) {
      wrapperNode.style.display = prop;
    }

    wrapperNode.appendChild(element.clone(true));

    return wrapperNode;
  }
}

Wrapper.register();
// usage
<li wrapper="ul">my item</li>

Whats are the cons?

Well, it is new! That means you need to rely on its documentation and examples it provides to learn it, but you can always subscribe to the youtube channel for tutorials. Being new means you discover the library as it gets popular.

Being new also means there is a lack of supporting tools specific for it but because of it is plain JavaScript, CSS, and HTML nature you can use the tools you normally use for vanilla web development. I use WebStorm and it works pretty well with default configurations and plugins.

Something new is a great opportunity for open-source contributors. Because of its low adoption, there could also be things to be improved upon or changed altogether which you will learn about as you use.

For some people, the fact it is purely OOP can be a turn-off. With its API flexibility, it is easy to wrap it in a functional facade but will probably need to be compiled or built which is a turn-off for many developers as well. For Web Component lovers, this is can be a really fun discovery.

Internally, it uses very modern JavaScript APIs which means if you target non-modern browsers, it can be a problem. You can check browser support for these main things to know if it is for you:

One main thing it does not support is extending native HTML elements. However, because of its OOP nature, you can create abstract components you can extend and personalize at will.

Learn with more usage examples

Conclusion

In general, this library is very vanilla and can be fun to work with but don’t take my word for it. Try it for yourself. My opinion is biased since I built it but let's discover it together and build fun things.

This library is inspired by the best features of the best libraries out there and built on the strong JavaScript and Browser foundation which will only get better.

Watch the embedded video

Illustration for “Introducing CWCO — The Only Web Components Solution You Need”

YouTube Channel: Before Semicolon Website: beforesemicolon.com

Share this article