Web Components in Vanilla JavaScript (WebC)

Web Components in Vanilla JavaScript (WebC)

What are Web Components?

Web Components are simply reusable, custom HTML elements where the functionality is strongly encapsulated from the rest of your code. Isn't this amazing? 🤩

That's not even the best part, web components are library-agnostic which means they can be used in any JavaScript project irrespective of the framework/library 🤯.

To give you an example of a web component our very own <video> tag. While video might seem like a single HTML element, but underneath, it consists of several HTML elements and custom logic that defines its behavior.

Pros

Two major advantages of web components are:

  1. Encapsulation: As mentioned earlier HTML, CSS and JS within the web components are protected and does not affect other code structure.

  2. Library-Agnostic: They can be used literally in any JS project be it Vanilla, React, Vue, Angular, etc.

Cons

  1. Web components require browsers to allow running JavaScript on your web app, even if your web component is purely presentational and does not contain any interactive JavaScript code.

  2. Typically, you would need to write your web components in an imperative fashion, rather than a declarative one which results in a somewhat clunky authoring experience.

Where does WebC come in 🤔?

As you guessed, WebC helps with some of the pains when working with web components.

WebC is a tool that generates markup for web components. It’s framework-agnostic and provides various useful compilation tools that make writing web components easier.

As mentioned in the cons, web components require JavaScript to be enabled, even for the web components that don’t have any JavaScript in them. But guess what with WebC we don't have to worry even if the browser has JavaScript disabled because WebC takes your web component code and compiles it to a simple HTML output.

WebC is so powerful that you can create single-file web components which are easy to write and maintain. They consolidate your HTML, CSS, and JavaScript code and simplify the process using features like templates and Shadow DOM.

This is just the tip of the iceberg of what WebC has to offer, you can gain more knowledge by going through their docs.

Getting hands dirty with WebC + Vanilla JS 🔥

Let's build our very own custom web component, excited 🤩? I sure am 🕺

We are going to build a cool transforming box as shown below 😎

To follow along you would need to download the following tools:

  1. Vite to quickly configure my vanilla JS project.

  2. Node.js installed on your machine

  3. We add WebC to our project by inserting the following command in the terminal

yarn add @11ty/webc
// OR
npm i @11ty/webc
  1. To run our script you can use the primary node main.js command to run but I'll be using nodemon to pick up any changes you make to your script and automatically re-run the file. We can do so by using the following command:
nodemon main.js -e js,webc,html

Once you've got all this down, we'll replace the boilerplate from main.js with our own script that will register the WebC page and write its content to index.html at the root of our project.

import { WebC } from "@11ty/webc";
import fs from "fs";
let page = new WebC();
page.setInputPath("page.webc");
page.defineComponents("./components/**.webc");
let { html, css, js, components } = await page.compile();
console.log(html);
fs.writeFile("./index.html", html, (err) => {
  if (err) {
    console.log({ err });
  }
});

Here's what's happening in the above code:

  1. First, we instantiate WebC page object. it's the page that will eventually contain our custom web components i.e pages are files that start with <!doctype or <html which encompasses the whole document.

  2. Then we define the content source of our page page.webc . WebC pages and components usually have .webc extension.

  3. page.defineComponents("./components/**.webc") defines our custom web component source where our component/s will reside. WebC components are any other WebC files that are used to display reusable UI pieces or fragments.

  4. page.compile() aggregates and compiles the page’s content into a static HTML output.

  5. Finally, we write the output from page.compile() to index.html

Now let's create page.webc in the same file level and run our file.

<!-- HTML-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebC tutorial</title>
  </head>
  <body>
    <my-component></my-component>
  </body>
  <style>
    body {
      padding: 20%;
    }
  </style>
</html>

That's right this is HTML and that's the beauty of WebC. With WebC we simply use conventional HTML, JavaScript, and CSS, well except for a few cases where you’d need to add some WebC syntax.

Ain't it awesome! 🤯

Now we just have to write our component and that's it! So let's get to it right away.

Create a folder named components and add a file in that folder named my-component.webc and add the following content to it:

<!-- HTML-->
<div id="square"></div>
<button id="flipper">Transfrom</button>
<style webc:scoped>
  #square {
    background-color: #edde09;
    width: 150px;
    height: 150px;
    border: 2px solid #cec659;
  }

  button {
    margin-top: 16px;
  }
    :host:not(:defined)>button {
    opacity: 0.2;
    cursor: not-allowed;
  }
</style>
<script>
    let deg = 0;
 class MyComponent extends HTMLElement {
  connectedCallback(){
  document.getElementById("flipper").onclick = () => {
    const square = document.getElementById("square");
    const nextDeg = deg === 0 ? 360 : 0;
    const scaleMeasure = nextDeg === 360 ? 1.15 : 1
    square.style.transition = "all 0.3s"
    square.style.borderRadius = `${nextDeg === 360 ? "50%": "0%"}`;
    square.style.transform = `scale(${scaleMeasure}) rotate(${nextDeg}deg)`
    deg = nextDeg;
      }
    }
  }
   window.customElements.define("my-component", MyComponent)
</script>

Try running the project with nodemon, you should see the output as shown at the start. If not please follow again to check if all the steps are followed correctly.

Now let's understand what's different in the above code:

  1. The first, eye-catching thing is the webc:scoped attribute inside <style> tag. This tag basically allows you to encapsulate your CSS code within your component. When you add this attribute to a style tag, WebC will automatically generate and assign a unique hashed string class to your component element during the compilation. It then prefixes that hash string in front of all of the CSS selectors you declare inside your component.

  2. We have also used Progressive enhancement here. This is a pattern that allows the users to access the basic content and functionality of a website first then if the user’s browser features and internet connection allows, users receive UI enhancements and more interactive features.
    That's why we have a class named MyComponent extends HTMLElement then we define our custom component using window.customElements.define("my-component", MyComponent) to be able to wait for JavaScript to become available.
    The :host:not(:defined) is a selector that comes from the Shadow DOM API and allows you to define special styling for your web components while they are loading.
    WebC adds its own spin on it 😲, when your code is compiled, it will substitute the :host part with the generated hash string class.

    If you were to turn JavaScript off for your local server then you will see something like this:

Wohoooooo!🥳 We built our very own custom web component 💪🏻. Pat on your back and treat yourself for coming this far also send me a million dollars (Just kidding 😜 or not 😈).

If you liked this or have some suggestions feel free to comment and like to show your support! 🙌🏻