A Web Component compiler for building fast, reusable UI components and static site generated Progressive Web Apps

Overview

npm Build & Test license

Stencil: A Compiler for Web Components and PWAs

npm init stencil

Stencil is a simple compiler for generating Web Components and static site generated progressive web apps (PWA). Stencil was built by the Ionic team for its next generation of performant mobile and desktop Web Components.

Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. It takes TypeScript, JSX, an asynchronous rendering pipeline to ensure smooth running animations, lazy-loading out of the box, and generates 100% standards-based Web Components that run on both modern browsers and legacy browsers.

Stencil components are just Web Components, so they work in any major framework or with no framework at all. In many cases, Stencil can be used as a drop in replacement for traditional frontend frameworks given the capabilities now available in the browser, though using it as such is certainly not required.

Stencil also enables a number of key capabilities on top of Web Components, in particular Server Side Rendering (SSR) without the need to run a headless browser, pre-rendering, and objects-as-properties (instead of just strings).

Note: Stencil and Ionic Framework are completely independent projects. Stencil does not prescribe any specific UI framework, but Ionic Framework is the largest user of Stencil (today!)

Why Stencil?

Stencil is a new approach to a popular idea: building fast and feature-rich apps in the browser. Stencil was created to take advantage of major new capabilities available natively in the browser, such as Custom Elements v1, enabling developers to ship far less code and build faster apps that are compatible with any and all frameworks.

Stencil is also a solution to organizations and library authors struggling to build reusable components across a diverse spectrum of frontend frameworks, each with their own component system. Stencil components work in React, Vue, Angular and Ember, as well as they work with jQuery or with no framework at all, because they are just plain HTML elements.

Compared to using Custom Elements directly, inside of every Stencil component is an efficient JSX rendering system, asynchronous rendering pipeline to prevent jank, and more. This makes Stencil components more performant while maintaining full compatibility with plain Custom Elements. Think of Stencil as creating pre-baked Custom Elements as if you wrote in those features yourself.

Getting Started

To create a new project using an interactive cli, run:

npm init stencil

To start developing your new Stencil project, run:

npm start

Creating components

Stencil components are TypeScript classes with decorator metadata. The decorators themselves are purely build-time annotations so the compiler can read metadata about each component, and removed entirely for smaller efficient components.

Create new components by creating files with a .tsx extension, such as my-component.tsx, and place them in src/components.

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

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css'
})
export class MyComponent {

  @Prop() first: string;

  @Prop() last: string;

  render() {
    return (
      <div>
        Hello, my name is {this.first} {this.last}
      </div>
    );
  }
}

Note: the .tsx extension is required, as this is the standard for TypeScript classes that use JSX.

To use this component, just use it like any other HTML element:

<my-component first="Stencil" last="JS"></my-component>

API

The API for stencil closely mirrors the API for Custom Elements v1.

Components

Decorator Description
@Component() Indicate a class is a Stencil component.
@Prop() Creates a property that will exist on the element and be data-bound to this component.
@State() Creates a local state variable that will not be placed on the element.
@Method() Expose specific methods to be publicly accessible.

Why "Stencil?"

A Stencil is a tool artists use for drawing perfect shapes easily. We want Stencil to be a similar tool for web developers: a tool that helps web developers build powerful Web Components and apps that use them, but without creating non-standard runtime requirements.

Stencil is a tool developers use to create Web Components with some powerful features baked in, but it gets out of the way at runtime.

Related

Testing powered by


BrowserStack Open-Source Program

License

