Święty Graal Komponentów wielokrotnego użytku: elementy niestandardowe, Shadow DOM i NPM

Opublikowany: 2022-03-10
Krótkie podsumowanie ↬ W tym artykule omówiono rozszerzanie kodu HTML za pomocą składników, które mają wbudowaną funkcjonalność i style. Dowiemy się również, jak sprawić, by te niestandardowe elementy można było ponownie wykorzystać w projektach przy użyciu NPM.

Nawet w przypadku najprostszych komponentów koszt pracy ludzkiej mógł być znaczny. Zespoły UX przeprowadzają testy użyteczności. Szereg interesariuszy musi zatwierdzić projekt.

Deweloperzy przeprowadzają testy AB, audyty dostępności, testy jednostkowe i testy w różnych przeglądarkach. Po rozwiązaniu problemu nie chcesz powtarzać tego wysiłku . Tworząc bibliotekę komponentów wielokrotnego użytku (zamiast budować wszystko od podstaw), możemy stale wykorzystywać wcześniejsze wysiłki i unikać ponownego odwiedzania już rozwiązanych problemów projektowych i programistycznych.

Zrzut ekranu strony internetowej Google z komponentami materiałowymi – przedstawiający różne komponenty.
Duży podgląd

Budowanie arsenału komponentów jest szczególnie przydatne dla firm takich jak Google, które posiadają pokaźne portfolio witryn o wspólnej marce. Kodyfikując swój interfejs użytkownika w widżety, które można komponować, większe firmy mogą zarówno przyspieszyć czas opracowywania, jak i osiągnąć spójność projektu wizualnego i interakcji z użytkownikiem w różnych projektach. W ciągu ostatnich kilku lat nastąpił wzrost zainteresowania przewodnikami stylistycznymi i bibliotekami wzorów. Biorąc pod uwagę wielu programistów i projektantów rozproszonych w wielu zespołach, duże firmy dążą do osiągnięcia spójności. Możemy zrobić lepiej niż proste próbki kolorów. Potrzebujemy łatwego do dystrybucji kodu .

Udostępnianie i ponowne używanie kodu

Ręczne kopiowanie i wklejanie kodu jest łatwe. Jednak aktualizowanie tego kodu to koszmar związany z konserwacją. Dlatego wielu programistów polega na menedżerze pakietów w celu ponownego wykorzystania kodu w różnych projektach. Pomimo swojej nazwy, Node Package Manager stał się niezrównaną platformą do zarządzania pakietami front-end . Obecnie w rejestrze NPM znajduje się ponad 700 000 pakietów, a każdego miesiąca pobierane są miliardy pakietów. Dowolny folder z plikiem package.json można przekazać do NPM jako pakiet do udostępniania. Chociaż NPM jest głównie związany z JavaScriptem, pakiet może zawierać CSS i znaczniki. NPM ułatwia ponowne użycie i, co ważne, aktualizację kodu. Zamiast zmieniać kod w niezliczonych miejscach, zmieniasz kod tylko w pakiecie.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Problem znaczników

Sass i Javascript są łatwe do przenoszenia za pomocą instrukcji importu. Języki szablonów dają HTMLowi te same możliwości — szablony mogą importować inne fragmenty HTML w postaci podszablonów. Możesz na przykład napisać znacznik dla swojej stopki, a następnie dołączyć go do innych szablonów. Stwierdzenie, że istnieje wiele języków szablonów, byłoby niedopowiedzeniem. Związanie się tylko z jednym poważnie ogranicza potencjalną możliwość ponownego wykorzystania kodu. Alternatywą jest kopiowanie i wklejanie znaczników oraz używanie NPM tylko dla stylów i javascript.

Jest to podejście przyjęte przez Financial Times z biblioteką komponentów Origami . W swoim przemówieniu „Czy nie możesz po prostu zrobić tego bardziej jak Bootstrap?” Alice Bartlett stwierdziła, że ​​„nie ma dobrego sposobu, aby ludzie dołączali szablony do swoich projektów”. Mówiąc o swoim doświadczeniu w utrzymywaniu biblioteki komponentów w Lonely Planet, Ian Feather powtórzył problemy związane z tym podejściem:

„Kiedy skopiują ten kod, zasadniczo wycinają wersję, która musi być utrzymywana w nieskończoność. Kiedy skopiowali znacznik działającego komponentu, miał on niejawny link do migawki CSS w tym momencie. Jeśli następnie zaktualizujesz szablon lub zmienisz CSS, musisz zaktualizować wszystkie wersje szablonu rozrzucone po Twojej witrynie”.

Rozwiązanie: komponenty sieciowe

