Як створювати перезавантажувані та реюзабельні вебкомпоненти в Stencil

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

У багатьох розробників з часом трапляється ситуація, коли при реалізації певного функціоналу виникає потреба перевикористання вже існуючого рішення. І якщо в деяких випадках можна просто скопіпастити код (що зазвичай не є хорошим варіантом) — наприклад, у межах одного проєкту це може призводити до непотрібного масштабування й нагромадження коду, — то коли йдеться про використання цього ж функціоналу з іншим фреймворком, без модифікації й адаптації коду вже не обійтися.

Це своєю чергою несе за собою інші потенційні труднощі й проблеми, такі як: реалізований функціонал поводить себе по-різному у Chrome i Safari, або у будь-яких інших браузерах, або виникають проблеми з накладанням стилів, або трапляється різна поведінка в межах різних версій одного і того ж самого браузера. Але є можливість уникнути усіх цих проблем, якщо код, який з великою ймовірністю буде використаний знову, реалізувати за допомогою вебкомпонент.

Привіт, мене звати Любомир і я Front-end Developer в ІТ-компанії EveryMatrix. Мені доводилося працювати з більшістю основних фреймворків для веброзробки. І створення вебкомпонент часто допомагало скоротити час розробки та уникнути певних майбутніх труднощів. По-перше, у цій статті я хочу ознайомити вас з цим підходом, його практичним застосуванням за допомогою Stencil, а також плюсами та мінусами використання вебкомпонент. У другу чергу я ставлю собі за мету долучитися до розвитку україномовного технічного контенту.

Що таке вебкомпоненти та для чого вони потрібні

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

Метою вебкомпонентів є створення елементів інтерфейсу, котрі забезпечують однакову поведінку у всіх частинах проєкту незалежно від базової технології, платформи, браузера чи його версії.

Отже, тепер можна окреслити переваги та ситуації, в котрих виникає потреба використовувати вебкомпоненти. За рахунок своєї незалежності від фреймворків, перформансу, інкапсульованості логіки та стилів, цей підхід зручно використовувати у випадку необхідності повністю ізолювати елемент, у проєктах, котрі використовують різні технології, при необхідності багаторазового перевикористання цього компоненту або для побудови мікрофронтендів.

Цей підхід несе в собі наступні елементи:

  1. Custom elements — набір js API, які дозволяють створювати нові елементи та їх поведінку.
  2. Shadow DOM — набір js API для приєднання ізольованого DOM-дерева до елемента, який відображається окремо від основного DOM-дерева.
  3. HTML templates — теги template і slot, які створюють шаблони розмітки, але не відображаються на відтвореній сторінці.

В результаті, коли розробник вирішує використовувати цей архітектурний метод у роботі, перед ним постає вибір: використовувати нативне API або ж звернутися за допомогою до сторонніх фреймворків, одним з яких є Stencil.

Нативне API слід використовувати, якщо нам потрібно зробити щось легеньке для простого проєкту, без надзвичайно складної логіки. Адже за цього вибору доведеться описувати багато коду, та робити все в ручну, що при нагромадженні логіки буде складно розібрати (документація).

Проте фреймворки по типу Stencil несуть ряд переваг, наприклад, робота з jsx, підтримка роутингу, TypeScript, Server Side Rendering, Hydration. Також це кращий вибір за необхідності побудувати власну UI-бібліотеку.

Stencil

Stencil — це компілятор, який генерує вебкомпоненти, що відповідають усім вебстандартам, і поєднує найкращі ідеї інших фреймворків у зручному інструменті збірки, який використовує TS, JSX та CSS.

Своєю чергою, згенеровані за допомогою Stencil компоненти можна використовувати у таких фреймворках, як React, Angular або Vue. Окрім спрощеної роботи з користувацькими елементами, у порівнянні з нативним API, Stencil пропонує ряд додаткових функцій, з котрих я хочу виділити: попередній рендеринг, передачу об’єктів у властивостях, підтримку перезавантаження в реальному часі.

