How To Build A Simple Web Component With Stencil

In this tutorial we will explore using StencilJS, a web-components compiler, to build a very simple web component that can be used on any other web project.

Stencil is a tool created by the team at Ionic, who are also behind some other great tools and frameworks including Capacitor, and - Ionic: a framework for building PWAs and native-like iOS and Android apps. When rewriting their Ionic framework library for v4, they decided to use web-components for the components that come readily available with their package. And to make this task easier, they built a tool called Stencil. Stencil leverages the lessons the team have learnt whilst building Ionic; the latest standards in web technology; and draws inspiration from React and Angular - the biggest front-end frameworks of 2018.

A web-component essentially allows a developer to create, and leverage, their own HTML element. For example, the <div> element is part of the HTML specification, but <my-blue-div> could be a web component that a developer builds and leverages in a website or web application project. Examples of seriously useful web components are still rare, but they are on the rise as more and more browsers and platforms add support for their spec.

How to build a simple Web Component using Stencil

In this guide we are going to build a very simple web component that doesn't do much, but is very extensible and open to your imagination. It will look like this:

<sponsored-link href="https://www.technouz.com" text="Technouz" nofollow="true"></sponsored-link>

It will be used on this website to replace the default anchor (<a>) tags used for sponsored links to partner websites and advertisers. For this tutorial, the web component will simply save me from having to add the rel attribute to all sponsored links by default. In the long-run, I plan to improve this web component to display a pop-up preview window when hovered over. Implementing this feature as a web-component means that when writing posts I will have a simple and clean HTML element to use, rather than re-implementing the feature over and over again. Since web-components compile to JavaScript, I will also be able to include this feature in any other web project by importing it using the <script> tag.

Step 1: To get started you'll need to have a text editor or IDE installed. I recommend VS Code, but you don't need anything fancy. You'll also need to install Node.js (v6+) and NPM. Some experience with JavaScript, JSX, Angular or React will help a lot.

Step 2: Open a new Terminal window and navigate to the directory you want to create your project in. Then initialise your project with the following command:

npm init stencil

You'll be asked some basic configuration questions which should be self explanatory. When asked which type of project you'd like to create be sure to select the option for components.

Step 3: Open the directory for the project within your code editor. You should see a a folder called my-component under src/components. Rename the directory to sponsored-link and also refactor all references from my-component to sponsored-post.

The most important file, sponsored-link.tsx now looks like this:

import { Component, Prop } from '@stencil/core';

@Component({
    tag: 'sponsored-link',
    styleUrl: 'sponsored-link.css',
    shadow: true
})
export class SponsoredLink {
    render() {
        return;
    }
}

Though not necessary, it is beneficial to use a more appropriate namespace value for your component library by modifying the namespace property in the file stencil.config.ts. I used the namespace: technouz.

Step 4: Start creating your component by adding some input properties. These will allow your component to access input data such as title, href and nofollow as described above. Add these properties using the @Props() decorator (stencil documentation).

The sponsored-link.tsx file will now look something like this:

import { Component, Prop } from '@stencil/core';

@Component({
    tag: 'sponsored-link',
    styleUrl: 'sponsored-link.css',
    shadow: true
})
export class SponsoredLink {
    @Prop() href: string;
    @Prop() text: string;
    @Prop() nofollow: boolean;

    render() {
        return;
    }
}

Both href and text are strings whilst nofollow is a boolean. The truthiness of nofollow will be used to determine whether or not the rel="nofollow" attribute is added to the anchor tag element.

Step 5: To help visualise as you build your component, you can add the following line to src/index.html:

<sponsored-link href="https://www.technouz.com" text="Technouz" nofollow="true"></sponsored-link>

Then run the following command in Terminal:

npm run start

This will start a development server and open a browser page to index.html. Every time your code is changed and saved, the browser window will automatically reload - giving you visual feedback on the code you are writing. Neat!

Step 6: At the moment, the component renders nothing. That's because the render() method in sponsored-link.tsx returns… Well… Nothing!

To add HTML attributes to the anchor tag dynamically, the input properties are used to build a HTMLAttributes object. To do this, add the following function to the sponsored-link.tsx file, within the class definition:

private getHtmlAttributes(): HTMLAttributes {
    let attributes = {};

    if (this.href) {
        attributes['href'] = this.href;
    }

    if (this.nofollow === true) {
        attributes['rel'] = "nofollow";
    }

    return attributes;
}

This function doesn't do anything special. It checks the input Prop() values of this.href and this.nofollow and based on their values, returns an key-value-pair object containing the additional attributes to be projected on to a standard anchor tag.

Step 7: The last step in constructing the web component is to use the getHtmlAttributes() method defined above. This is done by modifying the render() method with the following code:

render() {
    return <a {...this.getHtmlAttributes()}>{this.text}</a>;
}

The Object.spread operator is used to add the custom attributes to a standard anchor tag. {this.text} binds the text help within the text input Prop() to the DOM. And the render() object is executed every time the component needs to be painted, or repainted, to the screen.

Step 8: Confirm that the component works as expected using the index.html file to manipulate the values and preview the changes in the browser window running the development server. If everything looks good, you are ready to run the Stencil compiler to package your web component in to distribution build. The magic of Stencil does everything here. All you need to do is run the following command from a Terminal window:

npm run build

Step 9: The Stencil compiler outputs your collection of components from the src/components directory to the dist directory. This dist folder can be uploaded to NPM or any other compatible repository. Alternatively, you can rename the dist folder and include it in any other web project. Then, to use the web-component, you can include the main JavaScript file in your HTML as follows:

<script src="dist/technouz.js"></script>

And finally use the web-component anywhere the script has been imported like so:

<sponsored-link href="https://www.technouz.com" text="Technouz" nofollow="true"></sponsored-link>