Komponenty sieciowe rozwiązują ten problem, definiując znaczniki w JavaScript. Autor komponentu może dowolnie zmieniać znaczniki, CSS i JavaScript. Konsument komponentu może skorzystać z tych uaktualnień bez konieczności ręcznego przeszukiwania kodu projektu. Synchronizację z najnowszymi zmianami w całym projekcie można osiągnąć dzięki zwięzłej npm update za pośrednictwem terminala. Tylko nazwa komponentu i jego API muszą pozostać spójne.

Instalowanie komponentu WWW jest tak proste, jak wpisanie w terminalu npm install component-name . JavaScript może być dołączony do instrukcji import:

 <script type="module"> import './node_modules/component-name/index.js'; </script>

Następnie możesz użyć komponentu w dowolnym miejscu w znacznikach. Oto prosty przykładowy komponent, który kopiuje tekst do schowka.

Zobacz demo komponentu internetowego Pen Simple autorstwa CSS GRID (@cssgrid) na CodePen.

Zobacz demo komponentu internetowego Pen Simple autorstwa CSS GRID (@cssgrid) na CodePen.

Skoncentrowane na komponentach podejście do tworzenia front-endu stało się wszechobecne, zapoczątkowane przez framework React Facebooka. Nieuchronnie, biorąc pod uwagę wszechobecność frameworków w nowoczesnych przepływach pracy typu front-end, wiele firm zbudowało biblioteki komponentów przy użyciu wybranych przez siebie frameworków. Komponenty te nadają się do ponownego użycia tylko w tych konkretnych ramach.

Komponent IBM Carbon Design System
Składnik IBM Carbon Design System. Do użytku wyłącznie w aplikacjach React. Inne znaczące przykłady bibliotek komponentów wbudowanych w React to Atlaskit od Atlassian i Polaris od Shopify. (duży podgląd)

Rzadko zdarza się, aby duża firma miała jednolity interfejs, a przenoszenie z jednego frameworka na inny nie jest niczym niezwykłym. Ramy przychodzą i odchodzą. Aby umożliwić maksymalną ilość potencjalnego ponownego wykorzystania w różnych projektach, potrzebujemy komponentów, które są niezależne od frameworka .

Zrzut ekranu z npmjs.com pokazujący komponenty, które robią to samo, zbudowane wyłącznie dla konkretnych frameworków javascript.
Wyszukiwanie komponentów za pośrednictwem npmjs.com ujawnia pofragmentowany ekosystem JavaScript. (duży podgląd)
Wykres przedstawiający popularność frameworków w czasie. Popularność Ember, Knockout i Backbone spadła, zastąpiona nowszymi ofertami.
Stale zmieniająca się popularność frameworków w czasie. (duży podgląd)
„Przez lata budowałem aplikacje internetowe przy użyciu: Dojo, Mootools, Prototype, jQuery, Backbone, Thorax i React… Chciałbym móc przenieść ten zabójczy komponent Dojo, nad którym pracowałem, do mojego Reacta dzisiejsza aplikacja”.

— Dion Almaer, dyrektor ds. inżynierii, Google

Kiedy mówimy o komponencie sieciowym, mówimy o połączeniu niestandardowego elementu z shadow DOM. Elementy niestandardowe i shadow DOM są częścią zarówno specyfikacji W3C DOM, jak i standardu WHATWG DOM — co oznacza, że ​​komponenty sieciowe są standardem sieciowym . Niestandardowe elementy i shadow DOM mają w końcu uzyskać wsparcie w różnych przeglądarkach w tym roku. Korzystając ze standardowej części natywnej platformy internetowej, zapewniamy, że nasze komponenty przetrwają szybko zmieniający się cykl restrukturyzacji front-endu i przemyśleń architektonicznych. Składniki sieci Web mogą być używane z dowolnym językiem szablonów i dowolnym frameworkiem front-end — są naprawdę kompatybilne i interoperacyjne. Mogą być używane wszędzie, od bloga Wordpress po jednostronicową aplikację.

Projekt Custom Elements Everywhere autorstwa Roba Dodsona dokumentuje współdziałanie komponentów internetowych z różnymi frameworkami JavaScript po stronie klienta.
Projekt Custom Elements Everywhere autorstwa Roba Dodsona dokumentuje współdziałanie komponentów internetowych z różnymi frameworkami JavaScript po stronie klienta. Mamy nadzieję, że React, odstający tutaj, rozwiąże te problemy w React 17. (Duży podgląd)

Tworzenie komponentu internetowego

Definiowanie niestandardowego elementu

Zawsze można było wymyślać nazwy tagów i wyświetlać ich zawartość na stronie.

 <made-up-tag>Hello World!</made-up-tag>