Переваги технології:

  1. Динамічне створення компонентів: використовуючи шаблони розробки компонентів, Stencil автоматично генерує код, який створює макет-компоненти. Це пришвидшує подальшу розробку та допомагає розробникам.
  2. Інкапсуляція та Shadow DOM: Stencil використовує Shadow DOM для ізоляції стилів і функцій компонентів. Це гарантує, що стилі компонента не впливають на інші елементи вебзастосунка, дозволяючи розробляти окремі компоненти.
  3. Оптимізація продуктивності: Stencil реалізує різні методи, наприклад, lazy loading, щоб покращити продуктивність компонентів. Оскільки компоненти створюються лише за потреби, це значно скорочує час завантаження сторінки.
  4. Реактивність через вбудовані механізми: Stencil використовує декларативний підхід до керування станом компонентів, що дозволяє DOM автоматично реагувати на зміни.

Недоліки технології:

  1. Memory leaks: іноді після видалення компонентів Stencil не повністю очищає DOM, що своєю чергою призводить до нарощення споживання пам’яті. Проблема виникала, коли залишались активні слухачі подій або внутрішні посилання на елементи, які вже не відображалися. Отже, потрібно на це зважати.
  2. Lazy loading: у великих застосунках часто відображення першого компонента відбувається з затримкою, оскільки компоненти вантажитимуться динамічно, проте використання інлайнування імпортів для компонент, які використовуються найчастіше, може розв’язати цю проблему.
  3. Сумісність та підтримка старих браузерів: оскільки для створення вебкомпонент Stencil використовує Custom Elements та Shadow DOM, це вимагає використання поліфілів, тому що старі версії деяких браузерів не підтримують ці стандарти.
  4. Комплексність можливостей: в цій технології багато речей йдуть з коробки, щоб забезпечити цю гнучкість та реактивність, тому у випадку потреби створити просту компоненту ця технологія може видатися надмірною. В такому випадку варто подивитися в сторону простіших аналогів.
  5. Спільнота: попри чудову документацію, ця технологія не виділяється популярністю серед інших фреймворків, тому у разі виникнення специфічної проблеми відповідь від розробників або на технічних форумах доведеться чекати довше.

Життєвий цикл компоненти в Stencil.js має сім етапів:

  1. constructor — ініціалізація компоненти, також на цьому етапі відбувається Dependency Injection.
  2. componentWillLoad — викликається один раз, перед тим як компонента буде відрендерена. Зручне місце для асинхронних запитів.
  3. componentDidLoad — викликається після монтування компоненти в DOM. Тут вже можна використовувати DOM API.
  4. @Watch — викликається при зміні значення props або state.
  5. componentWillUpdate — викликається при зміні значення props або state до оновлення компоненти.
  6. componentDidUpdate — викликається після оновлення компоненти в DOM.
  7. disconnectedCallback — етап демонтажу компоненти з DOM.

Також ще є такий опціональний етап — componentWillRender. Він викликатиметься перед кожним рендером.

Створимо вебкомпонент разом

Для початку потрібно встановити stencil, я це роблю виконуючи команду npm init stencil. Після чого в терміналі відбуватиметься налаштування нашого проєкту. Нам необхідно вибрати шаблон component. Також не забуваємо виконати npm install після створення компоненти.

Отже, наразі ми отримали згенерований шаблон my-component, з яким працюватимемо надалі. Для наочності прикладу я створю кастомний компонент для вибору кольору, назвемо його ColorPicker, та змінюємо назву тегу на color-picker. Це буде дуже проста компонента для вибору і відображення кольору.

Використаємо декоратор @State() для того, щоб відстежувати та зберігати динамічний стан кольору:

@State() color: string = '#E6E6FA'; // Початковий колір (lavander)

