StencilJS: the compiler UI framework to keep in your radar

StencilJS has the potential to trigger a change in front-end development... Or at least I believe so 😁

The following article is dedicated to share some thoughts on Stencil - an interesting compiler / framework / framework compiler that could have an impact in our industry.

⚠ Please note: I've only scratched the surface of Stencil and I bet there's much to it that I don't know and that might change my perception, so feel free to elucidate me, feedback is always welcome 😄

TL;DR

  • Stencil is a mash-up of front-end frameworks (namely React, Vue and Angular) that compiles your schwifty code to web components;
  • Its main focus is for creating isolated components that work everywhere, but it can also be used as a full-blown framework from project set-up to testing;
  • Coming from React, I found the developing experience (DX) to be quite satisfying, but the software can still unstable for production work, although there are plenty of apps out there;
  • If I'd found Stencil a year ago, I'd consider abandoning React for it as it has some very cool features and abstractions, and ships super tiny bundles, but with current Hooks and Suspense development, I'll probably use it just for fiddling around with side projects;
  • Nevertheless, Stencil should be in your radar as it might help steer the JS framework community towards more compiler-based alternatives

What is StencilJS

In short, Stencil is a tool that compiles your react/angular-like typescript code into web components that leverage the browsers' native APIs. It has quite a lot of cool stuff packed into it, such as the generation of multiple bundles for different browsers (ES5 for IE11 and the likes vs. modern ES6+ JS), automatic code splitting and component lazy-loading, but it shines on being framework agnostic. Essentially, you can use Stencil components anywhere on the web, from insert your framework here apps to vanilla JS projects, and that's exactly why the Ionic team built it.

You should read more about it on their site and watch its announcement video, but, in essence, its main goal is to provide a way for Ionic 4 components to become usable in any front-end project, eliminating the need to re-write the UI library for each different stack. However, although Stencil's main focus is on compiling reusable components, it can also be used as a fully-fledge framework to build entire apps, and that's the main usage I'll cover in this article.

Why it might matter to you

If you're building a design system for a large company with several domains, apps, teams and stacks, the benefit of Stencil is obvious: write your components once and have them work for everyone, everywhere. This also applies to UI libraries: instead of having material-ui for React and angular-material for, you guessed it, Angular, we could unite efforts and resources into creating a single, universal library.

If, however, you're building full apps and are looking for a framework, StencilJS is also an option, especially if you need to deliver tiny packages to save users' bandwidth, as it excels in that regard. Stencil's docs are quite tiny and the software has a bunch of interesting decisions and features, so I suggest you take a look into it (should take about 1hr).