HTML został zaprojektowany tak, aby był odporny na błędy. Powyższe zostanie wyrenderowane, nawet jeśli nie jest to prawidłowy element HTML. Nigdy nie było dobrego powodu, aby to robić — odchodzenie od standardowych tagów było tradycyjnie złą praktyką. Definiując nowy tag za pomocą niestandardowego interfejsu API, możemy jednak rozszerzyć HTML o elementy wielokrotnego użytku, które mają wbudowaną funkcjonalność. Tworzenie niestandardowego elementu jest bardzo podobne do tworzenia komponentu w React — ale tutaj jest rozszerzenie HTMLElement .

 class ExpandableBox extends HTMLElement { constructor() { super() } }

Bezparametrowe wywołanie super() musi być pierwszą instrukcją w konstruktorze. Konstruktor powinien być używany do ustawiania stanu początkowego i wartości domyślnych oraz do konfigurowania wszelkich detektorów zdarzeń. Nowy element niestandardowy musi być zdefiniowany z nazwą swojego tagu HTML i elementów odpowiadających klasie:

 customElements.define('expandable-box', ExpandableBox)

Jest to konwencja pisania wielkich liter w nazwach klas. Składnia znacznika HTML to jednak coś więcej niż konwencja. Co by było, gdyby przeglądarki chciały zaimplementować nowy element HTML i chciałyby nazwać go expandable-box? Aby zapobiec kolizji nazw, żadne nowe standardowe tagi HTML nie będą zawierały myślnika. Natomiast nazwy elementów niestandardowych muszą zawierać myślnik.

 customElements.define('whatever', Whatever) // invalid customElements.define('what-ever', Whatever) // valid

Cykl życia elementu niestandardowego

API oferuje cztery niestandardowe reakcje elementów — funkcje, które można zdefiniować w ramach klasy, które będą automatycznie wywoływane w odpowiedzi na określone zdarzenia w cyklu życia elementu niestandardowego.

connectCallback jest uruchamiany, gdy niestandardowy element jest dodawany do DOM.

 connectedCallback() { console.log("custom element is on the page!") }

Obejmuje to dodanie elementu z JavaScript:

 document.body.appendChild(document.createElement("expandable-box")) //“custom element is on the page”

a także po prostu dołączenie elementu na stronie za pomocą tagu HTML:

 <expandable-box></expandable-box> // "custom element is on the page"

Wszelkie prace, które wiążą się z pobieraniem zasobów lub renderowaniem, powinny znajdować się tutaj.

DisconnectedCallback jest uruchamiane, gdy niestandardowy element zostanie usunięty z DOM.

 disconnectedCallback() { console.log("element has been removed") } document.querySelector("expandable-box").remove() //"element has been removed"

adoptedCallback jest uruchamiany, gdy niestandardowy element jest adoptowany do nowego dokumentu. Prawdopodobnie nie musisz się tym zbyt często martwić.

attributeChangedCallback jest uruchamiana po dodaniu, zmianie lub usunięciu atrybutu. Może być używany do nasłuchiwania zmian zarówno w standardowych atrybutach natywnych, takich jak disabled lub src , jak i wszelkich niestandardowych, które stworzymy. Jest to jeden z najpotężniejszych aspektów elementów niestandardowych, ponieważ umożliwia tworzenie przyjaznego dla użytkownika interfejsu API.

Niestandardowe atrybuty elementów

Istnieje wiele atrybutów HTML. Aby przeglądarka nie traciła czasu na wywoływanie naszego attributeChangedCallback , gdy jakikolwiek atrybut zostanie zmieniony, musimy dostarczyć listę zmian atrybutów, których chcemy nasłuchiwać. W tym przykładzie interesuje nas tylko jeden.

 static get observedAttributes() { return ['expanded'] }

Więc teraz nasz attributeChangedCallback zostanie wywołany tylko wtedy, gdy zmienimy wartość atrybutu rozwiniętego w elemencie niestandardowym, ponieważ jest to jedyny atrybut, który wymieniliśmy.

Atrybuty HTML mogą mieć odpowiadające wartości (pomyśl href, src, alt, value itp.), podczas gdy inne mają wartość true lub false (np . wyłączone, wybrane, wymagane ). W przypadku atrybutu z odpowiednią wartością w definicji klasy elementu niestandardowego uwzględnimy następujące elementy.

 get yourCustomAttributeName() { return this.getAttribute('yourCustomAttributeName'); } set yourCustomAttributeName(newValue) { this.setAttribute('yourCustomAttributeName', newValue); }

