Coding / Programming Videos

Post your favorite coding videos and share them with others!

What Is The Web Components And How To Understand Them?

Source link

What are web components?

First, you need to decide what exactly is included in the concept of web components. A good description of the technology is on MDN. Briefly, the following features are usually included in this concept:

  • Custom elements — the ability to register your HTML tags with a specific behavior
  • Shadow DOM — creating an isolated CSS context
  • Slots — the ability to combine external HTML content with internal HTML component

As an example, write a hello-world component that will greet the user by the name:

// Web components must be inherited from standard html elements
class HelloWorld extends HTMLElement {
constructor () {
super ();
// create Shadow DOM
this.attachShadow ({mode: "open"});
}
connectedCallback () {
const name = this.getAttribute ("name");
// Let's render our content inside the Shadow DOM
this.shadowRoot.innerHTML = `Hello, <strong> $ {name} </ strong>:)`;
}
}
// register our component as html tag
window.customElements.define ("hello-world", HelloWorld);

Thus, each time the <hello-world name = “% username%”> </ hello-world> a tag is placed on the page, a greeting will appear in its place. Very comfortable!

You still need frameworks

It is widely believed that the implementation of web components will make the framework unnecessary because the built-in functionality will be enough to create interfaces. However, it is not. Custom HTML tags really resemble Vue or React components, but this is not enough to replace them entirely. In browsers, there is a lack of a partial update of the DOM approach to the description of interfaces, when the developer simply describes the desired HTML, and the framework will take care of updating the DOM elements that have really changed from the previous state. This approach greatly simplifies working with large and complex components, so without it will be hard.

  • In addition, in the previous section with an example of a component, you might have noticed that we had to write some amount of code to register the component and activate the Shadow DOM. This code will be repeated in each component created, so it makes sense to bring it to the base class — and now we already have the beginnings of the framework! For more complex components, we will also need a subscription to change attributes, convenient templates, work with events, etc.

In fact, web-based frameworks already exist, for example, lit-element. It uses web components as well as lit-HTML to intelligently update the DOM inside the component so as not to render it entirely. Writing components in this way is much more convenient than through the native API.

More often talk about the benefits of web components in the form of reducing the size of the loaded Javascript. However, the lit-element that uses web components weighs 6 kb at best, while there is a preact that uses its components similar to React, but at the same time weighs 2 times less than 3 kb. Thus, the size of the code and the use of web components are orthogonal things and do not contradict one another.

Shadow DOM and performance

Styling large HTML pages may require a lot of CSS, and it may be difficult to come up with unique class names. Here comes the Shadow DOM. This technology allows you to create areas of isolated CSS. Thus, you can render a component with its own styles that will not overlap with other styles on the page. Even if you have a class name that matches something else, styles will not mix if each one lives in its own Shadow DOM. The Shadow DOM is created by calling the this.attachShadow () method, and then we have to add our styles inside the Shadow DOM, either with the <style></ style> tag or via <link rel = “stylesheet”>.

Thus, each component instance receives its own copy of CSS, which obviously should affect performance. This demo shows exactly how. If the render of ordinary elements without Shadow DOM takes about 30 ms, then with Shadow DOM it is about 50 ms. Perhaps in the future, browser manufacturers will improve performance, but now it’s better to abandon small web components and try to make components like <my-list items = “myItems”> instead of separate <my-item item = “item”>.

It is also worth noting that alternative approaches, such as CSS modules, do not have such problems since everything happens at the assembly stage, and regular CSS comes to the browser.

Global component names

Each web component is bound to its own tag name using custom elements.define. The problem is that the names of the components are declared globally, that is, if someone has already taken the name my-button, you cannot do anything about it. In small projects, where all component names are controlled by you, this is not a particular problem, but if you use a third-party library, then everything can suddenly break when you add a new component with the same name that you already used. Of course, this can be defended by the convention of naming using prefixes, but this approach is very similar to the problems with the names of CSS classes that web components promised us to get rid of.

Tree-shaking

Another problem follows from the global register of components — you do not have a clear connection between the place of registration of the component and its use. For example, in React, any component used must be imported into a module.

import { Button } from "./button";
//...
render() {
return <Button>Click me!</Button>
}

We explicitly import the Button component. If you delete the import, then we will have an error in rendering. With web components, the situation is different, we just render HTML tags, and they magically come to life. A similar example with a button on the lit-element would look like this:

import '@polymer/paper-button/paper-button.js';
// ...
render() {
return html`<paper-button>Click me!</paper-button>`;
}

There is no connection between import and use. If we remove the import, but it remains in some other file, the button will continue to work. If the import suddenly disappears from another file too, then only then something will break and it will be very sudden.

The absence of an explicit connection between import and use does not allow tree-shaking of your code, automatic removal of unused imports. For example, if we import several components, but not all of them, they will be automatically deleted:

import { Button, Icon } from './components';
//...
render() {
return <Button>Click me!</Button>
}

The icon in this file is not used and will be safely removed. In the situation with web components, this number will not work, because the Bandler is unable to track this connection. The situation is very similar to the year 2010 when we manually connected the necessary jquery plugins into the site header.

Problems with typing

Javascript is inherently a dynamic language, and not everyone likes it. In large projects, developers prefer typing, adding it with Typescript or Flow. These technologies are perfectly integrated with modern frameworks like React, checking the correctness of calling components:

class Button extends Component <{text: string}> {}
<Button /> // error: missing required text field
<Button text = "Click me" action = "test" /> // error: extra action field
<Button text = "Click me" /> // everything is as it should, no errors

With web components, this doesn’t work. The previous section explained that the location of a web component is statically unrelated to its use, and for the same reason, Typescript will not be able to display valid values for the web component. Here JSX.IntrinsicElements can come to the rescue — a special interface from where Typescript takes information for native tags. We can add a definition for our button there.

namespace JSX {
interface IntrinsicElements {
'paper-button': {
raised: boolean;
disabled: boolean;
children: string
}
}
}

Now Typescript will know about the types of our web component, but they are not related to its source code. If new properties are added to the component, in JSX the definition will need to be added manually. In addition, this declaration does not help us in any way when working with an element through a querySelector. There you have to cast the value to the desired type:

const component = document.querySelector('paper-button') as PaperButton;

Perhaps, as the standard spreads, Typescript will come up with a way to statically typify web components, but for now, using web components will have to say goodbye to type safety.

Bulk property update

Native browser components, such as <input> or <button>, accept values as text attributes. However, sometimes it may be necessary to transfer more complex data to our components, objects, for example. For this, it is proposed to use properties with getters and setters.

// find our component in the DOM
const component = document.querySelector ("users-list");
// pass data to it
component.items = myData;

On the component side, we define the setter that will process this data:

class UsersList extends HTMLElement {
set items (items) {
// save the value
this .__ items = items;
// redraw the component
this .__ render ();
}
}

In the lit-element there is a convenient decorator for this — property:

class UsersList extends HTMLElement {
@property()
users: User[];
}

However, it may happen that we need to update several properties at once:

const component = document.querySelector("users-list");
component.expanded = true;
component.items = myData;
component.selectedIndex = 3;

Each setter causes rendering because it does not know that other properties will be updated there. As a result, we will have two unnecessary updates with which we need to do something. The standard does not provide anything ready, so developers need to wriggle out themselves. In lit-element, this is solved by asynchronous rendering, that is, the setter does not directly cause the update, but leaves a request for deferred rendering, something like setTimeout (() => this .__ render (), 0). This approach allows you to get rid of unnecessary redrawing but complicates the work with the component, for example, it’s testing:

component.items = [{id: 1, name: "test"}];
// does not work, render has not yet occurred
// expect (component.querySelectorAll (". item")). toHaveLength (1);
await delay (); // need to wait for the update to apply
expect (component.querySelectorAll (". item")). toHaveLength (1);

Thus, the implementation of the correct component update is another argument for using the framework instead of working with web components directly.

Conclusion

After reading this article, it may seem that the web components are bad and they have no future. This is not entirely true; they can come in handy in some use cases:

  • Embedding client logic in a large server-render project. This is the path now under Github. They actively use web components for their interface and even published some of them in open-source. In a situation where you have most of the page static or rendered by the server, web components will help to give interactivity to some parts.
  • Implementation of micro-frontend. The page renders independent widgets that can be written in completely different frameworks and different teams, but they need to somehow get along together. In doing so, they dump their CSS into a global area and interfere with each other in every way. To combat this, we used to have only the iframe, but now we can turn individual micro-frontends into the Shadow DOM so that they live their lives there.

There are also things that I would not do on the web components:

  • The UI library will be inconvenient due to problems with tree-shaking and the types that are covered in this article. Writing UI components (buttons, inputs, etc.) on the same framework as the main part of the page (React, Vue, etc.) will allow them to better interact with the main part of the page.
  • For basic content pages, web components will not work. From the user’s point of view, rendering a page with a single <my-app /> web component is no different from using a SPA framework. The user will have to wait until all the Javascript is uploaded to finally see the content. And if in the case of Angular/React/Vue, this can be accelerated by pre-rendering the page on the server, then in the case of web components there are no such possibilities.
  • Encapsulating parts of your code into web components also makes no sense. You will get performance problems, lack of types and no special advantages in return.

I hope this information will be useful to you when choosing a technology stack this year.



Source link

Bookmark(0)
 

Leave a Reply

Please Login to comment
  Subscribe  
Notify of
Translate »