In case reading the docs is not in your plans, just know that Stencil is highly dependent upon Typescript, it uses decorators similar to Angular as well as JSX from React, and plugging in components in .html templates feels a bit like Vue (as far as I'm concerned). Below is a super simple sample component:

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

// If I wanted to isolate the CSS applied to this component from the rest of
// the stylesheet, I could use `shadow: true` in the @Component options ;)
@Component({
  tag: 'loading-spinner',
  styleUrl: 'loading-spinner.css',
})
export class LoadingSpinner {
  render() {
    return <div class="loader" />;
  }
}

Then, when calling it inside another stateful component, I'd use:

// ...
  render() {
    if (this.isLoading) {
      return <loading-spinner />;
    }
  }
// ...

Quite similar to React, huh? If this doesn't appeal to your style, however, Stencil is still relevant to you. Together with Svelte, Imba, Glimmer, and even Angular with its Ivy project, Stencil is riding the compiler wave in which frameworks focus more on build-time transformations in order to ship smaller and more performant code to the browser.

Personally, I'm super excited with this trend and believe it has the potential to dramatically change user experience on the web. In fact, I'm already benefiting from this concept at my agency with Gatsby, a static site/app generator that has been improving quite a lot since I began using it in 2017: simply by upgrading my dependencies for older sites I can see performance improvements.

Eventually, if Web Assembly reaches a point in which it can access the DOM in a performant way, then compilers can shift gears and focus solely on WASM, which could yield amazing returns! So, long story short, keep Stencil on your radar. It's backed by Ionic and for such I bet it's coming to last, and may help steer the whole front-end community towards native browser APIs such as web components, and towards this fancy compiler movement.

My experience with Stencil

Context set, let's get our feet wet and actually talk about Stencil for real apps. You might have heard of the RealWorld project in which people build the exact same app (a Medium-like clone) with different tools, be it for the front-end or the API. I'd been enamoring their repo for a while, so when I figured Stencil was worth the shot, it was an easy pick for testing the new framework.

So, in the pursuit of knowledge and a spot at the bleeding edge of front-end development, I created Stencil's RealWorld example app, which you can find live here. I've annotated my experience while I was at it, and summarized everything below 😉

Onboarding

As I put before, the Stencil way of working is quite similar to React, and for such its learning curve was super easy and quick to grasp... It also helps the fact that their docs are quite clear straight to the point, although it still contains some holes. Knowing Ionic's documentation, I trust Stencil's will become awesome over time when things mature.

There's also a community on Slack, although I didn't have much to say there and when I did no one paid much attention 😔 hahaha

Developer Experience (DX)

Below is a bunch of stuff hard to imagine if you've never used Stencil, and I was a bit lazy when deciding not to use examples... sorry for that! Feel free to skip to the next section 😉

What I love:

  • Tight Typescript integration, with type-checking tied to the console;
  • Awesome attention to details with effective and fast hot-reloading, nice display of the app's status through the page's favicon, easy project set-up, CLI downloading dependencies only when needed etc.;
  • Automatic (and effective) code splitting, period;
  • No need to import components into every file like you do in React: Stencil automatically generates TS types for each component in the src folder and allow you to insert them into JSX with automatic type assertion;
  • npm run start is quite fast when compared to Create React App and Gatsby
  • You don't have stateful vs. stateless components differentiation like in React, which is quite positive as you can simply add a @State decorator when you want to use it
    • With React Hooks, however, this becomes less of an advantage haha
    • This could be considered a flaw as we're adding classes for everything, but I doubt this has real impact on performance
  • Instead of React.Fragment or <></>, if you want to render multiple elements at the root of your component, you just use an array: jsx~return [<hr />, <button>Hello</button>]
    • The comma between tags can be a hassle if you miss it, be sure to add ts-lint with tslint-stencil
  • This one is both a blessing and a curse: errors on specific components don't crash the whole app
    • This is good for isolating bad stuff, but can be bad for debugging
  • Easy set-up of PWAs
  • Selecting components is super easy with the @Element decorator
    • Although you can't directly select tags inside the component, you can always use js~this.element.querySelector('tag-name') or something like that to go down the tree. See the docs for a clearer example.

What could be better:

The fact that you write components' class names in PascalCase and their tags as separated-by-hash can be a bit confusing;

You can only export one thing per .tsx file, which is understandable, as the compiler alerts numerous exports per file could cause inefficient bundling and code splitting... However, I believe Typescript interfaces and types should be exportable as they don't get included in the final code (dunno if that's feasible, tho 😝)

When testing, whenever you try to navigate the router goes crazy and the test crashes.

  • In fact, the router doesn't really integrate well into e2e testing as of @stencil/router 0.3.2 and @stencil/core 0.17.0, making it impossible to run any test

You can't ALT + CLICK (or equivalent) on components to go to their corresponding file, instead you're sent to its definition file, which is auto-generated by Stencil. A small price to pay for not having to import components, maybe?

Tooling is still in its infancy. I had so many hurdles with VS Code that writing code became a bit cumbersome at first, and the currently available plugin options aren't quite there yet. I'm optimistic, though!

