Getting Started With Stencil

Stencil is a new tool from the Ionic team that allows to create native web components, but with added niceties like virtual DOM, reactive data-binding, TypeScript and JSX. At build time, Stencil compiles away everything that’s not native to the web platform and you’re left with pure web components. Stencil was just announced at Polymer Summit 2017 and so far it’s looking really promising. In this short post we’ll go over how to get started, and we’ll build a simple counter component to explore the simple API.

Stencil can be used to build simple standalone web components, but it can also be used to build full fledged progressive web apps. It offers React-style routing and a SSR rendering API is also available right out of the box.

The next version of Ionic’s components will be built using Stencil, and this should greatly increase performance and decrease bundle size for apps built with Ionic. Plus, while it’ll still be possible to create Ionic apps using Angular, the framework part will be decoupled, so it’ll also be possible to build Ionic apps using Vue, React or even plain JavaScript.

At the time of this writing, Stencil is still really early stage, so tread carefully until a first stable version gets released.

Here's our simple counter component:

The component is integrated on the page with this simple markup:

<easy-counter start="10" step="5" max="150" min="2"></easy-counter>

Setup

The easiest way to get your feet wet is to clone Stencil’s starter app:

$ git clone https://github.com/ionic-team/stencil-starter.git easy-counter

Then you can cd into the easy-counter directory, and install the required dependencies using Yarn or npm:

$ cd easy-counter

$ npm install

# or, with Yarn:
$ yarn

Then, to build the project and run a local server, run:

$ npm start

# or, with Yarn:
$ yarn start

You can also build your project using the build script:

$ npm run build

# or, with Yarn:
$ yarn run build

Getting Started

Stencil components are built using TypeScript and JSX. Components go in the /src/components folder and the shape of a basic component looks like this:

my-fancy-component.tsx

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

@Component({
  tag: 'my-fancy-component',
  styleUrl: 'my-fancy-component.scss'
})
export class MyFancyComponent {
  @Prop() adjective: string = 'fancy';

  render() {
    return (
      <div>
        <p>I am a {this.adjective} component!</p>
      </div>
    );
  }
}

You’ll also want to make sure that your new component is included in the Stencil config file:

stencil.config.js

exports.config = {
  bundles: [{ components: ['my-fancy-component', 'some-other-component'] }]
};

// ...

You can then use the component using its tag name:

index.html

<!DOCTYPE html>
<html dir="ltr" lang="en">

<head>
  <!-- ...your meta and title tags -->
  <script src="build/app.js"></script>
</head>

  <body>

    <my-fancy-component></my-fancy-component>

  </body>
</html>

With this, the component will render as a paragraph like this:

I am a fancy component!

And you can pass a different value for our name property:

<my-fancy-component adjective="boring"></my-fancy-component>

…and the result:

I am a boring component!

A few takeaways

  • As you can see, Stencil blends some of the best ideas from Angular and React. For example, the use of decorators to define our component’s metadata and props as well as using JSX to define the markup of the rendered component as the return value of a render method.
  • We’re using the @Component decorator to define the metadata for our component and the @Prop decorator for the public properties/attributes that the component can accept.
  • The component decorator takes an object with a tag name and an optional relative path to a css or scss file. You can also provide multiple CSS or Sass files by using the stylUrls key and an array of relative paths.

Building a Counter Component

To build our counter component, we’ll introduce 3 more concepts:

  • The @State decorator is used to decorate properties for state that is internal to the component. Stencil re-renders a component when a property decorated with @State changes, but those properties are not available outside the component class.
  • Three lifecycle hooks are available: componentWillLoad, componentDidLoad and componentDidUnload. In the following example, we’re using componentWillLoad to set our internal value state property to the value of our start public property.
  • You can listen for native DOM events using the onClick={() => this.increment()} syntax in your JSX.

Here’s the full implementation for our counter:

components/counter/counter.tsx

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

@Component({
  tag: 'easy-counter',
  styleUrl: 'easy-counter.scss'
})
export class EasyCounter {
  @Prop() start: number = 1;
  @Prop() max: number = 100;
  @Prop() min: number = 0;
  @Prop() step: number = 1;

  @State() value: number;

  componentWillLoad() {
    this.value = this.start;
  }

  increment() {
    const newValue = this.value + this.step;
    if (newValue > this.max) {
      this.value = this.max;
    } else {
      this.value = newValue;
    }
  }

  decrement() {
    const newValue = this.value - this.step;
    if (newValue < this.min) {
      this.value = this.min;
    } else {
      this.value = newValue;
    }
  }

  render() {
    return (
      <div>
        <button type="button" onClick={() => this.increment()}>
          +
        </button>
        <span>
          {this.value}
        </span>
        <button type="button" onClick={() => this.decrement()}>
          -
        </button>
      </div>
    );
  }
}

Make sure to give a type to props even if they have an initial value (e.g.: @Prop() start: number = 1). Otherwise, when a prop is passed-in, its type may end up being treated as a string while you were expecting a number.

Our styles for this component are pretty simple:

components/counter/counter.scss

easy-counter {
  button,
  span {
    font-size: 3rem;
    font-family: monospace;
    padding: 0 .5rem;
  }

  button {
    background: pink;
    color: black;
    border: 0;
    border-radius: 6px;
    box-shadow: 0 0 5px rgba(173, 61, 85, .5);
  }

  button:active {
    background: #ad3d55;
    color: white;
  }
}

🎉 Our counter component is now ready to use! In following posts, we'll cover additional decorators like @Method and @Element as well as basic routing.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...