Christian Nwamba

Build A Fast Offline-First PWA with StencilJs

Offline-first is a concept that allows developers to assume that some of our application users do not have access to the latest device and the fastest internet connections. With this in mind, applications can be built and progressively enhanced to take advantage of network connectivity when available. With offline-first, users can still interact with your app even when there is no reliable connection available.

Fortunately, there are variety of tools available for developers to build a progressive web apps for users.

In this tutorial, I want to introduce you to StencilJs; a relatively new tool built by the Ionic Framework Team. It allows to build fast web applications using web components.

Not sure of what web components are? Relax, we will discuss that later in this article.

Note: It goes without saying that Stencil is not another JavaScript framework. It is simply a tool that allows you build a standard compliant web component that runs on its own without a framework in the browser.

Stencil is a compiler created with the following goals:

  • To generate standard compliant web components

  • To utilize additional API’s such as Virtual DOM, JSX and async rendering

  • To ideally suit Progressive Web Apps

The benefits to developers include generating custom components and speed-boosting in terms of rendering/execution times.

Progressive Web Apps describe a collection of technologies, design concepts, and Web APIs that work in order to provide an app-like experience for application users on the mobile web.

Web Components

Web components are a set of reusable user interface widgets. This offers you the flexibility of reusing your code and logic again with a custom html tag. To explore more about the details of web components, kindly visit the link.

Why StencilJS

This tool helps build faster, capable components that work across all major frameworks. Some of the improvements provided by StencilJS in generating web component are through the use of the following features:

  • JavaScriptXML allowing DOM nodes to be constructed with HTML-like syntax.

  • Implementing data-binding through binding state variables to onChange events allowing those states to be changed as the input values change.

Impressive, right?

Before we dive into code and get started with StencilJs, let’s take a quick look at what we will be building together in this tutorial.

What we will Build

This is a simple web application built using a web component to display the list of some famous JavaScript legends.

It is not a complex project. Although as simple as it looks, it thus provides a nice introduction to working with Stencil, and a brief introduction with a practical approach to building an offline first progressive web application.

What You Will Learn

While going through this tutorial, together we will explore the different syntax used within a Stencil component including :-

  • Decorators

  • Events

  • JSX for template rendering

Setting up StencilJS

I assume you already have Node and NPM installed. If not, quickly follow the link and install them on your system.

As at the time of writing this tutorial, there was no CLI tool to quickly set up a StencilJS project yet, so to start a new project, simply clone the stencil app-base from Github:

