This is the sixth in a series of articles on our “Home Office Challenges App (HOCA)”. It describes StencilJS and the way we used it for our application.

What is StencilJS?

Many of you have already heard about big JavaScript Frameworks like React, Angular, or Vue.js. They are well-suited for a wide range of applications and provide a large number of features.

However, it is often the case that large corporations or government agencies do not use one single JS Framework exclusively. Instead, they tend to use multiple such frameworks as buildings blocks for their specific purposes.

This poses one particular problem: Corporate Design. Those application building blocks should still look and feel the same, no matter which framework they use. Here is where StencilJS comes into play.

StencilJS is not a framework, but a compiler. Every component that we create with StencilJS is automatically compiled to Web Components. WebComponents are custom reusable components. They are supported by every modern browser, and can be re-used within all common JS Frameworks.

So, we decided to use StencilJS for our HOCA application – not only for specific Web Components, but for the whole Front-end. With that approach, we hoped to obtain a comprehensive overview of its problems and limitations.

What is JSX?

Let’s first have a look at the code within a StencilJS project. Here is what the JSX syntax looks like:

const element = <h1>Hello, world!</h1>

Looks strange at first glance, doesn’t it? Something of a mixture between JavaScript and HTML. It may remind you of a Template Language, but it is using the full capabilities of JavaScript. Rendering logic and UI logic, event handling, and state changes – they are all combined together.

Each component has a render function that returns a tree of components that are rendered to the DOM at runtime:

 function formatName(user) {
    return user.firstName + ‘ ’ + user.lastName;
 }

 const user = {
     firstName: ‘Harper’,
     lastName: ‘Perez’
 };

 const element = (
     <h1>
         Hello, {formatName(user)}!
     </h1>
 ); 

Data Binding

Using Curly Braces you can embed expressions into the HTML code of a StencilJS component. Every valid JavaScript expression is acceptable. So, you can do something simple and just display the value of a variable. Or, as you can see in the following screenshot, you can loop over a collection and display its values using the “map” function:

 render() {
     return (
         <div>
             {this.challenges.map((challenge) =>
                 <div>
                     <div>{challenge.name}</div>
                     <div>{challenge.duration}</div>
                 </div>
             )}
         </div>
     )
 } 

A few things to remember:

  • Do not use quotes around the curly braces when embedding expressions
  • We are using JavaScript here, so some HTML attributes will be named differently, e.g. “class” becomes “className”, “tab” becomes “tabIndex”, “for” becomes “htmlFor”

Binding an HTMLElement to a variable

To access a HTML element inside the JavaScript code of a component, you can use the HTML ref attribute. It allows you to bind a HTML element to a variable. To do this, we first prepare the variable:

chartComponent!: HTMLElement;

And then bind the element in our JSX code to this variable:

<canvas id="myChart" class="chartjs" ref={(el) => this.chartComponent = el as HTMLElement}></canvas>

Now, inside the componentDidRender() lifecycle method, we can use this variable, e.g. to initialize a chartjs chart:

 new Chart(this.chartComponent, {
     type: 'bar',
     data: graphData
 }); 

Properties and States

In many cases, we want to use components within other components (nesting). We also want these nested components to communicate with each other.

For example, we want to use an Activity component inside of a Challenge component, and pass the id of the Challenge to the Activity. With the “Prop” decorator, we can define which variables are passed on in this way.

 export class Activity {
   @Prop() challengeId: string; 

In the HTML template of the Challenge component, we add a HTML element for this Actitvity, then hand the Challenge’s id to it:

<activity challengeId={this.id}>

That is enough to set the challengeId in the Activity component to the correct value.

Sometimes, we do not want a parent component to change a specific variable in its child component. If we add the decorator “State” to a variable, StencilJS will know that it is only used for the internal data of the component, which can only be modified by the component itself.

For example, we want to toggle a dialog in the activity component, and need a state property for that which does not need to be set from the outside:

 export class Activity {
   @State() isDialogOpen: boolean; 

Events and Listeners

Stencil provides an API to specify the events a component can emit, and the events a component listens to. It does so with the “Event” and “Listen” decorators.

Components can emit data and events using the “Event” decorator, which is applied to a variable of the type EventEmitter. This will also dispatch custom DOM events to other components.

 @Event()
 selectedItem: EventEmitter<Item>;
 selectItem(item: item) {
   this.selectedItem.emit(item);
 } 

The “Listen” decorator is for listening to DOM events, including the ones dispatched from an “Event” as described above.

 @Listen('selectedItem')
 selectedItemHandler(event) {
   console.log(event);
 } 

Testing

Testing in StencilJS falls into two categories: Unit Tests and End-to-end tests. Both types of tests use Jest, a popular JavaScript testing framework.

Unit Tests are used to test a component in isolation. In the case of StencilJS, they can not only be used to test specific methods. It is also possible to check if a HTML page is rendered in the correct way, but for that, the behavior of the browser is just simulated.

With End-to-end tests (E2E), you can verify the behavior of your component in an actual browser. This is accomplished with the help of the Puppeteer library, which runs a Chrome browser and lets the E2E interact with it.

Lifecycle Methods

If you have worked with other Web Frameworks before, you are probably familiar with the concept of Lifecycle Hooks / Methods. The lifecycle of a component consists of several events, from the initial loading to its destruction at the end. StencilJS components have 9 such lifecycle events.

Developers can add specific actions at each stage of the component lifecycle.  To do this, you only need to implement the correct Lifecycle Method and add the desired statements to it. StencilJS will then execute the action at the exact right time. For example, if you want to execute an action whenever the component is updated, just add a method componentDidUpdate() and make it do whatever you want.

Conclusion

While StencilJS provides some of the basic functionality of well-established UI frameworks/libraries like Angular and React, it is clear that it should not be thought of as a replacement for them. Having said that, StencilJs can be very useful when one wants to create lightweight components whose UI and logic are relatively uncomplicated.


With these final remarks, we end our HOCA series. We hope that you enjoyed what you read, and that you found something which sparked your interest, or which will help you in your daily work!