Введение в StencilJS: компилятор, который генерирует веб-компоненты.

Что такое StencilJS?

StencilJS - это библиотека для генерации веб-компонентов, созданная командой разработчиков Ionic Framework.

Как вы, возможно, знаете, когда изначально был выпущен Ionic, он был создан специально для Angular. Затем начали появляться другие фреймворки, которые заняли большую часть фронтенд-сообщества - и Ionic хотел, чтобы его инструменты использовались кем угодно, независимо от выбранной фреймворка.

Переписать одни и те же компоненты для всех основных фреймворков было бы невыполнимой (и, вероятно, неправильной) задачей. Веб-компоненты позволяют решить эту проблему - но не без ограничений, которые все хорошо задокументированы.

Stencil пытается предоставить абстракцию поверх веб-компонентов, чтобы упростить их написание и доставку.

Одна из самых интересных особенностей Stencil заключается в том, что скомпилированный код поставляется без Stencil (как и Svelte, еще один «исчезающий» фреймворк): это позволяет компонентам быть невероятно легкими и делает их идеальными для использования. используется с другими фреймворками, такими как React, Vue или Angular.

Именно из-за их легкости и встроенной поддержки браузеров веб-компоненты все чаще используются для систем проектирования. Их можно даже включить в существующие модульные библиотеки компонентов React / Vue / Angular, созданные с использованием таких инструментов, как Bit, поскольку каждый компонент в этой библиотеке полностью независим.

Так, например, вы можете отправить недавно созданные веб-компоненты в свою коллекцию битов (библиотеку), которая до сих пор содержала только компоненты React (и позволить вашей команде использовать их в ваших проектах React).

Почему я выбрал трафарет для своего проекта

Недавно я занялся личным проектом, который в последнее время отнимает у меня много времени.

Когда мне пришлось выбирать, какие инструменты использовать, я начал исследовать библиотеку для создания коллекции компонентов со следующими условиями:

  • Это должно было быть очень быстро
  • Он должен был быть очень легким и простым
  • Он должен был быть максимально ориентированным на будущее
  • Это должно было быть поддержано активным и динамичным сообществом.

Мой вариант использования - создание сторонних виджетов, которые будут загружаться на веб-сайты пользователя. Как вы понимаете, важны производительность и вес. Никто не хочет, чтобы третья сторона замедляла работу их веб-сайта!

Возможные кандидаты

Помимо Stencil, я начал исследовать различные другие инструменты:

  • Angular Elements (фреймворк, который я знаю лучше всего, поэтому это был очень серьезный кандидат), Svelte, Preact и Lit-Element.

Хотя приведенные выше варианты являются чрезвычайно допустимыми и, вероятно, также очень хорошо сработали бы для моего варианта использования, я решил использовать Stencil по следующим причинам:

  • JSX: любите вы или ненавидите, он очень хорошо известен и используется во многих других фреймворках. Кто угодно мог забрать его в считанные часы.
  • Небольшие и умные пакеты: все компоненты загружаются лениво и будут использовать правильный пакет для используемого браузера благодаря дифференциальной загрузке
  • Первоклассная поддержка машинописного текста - это было для меня очень важно
  • Несмотря на то, что я использовал JSX в качестве языка шаблонов, как в первую очередь пользователь Angular, я обнаружил, что начать работу с ним чрезвычайно легко, и почти все с самого начала имело смысл. Есть некоторые вещи, на которые следует обратить внимание, как мы увидим в следующих разделах.

После того, как я с большим удовольствием использовал Stencil, я решил написать эту статью, чтобы познакомить вас с этим инструментом и поделиться своим опытом.

Строительные блоки StencilJS

В этом разделе мы увидим, как создать его с помощью Stencil, начиная с основ. Чтобы понять, как работает Stencil, мы рассмотрим его наиболее важные темы:

  • Определение компонента
  • Передача свойств
  • Обработка внутреннего состояния
  • Испускание событий
  • Методы экспонирования
  • Создание шаблонов с помощью JSX и слотов