Тепер залишається описати метод, який буде обробляти зміну кольору, а також сам шаблон цієї компоненти. Також я додав дрібну стилізацію, яку ви можете знайти в css-файлі на GitHub-репозиторії:

 handleColorChange(event: Event) {
   const input = event.target as HTMLInputElement;
   this.color = input.value;
 }

 render() {
   return (
     <div class="color-picker">
       <input
         class="color-picker__input"
         type="color"
         value={this.color}
         onInput={(event) => this.handleColorChange(event)}
       />
       <p class="color-picker__text">Selected color: {this.color}</p>
     </div>
   );
 }

Наш результат після виклику npm run start виглядатиме наступним чином:

Оскільки наша компонента є інкапсульованою, та використовує Shadow DOM. Я хочу додати, наприклад можливість змінювати стилі за допомогою props-компоненти так, щоб залишалася можливість видозмінити існуючі стилі у майбутньому. Наприклад, під конкретну кольорову гаму. Для цього використаємо декоратор @Prop() для визначення пропса, а також @Element() для доступу до кореневого елементу компоненти.

@Prop() customStyles: string = '';
@Element() el: HTMLElement;

Тепер необхідно підключити ці кастомні стилі до нашого компонента, я зробив це за допомогою створення нового style тегу та додавання його у Shadow DOM:

 componentWillLoad() {
   if (this.customStyles) {
     const styleElement = document.createElement('style');
     styleElement.textContent = this.customStyles;
     this.el.shadowRoot?.appendChild(styleElement);
   }
 }

Отже, тепер ми зможемо модифікувати існуючі стилі, або додавати нові для цієї компоненти наступним чином:

<color-picker custom-styles=".color-picker__text { color: red; }"></color-picker>

Для того, щоб наш компонент не залежав від того, чи підтримує браузер Custom Elements та Shadow DOM, чи ні, необхідно додати поліфіли. Але спочатку розберемося, як це працює — Stencil автоматично перевіряє, чи підтримує браузер ці технології, і лише у випадку, якщо браузер їх не підтримує, завантажуються поліфіли задля забезпечення сумісності.

Щоб підключити поліфіли для Custom Elements та Shadow DOM до Stencil-компоненти, необхідно додати до index.ts наступний імпорт:

import '@webcomponents/webcomponentsjs/webcomponents-bundle.js';

Посилання на GitHub-репозиторій.

Як інтегрувати Stencil-компоненти

Stencil-компоненти дуже просто інтегровувати у інші фреймворки та проєкти. Розглянемо приклади інтеграції нашої компоненти для вибору кольору в Angular, React i Vue-проєкти. Це можна зробити двома способами:

  1. Ми можемо опублікувати цю компоненту за допомогою npm, після чого імпортувати як звичайний пакет в проєкті.
  2. Підключати зібрані файли безпосередньо до проєкту.

Наприклад, щоб підключити зібрані файли безпосередньо до проєкту, спочатку потрібно збілдити компонент, для цього викликаємо команду npm run build. Після чого автоматично генерується папка dist, яка міститиме всі зібрані файли для нашої компоненти. Нас цікавить цей файл — color-picker.entry.js. Тому що він містить код для реєстрації компоненти як Custom Element, після підключення якого наша компонента стає доступною для використання через тег <color-picker></color-picker> в HTML.

Щоб підключити зібраний js-файл до Angular-проєкту, потрібно додати його, наприклад, в src/assets/dist, після чого у файлі angular.json імпортувати наш js.

{
  "projects": {
    "rnd-angular-project": {
      "architect": {
        "build": {
          "options": {
            "scripts": [
              "src/assets/dist/color-picker.entry.js"
            ]
          }
        }
      }
    }
  }
}

У випадку React чи Vue буде достатньо додати цей зібраний js-файл у папку public та імпортувати його в index.html наступним чином:

<script src="/path/to/color-picker.entry.js"></script>

або імпортувати безпосередньо в необхідній нам компоненті:

import '/path/to/color-picker.entry.js';

Після чого що у Angular, так і у React, Vue-компонент буде доступний за тегом <color-picker></color-picker>.

