A few weeks ago in Polymer Summit 2017 the Ionic Framework team announced a new compiler which creates native Web Components. The compiler is called Stencil and it combines a lot of aspects from known libraries and frameworks such as React and Angular.
In this post I’ll try to introduce Stencil and why I was excited to take a look at it in the first place.
The Web Components API Saga
I’ve been talking about the Web Components APIs for a few years already in conferences and in local events. The state of the APIs is in flux for years and support in browsers isn’t great (unless you use Chrome or Safari). My hope is that they will be standardized and will enable us to create reusable native web components.
Until that happens, we relay on libraries and frameworks that enable us to build components such as React or Angular.
If You Build It, They Will Come
The Ionic Framework team released the first bits and bytes of Stencil a few weeks ago. As written in the Stencil website, it is
The magical, reusable web component generator
Stencil is a build-time tool and that is a very important thing to understand. The output of Stencil is a 100% standards-compliant Custom Element. So what does it mean to you as a web developer? It means that you can consume the compiled component from any other library or framework. For example, if you’ve created a dialog in Stencil, that dialog can be used in React, Angular, Polymer or whatever. You will still need to use a HTML5 Web Components polyfill in some browsers but the support for the APIs is on it’s way in most of the browsers.
Stencil is based upon aspects such as:
- Virtual DOM
- Async rendering
- Reactive data-binding
- TypeScript
- JSX
After a few projects both in Angular and in React it was really natural to me to build my first component in Stencil.
Creating a Stencil Project
So now that we understand what Stencil is, let’s build a component with it. The first thing that you will want to do is to create a new project.
Make sure that npm is installed on your machine :)
The Ionic Framework team already created a seed for creating a new project which can be found in https://github.com/ionic-team/stencil-starter.git.
Just write the following in command line:
git clone https://github.com/ionic-team/stencil-starter.git my-first-stencil-component
cd my-first-stencil-component
git remote rm origin
npm install
and you will be ready for work. The project should look like:
Creating a Simple Stencil Component
If you are new to TypeScript or JSX, I suggest to find tutorials about these techs first before going farther in this post.
Now that we have our project we can start working on our new component. In the example I’m going to create a simple collapsible panel.
I renamed the created my-name component from the seed to collapsible-panel and here is the code for the component:
import {Component, Prop, State, Method} from '@stencil/core';
@Component({
tag: 'collapsible-panel',
styleUrl: 'collapsible-panel.css'
})
export class CollapsiblePanel {
@Prop() title: string;
@State() collapsed: boolean;
@Method()
toggle() {
this.collapsed = !this.collapsed;
}
render() {
return (
<div>
<div id="header" onClick={this.toggle.bind(this)}>
<span>{this.title}</span>
</div>
<div id="content" hidden={this.collapsed}>
<slot />
</div>
</div>
);
}
}
Let’s understand what is going on.
First of all, the file’s extension is .tsx indicating that the code is written in TypeScript and JSX. In order to create a Stencil component you use the Component decorator which is configuring the component with the name in the DOM and the component’s style.
Then, you create a class for the component and implement it. In the class I’m using two other Stencil decorators — the Prop and State decorators. The first indicates a property that the component will get as a component attribute. The second is an inner state of the component.
The magic of the component exists in the render function. In the render function you will tell Stencil compiler how to render the component. A few things to notice in the render function are the usage of curly brackets for binding (for example the click event or the title property) and the slot element, which is used to indicate that the content will be provided by the component’s user (like transclusion in AngularJS).
Last but not least, you can also spot the Method decorator which indicate that a component function should be exposed by the component as its API.
I added some style to the collapsible-panel.scss file:
collapsible-panel {
display: block;
border: black dashed 1px;
}
#header {
background: blue;
color: white;
cursor: pointer;
padding: 2px;
}
and updated the index.html file with the following element:
<collapsible-panel title="Collapse me!">
<ul>
<li>Components are awesome!</li>
<li>They drive the web</li>
</ul>
</collapsible-panel>
Last thing I had to do is to register the component in the stencil.config.js file:
exports.config = {
bundles: [
{ components: ['collapsible-panel'] }
],
collections: [
{ name: '@stencil/router' }
]
};
exports.devServer = {
root: 'www',
watchGlob: '**/**'
}
Running the app using the npm start command produced the following web page with the collapsible component:
The Compiled Component
If you ask yourself how the collapsible-panel component looks like after Stencil’s compilation, here is the compiled code:
/*! Built with http://stenciljs.com */
App.loadComponents(
/**** module id (dev mode) ****/
"collapsible-panel",
/**** component modules ****/
function importComponent(exports, h, t, Context, publicPath) {
var CollapsiblePanel = /** @class */ (function () {
function CollapsiblePanel() {
}
CollapsiblePanel.prototype.toggle = function () {
this.collapsed = !this.collapsed;
};
CollapsiblePanel.prototype.render = function () {
return (h("div", 0,
h("div", { "o": { "click": this.toggle.bind(this) }, "a": { "id": "header" } },
h("span", 0, this.title)),
h("div", { "a": { "id": "content", "hidden": this.collapsed } },
h(0, 0))));
};
return CollapsiblePanel;
}());
exports['COLLAPSIBLE-PANEL'] = CollapsiblePanel;
},
/***************** collapsible-panel *****************/
[
/** collapsible-panel: tag **/
"COLLAPSIBLE-PANEL",
/** collapsible-panel: members **/
[
[ "collapsed", /** state **/ 5 ],
[ "title", /** prop **/ 1 ]
],
/** collapsible-panel: host **/
{},
/** collapsible-panel: events **/
0 /* no events */,
/** collapsible-panel: propWillChanges **/
0 /* no prop will change methods */,
/** collapsible-panel: propDidChanges **/
0 /* no prop did change methods */,
/** collapsible-panel: shadow **/
1 /* use shadow dom */
]
)
The h function in the compiled render function is based on Preact and it’s the function to transform the JSX code you wrote into regular JavaScript.
Summary
Stencil looks very promising. It enables you to create standard web components which can be consumed by any library and framework. The component I created is very simple and I didn’t cover a lot of aspects of Stencil. In future posts I’ll cover other aspects of the compiler so there are things to expect ☺
As a side note, I really love the native Web Components idea and hope that they will be adapted by all of the browsers soon.