StencilJS - компилятор web компонентов

Stencil - это компилятор, который создает веб-компоненты (custom elements).

Как заявляют авторы, Stencil объединяет лучшие концепции самых популярных фреймворков в простой инструмент. Stencil использует такие функции, как:

  • Виртуальный DOM
  • Асинхронный рендеринг (вдохновленный React Fiber)
  • Реактивная привязка данных
  • TypeScript
  • JSX

Напишем простое модальное окно на StencilJS

Начнем

Нужен npm версии 6 и выше. Вводим команду npm init stencil.

e:\home\js> npm init stencil
npx: installed 1 in 3.925s

√ Pick a starter » component
√ Project name » tyapk-modal

✔ All setup in 33 ms

  Next steps:
   > cd tyapk-modal
   > npm start

  Further reading:
   - https://github.com/ionic-team/stencil-component-starter   

Собственно переходим в папку и запускаемся npm start. На http://localhost:3333/ нас приветствует страничка:

Hello, World! I'm Stencil 'Don't call me a framework' JS

Это пример встроенного веб-компонета.

Разметка и стили

HTML Разметка компонента:

  <div class="dialog"> <!-- центрирующий содержимое fixed слой на весь экран, z-index = 1000 -->
    <div class="dialog__content"> <!-- модальное окно -->
      <header>
        <h2>Заголовок модального окна</h2>
      </header>
      <main>
        Содержимое окна
      </main>
      <footer>
        <button class="dialog__ok-btn">OK</button>
        <button class="dialog__cancel-btn">Отмена</button>
      </footer>
      <div class="dialog__close-btn"></div> <!-- кнопка Х "закрыть окно" -->
    </div>
  </div>
  <div class="overlay"></div> <!-- слой затемнения на весь экран, z-index = 999 -->

СSS Стили компонента:

.overlay {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed;
  background-color:  rgba(52, 74, 94, 0.8);
  z-index: 999;
  overflow-x: hidden;
  overflow-y: auto;
  outline: none;
}

.dialog {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed;
  z-index: 1000;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.dialog__content {
  position: relative;
  max-width: 520px;
  margin-bottom: 5%;
  background-color: white;
  padding: 30px;
  color: #344a5e;
  border-radius: 5px;
  box-shadow: 0 30px 30px 0 rgba(52, 74, 94, 0.8);
}

.dialog h2 {
  margin: 0;
  margin-bottom: 10px;
}

.dialog__ok-btn {
  cursor: pointer;
  height: 30px;
  padding: 6px 15px;
  margin-right: 15px;
  min-width: 60px;
  transition-duration: .25s;
  transition-property: background-color, color;
  text-align: center;
  border-width: 1px;
  border-style: solid;
  border-radius: 3px;
  box-shadow: 0 1px 2px 0 rgba(64, 61, 4, 0.44);
  font-size: 14px;
  color: white;
  border-color: transparent;
  background-color: #0279c0;
}

.dialog__cancel-btn {
  cursor: pointer;
  color: #0279c0;
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
  font-family: -apple-system, BlinkMacSystemFont, Ubuntu, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;

}

.dialog__cancel-btn:hover {
  text-decoration: underline;
}

.dialog__close-btn {
  border: 0;
  background: none;
  position: absolute;
  top: 20px;
  right: 20px;
  width: 15px;
  height: 15px;
  cursor: pointer;
}

// кнопка X
.dialog__close-btn::before, .dialog__close-btn::after {
  position: absolute;
  top: -2px;
  right: 6px;
  display: block;
  width: 2px;
  height: 19px;
  content: '';
  border-radius: 3px;
  background-color: #d9d9d9;
}

.dialog__close-btn::before {
  transform: rotate(45deg);
}

.dialog__close-btn::after {
  transform: rotate(-45deg);
}

StencilJS

StencilJS так же как и Angular активно использует декораторы.

  • @Component() - декоратор класса компонента. Задаёт общую информацию
    • используемый тег
    • файл css стилей
    • включение shadow dom
      @Component({
        tag: 'tyapk-modal',
        styleUrl: 'tyapk-modal.css',
        shadow: true
      })
      export class TyapkModalComponent {}
  • @Prop() - декоратор "входящего" атрибута/свойства компонента. Способ получить данные извне. Механизм отправить данные из родительского компонента в дочерний. По-умолчанию immutable. Можно явно разрешить мутацию через ({ mutable: true }). В модальном окне заголовок будет задаваться через @Prop()
    /** Header */
    @Prop() header: string;

    Использование:

    <tyapk-modal header="Мамкин программист"></tyapk-modal>
  • @State() - декоратор свойства компонента. Хранит внутреннее данные компонента. Данные не могут быть изменены снаружи компонента, но компонент может изменить их так, как считает нужным. Любые изменения @State() свойства приведут перерисовке компонента. В модальном окне будет хранится состояние открытости/закрытости компонента.
    /** Whether the model is open or not */
    @State() private _show = false;
  • @Event() - декортор свойства компонента. Способ отправить данные "наружу" или родительскому компоненту. Выкидает Custom DOM events события. В модальном окне будет выкидываться событие "закрытие окна" с информацией, что нажато (ОК, Отмена, крестик).

    /** Close event */
    @Event() private close: EventEmitter;
    ...
    handleClick() {
        this._show = false;
        this.close.emit('ok');
    }
    
    closeModal(result: 'cancel'|'close') {
        this._show = false;
        this.close.emit(result);
    }

    При использовании компоненте на custom event подписываются как на обычные события:

    document.querySelector('tyapk-modal').addEventListener('close', function ($) {
      console.log('Результат: ', $);
    });

Никаких препроцессоров HTML или CSS в StencilJS нет. В качестве шаблонизатора используется JSX. Насколько точно он соответствует React мне трудно судить. В модальном окне у меня получился такой шаблон:

  render() {
    if (this._show) {
      return (
        <div>
          <div class="dialog">
            <div class="dialog__content">
              <header>
                <h2>{this.header}</h2>
              </header>
              <main>
                <slot />
              </main>
              <footer>
                <button class="dialog__ok-btn" onClick={() => this.handleClick()}>OK</button>
                <button class="dialog__cancel-btn" onClick={() => this.closeModal('cancel')}>Отмена</button>
              </footer>
              <div class="dialog__close-btn" onClick={() => this.closeModal('close')}></div>
            </div>
          </div>
          <div class="overlay"></div>
        </div>
      );
    }
  }

Результат на github

При билде StencilJS создаёт набор js файлов, чтобы оптимизировать загрузку по уровню поддержки браузерами определенных возможностей.

Пример использования модального окна

<tyapk-modal header="Tyapk">
    <p>У меня была одна проблема, поэтому я решил написать программу, которая её решит. 
    Теперь у меня есть 1 проблема, 9 ошибок и 12 предупреждений.</p>
</tyapk-modal>

Работает корректно в браузерах с поддержкой slot&template.

У меня была одна проблема, поэтому я решил написать программу, которая её решит. Теперь у меня есть 1 проблема, 9 ошибок и 12 предупреждений.

Вместо заключения.

С учётом того, что css стили были готовы разобраться в StencilJS и написать такой компонент у меня заняло 4 часа. Это безусловно намного меньше, чем разобраться с "голыми" веб-компонентами и написать веб-компонент с нуля (несколько дней).


Похожие записи

Svelte for web components

Написание простого модального окна с использованием фреймворка Svelte и компиляцией в веб компонент

TS. Event bus

Создаётся providedIn: 'root' сервис событий. Затем отправляются события на шину, и если какой-либо слушатель подписан на эти события, он получает уведомления.