Как только вы освоите эти концепции, вы сможете приступить к написанию компонентов Stencil за очень короткое время! Да, это так просто.

Анатомия компонента трафарета

Компонент трафарета объявляется с помощью декоратора Component; да, это может быть знакомо, это действительно похоже на Angular.

Мы определяем:

  • имя его тега со свойством tag
  • стили компонента с использованием свойства styleUrls
  • функция с именем render, отвечающая за определение шаблона с помощью JSX. И снова да, это знакомо, потому что он работает аналогично компонентам класса React.
// single-choice.tsx
import { Component, h } from '@stencil/core';
@Component({
  tag: 'single-choice',
  styleUrls: ["./single-choice.css"]
})
export class SingleChoiceComponent {
  render() {
    return 'I will be a single choice field!';
  }
}

Примечание: импорт h необходим, если мы используем JSX в функции рендеринга.

После того, как мы создадим компонент с помощью компилятора Stencil и импортируем скрипты на нашу веб-страницу, мы можем просто вызвать компонент, как и с обычным элементом HTML:

<single-choice></single-choice>

Объемная и собственная теневая модель DOM

Веб-компоненты могут быть ограничены с помощью Shadow DOM (с помощью свойства shadow), но это по-прежнему не поддерживается во всех браузерах (такие как IE11 и Safari поддерживают его лишь частично).

Так же, как инкапсуляция эмулируемого представления Angular, Stencil предоставляет свойство под названием scoped, которое будет имитировать такое же поведение и инкапсулировать стиль наших компонентов.

По умолчанию я всегда устанавливаю scoped вместо shadow.

@Component({
  tag: 'single-choice',
  scoped: true
})

Передача свойств компонентам

Передача свойств компонентам также во многом напоминает работу Angular. Чтобы передать свойства, мы можем определить свойства класса и украсить их декоратором Prop.

import { ..., Prop } from "@stencil/core";
@Component({...})
export class SingleChoiceComponent {
  @Prop() id: string;
  render() {
    return (
       <label for={this.id}></label>
    );
  }
}

Однако этот декоратор может принимать некоторую конфигурацию, о которой вы можете не знать:

  • атрибут: имя передаваемого атрибута, если имя свойства класса должно быть другим.
  • изменяемый: по умолчанию свойства неизменяемы. Если он установлен извне компонента, его нельзя изменить, если только мы явно не установим для этого свойства значение true.
  • отражать: если мы хотим предоставить атрибут в DOM компонента, мы можем установить это свойство, чтобы мы могли получить доступ к свойству извне.
@Component({...})
export class SingleChoiceComponent {
  @Prop({
     attribute: 'id',
     mutable: true,
     reflect: true
  }) fieldId: string;
  
  render() {...}
}

Теперь, когда мы предоставили свойство id, мы можем получить к нему доступ с помощью DOM API:

const singleChoice = document.querySelector('single-choice');
const id = singleChoice.id;

Внутреннее состояние

Stencil пытается максимизировать производительность и эффективность за счет повторного рендеринга только при необходимости. Если вы привыкли к таким фреймворкам, как Angular или Svelte, вы можете не сразу понять, почему ваш компонент не обновляет свое представление.

Чтобы запустить повторную визуализацию при изменении свойства, Stencil предоставляет декоратор State:

@Component({...})
export class SingleChoiceComponent {
  @Prop() id: string;
  @State() value: string;
  render() {...}
}

Примечание. Если мы забудем украсить свойство value, представление не будет повторно отображено.

События

Конечно, компоненты также могут открывать события своим родителям, чтобы обеспечить общение между родителями и детьми.

Событие определяется следующим образом:

  • мы импортируем декоратор Event и устанавливаем его относительное свойство
  • мы определяем его тип, это EventEmitter
