5
头图

In the above, we briefly understood the usage scenarios of Web Components, which allows us to use the components we define like using native tags, and Stencil allows us to write Web Components as efficiently as writing React.

The component part of JD.com's cross-end framework Taro is developed with Stencil, a tool chain based on Web Components. It can be seen that Stencil and Web Components have been gradually recognized and accepted by front-end developers.

Then you will have questions:

  • What APIs does Web Components provide to define label components that browsers can recognize?
  • How does Stencil encapsulate syntactic sugar based on native Web Components to improve development efficiency?

With the above two questions, let's learn about Web Components and Stencil step by step.

Web Components

First, let's understand the basic concept of Web Components. Web Component refers to a series of features of HTML and DOM added to w3c. The purpose is to realize componentization from the native level, allowing developers to develop, reuse, and extend custom components to achieve custom labels.

This is a major breakthrough in current front-end development. It means that when our front-end developers develop components, we don't have to care about the compatibility of those other MV* frameworks, and we can really achieve "Write once, run anywhere".

E.g:

 // 假如我已经构建好一个 Web Components 组件 <hello-world>并导出
// 在 html 页面,我们就可以直接引用组件
<script src="/my-component.js"></script>

// 而在 html 里面我们可以这样使用
<hello-world></hello-word>

And it has nothing to do with any framework, which means that it does not need any external runtime support, nor does it need complex Vnode algorithms to map to the actual DOM, but the browser api itself does some compilation processing for the internal logic of the tag, and the performance will definitely be better than some MV* The frame is better.

So how does it achieve high performance? Mainly related to its core API. In fact, in the last article, we have briefly mentioned the three core APIs of Web Components. Next, I will take you to analyze the functions and practical usage of each API in detail. After knowing the core technology of Web Components, you will not know about it. I feel unfamiliar.

Three core APIs

Custom elements

Let's first understand the custom element, in fact, it is the cornerstone of Web Component. So let's take a look at what methods this cornerstone provides for us to build high-rise buildings.

  1. Custom element mount method

Custom elements use CustomElementRegistry to customize html elements that can be rendered directly, and mount them in window.customElements.define for developers to call. The demo is as follows:

 // 假如我已经构建好一个 Web Components 组件 <hello-world>并导出