Аналоги Stencil

Якщо говорити про створення вебкомпонент, я хочу виділити два аналоги, на які особливо варто звернути увагу.

Lit (LitElement). Один з найпопулярніших інструментів у цій ніші. Має активне ком’юніті, простий у використанні та фокусується на легкості і продуктивності. Ідеально підходить для невеликих компонентів або якщо потрібна мінімальна надбудова над Web Components API. Svelte. Хоча Svelte — це повноцінний фреймворк для SPA, він також дозволяє компілювати компоненти у формат Web Component. Синтаксис простий, декларативний і з мінімальною кількістю зайвого коду. Підходить, якщо ви вже використовуєте Svelte або шукаєте максимально просту розробку окремих ізольованих елементів.

Lit (LitElement): є більш популярним, має більше ком’юніті серед розробників, також простіший за Stencil і в цілому фокусується саме на швидкості рендерингу та мінімізації розміру компонент.

Svelte: це повноцінний фреймворк для SPA, але він також дозволяє компілювати вебкомпоненти, він є простішим у синтаксисі та менш абстрактним.

Але я схилився до вибору Stencil, тому що він орієнтований саме на створення незалежних вебкомпонентів та несе в собі повноцінну підтримку TypeScript, SSR, lazy loading та Hydration. Плюсом також є JSX-синтаксис, що значно спрощує адаптацію коду.

Висновок

Ця технологія є простим, корисним і ефективним інструментом для створення вебкомпонентів. Сумісність з іншими фреймворками дозволяє легко інтегрувати компоненти в проєкти на основі Angular, Vue і React. Це комплексне рішення, яке пропонує підтримку попередніх версій браузера, оперативність і гнучкість. На мою думку, Stencil є корисним інструментом для створення складних вебкомпонент у вашому проєкті, а також для створення мікрофронтендів і модульних інтерфейсів.

👍ПодобаєтьсяСподобалось14
До обраногоВ обраному6
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

І ще помітив нюанс, там де перераховуєте альтернативи, там трошки змішались Svelte і LitElement

Lit (LitElement). Один з найпопулярніших інструментів у цій ніші. Має активне ком’юніті, простий у використанні та фокусується на легкості і продуктивності. Ідеально підходить для невеликих компонентів або якщо потрібна мінімальна надбудова над Web Components API. Svelte. Хоча Svelte — це повноцінний фреймворк для SPA, він також дозволяє компілювати компоненти у формат Web Component. Синтаксис простий, декларативний і з мінімальною кількістю зайвого коду. Підходить, якщо ви вже використовуєте Svelte або шукаєте максимально просту розробку окремих ізольованих елементів.

Я так розумію це мали б бути два окремих абзаци

Дуже цікава стаття, дякую за роботу. Єдине саме визначення веб-компонентів, яке ви наводите, мене якось ввело в оману

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

Потім коли про API розповідаєте, то вже стало ясно, бо подумав що мова загалом про концепт розбивки інтерфейсів на компоненти, без привʼязки до технологій. Якщо перекладати визначення з MDN:

Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.

То буде щось на кшталт: це набір(suite) технологій, який дозволяє створювати свої кастомні елементи.

Але то я вже чіпляюсь, загалом топ.

Дуже дякую за влучний коментар, маєте рацію, потрібно було трішки по іншому подати свою думку, аби не конфузити

В Angular також є можливість створювати Custom Elements. Ми скористались цим підходом для компонента вибору адреси: у флоу на Angular він використовується напряму як звичайний компонент, а для React-додатка ми збираємо його у вигляді Custom Element і вставляємо як веб-компонент.

Ви дійсно маєте рацію, але якщо умовний проєкт містить в собі 2-3 фреймворки одночасно, і цю компоненту потрібно задіяти одразу у всіх, то на мою думку зручніше буде використовувати саме ізольовані вебкомпоненти реалізовані без привʼязки до певного фрейморку

Підписатись на коментарі