Stencil is missing an alternative to Redux. Although they provide @stencil/state-tunnel (which is similar to React's Context), the package is highly unstable as of version 0.0.8

  • In fact, state-tunnel had an unreleased version 0.0.9-1 that I found not from the npm page, but rather from a code example extracted from one of Stencil's repos, and that one actually worked without crashes during compile time...
  • Plus, state-tunnel seems to be quite a big package, although that could just be a fault of the Import Cost extension
  • I ended up not relying on the unstable API and went for good 'ol prop drilling, passing it down the component tree 😅

Stuff that might be my own fault:

  • I couldn't get auto-completion of components' tags working inside JSX, which is a bummer: it slows down development and makes it more error-prone, as I don't get any error for wrongly-typed names;
    • In fact, I spent a good half an hour trying to debug the router when I finally discovered that I had typed it wrong.
  • When running e2e tests, if you don't know what you're doing, having to re-run the app every time is a bit cumbersome;
  • In JSX, when adding the tag for a component, VS Code's auto-complete adds a bunch of useless HTML suggestions, making it hard to find the name/type of props you actually want to use;
  • I didn't really find a way to have the router redirect to home if the switch doesn't find a possible route, so I had to create a not-found component that redirected on render;

The end result

Even with all the hurdles, I found the experience to be quite enriching, and the end result is quite satisfying:

  • It's one of tiniest bundles in the RealWorld repository;
  • The code is quite clean and easy to understand (at least for me 👀);
  • By providing an ES6+ bundle for modern browsers, I don't feel like I'm downgrading my app during compile time;
    • And, for the hopefully tiny slice of my audience that accesses from IE11 and the likes, Stencil has an ES5 bundle filled with polyfills for everything you might need.
    • I've tested this behavior and got to ~15% bundle reduction when using Chrome when compared to IE
  • Typescript integration made my life so much easier and, hopefully, the repository much more maintainable;

Besides the small caveats presented in the previous sections, my only complaint is that, currently, Stencil depends on initial parsing of Javascript to decide which bundle to download, and that culminates in a longer times of white screens before the final app is loaded and poor loading performance on bad connections / slow CPUs 😔

Stencil vs. React

Note: This comparison might serve to other frameworks, but I don't have relevant experience to say so!

First of all, let me say it's a very smooth transition to make and that you can easily apply Stencil to your next hobby project or maybe create components for production work. Without further ado, let's go to pros and cons:

  • Pros for Stencil:
    • smaller packages;
    • lazy loading and code splitting without lifting a finger;
    • e2e tests integrated into an official and easy workflow;
    • promise of visual diffing in the future;
    • Less lock-in: if you need to change frameworks, you can reutilize part of your Stencil app without many alterations due to its compiler nature;
    • No need to import everything all the time and pollute your code with "useless" (not) code;
    • Tight Typescript integration and awesome CLI for a great runtime;
    • You can supposedly pre-render stuff, though I'm not sure how.
  • Cons for Stencil:
    • First load is a bit slow due to relying on initial parse to fetch first bundle;
    • Still unstable and will probably break things before 1.0.0;
    • Won't (and can't) evolve in React's or Angular's speed, so not sure how long it can remain competitive;
    • React's Context, Hooks and Suspense change the game quite a bit, and made developing in React super fun again and much more effective;
    • The ecosystem is still close to inexistent;
    • And maybe the most worrying: **I'm not sure developing Stencil has a high priority in Ionic's agenda now that they released Ionic 4

The future of Stencil

If Ionic continues to spend resources on Stencil, I see a very bright future for it. I doubt it will one day reach React or Angular's ubiquity or relevance, but it can become quite a powerful and stable option for those looking for frameworks closer to the metal and better aligned to browser APIs.

More important than Stencil, however, is what it can represent and unlock in the front-end scene: As put before, bringing optimizations to compile-time is a necessary step into achieving true performant apps that can reach everyone, and with Stencil's partial lead, the rest can follow.

For the time being, however, it's a fun tool to play, with great abstractions and promising concepts, but I'll keep with React for the "real" stuff 😄