class HelloWorld extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
           <style>
                :host {
                    display: block;
                    padding: 10px;
                    background-color: #eee;
                }
            </style>
            <h1>Hello World!</h1>
        `;
    }
}

// 挂载
window.customElements.define('hello-world', HelloWorld)

// 然后就可以在 html 中使用
<hello-world></hello-world>

Note: Custom elements must be connected with a '-' connector, as a special distinction, if no custom element is detected, the browser will treat it as an empty div.

Rendering result:

  1. class of custom element

From the above example "class HelloWorld extends HTMLElement { xxx } ", it is found that the construction of custom elements is based on HTMLElement, so it inherits the characteristics of HTML elements. Of course, it can also inherit derived classes of HTMLElement, such as: HTMLButtonElement, etc., as An extension of existing tags.

  1. Lifecycle of custom elements

Similar to the life cycle of the existing MV* framework, the base class of the custom element also contains a complete life cycle hook to provide developers with some business logic applications:

 class HelloWorld extends HTMLElement {
    constructor() {
        // 1 构建组件的时候的逻辑 hook
        super();
    }
  // 2 当自定义元素首次被渲染到文档时候调用 
  connectedCallback(){
  } 
  // 3 当自定义元素在文档中被移除调用 
  disconnectedCallback(){ 
  } 
  // 4 当自定义组件被移动到新的文档时调用
  adoptedCallback(){ 
  } 
  // 5 当自定义元素的属性更改时调用
  attributeChangedCallback(){  
  }
}
  1. Add custom methods and properties

Since custom elements are constructed from a class, adding custom properties and methods is the same as how you would normally develop a class.

 class HelloWorld extends HTMLElement {
    constructor() {
        super();
    }
    

    tag = 'hello-world'
    
    say(something: string) {
        console.log(`hello world, I want to say ${this.tag} ${something}`)
    }
}



// 调用方法如下
const hw = document.querySelector('hello-world'); 
hw.say('good'); 


// 控制台打印效果如下

Shadow DOM

With custom elements as the cornerstone, if we want to carry out componentized encapsulation more smoothly, we must operate on the DOM tree. So good, Shadow DOM (shadow DOM) came into being.

As the name implies, shadow DOM is used to isolate custom elements from external styles or some side effects, or some internal features will not affect the external. Keep custom elements in a relatively independent state.

In our daily development of html pages, we will also come into contact with some tags using Shadow DOM, such as: audio and video, etc. In the specific DOM tree, it will exist one by one tag, which will hide the internal structure, but the controls in it, For example: progress bar, sound control, etc., will exist inside the tag as a Shadow DOM. If you want to view the specific DOM structure, you can try to view the chrome console -> Preferences -> Show user agent Shadow DOM. to the internal structure.

If the component uses Shadow host, there will be a Shadow host node in the regular document to mount the Shadow DOM, and there will also be a DOM tree inside the Shadow DOM: Shadow Tree, the root node is Shadow root, which can be accessed externally with the pseudo-class: host , Shadow boundary is actually the boundary of Shadow DOM. The specific structure diagram is as follows:

Let's take a look at the practical use of Shadow DOM through a simple example:

 // Shadow DOM 开启方式为

this.attachShadow( { mode: 'open' } ); 
  • Not using Shadow DOM
 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style>
        h1 {
            font-size: 20px;
            color: yellow;
        }
    </style>
  </head>

  <body>
    <div></div>
    <hello-world></hello-world>
    <h1>Hello World! 外部</h1>
    <script type="module">
        class HelloWorld extends HTMLElement {
            constructor() {
                super();
                // 关闭 shadow DOM
                // this.attachShadow({ mode: 'open' });


                const d = document.createElement('div');
                const s = document.createElement('style');
                s.innerHTML = `h1 {
                            display: block;
                            padding: 10px;
                            background-color: #eee;
                        }`
                d.innerHTML = `
                    <h1>Hello World! 自定义组件内部</h1>
                `;

                this.appendChild(s);
                this.appendChild(d);
            }

            tag = 'hello-world'
    
            say(something) {
                console.log(`hello world, I want to say ${this.tag} ${something}`)
            }
        }

        window.customElements.define('hello-world', HelloWorld);
        const hw = document.querySelector('hello-world'); 
        hw.say('good'); 
    </script>
  </body>
</html>

The rendering effect is, you can see that the styles have polluted each other:

  • Using Shadow DOM
 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style>
       h1 {
            font-size: 20px;
            color: yellow;
        }
    </style>
  </head>
  <body>
    <div></div>
    <hello-world></hello-world>
    <h1>Hello World! 外部</h1>
    <script type="module">
        class HelloWorld extends HTMLElement {
            constructor() {
                super();
                this.attachShadow({ mode: 'open' });
                this.shadowRoot.innerHTML = `
                    <style>
                        h1 {
                            font-size: 30px;
                            display: block;
                            padding: 10px;
                            background-color: #eee;
                        }
                    </style>
                    <h1>Hello World! 自定义组件内部</h1>
                `;
            }

            tag = 'hello-world'
    
            say(something) {
                console.log(`hello world, I want to say ${this.tag} ${something}`)
            }
        }

        window.customElements.define('hello-world', HelloWorld);
        const hw = document.querySelector('hello-world'); 
        hw.say('good'); 
    </script>
  </body>
</html>

The rendered result is:

You can clearly see that the styles are directly isolated from each other without pollution, which is the benefit of Shadow DOM.

HTML templates

The template template can be said to be a familiar tag. We often use it in single-page components in Vue projects, but it is also a tag provided by the Web Components API. Its feature is the HTML fragment wrapped in the template. It will not be parsed and rendered when the page is loaded, but it can be accessed by js to perform some operations such as insertion and display. Therefore, as the core content of custom components, it is used to host HTML templates and is an indispensable part.

The usage scenarios are as follows:

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

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style>
        h1 {
            font-size: 20px;
            color: yellow;
        }
    </style>
</head>

<body>
    <div></div>
    <hello-world></hello-world>

    <template id="hw"> 
    <style> 
    .box { 
        padding: 20px;
    } 

    .box > .first { 
        font-size: 24px; 
        color: red;
    } 

    .box > .second { 
        font-size: 14px; 
        color: #000;
    }

    </style> 
   

    <div class="box"> 
        <p class="first">Hello</p> 
        <p class="second">World</p> 
    </div> 
    </template>

    <script type="module">
        class HelloWorld extends HTMLElement { 
            constructor() {
                super(); 
                const root = this.attachShadow({ mode: 'open' });
               root.appendChild(document.getElementById('hw').content.cloneNode(true));
            }
        } 
        window.customElements.define('hello-world', HelloWorld);
    </script>
</body>

</html>

The rendered result is:

Everyone should be familiar with Slot , which is equivalent to a placeholder mechanism that connects the inside and outside of a component, and can be used to pass HTML code snippets.

After talking about the "troika" of Web Components, you must have an in-depth understanding of Web Components and are familiar with some conventional ways of writing Web Components.

However, after in-depth understanding, we found that the native Web Component processing package components is not smooth, we need a lot of special processing for data monitoring, DOM rendering, etc., so for these situations that do not conform to the current development mode, help us improve The "wheel" of development efficiency, Stencil, came into being.

So what is Stencil? What problem does it solve? What are the advantages over native Web Component writing? Let's keep exploring.

Stencil

First, let's talk about its background. Stencil is launched by the Ionic core team, jointly maintained by the team member community, and already has 10K+ stars on github.

Stencil can be understood as a toolset for quickly building Web Components. It can also be understood as a compiler, which means that once your component is built, it will leave Stencil and no longer depend on it. And Stencil provides a complete project directory structure and configuration compared to native Web Components, and provides a lot of syntactic sugar and encapsulation functions.

Why use Stencil to build Web Components? What are its advantages? We continue to explore.

First, let's take a look at the advantages of Stencil officially described:

  • Virtual DOM

<!---->

  • Async rendering (inspired by React Fiber) Fiber performance benefits Fiber-like scheduling mode

<!---->

  • Reactive data-binding unidirectional data flow

<!---->

  • TypeScript

<!---->

  • Component lazy loading

<!---->

  • JSX support

<!---->

  • No dependency components

<!---->

  • virtual DOM

<!---->

  • Static Site Generation (SSG)

A bunch of advantages are listed, "unknown and serious", but we can't feel anything in this way, let's take a look at its Demo:

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


@Component({
  tag: 'my-first-component',
})
export class MyComponent {
  @Prop() name: string;

  render() {
    return (
      <p>
        My name is {this.name}
      </p>
    );
  }
}

Is it very similar to React's writing method, and the @ decorator seems to find some shadows of Angular, and the overall style is more inclined to the current mainstream framework.

We develop realistically, coupled with my experience, let’s get down to Stencil and compare the development of native Web Components to what pain points can we solve:

  1. Complete documentation. Detailed and complete documents can be found on Stencil's official website, from project initialization, development, deployment, access methods to each framework, FAQ, etc., which are very complete. It can solve many problems we encountered in specific development. This shows that the official website is really attentively maintaining this framework.
  2. Stencil provides a complete set of entry settings and cli tools, starting from "npm init stencil", Stencil will provide nanny-style configuration options:

After configuration, Stencil will provide a complete set of project directories, including various initialization configurations, so that it can be used truly out of the box.

  1. As can be seen from the above example of Web Components using DOM, native Web Components are not very smooth to operate DOM, similar to the native writing method is not efficient, for example:
 const d = document.createElement('div');

const s = document.createElement('style');

s.innerHTML = `h1 {

            display: block;

            padding: 10px;

            background-color: #eee;

        }`

d.innerHTML = `

    <h1>Hello World! 自定义组件内部</h1>