git clone [https://github.com/ionic-team/stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) stencil-pwa-app

The command above will clone the repository into a folder called stencil-pwa-app. Obviously, this will be the name of our project, but please feel free to edit and name it as preferred.

After cloning, change directory into the project folder and install the necessary dependencies like this:

cd stencil-pwa-app
git remote rm origin
npm install

Then, to start a live reload server, run:

npm start

This will start a local server on port 3333:

This starter application comes with an already set up project for Stencil. A component called my-name has already been created. This can be found in src/components/my-name:

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


@Component({
  tag: 'my-name',
  styleUrl: 'my-name.scss'
})
export class MyName {

  @Prop() first: string;

  @Prop() last: string;

  render() {
    return (
      <div>
        Hello, my name is {this.first} {this.last}
      </div>
    );
  }
}

Components

To start a Stencil component, the required node module/package are first imported

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

Followed by the component decorator that defines the metadata for the component and some other basic information, like the tag value that the HTML element will use, including the external styles to be used:

@Component({
 tag: ‘my-name’,
 styleUrl: ‘my-name.scss’
})

Next to this is the component class that declares the public properties that are set up by the user and used to pass data. Stencil utilizes a Prop decorator to know what properties can be set by other components. And lastly within the component is a render method that contains the combination of a JavaScript and HTML-like syntax (JSX) that returns the HTML representation of the data passed to the DOM:

export class MyName {

@Prop() first: string;

@Prop() last: string;

render() {
  return (
    <div>
      Hello, my name is {this.first} {this.last}
    </div>
  );
 }
}

To continue the development of our application, we will be using some additional features of Stencil like Events, Dynamic Content and State Management.

Build your first component

As mentioned earlier in this article, we will be building a very simple application using Web Component. Now that we are familiar with basics, we will create our own custom web component.

In our existing cloned project, create a new directory within stencil-pwa-app/src/components called my-legend. This will house all our component logic and styling.

Then add two new files within the directory you just created my-legend.tsx and my-legend.scss.

Within the my-legend.tsx enter the code below

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

@Component({
  tag: 'my-legend',
  styleUrl: 'my-legend.scss'
})
export class MyLegend {

  // Array of legends
  public lists : Array<any> = [
    {  name: 'John Resig',
       description: 'John Resig is an American software engineer and entrepreneur, best known as the creator and lead developer of the jQuery JavaScript library',
       imageUrl: '/assets/images/john-resig.jpg'
    },

    {
      name : 'Christian Nwamba',
      description : 'JavaScript preacher. Building the web with the community. @ngnigeria organizer. #Webpack Ambassador. Advocacy for The Next Billion Users.',
      imageUrl: '/assets/images/chris.jpg'
    },

    {
       name: 'Evan you',
       description : 'Design, code & things in between. Living the dream working on @vuejs. Previously @meteorjs & @google, @parsonsamt alumnus.',
       imageUrl: '/assets/images/evans.jpeg'
    },

    {
       name : 'Otemuyiwa Prosper',
       description : 'Prosper is a full stack software engineer and writer who’s worked on biometric, health, financial and developer tools. He currently works with Auth0 as a Technical Writer.',
       imageUrl: '/assets/images/prosper.jpg'
    },

    {
       name : 'Wes Bos',
       description : 'Wes Bos, a full stack web developer and designer from Hamilton, Canada. He loves to share what he knows through training products and teaching ',
       imageUrl: '/assets/images/wes bos.jpg'
    }
 ];

  /**
   * @type boolean
   */
  @State() toggle: boolean = false

  toggleComponent() : void {
    this.toggle = !this.toggle;
  }

  render() {
    return (
      <div id="wrapper">
        <div id="holder">
          <p> JavaScript Legends</p>
          <button onClick={() => this. toggleComponent()}>Toggle List</button>
        </div>

      <div class={ this.toggle ? 'active' : 'inactive' }>
         {this.lists.map(list =>
            <div class="card">
            <h3>{list.name}</h3>
            <p>{list.description}</p>
            <img src={list.imageUrl} alt="Random Avatar"/>
            </div>
          )}
      </div>
      </div>
    );
  }
}

What we have done is to import the required module to be used in our component. This includes:

  • Component: As mentioned earlier, it provides and declares component metadata.

  • State: This decorator monitors the changes within the component using the toggle property and determines whether or not the content should be visible.

We also declare an array that contains the list we intend to display within our application.

Our application will display the list from the array declared above whenever a user clicks on the button to toggle the list. This button can make the list either visible or hidden. To accomplish communicating and handling these events, our application needs to listen and be able to carry out the appropriate action. That is why we declare a method toggleComponent() from above.

With my-legend.tsx covered, let’s proceed to adding style rules to our application. Open my-legend.scss and fill it with the code below:

my-legend {
  font-family: Arial, sans-serif;
  background-color: white;

  button {
    background-color: green;
    border: none;
    outline: none;
    font: inherit;
    cursor: pointer;
    color: #ffffff;
    padding: 10px;
    border-radius: 4px;
    text-align: center;
    margin: 0 auto;
  }

  span {
    position: absolute;
    right: 1em;
    top: 0.75em;
    font-size: 0.5em;
 }

 .active {
  display: block;
}

.inactive {
  display: none;
}

#wrapper {
  margin-top: 40px;
}

#holder {
  text-align: center;
  margin: 0 auto;
  width: 300px;
}

.card {
  background: #f5f5f5;
  padding: 10px;
  display: inline-block;
  margin: 0 0 1em;
  margin: 10px;
  width: 30%;
  cursor: pointer;
  -webkit-perspective: 1000;
  -webkit-backface-visibility: hidden;
  transition: all .1s ease-in-out;

  h3 {
    text-align: center;
  }

  img {
    display: block;
    width: 100%;
  }
}