Comments
  • Add Source-Maps

    Add Source-Maps

    Repost of automatically closed issue #1255

    Identical issues:

    • #219
    • #884
    • #1486

    Stencil version:

     @stencil/[email protected]
    

    I'm submitting a:

    [ ] bug report [x] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

    Current behavior: I'm using the stencil starter project found here (https://github.com/ionic-team/stencil-component-starter) and I would like to debug the code in visual studio code. The following configuration lets me launch chrome from within visual studio code:

    "configurations": [
     {
      "type": "chrome",
      "request": "launch",
      "name": "Launch Chrome against localhost",
      "url": "http://localhost:3333",
      "sourceMaps": true,
      "webRoot": "${workspaceFolder}/www"
     }
    ]
    

    Expected behavior: When I place a breakpoint in visual studio code, I expect execution to stop there. This doesn't happen. Instead I get a warning saying 'unverified breakpoint' for all the breakpoints I place in the code.

    Steps to reproduce: See Current behavior and Expected behavior

    Main advantages of having source maps

    • Editor integration
    • IE11 debugging
    Feature: Want this? Upvote it! 
    opened by tricki 58
  • How to include third party javascript library in stenciljs?

    How to include third party javascript library in stenciljs?

    I need to inlude this script in my stencil component. I included this in the index.html which is in src folder. This is working exactly how i need in the development mode but once i run npm build . This doesn't work. Can someone tell me where do i include this third party library?

    Awaiting Reply 
    opened by kartikgreen 48
  • Mobile performance concern

    Mobile performance concern

    Stencil version:

     @stencil/[email protected]
    

    I'm submitting a:

    [x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

    Current behavior:

    When multiple elements are added to a screen simultaneously using a documentFragment, the Stencil elements render in a rather random way. This becomes especially pronounced in lower-powered processor scenarios like a mobile device.

    After a bit of testing, I think stencil 1.x components may be rendering slower than pre-1.x components. Stencil 1.x is definitely first to start rendering elements which is great, but the rather random introduction of completed elements is mildly confusing (more-so for the users). We were first alerted to this in a mobile application because all other html showed up quickly. Of all the html added, there were 200 Stencil components that showed up 'seemingly' slowly (I know part of this is due to the upgrade process when connected to the DOM), but the random rendering made it little weirder.

    Expected behavior:

    I think just more consistently? Prior to 1.x things output a bit more like one might expect. Raw web components seem to be rendered very similarly to the way pre-1.x renders them.

    Steps to reproduce:

    1. Copy the following into 2 files and open them in Chrome.
    2. Change which head links are commented/uncommented in the 2nd file

    I admit this example is excessive, but otherwise I would have to ask you to open developer tools and turn on CPU throttling. I used Ionic 4.5.0 since I think it was the last time Stencil non-1.x was used. The other is whatever the latest release includes.

    If you run them both, the latest version starts rendering first, but the older version completes well before the latest finishes.

    Related code:

    <html lang="en">
    <head>
      <meta charset="UTF-8"> 
      <!-- <script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
      <script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script> 
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css"/> -->
      <script src="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/dist/ionic.js"></script>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/css/ionic.bundle.css"/>
    </head>
    <body>
      <table>
        <tbody></tbody>
      </table>
      <script>
      const fragment = new DocumentFragment();
      const content = document.querySelector('tbody');
      const rowTemplate = document.createElement('tr');
      const cellTemplate = document.createElement('td');
      cellTemplate.innerHTML = `<ion-button color="primary">Primary</ion-button>`;
    
      function main() {
        const row = buildRow();
        for (let i = 0; i < 100; i++) {
          fragment.append(row.cloneNode(true));
        }
        // stops the process in case you wish to tag in metrics
        alert('Document fragment complete. Shall we append?'); 
        content.append(fragment);
      }
      function buildRow() {
        const row = rowTemplate.cloneNode();
        for (let i = 0; i < 100; i++) {
          row.append(buildCell());
        }
        return row;
      }
      function buildCell() {
        return cellTemplate.cloneNode(true);
      }  
      main();
    </script>
    </body>
    </html>
    

    Other information:

    All of our testing for this issue was done in Chrome and the original issue was seen on an Android device during QA.

    As part of our testing, we created a simple, raw web component and added it to the screen with the same logic used above:

    const sheet = new CSSStyleSheet();
      sheet.replaceSync(`
        :host {
          display: inline-block;
          height: 24px;
          position: relative;
          vertical-align: top;
          width:24px;
        }
        svg {
          fill: currentColor;
        }
      `);
      const SVG = document.createElement('div');
      SVG.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path opacity=".87" fill="none" d="M0 0h24v24H0V0z"/><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.59-13L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41z"/></svg>`;
    
      customElements.define('custom-icon', class extends HTMLElement {
        constructor() {
          super();
          const shadowRoot = this.attachShadow({ mode: 'open' });
          shadowRoot.appendChild(SVG.cloneNode(true));
          shadowRoot.adoptedStyleSheets = [sheet];
        }
      });
    

    Under the same conditions it "seems" to render the same way pre-1.x did. I don't know if that helps but you never know.

    Let me know if you need any more information and thanks for the awesome library!

    triage 
    opened by cary-smith 39
  • Error On Any Inheritance

    Error On Any Inheritance

    Stencil version:

     @stencil/[email protected]
    

    I'm submitting a:

    [X] bug report

    The change in 0.12 to introduce a compiler error when a Stencil component inherits from any class is causing us a lot of problems. Most of our components inherit from non-Stencil base classes that define common properties (not @Props) and methods.

    While I can understand that supporting inheritance between components is hard, blocking all inheritance seems like a poor solution. Can the error be changed to stop you inheriting only from other classes that are also annotated with @Component and leave open the option of inheriting from non-component classes? At the very least can we get some kind of flag or option to disable this check? As it stands we can't move to 0.12.

    opened by lupiter 37
  • Component Lifecycle Methods are not called in the correct order

    Component Lifecycle Methods are not called in the correct order

    Stencil version:

     @stencil/[email protected]
    

    I'm submitting a:

    [x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/

    Current behavior:

    The lifecycle methods are not called in the correct order: NOTE: I've changed CmpA (cmp-a) to CmpZ (cmp-z) which makes the bug appear more often

    CmpD - a1-child client componentWillLoad
    CmpD - a2-child client componentWillLoad
    CmpD - a3-child client componentWillLoad
    CmpD - a4-child client componentWillLoad
    CmpB client componentWillLoad
    CmpZ client componentWillLoad <--
    CmpC client componentWillLoad
    CmpD - c-child client componentWillLoad
    CmpD - a1-child client componentDidLoad
    CmpD - a2-child client componentDidLoad
    CmpD - a3-child client componentDidLoad
    CmpD - a4-child client componentDidLoad
    CmpZ client componentDidLoad <--
    CmpD - c-child client componentDidLoad
    CmpC client componentDidLoad
    CmpB client componentDidLoad
    

    Fails in Chrome on Windows, Safari on macOS and iOS/iPhone, Chrome on Android. IE11 and Edge has the correct hiarchy but childs are loaded reversed.

    Expected behavior:

    The lifecycle methods should be called in the same order on server and client.

    CmpZ server componentWillLoad <--
    CmpD - a1-child server componentWillLoad
    CmpD - a2-child server componentWillLoad
    CmpD - a3-child server componentWillLoad
    CmpD - a4-child server componentWillLoad
    CmpB server componentWillLoad
    CmpC server componentWillLoad
    CmpD - c-child server componentWillLoad
    CmpD - a1-child server componentDidLoad
    CmpD - a2-child server componentDidLoad
    CmpD - a3-child server componentDidLoad
    CmpD - a4-child server componentDidLoad
    CmpD - c-child server componentDidLoad
    CmpC server componentDidLoad
    CmpB server componentDidLoad
    CmpZ server componentDidLoad <--
    

    Steps to reproduce:

    Refresh the following page and compare the server, and client messages. They should be in the same order: https://stencil-bug.firebaseapp.com/prerender-z/

    Changing the prerender-test component "cmp-a" tag, to "cmp-z" makes the lifecycle methods to be called out of order. This is the original test: https://stencil-bug.firebaseapp.com/prerender-a/

    Related code: PR for tests: #1266

    Other information:

    #1130

    triage 
    opened by knutto 35
  • feat(loader): inject import statements that are statically analyzable

    feat(loader): inject import statements that are statically analyzable

    Current behaviour

    Stencil provides it's neat lazy-loader - include once then forget 🚀 - it's super useful. The loader comes in 2 flavours. 1) is designed to be loaded via a