`;

this.appendChild(s);

this.appendChild(d);

In order to solve this problem, Stencil has added the JSX syntax to make the operation of the DOM have a React experience.

 render() {
  return (
    <div>
    {this.name
      ? <p>Hello {this.name}</p>
      : <p>Hello World</p>
    }
    </div>
  );
}
  1. The "@" syntax sugar decorator provided by Stencil can provide radio data flow, data change hook, etc., combined with JSX, brings us a smooth development experience. details as follows:
  • @Component() declares a new web component
  • @Prop() declares an exposed property/attribute
  • @State() declares an internal state of the component
  • @Watch() declares a hook that runs when a property or state changes
  • @Element() declares a reference to the host element
  • @Method() declares an exposed public method
  • @Event() declares a DOM event the component might emit
  • @Listen() listens for DOM events
 // 定义 props name

// 传入值有变化时,触发重新渲染

@Prop() name: string;



render() {

return (
  <p>
    My name is {this.name}
  </p>
);
}
  1. Virtual DOM provides a mapping to the real DOM, from the diff between the virtual DOM, and patch the diff info to the real DOM, similar to React and Vue, such a virtual DOM mapping will make the tracking data change and re-render. Process is more efficient.
  2. Stencil also provides a more complete lifecycle.

  1. Built-in perfect unit test and e2e test framework, when we generate components, when we use component generation instructions, we provide supporting unit and e2e template files.
  2. Provide custom elements polyfill to give more support to lower version frameworks.

  1. There are also some other features, such as Async rendering similar to fiber, lazy loading of components, etc., which are also more practical skills in our daily development.

From the above features, it can be seen that Stencil is more in line with our current development method than native Web Components, and provides complete syntactic sugar and life cycle. The supporting infrastructure tools allow us to painlessly convert the technology stack.

After understanding the above knowledge points, you may have a preliminary impression of Stencil, but it is not deep, it does not matter. I will carefully analyze and practice it for you in later chapters. Make sure you know the Stencil framework well.

Next Chapter: Analysis of Stencil Decorators and Lifecycle


SuperX
3.6k 声望5.9k 粉丝

蓝湖内推