Mocne strony i zalety mikro frontendów
Opublikowany: 2022-03-11Architektura mikro-frontendowa to podejście projektowe, w którym aplikacja front-endowa jest rozkładana na pojedyncze, pół-niezależne „mikroaplikacje” luźno ze sobą współpracujące. Koncepcja mikro-frontendu jest niejasno inspirowana i nazwana od mikroserwisów.
Zalety wzorca micro-frontend obejmują:
- Architektury mikrofrontendowe mogą być prostsze, a przez to łatwiejsze do zrozumienia i zarządzania.
- Niezależne zespoły programistyczne mogą łatwiej współpracować nad aplikacją typu front-end.
- Mogą zapewnić możliwość migracji ze „starej” aplikacji, ponieważ obok niej działa „nowa” aplikacja.
Chociaż mikrofrontendy cieszą się ostatnio dużym zainteresowaniem, jak dotąd nie ma jednej dominującej implementacji ani wyraźnego „najlepszego” frameworka mikrofrontendowego. W rzeczywistości istnieje wiele podejść w zależności od celów i wymagań. Zobacz bibliografię niektórych z bardziej znanych implementacji.
W tym artykule pominiemy większość teorii mikrofrontendów. Oto, czego nie omówimy:
- „Pokrojenie” aplikacji na mikroaplikacje
- Problemy z wdrażaniem, w tym dopasowanie mikrofrontendów do modelu CI/CD
- Testowanie
- Czy mikroaplikacje powinny być wyrównane jeden-do-jednego z mikrousługami na zapleczu
- Krytyka koncepcji mikro-frontendu
- Różnica między mikrofrontendami a zwykłą starą architekturą komponentów
Zamiast tego przedstawimy samouczek dotyczący mikro-frontendu skupiający się na konkretnej implementacji, podkreślający ważne problemy w architekturze mikro-frontendowej i ich możliwe rozwiązania.
Nasza realizacja nazywa się Yumcha. Dosłowne znaczenie słowa „yum cha” w języku kantońskim to „picie herbaty”, ale jego codzienne znaczenie to „wychodzenie na dim sum”. Pomysł polega na tym, że poszczególne mikroaplikacje w makroaplikacji (jak nazwiemy skomponowaną aplikację najwyższego poziomu) są analogiczne do różnych koszy z porcjami wielkości kęsa, które są wydawane podczas lunchu dim sum.
Czasami będziemy odnosić się do Yumchy jako „frameworka mikro-frontendowego”. W dzisiejszym świecie termin „framework” jest zwykle używany w odniesieniu do Angular, React, Vue.js lub innych podobnych nadbudówek dla aplikacji internetowych. W ogóle nie mówimy o ramach w tym sensie. Yumcha nazywamy frameworkiem tylko ze względu na wygodę: w rzeczywistości jest to bardziej zestaw narzędzi i kilka cienkich warstw do tworzenia aplikacji opartych na mikro-frontendzie.
Samouczek dotyczący mikro-frontendu Pierwsze kroki: znaczniki dla skomponowanej aplikacji
Zanurzmy się w tym, jak możemy zdefiniować makroaplikację i mikroaplikacje, które ją tworzą. Znaczniki zawsze były sercem sieci. Nasza macroapp nie będzie więc określona przez nic bardziej skomplikowanego niż ten znacznik:
<html> <head> <script src="/yumcha.js"></script> </head> <body> <h1>Hello, micro-frontend app.</h1> <!-- HERE ARE THE MICROAPPS! --> <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal> <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal> </body> </html>
Zdefiniowanie naszej makroaplikacji za pomocą znaczników daje nam pełny dostęp do możliwości HTML i CSS w celu rozplanowania i zarządzania naszymi mikroaplikacjami. Na przykład jedna mikroaplikacja może znajdować się na drugiej lub z boku, być w rogu strony, w jednym okienku akordeonu lub pozostać ukryta, dopóki coś się nie wydarzy, lub pozostać na stałe w tle. .
Niestandardowy element używany w mikroaplikacjach nazwaliśmy <yumcha-portal>
, ponieważ „portal” jest obiecującym terminem dla mikroaplikacji użytych w propozycji portalu, która jest wczesną próbą zdefiniowania standardowego elementu HTML do użytku w mikrofrontendach.
Implementacja elementu niestandardowego <yumcha-portal>
Jak powinniśmy zaimplementować <yumcha-portal>
? Ponieważ jest to element niestandardowy, oczywiście jako składnik sieciowy! Możemy wybierać spośród wielu silnych konkurentów do pisania i kompilowania komponentów webowych typu micro-frontend; tutaj użyjemy LitElement, najnowszej iteracji projektu Polymer. LitElement obsługuje cukier składniowy oparty na TypeScript, który obsługuje większość elementów niestandardowych. Aby udostępnić <yumcha-portal>
na naszej stronie, musimy dołączyć odpowiedni kod jako <script>
, tak jak to zrobiliśmy powyżej.
Ale co właściwie robi <yumcha-portal>
? Pierwszym przybliżeniem byłoby po prostu utworzenie iframe
z określonym źródłem:
render() { return html`<iframe src=${this.src}></iframe>`; }
…gdzie render
jest standardowym hakiem renderującym LitElement, korzystającym z literału szablonu ze znacznikami html
. Ta minimalna funkcjonalność może wystarczyć w niektórych trywialnych przypadkach użycia.
Osadzanie mikroaplikacji w iframe
s
Elementy iframe
s to element HTML, którego wszyscy uwielbiają nienawidzić, ale w rzeczywistości zapewniają niezwykle przydatne, solidne zachowanie w piaskownicy. Jednak nadal istnieje długa lista problemów związanych z praniem, o których należy pamiętać podczas korzystania z elementów iframe
, które mogą mieć potencjalny wpływ na zachowanie i funkcjonalność naszej aplikacji:
- Po pierwsze,
iframe
mają dobrze znane dziwactwa, jeśli chodzi o ich rozmiar i układ. - CSS będzie oczywiście całkowicie odizolowany od elementu
iframe
, na dobre lub na złe. - Przycisk „wstecz” przeglądarki będzie działał dość dobrze, chociaż aktualny stan nawigacji elementu
iframe
nie zostanie odzwierciedlony w adresie URL strony , więc nie mogliśmy ani wycinać i wklejać adresów URL, aby uzyskać ten sam stan skomponowanej aplikacji, ani precyzyjnego linku do nich. - Komunikacja z
iframe
z zewnątrz, w zależności od naszej konfiguracji CORS, może wymagać przejścia przez protokółpostMessage
. - Trzeba będzie dokonać uzgodnień dotyczących uwierzytelniania poza granicami
iframe
. - Niektóre czytniki ekranu mogą potykać się o granicę elementu
iframe
lub potrzebują, aby elementiframe
miał tytuł, który mogą ogłosić użytkownikowi.
Niektórych z tych problemów można uniknąć lub złagodzić je, nie używając elementów iframe
— alternatywy, którą omówimy w dalszej części artykułu.
Plusem jest to, że iframe
będzie miał własną, niezależną Content-Security-Policy
(CSP). Ponadto, jeśli mikroaplikacja wskazywana przez element iframe
korzysta z elementu Service Worker lub implementuje renderowanie po stronie serwera, wszystko będzie działać zgodnie z oczekiwaniami. Możemy również określić różne opcje piaskownicy dla elementu iframe
, aby ograniczyć jego możliwości, na przykład możliwość nawigowania do górnej ramki.
Niektóre przeglądarki dostarczyły lub planują dostarczyć atrybut loading=lazy
dla iframe
, który odracza ładowanie ramek iframe
poniżej części przewinięcia, dopóki użytkownik nie przewinie ich w pobliżu, ale nie zapewnia to precyzyjnej kontroli nad leniwym ładowaniem. chcemy.
Prawdziwym problemem z iframe
s jest to, że zawartość iframe
będzie wymagała wielu żądań sieciowych do pobrania. Plik index.html
najwyższego poziomu zostaje odebrany, jego skrypty są ładowane, a jego kod HTML jest analizowany — ale potem przeglądarka musi zainicjować kolejne żądanie kodu HTML elementu iframe
, poczekać na jego otrzymanie, przeanalizować i załadować jego skrypty oraz wyrenderować zawartość iframe
. W wielu przypadkach kod JavaScript elementu iframe
nadal musiałby się rozkręcić, wykonać własne wywołania interfejsu API i pokazywać znaczące dane dopiero po zwróceniu tych wywołań interfejsu API i przetworzeniu danych do przeglądania.
Spowoduje to prawdopodobnie niepożądane opóźnienia i artefakty renderowania, zwłaszcza gdy zaangażowanych jest kilka mikroaplikacji. Jeśli aplikacja iframe
implementuje SSR, pomoże to, ale nadal nie uniknie konieczności dodatkowych podróży w obie strony.
Tak więc jednym z kluczowych wyzwań, przed którymi stoimy podczas projektowania wdrożenia naszego portalu, jest sposób radzenia sobie z tym problemem w obie strony. Naszym celem jest, aby pojedyncze żądanie sieciowe spowodowało zamknięcie całej strony ze wszystkimi jej mikroaplikacjami, w tym jakąkolwiek treścią, którą każda z nich jest w stanie wstępnie wypełnić. Rozwiązaniem tego problemu jest serwer Yumcha.
Serwer Yumcha
Kluczowym elementem prezentowanego tutaj rozwiązania mikro-frontendowego jest ustawienie dedykowanego serwera do obsługi kompozycji mikroaplikacji. Ten serwer proxy wysyła żądania do serwerów, na których hostowana jest każda mikroaplikacja. To prawda, że konfiguracja tego serwera i zarządzanie nim będzie wymagać pewnego wysiłku. Niektóre podejścia do mikro-frontendu (np. single-spa) próbują zrezygnować z takich specjalnych konfiguracji serwera w imię łatwości wdrażania i konfiguracji.
Jednak koszt ustanowienia tego odwrotnego proxy jest więcej niż równoważony przez korzyści, które uzyskujemy; w rzeczywistości istnieją ważne zachowania aplikacji opartych na mikrofrontendzie, których po prostu nie możemy osiągnąć bez tego. Istnieje wiele komercyjnych i bezpłatnych alternatyw dla utworzenia takiego zwrotnego serwera proxy.
Odwrotny serwer proxy, oprócz kierowania żądań mikroaplikacji do odpowiedniego serwera, kieruje również żądania makroaplikacji do serwera makroaplikacji. Ten serwer w specjalny sposób przygotowuje kod HTML dla skomponowanej aplikacji. Po otrzymaniu żądania o index.html
z przeglądarki za pośrednictwem serwera proxy pod adresem URL, takim jak http://macroapp.example.com
, pobiera index.html
, a następnie poddaje go prostej, ale kluczowej transformacji przed zwróceniem to.
W szczególności HTML jest analizowany pod kątem <yumcha-portal>
, co można łatwo zrobić za pomocą jednego z kompetentnych parserów HTML dostępnych w ekosystemie Node.js. Używając atrybutu src
do <yumcha-portal>
, nawiązywany jest kontakt z serwerem, na którym działa mikroaplikacja, i pobierany jest jego index.html
— w tym treść renderowana po stronie serwera, jeśli taka istnieje. Wynik jest wstawiany do odpowiedzi HTML jako <script>
lub <template>
, aby nie został wykonany przez przeglądarkę.