Dla naszego przykładowego elementu atrybut będzie miał wartość true lub false, więc definiowanie funkcji pobierającej i ustawiającej jest trochę inne.

 get expanded() { return this.hasAttribute('expanded') } // the second argument for setAttribute is mandatory, so we'll use an empty string set expanded(val) { if (val) { this.setAttribute('expanded', ''); } else { this.removeAttribute('expanded') } }

Teraz, gdy już uporaliśmy się ze schematem, możemy skorzystać z attributeChangedCallback .

 attributeChangedCallback(name, oldval, newval) { console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`); // do something every time the attribute changes }

Tradycyjnie konfigurowanie komponentu JavaScript wiązałoby się z przekazaniem argumentów do funkcji init . Korzystając z attributeChangedCallback , możliwe jest utworzenie elementu niestandardowego, który można skonfigurować tylko za pomocą znaczników.

Shadow DOM i elementy niestandardowe mogą być używane osobno, a same elementy niestandardowe mogą być przydatne. W przeciwieństwie do DOM cienia, można je wypełniać. Jednak obie specyfikacje działają dobrze w połączeniu.

Dołączanie znaczników i stylów za pomocą Shadow DOM

Do tej pory zajmowaliśmy się zachowaniem elementu niestandardowego. Jednak w odniesieniu do znaczników i stylów nasz niestandardowy element jest równoważny pustemu unstyled <span> . Aby zawrzeć HTML i CSS jako część komponentu, musimy dołączyć shadow DOM. Najlepiej zrobić to w ramach funkcji konstruktora.

 class FancyComponent extends HTMLElement { constructor() { super() var shadowRoot = this.attachShadow({mode: 'open'}) shadowRoot.innerHTML = `<h2>hello world!</h2>` }

Nie martw się o zrozumienie, co oznacza ten tryb — jego schemat, który musisz uwzględnić, ale prawie zawsze będziesz chciał open . Ten prosty przykładowy komponent po prostu wyrenderuje tekst „hello world”. Podobnie jak większość innych elementów HTML, element niestandardowy może mieć dzieci — ale nie domyślnie. Jak dotąd powyższy niestandardowy element, który zdefiniowaliśmy, nie wyrenderuje żadnych dzieci na ekranie. Aby wyświetlić jakąkolwiek zawartość między tagami, musimy skorzystać z elementu slot .

 shadowRoot.innerHTML = ` <h2>hello world!</h2> <slot></slot> `

Możemy użyć tagu stylu, aby zastosować CSS do komponentu.

 shadowRoot.innerHTML = `<style> p { color: red; } </style> <h2>hello world!</h2> <slot>some default content</slot>`

Te style będą miały zastosowanie tylko do komponentu, więc możemy swobodnie korzystać z selektorów elementów bez wpływu stylów na cokolwiek innego na stronie. Upraszcza to pisanie CSS, czyniąc zbędnymi konwencje nazewnictwa, takie jak BEM.

Publikowanie komponentu na NPM

Pakiety NPM są publikowane za pomocą wiersza poleceń. Otwórz okno terminala i przejdź do katalogu, który chcesz zamienić w pakiet wielokrotnego użytku. Następnie wpisz w terminalu następujące polecenia:

  1. Jeśli Twój projekt nie ma jeszcze pliku package.json, npm init przeprowadzi Cię przez jego wygenerowanie.
  2. npm adduser łączy twoją maszynę z twoim kontem NPM. Jeśli nie masz wcześniej istniejącego konta, utworzy ono dla Ciebie nowe.
  3. npm publish
Pakiety NPM są publikowane za pomocą wiersza poleceń
Duży podgląd

Jeśli wszystko poszło dobrze, masz teraz komponent w rejestrze NPM, gotowy do zainstalowania i użycia we własnych projektach — i udostępnienia go całemu światu.

Przykładowy komponent w rejestrze NPM, gotowy do zainstalowania i wykorzystania we własnych projektach.
Duży podgląd

Interfejs API komponentów internetowych nie jest doskonały. Elementy niestandardowe nie mogą obecnie uwzględniać danych w przesyłanych formularzach. Historia progresywnego ulepszania nie jest świetna. Radzenie sobie z dostępnością nie jest tak łatwe, jak powinno być.

Chociaż pierwotnie ogłoszono to w 2011 roku, obsługa przeglądarek nadal nie jest uniwersalna. Wsparcie dla Firefoksa ma nastąpić jeszcze w tym roku. Niemniej jednak niektóre głośne strony internetowe (takie jak Youtube) już z nich korzystają. Pomimo ich obecnych niedociągnięć, dla powszechnie udostępnianych komponentów są one jedyną opcją, aw przyszłości możemy spodziewać się ekscytujących dodatków do tego, co mają do zaoferowania.