.card:hover {
  transform: translateY(-.5em);
  background: #ebebeb;
}

To finally be able to view and use our component in action, we need to include the created HTML tag within index.html file. So open src/index.html and fill in this manner:

<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <title>Stencil Starter App</title>

  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
  <meta name="theme-color" content="#16161d">
  <meta name="apple-mobile-web-app-capable" content="yes">

  <script src="/build/app.js"></script>

  <link rel="apple-touch-icon" href="/assets/icon/icon.png">
  <link rel="icon" type="image/x-icon" href="/assets/icon/favicon.ico">
  <link rel="stylesheet" href="/assets/style.css">
  <link rel="manifest" href="/manifest.json">
</head>
<body>
  <header style="background: green;">
    <span>JavaScript Legends</span>
  </header>
  <!-- the default html tag was commented -->
  <!-- <my-name first="Stencil" last="JS"></my-name> -->
  <my-legend></my-legend>

</body>
</html>

From the file above, you will realize that we also added a stylesheet style.css which was used to style the navigation bar.

stencil-pwa-app/src/assets/style.css:

header {
  margin: 0;
  height: 56px;
  padding: 0 16px 0 24px;
  background-color: #35495e;
  color: #fff;
}

header span {
  display: block;
  position: relative;
  font-size: 20px;
  line-height: 1;
  letter-spacing: .02em;
  font-weight: 400;
  box-sizing: border-box;
  padding-top: 16px;
}

So far, we have been able to declare a web component and add logic to toggle the list of JavaScript legends within our public list. Now it’s time to view our application in a browser. If you haven’t done so, proceed to the terminal and run this command:

npm start

If it is successfully accomplished, your page should look similar to this:

Making our app a PWA

Part of the main objective of this article is to make our Stencil application progressive. Fortunately, Stencil is built with support for service workers out of the box. Service Workers is a very powerful API that powers progressive web apps.

To make this work, we will do a production build of our application by running this command:

npm run build

This command will prepare our application for production by adding built files into stencil-pwa-app\www folder. More importantly, it will generate a service worker and inject the necessary code to register the service worker into our index.html file.

To configure service worker open stencil-pwa-app/stencil.config.js and fill it with this:

exports.config = {
  bundles: [
    { components: ['my-name', 'my-legend'] }
  ],
  collections: [
    { name: '@stencil/router' }
  ],
  serviceWorker: {
    globPatterns: [
      '**/*.{js,css,json,html,ico,png,jpg,jpeg}'
    ]
  }
};

exports.devServer = {
  root: 'www',
  watchGlob: '**/**'
}

To test our progressive application locally let’s serve the generated build file within the www folder. There are a variety of options for serving files on a local host, but for this tutorial we will use serve :

#install serve
npm install serve

#serve
serve www

This will launch our application on localhost port 5000. If we navigate to localhost:5000, we should still see the page running as before.

To inspect our configured service worker, open the developer tool on Chrome and click the Application tab. Now select service workers — you should see a registered service worker.

Let’s attempt going offline by clicking the check box. Reload the page, and our app should still be running fine offline:

This is because we already configured our service worker to cache all files and assets:

// stencil-pwa-app/stencil.config.js

serviceWorker: {
 globPatterns: [
 ‘**/*.{js,css,json,html,ico,png,jpg,jpeg}’
 ]
}

This application can be run offline without any fear of fast internet connection.

You can clone the demo and see it work on your machine.

Conclusion

Building web applications with web components and the ability to share this component, irrespective of the frameworks used, will transform how we build web positively.

You might have realized that it is easy to quickly build an offline first progressive web application with StencilJs. Even though Stencil is still in the early stage of development, moving forward, it will definitely make working with web component easy.

StratusUpdate

Sign up for the Stratus Update newsletter

With our monthly newsletter, we’ll keep you up to date with a curated selection of the latest cloud services, projects and best practices.
Click here to read the latest issue.