Do zalet tej konfiguracji należy przede wszystkim to, że przy pierwszym żądaniu pliku index.html
dla złożonej strony serwer może w całości pobrać poszczególne strony z poszczególnych serwerów mikroaplikacji — łącznie z treścią renderowaną przez SSR, jeśli any — i dostarczaj pojedynczą, kompletną stronę do przeglądarki, w tym zawartość, która może być użyta do wypełnienia ramki iframe
bez dodatkowych podróży w obie strony serwera (przy użyciu niewykorzystanego atrybutu srcdoc
). Serwer proxy zapewnia również, że wszelkie szczegóły dotyczące miejsca, z którego obsługiwane są mikroaplikacje, są ukryte przed wzrokiem ciekawskich. Wreszcie upraszcza to kwestie związane z CORS, ponieważ wszystkie żądania aplikacji są wysyłane do tego samego źródła.
Po powrocie do klienta tag <yumcha-portal>
zostaje utworzony i znajduje treść w miejscu, w którym został umieszczony w dokumencie odpowiedzi przez serwer, aw odpowiednim czasie renderuje iframe
i przypisuje treść do swojego atrybutu srcdoc
. Jeśli nie używamy iframe
s (patrz poniżej), zawartość odpowiadająca temu <yumcha-portal>
jest wstawiana albo do cienia DOM elementu niestandardowego, jeśli tego używamy, albo bezpośrednio w dokumencie.
W tym momencie mamy już częściowo działającą aplikację opartą na mikrofrontendzie.
To tylko wierzchołek góry lodowej, jeśli chodzi o interesującą funkcjonalność serwera Yumcha. Na przykład chcielibyśmy dodać funkcje, aby kontrolować sposób obsługi odpowiedzi HTTP na błędy z serwerów mikroaplikacji lub jak radzić sobie z mikroaplikacjami, które reagują bardzo wolno — nie chcemy czekać w nieskończoność, aby wyświetlić stronę, jeśli jedna mikroaplikacja nie jest odpowiadam! Te i inne tematy zostawiamy w innym poście.
Logika transformacji macroapp index.html
Yumcha może być łatwo zaimplementowana w sposób bezserwerowej funkcji lambda lub jako oprogramowanie pośredniczące dla frameworków serwerowych, takich jak Express lub Koa.
Kontrola mikroaplikacji oparta na skrótach
Wracając do strony klienta, jest jeszcze jeden aspekt implementacji mikroaplikacji, który jest ważny dla wydajności, leniwego ładowania i renderowania bez szarpnięć. Moglibyśmy wygenerować tag iframe
dla każdej mikroaplikacji, albo z atrybutem src
— który powoduje kolejne żądanie sieciowe — albo z atrybutem srcdoc
wypełnionym treścią wypełnioną dla nas przez serwer. Ale w obu tych przypadkach kod w tym iframe
rozpocznie się natychmiast, włączając w to ładowanie wszystkich jego tagów skryptów i linków, ładowanie początkowe oraz wszelkie początkowe wywołania API i powiązane przetwarzanie danych — nawet jeśli użytkownik nigdy nie uzyskuje dostępu do danej mikroaplikacji.
Naszym rozwiązaniem tego problemu jest początkowo przedstawianie mikroaplikacji na stronie jako małych nieaktywnych skrótów, które można następnie aktywować. Aktywacja może być kierowana albo przez region mikroaplikacji, który pojawia się w widoku, przy użyciu słabo wykorzystywanego interfejsu API IntersectionObserver
, albo, częściej, przez wcześniejsze powiadomienia wysyłane z zewnątrz. Oczywiście możemy również określić, że mikroaplikacja ma być aktywowana natychmiast.
W każdym razie, kiedy i tylko wtedy, gdy mikroaplikacja jest aktywna, element iframe
jest faktycznie renderowany, a jego kod jest ładowany i wykonywany. Jeśli chodzi o naszą implementację z użyciem LitElement i zakładając, że stan aktywacji jest reprezentowany przez activated
zmienną instancji, otrzymalibyśmy coś takiego:
render() { if (!this.activated) return html`{this.placeholder}`; else return html` <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`; }
Komunikacja między mikroaplikacjami
Chociaż mikroaplikacje tworzące makroaplikację są z definicji luźno powiązane, nadal muszą być w stanie komunikować się ze sobą. Na przykład mikroaplikacja nawigacyjna musiałaby wysłać powiadomienie, że inna mikroaplikacja wybrana właśnie przez użytkownika powinna zostać aktywowana, a aplikacja, która ma zostać aktywowana, musi otrzymywać takie powiadomienia.
Zgodnie z naszym minimalistycznym nastawieniem, chcemy uniknąć wprowadzania wielu maszyn przekazujących wiadomości. Zamiast tego, w duchu komponentów webowych, użyjemy zdarzeń DOM. Zapewniamy trywialny interfejs API rozgłaszania, który wstępnie powiadamia wszystkie kody pośredniczące o zbliżającym się zdarzeniu, czeka na aktywację tych, które zażądały aktywacji w celu aktywacji tego typu zdarzenia, a następnie wysyła zdarzenie do dokumentu, na którym może nasłuchiwać dowolna mikroaplikacja to. Biorąc pod uwagę, że wszystkie nasze elementy iframe
są tego samego pochodzenia, możemy dotrzeć z elementu iframe
do strony i odwrotnie, aby znaleźć elementy, przeciwko którym można uruchomić zdarzenia.
Rozgromienie
W dzisiejszych czasach wszyscy oczekujemy, że pasek adresu URL w SPA będzie reprezentował stan widoku aplikacji, więc możemy wycinać, wklejać, wysyłać, wysyłać tekst i linkować do niego, aby przejść bezpośrednio do strony w aplikacji. Jednak w aplikacji mikrofrontendowej stan aplikacji jest w rzeczywistości kombinacją stanów, po jednym dla każdej mikroaplikacji. Jak mamy to reprezentować i kontrolować?
Rozwiązaniem jest zakodowanie stanu każdej mikroaplikacji w pojedynczy złożony adres URL i użycie małego routera makroaplikacji, który wie, jak połączyć ten złożony adres URL i rozdzielić go. Niestety wymaga to logiki specyficznej dla Yumcha w każdej mikroaplikacji: odbierania komunikatów z routera macroapp i aktualizowania stanu mikroaplikacji i odwrotnie, informowania routera macroapp o zmianach w tym stanie, aby można było zaktualizować złożony adres URL. Na przykład można sobie wyobrazić YumchaLocationStrategy
dla Angulara lub element <YumchaRouter>
dla Reacta.
Sprawa bez ramki iframe
Jak wspomniano powyżej, hosting mikroaplikacji w iframe
s ma pewne wady. Istnieją dwie alternatywy: umieścić je bezpośrednio w kodzie HTML strony lub umieścić w cieniu DOM. Obie alternatywy odzwierciedlają nieco zalety i wady iframe
, ale czasami na różne sposoby.
Na przykład poszczególne zasady CSP dla mikroaplikacji musiałyby zostać w jakiś sposób połączone. Technologie wspomagające, takie jak czytniki ekranu, powinny działać lepiej niż w przypadku elementów iframe
, zakładając, że obsługują one shadow DOM (co jeszcze nie wszystkie). Powinno być proste zaaranżowanie rejestracji pracowników usług mikroaplikacji przy użyciu koncepcji Service Worker „zakres”, chociaż aplikacja musiałaby zapewnić, że jej Service Worker jest zarejestrowany pod nazwą aplikacji, a nie "/"
. Żaden z problemów z układem związanych z iframe
nie dotyczy metod wbudowanego lub cieniowanego modelu DOM.
Jednak aplikacje zbudowane przy użyciu frameworków, takich jak Angular i React, prawdopodobnie nie będą szczęśliwe żyjąc w trybie inline lub w cieniu DOM. W tym przypadku prawdopodobnie będziemy chcieli użyć iframe
s.
Metody inline i shadow DOM różnią się pod względem CSS. CSS będzie czysto zamknięty w cieniu DOM. Jeśli z jakiegoś powodu chcielibyśmy udostępnić CSS zewnętrzny za pomocą shadow DOM, musielibyśmy użyć konstruowalnych arkuszy stylów lub czegoś podobnego. Dzięki wbudowanym mikroaplikacjom wszystkie CSS byłyby udostępniane na całej stronie.
Ostatecznie implementacja logiki dla mikroaplikacji inline i shadow DOM w <yumcha-portal>
jest prosta. Pobieramy zawartość danej mikroaplikacji z miejsca, w której została wstawiona na stronę przez logikę serwera jako element HTML <template>
, klonujemy ją, a następnie dołączamy do elementu, który LitElement nazywa renderRoot
, który zwykle jest cieniem DOM elementu, ale może również być ustawiony na sam element ( this
) dla przypadku wbudowanego (nie-shadow DOM).
Ale poczekaj! Treść obsługiwana przez serwer mikroaplikacji to cała strona HTML. Nie możemy wstawić strony HTML dla mikroaplikacji, wraz z tagami html
, head
i body
, w środku strony dla macroapp, prawda?
Rozwiązujemy ten problem, wykorzystując dziwactwo tagu template
, w którym opakowana jest zawartość mikroaplikacji pobrana z serwera mikroaplikacji. Okazuje się, że kiedy nowoczesne przeglądarki napotykają znacznik template
, chociaż go nie „wykonują”, przetwarzają go, a czyniąc to usuwają nieprawidłową zawartość, taką jak <html>
, <head>
i <body>
, zachowując ich wewnętrzną zawartość. Tak więc tagi <script>
i <link>
w <head>
, jak również zawartość <body>
, są zachowane. Właśnie tego chcemy, aby wstawić zawartość mikroaplikacji na naszą stronę.
Architektura mikro-frontendu: diabeł tkwi w szczegółach
Mikrofrontendy zakorzenią się w ekosystemie aplikacji internetowych, jeśli (a) okażą się lepszym podejściem architektonicznym oraz (b) będziemy mogli wymyślić, jak je wdrożyć w sposób, który spełni niezliczone praktyczne wymagania dzisiejszej sieci.
Jeśli chodzi o pierwsze pytanie, nikt nie twierdzi, że mikrofrontendy są odpowiednią architekturą dla wszystkich przypadków użycia. W szczególności nie byłoby powodu, dla którego jeden zespół rozwijałby od podstaw do przyjęcia mikrofrontendów. Pytanie, jakie typy aplikacji w jakich rodzajach kontekstów mogłyby najbardziej skorzystać na wzorcu mikro-frontendu, zostawię innym komentatorom.
Jeśli chodzi o implementację i wykonalność, widzieliśmy, że istnieje wiele szczegółów, którymi należy się zająć, w tym kilka, o których nawet nie wspomniano w tym artykule – w szczególności uwierzytelnianie i bezpieczeństwo, duplikacja kodu i SEO. Niemniej jednak mam nadzieję, że ten artykuł przedstawia podstawowe podejście do implementacji mikrofrontendów, które po dalszym udoskonaleniu mogą sprostać wymaganiom świata rzeczywistego.
Bibliografia
- Mikro fronty — robienie tego w stylu kątowym — część 1
- Mikro fronty — robienie tego w stylu kątowym — część 2
- Ewoluowanie aplikacji AngularJS przy użyciu mikrofrontendów
- Mikrofronty
- Mikroserwisy UI — odwracanie antywzorca (mikro frontendy)
- Mikroserwisy UI — antywzorek?
- Tworzenie strony przy użyciu Micro-Frontends opiera się na podejściu podobnym do Yumcha, jak reverse proxy i SSI, co bardzo polecam.
- Zasoby mikrofrontendów
- Podium
- Nie rozumiem mikro-frontendów. To całkiem dobry przegląd typów architektur mikro-frontendowych i przypadków użycia.
- Bezserwerowe mikronakładki wykorzystujące Vue.js, AWS Lambda i Hypernova
- Micro Frontends: świetny, kompleksowy przegląd.