@Component({...})
export class SingleChoiceComponent {
  @Prop() id: string;
  @State() value: string;
  @Event() valueChanged: EventEmitter<Option>;
  onClick(option: Option) {
    this.valueChanged.emit(option);
  }
  render() {...}
}

Если вы используете событие с дочерним элементом, отображаемым в функции рендеринга, вы можете просто передать событие и вызвать метод:

<parent-component>
  <single-choice 
    valueChanged={(e) => this.choiceSelected(e))
  ></single-choice>;
</parent-component>

В случаях, когда наш дочерний компонент вложен глубоко в дочерние элементы родителя, мы можем прослушивать пользовательские события благодаря декоратору Listen.

class ParentComponent {
  @Listen('valueChanged')
  valueChanged(value) {
    // do something with value
  }
}

Методы

Методы компонента не следует путать с методами, которые вы обычно определяете в классе компонента. Stencil позволяет предоставлять определенные методы как общедоступный API, украсив их декоратором Method.

@Component({...})
export class SingleChoiceComponent {
  @Prop() id: string;
  @State() value: string;
  @Method() 
  async getValue() {
     return this.value; 
  }
}

Как только метод определен, мы можем вызвать его извне:

const singleChoice = document.querySelector('single-choice');
const value = singleChoice.getValue();

Проблемы:

  • Публичные методы всегда должны быть async
  • Не рекомендуется использовать общедоступные методы для раскрытия источника достоверности компонента. Предлагается вместо этого полагаться исключительно на события и реквизит.

Шаблоны: JSX и слоты

Если вы когда-либо работали с React, Preact или любой другой библиотекой, использующей JSX, вам не так много нового, чтобы научиться использовать Stencil. Если нет, то есть чему поучиться, но, к счастью, JSX довольно прост.

Конечно, вы также можете определить функциональные компоненты и использовать их в функции рендеринга:

const Label = (_, text: JSX.Element) => 
  <label>{text}</label>
@Component({...})
export class SingleChoiceComponent {
  render() {
    return (
      <Label>
       <slot />
      </Label>
    );
  }
}

Как вы можете видеть выше, слоты могут быть полезны для визуализации содержимого компонента. Вы также можете определить именованные слоты, чтобы контролировать, где будет отображаться контент.

class MyComponent() {
  render() {
    return (
      <div>
        <slot name="heading">
      </div>
    );
  }
}
<my-component>
   <h1 slot="heading">Heading</h1>
</my-component>

Заключительные слова

StencilJS - отличная библиотека для работы. Поскольку я работаю в основном с Angular, я привык к действительно хорошему опыту разработчика и богатой экосистеме, но Stencil не подвел меня в этом отношении.

Это очень правильный выбор, если вы хотите дополнить свои приложения набором многократно используемых компонентов. Как упоминалось ранее, вы можете постепенно создавать дизайн-систему, состоящую из веб-компонентов, или заменять существующую (реализованную с помощью некоторой инфраструктуры) с помощью таких инструментов, как Bit. Это обеспечит перспективу вашей системы проектирования и сделает ее доступной для всех интерфейсных технологий, используемых в вашей компании. Это также правильный выбор, если вы используете простой Javascript, поскольку добавляемые накладные расходы крайне минимальны.



Конечно, я иногда сталкиваюсь с ошибками, но команда, как правило, очень отзывчива и очень часто выпускает новые версии. Использование компонентов Ionic в качестве эталона также является отличным способом понять передовой опыт и увидеть, как основная команда подошла к своей архитектуре.

Ожидайте от меня больше статей о Stencil в будущем!

Ресурсы

Если вам нужны какие-либо разъяснения, или если вы считаете, что что-то неясно или неправильно, оставьте, пожалуйста, комментарий!

Надеюсь, вам понравилась эта статья! Если да, подпишитесь на меня в Medium, Twitter или на моем веб-сайте, чтобы увидеть больше статей о разработке программного обеспечения, Front End, RxJS, Typescript и многом другом!

Учить больше