Stencil.js was introduced recently at the latest Polymer Summit. Some of our partners had questions as to what exactly Stencil is, what it creates, and what are the implications of its use. We thought we'd have a look. Here's what we discovered.
Stencil is three things:
Stencil provides a fairly immersive development environment which is installed via a clone of either their application starter project, or a simplified component builder project. The former allows you to build web components within the context of an application, presumably where there is a tight coupling between component design and application needs. The latter appears to be a more standalone approach to building web components.
There is a heavy influence of React here, with the application starter project feeling similar in nature to Facebook’s create-react-app project template. Beyond the development environment particulars, Stencil leans on React-like idioms for binding component state change to a render function that outputs JSX.
This is a similar approach to web component architectural design as Skate.js with the recent difference of Skate’s offering of pluggable renderers (JSX, lit-html, template strings, etc) versus Stencil’s fixed reliance on JSX. Skate appears to offer a much reduced development environment beyond the JavaScript libraries it provides for its implementation.
Stencil specifies a canonical class model for defining a web component, relying on ES7 decorators for signaling to the build system how the resulting definition is to be assembled with Stencil library routines. The result is then transpiled into ES5 JavaScript consumable web component code, coupled with demand-loaded polyfills where required. Stencil refers to this build pipeline as a compiler.
Where the “compiled” aspect comes in is the binding of the Stencil component definition code with library code that somewhat opaquely provides partial-specification web component support. More on that later.
The Stencil component class template is an abstraction that binds with Stencil library code. In this sense, Stencil is a framework and the usage of the Vanilla JavaScript™ terminology feels a bit like a cheat, if you’re of the opinion that Vanilla JavaScript implies the code is all in front of you as a developer with nothing tricky up the sleeves. In the sense that it has nothing at all to do with Polymer and Polymer’s tricks, then, yes, it’s vanilla. The discussion of what should define a Vanilla Javascript component is probably worth a blog post of its own.
But as an example of this sleight of hand, Stencil builds components that do
not make use of or rely on the Shadow DOM portion of the specification,
resulting in custom elements alone, yet it magically supports the use of the
<slot>
tag. It does so by providing framework/library code
that parses for named slots and manages the child element merger on its own.
Whether the full, expected functionality of slots under Shadow DOM is
supported, experimentation and probably more than a little cold sweat might
be called for.
There are other examples of this sort of polyfill/framework/non-vanilla construct within Stencil’s definition/bind-to-library/transpile lifecycle, including enforcing property immutability within a component’s internal code, but the key point in analyzing Stencil is to recognize it’s not just an ES5-generator for web components (as well as, don’t forget, not a React component generator, or a Vue component generator, etc, etc). It’s an opinionated framework.
The documentation describes the use of several decorators for fleshing out a component’s definition:
@Prop
decorator specifies properties, reflected to
attributes, that are immutable component code internally.
@State
decorator enables a component to have internal
state that cannot be changed directly by a user. A change in state results
in the render function being called.
@Method
decorator identifies public API methods
@Element
decorator is how to get access to the host
element within the class instance. This returns an instance of an
HTMLElement
, so standard DOM methods/events can be used here.
@Event
and @Listen
set up native DOM events and
event handlers
Stencil also provides hooks for specifying web component lifecycle callback code.
Further in the way of framework flavor, Stencil provides mechanisms for rendering Stencil web components via server side rendering, and for generating pre-rendered static html+css pages. Stencil also provides objects for integrating with Node.js/Express.js web servers.
The inclusion of an add-on stencil-router
is an additional
reminder that Stencil is as much about building Stencil-based applications as
it is about generating web components.
Stencil provides a non-transparent means for setting up Stencil components for
distribution. It’s non-transparent in the sense that it relies on its build
tools coupled with a stencil.config.js
configuration file and
expected additions to the project’s package.json
file. Once
published to npm, the component package can be referenced through HTML via:
<script src='https://unpkg.com/my-name@0.0.1/dist/myname.js'></script>
In a Stencil application, a published and npm-installed component package can
be accessed through the application’s stencil.config.js
file
where the components to be “imported” are added to the collections key. It’s
not apparent to me whether JavaScript import
is supported.
As a development platform, Stencil.js looks as enticing as React in that expressiveness through abstraction and FRP idioms make reasoning about the architecture easy. The toolset is impressive as a means of building web component based web applications, and feels like the result of a team actually dogfooding their own products against it - just as the Stencil team suggests in their talks.
The potential drawbacks in adopting it for your own project are
clear, though: the fixed JSX renderer, abstractions hidden behind the
library/transpile system, and the adaptation of <slot>
into a Custom Element implementation despite its Shadow Dom origins. These
framework decisions might make moving away from Stencil difficult while
preserving the component design in another web component development
environment.