DevUI – это команда, которая занимается как проектированием, так и проектированием, обслуживая HUAWEI CLOUD.DevCloudПлатформа и несколько промежуточных и серверных систем в Huawei предназначены для дизайнеров и проектировщиков.
Официальный сайт:devui.design
Библиотека компонентов Ng:ng-devui(Добро пожаловать в Звезду)
введение
До сих пор помню Дэна Абрамова, автора редукса в 19 лет, про микро фронтендTwitterВ то время это вызвало широкие дебаты в области фронтенда, многие люди говорили, что микро-фронтенд был ложным предложением, но с наступлением 2020 года одна за другой появлялись различные статьи и фреймворки, связанные с микро-фронтендом, и продвигали эту идею. тема на передний план.Факты доказывают, что микро-интерфейс после модульности и компонентизации постепенно был принят в отрасли в качестве еще одной модели архитектуры внешнего интерфейса.В крупномасштабных сценариях разработки средних и фоновых корпоративных приложений ToB он будет играют все более важную роль.Поэтому сейчас самое время поговорить Давайте поговорим о микро интерфейсе. Эта статья разделена на верхнюю и нижнюю части.В верхней части в основном обсуждается происхождение и сценарии применения микро-фронтендов, DevUI исследует эволюцию микро-фронтендов и подробное изучение одиночного спа.Обсудим подробно, как разработайте решение микроинтерфейса корпоративного уровня Я надеюсь, что эта статья может быть использована в качестве важного справочника для исследователей микроинтерфейса, чтобы войти в яму.
источник
Концепция микро-интерфейса заключается в том, что с появлением внутренних микросервисов бизнес-команда делится на разные небольшие команды разработчиков, каждая из которых имеет фронт-энд, бэк-энд, тестирование и другие роли, службы могут звонить друг другу через http или rpc, интерфейс также может быть интегрирован и агрегирован через API-шлюз, далее следует надежда, что фронтенд-команда также сможет самостоятельно разрабатывать микро-приложения, а затем агрегировать эти микро-приложения. приложений на определенном этапе интерфейса (сборка, выполнение) для формирования законченного Большого веб-приложения. Так что эта концепция была предложена в обзоре Thoughtworks Technology Radar 2016 года.
Для концепции микрофронтенда ее суть заключается в повторном использовании и интеграции веб-приложений, особенно когда появляются одностраничные приложения, каждая команда не может разрабатывать страницы в соответствии с прежней моделью маршрутизации на стороне сервера и извне. шаблоны блоков.Маршрутизация берется на себя передним интерфейсом, поэтому два наиболее важных вопроса – как интегрировать веб-приложения и на каком этапе.Окончательная схема реализации для различных вариантов также будет сильно различаться в зависимости от вашего бизнес-сценария, но для больших Для большинства команд обычные требования для рассмотрения модели архитектуры микроинтерфейса следующие:
- Независимая разработка, независимое развертывание, добавочное обновление: Согласно картинке выше, команды A, B и C предпочтительно не должны знать друг о друге, и каждое подприложение должно разрабатываться, развертываться и обновляться в соответствии с собственным ритмом версий.
- Независимость от стека технологий: Команды A, B и C могут выбрать любую структуру для разработки в соответствии со своими потребностями, и их не нужно заставлять быть последовательными.
- Изоляция и совместное использование во время выполнения: Во время выполнения приложения A, B и C образуют законченное приложение, доступ к которому можно получить через основную запись приложения.Необходимо убедиться, что js и css, соответствующие A, B и C, изолированы друг от друга и В то же время существует механизм связи, гарантирующий, что A, B, C могут общаться друг с другом или обмениваться данными.
- Хороший опыт для одностраничных приложений: при переключении с одного подприложения на другое изменение маршрутизации не приведет к перезагрузке всей страницы, а эффект переключения аналогичен переключению внутрисайтовой маршрутизации одностраничного приложения.
веб-интеграция
Обычно существует два этапа интеграции приложений, а именно время построения и время выполнения, соответствующие методы реализации разных этапов также различаются, в общем случае можно выделить следующие:
- Интеграция времени сборки: Интеграция в основной репозиторий приложения на этапе сборки с помощью подмодуля git или пакета npm. Команды A, B и C разрабатывают независимо. Преимущество заключается в простоте реализации и совместном использовании зависимостей. Недостатком является то, что A, B и C не могут быть обновлены независимо друг от друга.Один из Когда происходит обновление, необходимо собрать и развернуть основное приложение, чтобы обновить все приложение. следующим образом:
Этот способ больше подходит для небольших команд, так как когда пакетов становится все больше и больше, основное приложение будет часто выпускаться и обновляться, кроме того, увеличится скорость построения основного приложения, а стоимость обслуживания кода станет выше. и выше, поэтому большинство интерфейсных архитектур Micro хотят иметь возможность интеграции во время выполнения.
- Интеграция шаблонов на стороне сервера:Определите шаблон на домашней странице основного приложения и позвольте серверу динамически выбирать, какое подприложение для интеграции групп A, B и C с помощью технологии, аналогичной SSI nginx, следующим образом:
index.html
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Feed me</title>
</head>
<body>
<h1>content here</h1>
<!--# include file="$CONTENT.html" -->
</body>
</html>
Соответствующая конфигурация nginx nginx.conf
server {
root html; #ssi配置开始
ssi on;
ssi_silent_errors on;
ssi_types text/shtml;
#ssi配置结束
index index.html index.htm; rewrite ^/$ http://localhost/appa redirect;
location /appa {
set $CONTENT 'appa';
}
location /appb {
set $CONTENT 'appb'; }
location /appc {
set $CONTENT 'appc' }
}
Конечным результатом работы команд A, B и C является файл шаблона, расположенный на сервере.Как и PHP, интеграция с JSP-сервером основана на том же принципе.На стороне сервера различные шаблоны выбираются путем маршрутизации, а содержимое домашней страницы собран и укомплектован. Во-первых, этот режим идет вразрез с общей тенденцией разделения фронтенда и бэкенда, что приведет к сопряжению, в то же время требует определенного объема работы по обслуживанию серверной части, не подходит для больших масштабные сценарии интеграции одностраничных приложений.
- Интеграция с Iframe во время выполнения:Можно сказать, что этот метод должен быть самым простым и эффективным на ранней стадии.Различные команды могут разрабатывать и развертывать независимо друг от друга.Только одно основное приложение должно указывать на адреса, соответствующие приложениям A, B и C через iframe. Если требуется связь между основным приложением и вспомогательными приложениями, это также можно легко сделать с помощью почтового сообщения. ,следующим образом:
<html>
<head>
<title>index.html</title>
</head>
<body>
<iframe id="content"></iframe>
<script type="text/javascript">
const microFrontendsByRoute = {
'/appa': 'https://main.com/appa/index.html',
'/appb': 'https://main.com/appb/index.html',
'/appc': 'https://main.com/appc/index.html',
};
const iframe = document.getElementById('content');
iframe.src = microFrontendsByRoute[window.location.pathname];
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
var origin = event.origin
if (origin === "https://main.com") {
// do something
}
}
}
</script>
</body>
</html>
Тем не менее, недостатки этого метода также очевидны, особенно пользовательский опыт будет очень плохим, перезагрузка всей страницы, вызванная iframe, а в некоторых сценариях (например, какой-то глобальный диалог или модальное отображение в iframe, потеря вторичное состояние маршрутизации, совместное использование сеансов, трудности разработки и отладки) и другие проблемы по-прежнему определяют, что его нельзя использовать в качестве предпочтительного решения для веб-интеграции в режиме микроинтерфейса.
- Интеграция JS во время выполнения:У этого метода интеграции обычно есть два режима: первый — упаковать приложения A, B и C в разные пакеты, а затем загрузить разные пакеты через загрузчик, динамически запустить логику пакета и отобразить страницу следующим образом:
В настоящее время приложения A, B и C совершенно не знают друг о друге и могут быть разработаны с использованием любой среды. Переключение приложений, вызванное переключением маршрутизации, не приведет к перезагрузке страницы. Если приложения A, B и C хотят обмениваться данными во время выполнения , используйте CustomEvent или self. Можно определить EventBus. A, B и C также могут обеспечить изоляцию приложений с помощью механизма изоляции различных платформ или некоторых механизмов песочницы.Так хорошо выглядит.
Второй способ интеграции во время выполнения заключается в использовании веб-компонентов.Приложения A, B и C пишут свою собственную бизнес-логику в виде веб-компонента и упаковывают его в пакет, который затем загружается, выполняется и отображается основным приложением следующим образом. :
<html>
<body>
<script src="https://main.com/appa/bundle.js"></script>
<script src="https://main.com/appb/bundle.js"></script>
<script src="https://main.com/appc/bundle.js"></script>
<div id="content"></div>
<script type="text/javascript">
const routeTypeTags = {
'/appa': 'app-a',
'/appb': 'appb',
'/appc': 'app-c',
};
const componentTag = routeTypeTags[window.location.pathname];
const content = document.getElementById('content');
const component = document.createElement(componentTag);
content.appendChild(component);
</script>
</body>
</html>
Этот метод обычно имеет проблемы с совместимостью браузера (необходимо ввести полифилл), и он не очень удобен для пользователей трех основных фреймворков (метод написания компонентов, стоимость преобразования, библиотека компонентов версии веб-компонентов, эффективность разработки, экология и т. д.), Для сложных приложений выбор этого режима разработки для всего сайта натолкнется на множество подводных камней, но пробовать не стоит.Если какие-то области (небольшой кусок) страницы необходимо самостоятельно разработать и развернуть, то можно использовать и этот метод интеграции (В настоящее время DevUI использует веб-компоненты для отображения некоторых областей страницы). В настоящее время существует множество фреймворков, которые учли некоторые из вышеперечисленных ограничений и максимально оптимизировали их для достижения нестандартных эффектов.stencil, которые помогут вам быстро развиваться.
Подводя итог, с точки зрения интеграции веб-приложений, текущая более подходящая архитектура микроинтерфейса должна заключаться в построении структуры модели приложения «ведущий-ведомый» с помощью JavaScript во время выполнения, а затем интеграции различных подприложений через разные маршруты, соответствующие к вышеупомянутым различным методам. DevUI фактически прошел следующие этапы.
Эволюция режима интеграции интерфейса DevUI
Как показано на рисунке выше, отличительными чертами интерфейса devui являются:
1) сервисов много, у каждого сервиса свой репозиторий кода фронтенда, который нужно самостоятельно разработать, протестировать и развернуть;
2) Передняя часть каждой службы состоит из заголовка и области содержимого, которая представляет собой одностраничное приложение на основе Angular.У разных служб различается только область содержимого, а заголовки одинаковы;
Проще говоря, это независимая разработка каждой бизнес-команды, которая через маршрутизацию распределяется по разным сервисам, а фронтенд каждого сервиса представляет собой полноценное одностраничное приложение. Для такого бизнес-сценария режим интеграции и повторного использования между сервисами примерно прошел следующие этапы.
Фаза 1: Общая компонентизация + гиперссылки между сервисами
На этом этапе мы разделяем заголовок и другие области, используемые каждой службой, на компоненты, чтобы решить проблему повторного использования.Переход между службами по-прежнему использует наиболее распространенные гиперссылки, как показано ниже:
Самые большие оставшиеся проблемы на этом этапе: белый экран перехода между сервисами очевиден, управление сеансом между сервисами разделено, переход между сервисами требует повторной аутентификации, а пользовательский опыт крайне плохой.
Этап 2: App Shell (предварительный рендеринг) + общий доступ к сеансу
В отрасли существуют стандартные решения проблемы рендеринга белого экрана для одностраничных приложений.Обычно используются SSR (рендеринг на стороне сервера) и предварительный рендеринг (Prerender).Разница между ними заключается в том, что будет выполняться SSR. на стороне сервера (обычно Node).Для некоторой логики HTML, соответствующий текущему маршруту, сначала генерируется, а затем возвращается в браузер, в то время как Prerender обычно генерирует соответствующий HTML-контент в соответствии с некоторыми правилами на этапе сборки и возвращает непосредственно в браузер, когда пользователь получает к нему доступ, следующим образом:
С точки зрения пользовательского опыта и эффектов, SSR, несомненно, лучший, но если весь сайт является SSR, стоимость очень велика (каждому сервису нужно добавить слой слоя рендеринга Node, а у SSR высокие требования к качеству кода, Angular's собственный SSR недостаточно развит), поэтому, взвесив друг друга, мы выбрали Prerender для решения проблемы белого экрана путем создания оболочки приложения на этапе сборки следующим образом:
На этом этапе мы разделили часть логики шапки и других сервисов на две части, одна часть — это левая часть шапки, которую можно увидеть интуитивно при обновлении страницы, эта часть вместе с некоторыми глобальными состояниями и встроенным -in event bus (с Это все сделано в пакете npm, который единообразно инжектится в index.html дельца при построении, а правая часть шапки по прежнему является Angular компонентом (выпадающие меню и прочее области, которые требуют, чтобы действия пользователя были видны, даже если отложенная визуализация не повлияет на опыт), бизнес должен быть представлен в собственном дереве компонентов. На этапе выполнения, когда пользователь обращается к index.html, часть оболочки Сначала визуализируется все приложение, затем загружаются статические ресурсы, соответствующие angular, а затем визуализируется правая сторона.Раскрывающееся меню заголовка и бизнес-контент.Заголовок связывается с бизнесом через шину событий.Когда контент, такой как раскрывающееся меню в правой части заголовка успешно отображается, мы добавляем это содержимое ко всей области заголовка.
В то же время мы также решили проблему проверки повторного входа между сервисами, перескакивающими через общий доступ к сеансу поддомена.Благодаря этой модели прогрессивного рендеринга + предварительного рендеринга пользовательский интерфейс улучшается.Хотя это многостраничное приложение, переход между службами оптимизирован, чтобы дать людям ощущение прыжка по сайту, и в то же время он может обеспечить независимую разработку и развертывание различных команд.Самая большая оставшаяся проблема на этом этапе заключается в том, что общедоступные компоненты, такие как заголовки, по-прежнему доставляются в разные службы в виде пакетов npm.После обновления общедоступной логики в заголовках каждому бизнесу придется пассивно выпускать версии, что приводит к потерям рабочей силы, так что каждый Есть надежда, что общие компоненты могут быть разделены.
Этап 3: виджет (микроприложение)
До сих пор общий компонент, такой как заголовок, был очень сложным компонентом, представляющим большую часть общедоступной логики devui.В дополнение к некоторым собственным представлениям, которые необходимо отображать, он также должен выполнять большую часть общедоступной логики, кешировать интерфейс запрашивать данные и т. д. Бизнес-потребление, поэтому на данном этапе мы надеемся, что такие компоненты, как заголовки, могут быть независимо разработаны, развернуты общедоступными командами и интегрированы с каждым бизнесом во время выполнения для формирования законченного приложения следующим образом:
Мы надеемся, что бизнес выработает свою собственную логику, заголовок выработает публичную логику, не будет мешать друг другу и будет выпускать обновления самостоятельно, а затем во время выполнения бизнес будет ссылаться на заголовок через что-то похожее на header-loader (обратите внимание, что это ссылка во время выполнения) Таким образом, можно избежать пассивной работы по обновлению, вызванной обновлением общедоступной логики заголовка для бизнеса, и бизнес не знает о заголовке. Таким образом, основная проблема здесь заключается в том, как интегрируется заголовок.Согласно предыдущей главе, есть два способа: использование интеграции iframe и использование javascript для динамического рендеринга. iframe явно не подходит для такого сценария как по эффекту реализации, так и по сложности делового общения, поэтому здесь мы реализуем его по аналогии с веб-компонентами (в реальном процессе можно выбрать любой фреймворк, лишь бы он может удовлетворить Загрузить пакет, выполнить логику и отобразить такой шаблон)
На этом этапе мы решили проблему пассивных бизнес-обновлений, вызванных обновлениями общей логики, значительно уменьшив деловую работу и значительно улучшив скорость отклика на откаты обновления общей логики.Бизнес и общая логика разрабатываются и развертываются независимо друг от друга. По сути, в рамках большого приложения мирно сосуществуют различные дочерние предприятия и публичные команды. Но проблемы есть всегда, и бизнес ставит перед собой более высокие цели.
Этап 4. Оркестрация и интеграция между приложениями
Представьте себе такой сценарий, для крупного предприятия внутри много мидл и бэкенд приложений, его можно представить как пул приложений (рынок приложений), для некоторых бизнесов надеюсь вывести C, D, E интегрирует их все, чтобы сформировать для пользователей крупномасштабный бизнес A. В то же время я надеюсь изъять D, E и F с рынка приложений и интегрировать их в крупномасштабный бизнес B, чтобы обеспечить единый вход для пользователи могут использовать.Среди них приложения A, B, C, D, E, F разрабатываются и поддерживаются разными командами. В этом случае должен быть механизм для определения правил, которым должно следовать стандартное подприложение, и того, как основное приложение интегрируется (загрузка, рендеринг, логика выполнения, изоляция, связь, маршрутизация ответов, совместное использование зависимостей, фреймворки). неважно и др.)
Такой механизм - это то, что нужно обсудить самому микро-фронтенду.На данном этапе как его реализовать зависит от сложности вашего бизнеса.Он может быть простым или сложным, или даже сервисным продуктом и предоставлять полный набор решения, которые помогут вам достичь таких целей (см.Система микро-фронтальной архитектуры), весь DevUI также находится в такой исследовательской стадии, и некоторые основные моменты будут описаны во второй половине этой статьи. В настоящее время, согласно таким требованиям, нам сначала нужно изучить, как реализовать микро-интерфейс в этом режиме приложения master-slave.
Использование одного СПА
Во всей отрасли существует множество решений для реализации микроинтерфейса, которые широко приняты всеми.single-spa, это самая ранняя реализация решения микро-фронтенда на основе режима master-slave, а так же она заимствована различными более поздними решениями (такими как qiankun, mooa и т.д.) Не будет преувеличением сказать, что если вы хотите чтобы изучить микро-фронтенды, вам нужно сначала глубоко погрузиться в single-spa и то, как он работает.
Классификация микрофронтендов:single-spa делит микрофронтенды на следующие три категории:
- стандартное вспомогательное приложение single-spa: различные компоненты могут отображаться с помощью единого спа-центра, соответствующего разным маршрутам, обычно это полное подприложение;
- single-spa parcels: Посылка обычно не связана с маршрутом, а просто областью на странице (аналогично упомянутому выше виджету)
- utility module: Некоторые подмодули, разработанные независимо, не отображают страницу, а только выполняют некоторую общую логику.
Первые две категории находятся в центре нашего исследования.Здесь мы берем angular8 в качестве примера, чтобы показать использование single-spa и вышеупомянутых концепций.
Шаг 1 создайте подприложение:Сначала создайте корневой каталог
mkdir microFE && cd microFE
Затем используйте angular cli для создания двух проектов в этом каталоге следующим образом:
ng new my-app --routing --prefix my-app
Внесите single-spa-angular в корневую директорию проекта (поскольку single-spa — это микро-фронтенд-фреймворк, который не имеет ничего общего с конкретными фреймворками, проекты разных фреймворков имеют разные методы рендеринга, чтобы писать под- приложения, написанные каждой структурой, являются абстрагированными как стандартное подприложение single-spa, поэтому для платформы необходимо внести некоторые изменения.здесь)
ng add single-spa-angular
Операция здесь в основном делает следующие вещи:
1) Преобразуйте запись углового приложения из main.ts в main.single-spa.ts следующим образом:
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { ɵAnimationEngine as AnimationEngine } from '@angular/animations/browser';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import singleSpaAngular from 'single-spa-angular';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';
if (environment.production) {
enableProdMode();
}
const lifecycles = singleSpaAngular({
bootstrapFunction: singleSpaProps => {
singleSpaPropsSubject.next(singleSpaProps);
return platformBrowserDynamic().bootstrapModule(AppModule);
},
template: '<my-app-root />',
Router,
NgZone: NgZone,
AnimationEngine: AnimationEngine,
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
Из этого видно, что стандартное подприложение single-spa должно предоставлять три операции жизненного цикла, а именно начальную загрузку, монтирование и размонтирование.
2) В каталоге src/single-spa создаются два файла, один — single-spa-props для передачи пользовательских свойств, а другой — assets-url.ts для динамического получения пути к статическому ресурсу текущего приложения.
3) В каталоге src создается пустой маршрут, чтобы одно приложение могло отображать пустой маршрут, когда маршрут не может быть найден при переходе между приложениями
app-routing.module.ts
const routes: Routes = [ { path: '**', component: EmptyRouteComponent }];
4) Добавлены две команды build:single-spa и serve:single-spa в package.json для создания субприложения single-spa и запуска субприложения single-spa соответственно.
5) Создал пользовательский файл конфигурации webpack в корневом каталоге, и ввел конфигурацию webpack single-spa-angular (содержимое которого мы разберем позже)
Затем вам нужно добавить базовый href/в app-routing.module.ts следующим образом, чтобы избежать конфликта между всем подприложением и всем маршрутом angular при переключении маршрута angular:
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]})
export class AppRoutingModule { }
В это время, если вы используете команду npm run serve:single-spa, субприложение single-spa будет запущено на соответствующем порту (здесь 4201) следующим образом:
На странице ничего не отображается, но соответствующий single-spa создается как пакет main.js и сопоставляется с портом 4201.
В то же время выполните описанные выше шаги, чтобы создать другое приложение my-app2 и сопоставить его пакет с портом 4202. На данный момент наша структура каталогов выглядит следующим образом:
- my-app: одно спа-приложение 1
- my-app2: одно вспомогательное спа-приложение 2
Мы создаем root-html в корневом каталоге проекта и генерируем файл package.json.
npm init -y && npm i serve -g
{ "name": "root-html",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "serve -s -l 4200"
},
"keywords": [],
"author": "",
"license": "ISC"
}
В сценариях serve будет вызываться для запуска веб-сервера для сопоставления содержимого каталога.
Создайте index.html в этом каталоге со следующим содержимым:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Your application</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="importmap-type" content="systemjs-importmap">
<script type="systemjs-importmap">
{
"imports": {
"app1": "http://localhost:4201/main.js",
"app2": "http://localhost:4202/main.js",
"single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js"
}
}
</script>
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js" as="script" crossorigin="anonymous" />
<script src='https://unpkg.com/core-js-bundle@3.1.4/minified.js'></script>
<script src="https://unpkg.com/zone.js"></script>
<script src="https://unpkg.com/import-map-overrides@1.6.0/dist/import-map-overrides.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/system.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/amd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-exports.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-register.min.js"></script>
</head>
<body>
<script>
System.import('single-spa').then(function (singleSpa) {
singleSpa.registerApplication(
'app1',
function () {
return System.import('app1');
},
function (location) {
return location.pathname.startsWith('/app1');
}
);
singleSpa.registerApplication(
'app2',
function () {
return System.import('app2');
},
function (location) {
return location.pathname.startsWith('/app2');
}
)
singleSpa.start();
})
</script>
<import-map-overrides-full></import-map-overrides-full>
</body>
</html>
В настоящее время с помощью маршрутизации можно добиться эффекта перехода между приложением 1 и приложением 2. Если вы хотите настроить вторичную маршрутизацию субприложения, вы можете обратиться к коду, стоящему за статьей.
Шаг 3. Создайте заявку на посылку:
Вышеуказанные два шага реализуют переключение подприложений по разным маршрутам.Если вы хотите, чтобы команда самостоятельно разработала фрагмент страницы и интегрировала его в любое из вышеперечисленных приложений, как этого добиться? 5.X, который может Таким образом, компонент, написанный другим фреймворком, загружается и отображается в любом подприложении.
Сначала мы создаем новый проект с помощью vue-cli в корневом каталоге:
vue create my-parcel
Затем добавить single-spa под проект (конкретная операция здесь подробно не описана, можно посмотреть, что сделано в документации)
vue add single-spa
Затем создайте и запустите приложение для посылок,npm run serve
В это время пакет подприложения, упакованный проектом vue, также будет запущен на порту localhost: 8080. Мы настраиваем его в index.html корневого приложения, чтобы systemjs мог его найти.
Затем мы загружаем и отображаем его в my-app2.
app.component.ts моего приложения2
import { Component,ViewChild, ElementRef, OnInit, AfterViewInit } from '@angular/core';
import { Parcel, mountRootParcel } from 'single-spa';
import { from } from 'rxjs';
@Component({
selector: 'my-app2-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']})
export class AppComponent implements OnInit, AfterViewInit {
title = 'my-app2';
@ViewChild('parcel', { static: true }) private parcel: ElementRef;
ngOnInit() {
from(window.System.import('parcel')).subscribe(app => {
mountRootParcel(app, { domElement :this.parcel.nativeElement});
})
}
}
При инициализации мы получаем точку монтирования посылки на компоненте, загружаем пакет субприложения vue, а затем вызываем метод mountRootParcel, предоставленный single-spa, для монтирования субкомпонента (приложения).Второй параметр, переданный этим method — это элемент dom точки монтирования.Первый параметр — это подприложение посылки.Важным отличием между подприложением посылки и подприложением с одним спа является то, что приложение посылки может предоставлять необязательный метод обновления.
main.js проекта vue
import './set-public-path';
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import App from './App.vue';
Vue.config.productionTip = false;
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {render: (h) => h(App),
},
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
Эффект следующий: когда мы переключаемся на подприложение App2, мы обнаруживаем, что наш компонент представления также отображается:
Принципиальный анализ Single-SPA
В предыдущей главе мы использовали single-spa для реализации различных подприложений, загружающих приложения посылок через коммутацию маршрутизации и режим без маршрутизации. У вас есть определенное представление о том, что такое single-spa и как им пользоваться. В это время всем должно быть любопытно, что делается внутри single-spa и можно ли реализовать такой механизм. Давайте разберем single-spa и single-spa- угловая внутренняя логика.
модули приложений и посылок: Во-первых, singles-spa предоставляет два API-интерфейса: один API-интерфейс приложений, который можно использовать напрямую путем импорта из single-spa, обычно для операций подприложения и основного приложения, а другой API-интерфейс пакетов, обычно посылка соответствует этим двум модулям, и соответствующий API может ссылаться наздесь;
модуль инструментов разработки: После single-spa5 предоставляется devtools, который может напрямую просматривать состояние текущего суб-приложения через chrome и т. д., поэтому модуль devtools в основном оборачивает некоторые API, необходимые инструментам разработчика, и назначает их окну.__SINGLE_SPA_DEVTOOLS__. переменнаяposedMethods, для вызова devtools;
модуль утилит:Модуль utils реализует некоторые методы и функции в основном для совместимости с браузерами;
модуль жизненных циклов:Модуль жизненных циклов в основном абстрагирует жизненный цикл субприложения одиночного спа и субприложения участка и определяет следующие этапы:
- Для подприложений с одним спа: load->bootstrap->Mount->Unmount->Unload
- Для подкомпонентов посылок (приложений): bootstrap->Mount->Unmount->Update
Независимо от того, для пакетов или субприложений single-spa, по крайней мере, трехэтапные методы должны быть доступны внешнему миру, а именно этапные операции начальной загрузки, монтирования и размонтирования, чтобы single-spa вызывал различные процессы жизненного цикла при переключении между приложениями. Реализация фреймворка для этих трех этапов отличается, и single-spa не может сгладить эту разницу, и может быть реализован только через дополнительные библиотечные функции, такие как single-spa-angular или single-spa-vue.
навигационный модуль:При переключении маршрута одностраничного приложения обычно запускаются два разных события, а именно: hashchange и popstate, которые одновременно соответствуют хэш-маршрутизации и маршрутизации истории.Single-spa отслеживает эти события глобально в модуле навигации. переключатели маршрута приложения (соответствующие маршруту), сначала введите index.html, выполнит захват текущего маршрута single-spa, вызовет функцию активности, настроенную, когда подприложение зарегистрировано в соответствии с текущим маршрутом, определите, какое под- приложение принадлежит, а затем вызовите функцию загрузки для загрузки подприложений, поток подприложений в соответствии с предыдущим жизненным циклом, размонтируйте и размонтируйте старое приложение, соответствующее текущему маршруту, и вызовите начальную загрузку, чтобы запустить новое приложение и смонтировать новое приложение. В то же время singles-spa также предоставляет API для ручного запуска переключения приложений. Механизм такой же, как у пассивного обновления маршрута. Кроме того, этот модуль также предоставляет в качестве записи метод перенаправления, который при переключении маршрута выполняет вышеуказанные операции по очереди.
jquery-support.js: поскольку jquery использует прокси-серверы событий, многие прокси-серверы событий привязаны к окну.Если в jquery зарегистрированы события hashchange и popstate, требуется специальная обработка.
start.js: ввести всю логику перенаправления в навигацию и явно запустить single-spa.
single-spa.js:В качестве точки входа single-spa API-интерфейсы, предоставляемые вышеуказанными модулями, объединяются и экспортируются для внешних вызовов.
Таким образом, с вышеуказанной точки зрения, независимо от того, какая структура используется для написания приложения, пока оно подключено к single-spa, оно должно реализовать три метода жизненного цикла bootstrap, mount и Unmout для вызова single-sap, как показано ниже:
В модуле App2 загружаются методы Bootstrap, Mount и Messount
Согласно вышеуказанному анализу, единый спа примерно, а процесс выглядит следующим образом:
Среди них три шага 5, 7 и 8 должны быть реализованы с помощью библиотеки, похожей на single-spa-angular из-за различий во фреймворке. реализовано.
Анализ Single-SPA-Angular
single-spa-angular разделен на четыре части, структура каталогов src выглядит следующим образом:
Каждая из этих частей соответствует тому, что мы делали в предыдущем разделе с ng add single-spa-angular:
каталог веб-пакетовСодержимое :index.ts выглядит следующим образом:
import * as webpackMerge from 'webpack-merge';
import * as path from 'path'
export default (config, options) => {
const singleSpaConfig = {
output: {
library: 'app3',
libraryTarget: 'umd',
},
externals: {
'zone.js': 'Zone',
},
devServer: {
historyApiFallback: false,
contentBase: path.resolve(process.cwd(), 'src'),
headers: {
'Access-Control-Allow-Headers': '*',
},
},
module: {
rules: [
{
parser: {
system: false
}
}
]
}
}
// @ts-ignore
const mergedConfig: any = webpackMerge.smart(config, singleSpaConfig)
removePluginByName(mergedConfig.plugins, 'IndexHtmlWebpackPlugin');
removeMiniCssExtract(mergedConfig);
if (Array.isArray(mergedConfig.entry.styles)) {
// We want the global styles to be part of the "main" entry. The order of strings in this array
// matters -- only the last item in the array will have its exports become the exports for the entire
// webpack bundle
mergedConfig.entry.main = [...mergedConfig.entry.styles, ...mergedConfig.entry.main];
}
// Remove bundles
delete mergedConfig.entry.polyfills;
delete mergedConfig.entry.styles;
delete mergedConfig.optimization.runtimeChunk;
delete mergedConfig.optimization.splitChunks;
return mergedConfig;
}
function removePluginByName(plugins, name) {
const pluginIndex = plugins.findIndex(plugin => plugin.constructor.name === name);
if (pluginIndex > -1) {
plugins.splice(pluginIndex, 1);
}
}
function removeMiniCssExtract(config) {
removePluginByName(config.plugins, 'MiniCssExtractPlugin');
config.module.rules.forEach(rule => {
if (rule.use) {
const cssMiniExtractIndex = rule.use.findIndex(use => typeof use === 'string' && use.includes('mini-css-extract-plugin'));
if (cssMiniExtractIndex >= 0) {
rule.use[cssMiniExtractIndex] = {loader: 'style-loader'}
}
}
});
}
Мы представили эту конфигурацию через настраиваемый файл конфигурации webpack в предыдущем разделе и позволили angular-cli использовать эту конфигурацию для упаковки.Эта конфигурация упаковывает наш окончательный выходной пакет в формате umd и дает ему экспорт с именем app3 , extract зону и js, и расшарить их прямо в index.html, при этом, чтобы вебпак не перезаписывал системную глобальную переменную, выставляем system под парсером в false, а оставшаяся операция - удалить все записи включая глобальный css, сохраните только основную запись, чтобы гарантировать, что только один main.js будет упакован в угловое подприложение в конце.
каталог схемы:Если вы не знаете о схемах, вы можете временно подумать, что они могут расширить или переопределить команду добавления angular cli и выполнить некоторые пользовательские операции над командой добавления. Код ядра, выполненный в каталоге схем, не будет опубликован.На самом деле, в результате, когда вы вводите ng add single-spa-angular, он выполняет четыре действия:
1) Обновите package.json в корневом каталоге проекта и пропишите зависимости, относящиеся к single-spa-angular, такие как @angular-builders/custom-webpack, single-spa-angular и т. д.
2) Будут созданы четыре файла со встроенными шаблонами: main.single-spa.ts, single-spa-props.ts, assets-url.ts, extra-webpack.config.js;
3) Обновит angular.json для использования @angular-builders/custom-webpack:browser и @angular-builders/custom-webpack:dev-server builder
4) Обновите packages.json и добавьте две новые команды: build:single-spa и serve:single-spa, которые используются для сборки и запуска субприложения single-spa.
каталог застройщика:Что такое угловой построитель, здесь не представлено, вам нужно только понимать, что использование построителя может охватывать команды сборки и обслуживания расширенного углового cli.Операции двух команд build:single-spa и serve:single-spa в До angular8 это было реализовано с помощью билдера.После angular8 это было реализовано напрямую с помощью custom-webpack.Если вы используете angular8 и выше, эти коды не будут выполняться здесь.
каталог браузера-библиотеки:Основной код выглядит следующим образом
/* eslint-disable @typescript-eslint/no-use-before-define */
import { AppProps, LifeCycles } from 'single-spa'
const defaultOpts = {
// required opts
NgZone: null,
bootstrapFunction: null,
template: null,
// optional opts
Router: undefined,
domElementGetter: undefined, // only optional if you provide a domElementGetter as a custom prop
AnimationEngine: undefined,
updateFunction: () => Promise.resolve()
};
export default function singleSpaAngular(userOpts: SingleSpaAngularOpts): LifeCycles {
if (typeof userOpts !== "object") {
throw Error("single-spa-angular requires a configuration object");
}
const opts: SingleSpaAngularOpts = {
...defaultOpts,
...userOpts,
};
if (typeof opts.bootstrapFunction !== 'function') {
throw Error("single-spa-angular must be passed an opts.bootstrapFunction")
}
if (typeof opts.template !== "string") {
throw Error("single-spa-angular must be passed opts.template string");
}
if (!opts.NgZone) {
throw Error(`single-spa-angular must be passed the NgZone opt`);
}
return {
bootstrap: bootstrap.bind(null, opts),
mount: mount.bind(null, opts),
unmount: unmount.bind(null, opts),
update: opts.updateFunction
};
}
function bootstrap(opts, props) {
return Promise.resolve().then(() => {
// In order for multiple Angular apps to work concurrently on a page, they each need a unique identifier.
opts.zoneIdentifier = `single-spa-angular:${props.name || props.appName}`;
// This is a hack, since NgZone doesn't allow you to configure the property that identifies your zone.
// See https://github.com/PlaceMe-SAS/single-spa-angular-cli/issues/33,
// https://github.com/single-spa/single-spa-angular/issues/47,
// https://github.com/angular/angular/blob/a14dc2d7a4821a19f20a9547053a5734798f541e/packages/core/src/zone/ng_zone.ts#L144,
// and https://github.com/angular/angular/blob/a14dc2d7a4821a19f20a9547053a5734798f541e/packages/core/src/zone/ng_zone.ts#L257
opts.NgZone.isInAngularZone = function() {
// @ts-ignore
return window.Zone.current._properties[opts.zoneIdentifier] === true;
}
opts.routingEventListener = function() {
opts.bootstrappedNgZone.run(() => {
// See https://github.com/single-spa/single-spa-angular/issues/86
// Zone is unaware of the single-spa navigation change and so Angular change detection doesn't work
// unless we tell Zone that something happened
})
}
});
}
function mount(opts, props) {
return Promise
.resolve()
.then(() => {
const domElementGetter = chooseDomElementGetter(opts, props);
if (!domElementGetter) {
throw Error(`cannot mount angular application '${props.name || props.appName}' without a domElementGetter provided either as an opt or a prop`);
}
const containerEl = getContainerEl(domElementGetter);
containerEl.innerHTML = opts.template;
})
.then(() => {
const bootstrapPromise = opts.bootstrapFunction(props)
if (!(bootstrapPromise instanceof Promise)) {
throw Error(`single-spa-angular: the opts.bootstrapFunction must return a promise, but instead returned a '${typeof bootstrapPromise}' that is not a Promise`);
}
return bootstrapPromise.then(module => {
if (!module || typeof module.destroy !== 'function') {
throw Error(`single-spa-angular: the opts.bootstrapFunction returned a promise that did not resolve with a valid Angular module. Did you call platformBrowser().bootstrapModuleFactory() correctly?`)
}
opts.bootstrappedNgZone = module.injector.get(opts.NgZone)
opts.bootstrappedNgZone._inner._properties[opts.zoneIdentifier] = true;
window.addEventListener('single-spa:routing-event', opts.routingEventListener)
opts.bootstrappedModule = module;
return module;
});
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function unmount(opts, props) {
return Promise.resolve().then(() => {
if (opts.Router) {
// Workaround for https://github.com/angular/angular/issues/19079
const routerRef = opts.bootstrappedModule.injector.get(opts.Router);
routerRef.dispose();
}
window.removeEventListener('single-spa:routing-event', opts.routingEventListener)
opts.bootstrappedModule.destroy();
if (opts.AnimationEngine) {
const animationEngine = opts.bootstrappedModule.injector.get(opts.AnimationEngine);
animationEngine._transitionEngine.flush();
}
delete opts.bootstrappedModule;
});
}
Суть здесь заключается в реализации трех методов начальной загрузки, монтирования и отключения.Этап boostrap выполняется только после завершения загрузки подприложения, чтобы пометить угловое приложение с несколькими экземплярами и сообщить, что zonejs single-spa запускает переключение подприложения, и должен начать обнаружение изменений. На этапе монтирования метод angular platformBrowserDynamic().bootstrapModule(AppModule) вызывается для ручного запуска приложения angular, и экземпляр запущенного модуля сохраняется. На этапе unmout вызывается метод уничтожения запущенного экземпляра модуля, субприложение уничтожается, а для особых случаев выполняется некоторая обработка. Ключевым моментом здесь является крепление.
Суммировать:
В верхней части этой статьи мы описали происхождение микро-фронтендов и различные методы интеграции веб-приложений.Описав случай режима веб-интеграции DevUI, мы углубили наше понимание этой части контента, и использовали single-spa для реализации микро-интеграции.Проводятся интерфейсная модель и принципиальный анализ единого спа-центра.Во второй половине мы подробно обсудим процесс преобразования микро-интерфейса DevUI и опишем, как разработать микроинтерфейсное решение корпоративного уровня. Код https://github.com/myzhibie/microFE-single-spa.
Присоединяйтесь к нам
МыКоманда DevUIДобро пожаловать и присоединяйтесь к нам, чтобы создать элегантную и эффективную систему человеко-машинного проектирования/исследований и разработок. Электронная почта для набора: muyang2@huawei.com.
Текст/DevUI мыжибие
Рекомендуемые статьи в прошлом
Модульный механизм Quill, современного редактора форматированного текста