предисловие
С развитием нашего бизнеса наша система становилась все больше и больше, что ставит большие задачи в отношении скорости построения, размера статических ресурсов и производительности приложений.
Система состоит из множества небольших модулей, и у большинства пользователей нет прав доступа ко всем модулям, поэтому наш первый метод оптимизации:code split
, код каждого маленького модуля делится и загружается по запросу, также были достигнуты определенные результаты
Однако, когда количество систем увеличивается, пользователи начинают жаловаться на то, что порталов слишком много, и надеются, что все функции сможет выполнить единый портал.Для этого сценария есть несколько решений.
-
Объединение всех систем в одну большую систему
- преимущество
- Пользовательский интерфейс может быть лучшим, а одностраничное приложение имеет высокую скорость работы.
- дефект
- Легко стать монолитным приложением, и при разработке и сборке возникнут проблемы с производительностью.
- Модификация любого небольшого модуля может сделать всю большую систему непригодной для использования.
- Среда разработки ограничена, и ее сложно обновить в будущем.
- преимущество
-
Быть фреймворком приложения и использовать IFrame для встраивания целевой системы
- преимущество
- Стоимость преобразования низкая, необходимо разработать только структуру приложения.
- Может поддерживать одновременное открытие нескольких систем и переключение между вкладками
- При переключении систем состояние страницы может сохраняться естественным образом, что позволяет пользователям продолжать предыдущий рабочий путь.
- Каждое приложение развертывается независимо, не мешая друг другу
- дефект
- Изменения маршрутизации в IFrame не могут быть отражены в URL-адресе платформы приложения. Пользователь вернется на исходную страницу, как только пользователь обновится, что влияет на работу. Необходимо разработать механизм связи самостоятельно, чтобы разрешить приложению framework для сохранения маршрутизации системы в IFrame.
- IFrames загружаются медленно
- Если в интерфейсе больше IFrame, структура dom усложняется, что влияет на производительность системы.
- преимущество
-
Разработайте единую панель навигации, замените панель навигации каждой системы и передайте панель навигации в панель навигации.
<a>
Этикетки реализуют переключение системы- преимущество
- Стоимость трансформации относительно невелика, и необходимо разработать навигационную панель, которую можно быстро интегрировать в разные системы, если требуется унифицированное доменное имя, необходимо трансформировать каждую систему, а все запросы должны нести уникальные подпути
- В основном не влияет на опыт пользователя при использовании одной системы
- дефект
- Переключение между системами, по сути, открывает новую систему, и производительность загрузки будет влиять на взаимодействие с пользователем.
看上去
Как и при использовании системы, если пользователь переключается чаще, ощущение будет сильнее. - Связь между системами может основываться только на
localStorage/sessionStorage
Подождите, пока браузер сохранит - Он не поддерживает одновременное открытие нескольких систем, и состояние страницы не может быть восстановлено естественным образом.
- Переключение между системами, по сути, открывает новую систему, и производительность загрузки будет влиять на взаимодействие с пользователем.
- преимущество
-
Преобразуйте приложение с помощью микроинтерфейсной архитектуры
- преимущество
- Реально можно использовать все функции в одной записи
- Опыт переключения между различными приложениями лучше, за исключением того, что первое переключение занимает определенное время для парсинга js, а последующее переключение происходит относительно плавно.
- Основное приложение может предоставлять общие функции для использования подприложениями.
- Разные приложения могут разрабатываться разными командами с использованием разных технологических стеков.
- дефект
- Есть определенный объем ремонтных работ
- Основное приложение включает в себя все впускные отверстия, фактически увеличивая давление в системе.
- преимущество
Давайте рассмотрим текущую ситуацию
-
Все системы разрабатываются на основе внутреннего унифицированного фреймворка, с унифицированной верхней и боковой панелью.
-
Все системы имеют собственный уровень Nodejs для рендеринга страниц и переадресации запросов API.
-
Все системы имеют разные доменные имена, нет конкретных подпутей доменных имен.
-
Разные системы разрабатываются собственными небольшими командами, некоторые из которых используют разные версии React и Ant Design.
-
Разработка обычно требует, чтобы при разработке новых функциональных модулей в будущем можно было использовать технологии, идущие в ногу со временем.
Основываясь на анализе статус-кво, микро-фронтенд — это направление, которое можно попробовать, поэтому путь наступания на яму начал превращать существующую систему в под-приложение микро-фронтенда.
В целях унификации языка существующая система в дальнейшем именуется подприложением
Наступая на дорогу
Выбор
Мы используемqiankunПоставляется как библиотека реализации для микро-интерфейсов, которая (предположительно) может быть быстро преобразована
Модернизация приложения
добавить подпуть
Цянькунь основан наsingle-spaИнкапсулированный механизм загрузки под приложением, реализованный внутренне, реализуется на основе URL-адреса браузера, и какое поддержание для загрузки определяется первым подпунктом, таким как
- $ {Ваше доменное имя} / appa / ...: указывает на загрузку приложения
- ${ваше доменное имя}/appB/...: указывает на загрузку приложения b
Следовательно, преобразование каждого подприложения таким образом, чтобы все обращения добавляли подпути, — это первый шаг, который мы должны сделать.
-
Добавьте префикс к каждому маршруту, пример кода koa выглядит следующим образом
// 直接访问根路径,转发增加路由前缀 router.get('/', controller.redirect); // 渲染页面,这里的 authMiddleware 是校验中间件,实现登陆校验逻辑 router.get('/appA/*', authMiddleware, controller.index); // 这里使用 ${子应用名} + '_apis' 来表示特定应用的 api 请求, // 方便在主应用中做区分进行转发,同时也方便 Nginx 配置转发(共享域名) router.use('/appA_apis/*', authMiddleware, controller.transfer); // 剩下的路由忽略 ...
-
Измените каждый запрос API на странице, чтобы он соответствовал
${子应用名} + '_apis'
Этот шаг относительно трудоемок, а существующие подприложения прописаны в коде страницы.
/apis
Префикс , если он не обрабатывается в едином месте, изменить будет очень проблематично. Исходя из сложившейся ситуации, мы использовали хитрый способ:Перехватывать все Ajax-запросы и изменять их префиксы по мере необходимости.. Конкретный код выглядит следующим образом(() => { if (!XMLHttpRequest.prototype['nativeOpen']) { XMLHttpRequest.prototype['nativeOpen'] = XMLHttpRequest.prototype.open; const customizeOpen = function(method, url, ...params) { if ( // 不需要修改前缀的请求,如果情况比较多,可以单独抽取出来 url.indexOf('hot-update.json') < 0 ) { // 将 /apis 前缀转化为 /appA_apis 前缀,这里是在框架里 // 处理成 routerPrefix 注入到 window 对象的 url = `${window['routerPrefix']}_${url.slice(1)}`; } this.nativeOpen(method, url, ...params); }; XMLHttpRequest.prototype.open = customizeOpen; } })();
-
Измените путь к статическим файлам и измените исходный путь /statics, чтобы он соответствовал
${子应用名} + '_statics'
Этот шаг примерно соответствует настройке веб-пакета, в основном для изменения конфигурации, связанной с выводом и publicPath. Вы можете управлять им в соответствии с реальным проектом. Я не буду повторяться здесь.
После вышеперечисленных шагов подприложение уже может поддерживать доступ к подпути, но здесь отсутствует еще один важный шаг, который не повлияет на вашу трансформацию, но повлияет на нормальный доступ пользователей после вашей трансформации. Например, пользователь сохраняет адрес страницы своей системы в избранное, напримерxxx.site.com/pages/user
Если вы развертываете в это время, он приведет к появлению доступа пользователя 404, поэтому вам также необходимо совместимо в файле маршрутизации
// 直接通过 URL 访问旧路由时,重定向到新的匹配路由,redirectToNewPrefix 的实现很简单,取出 ctx.url 并且替换掉原先的路由前缀即可
router.get('/pages/*', controller.redirectToNewPrefix);
На этом мы закончили.为子应用增加路由前缀
работай.
Добавить основное приложение
Ссылаясь на официальный сайт, для сборки простейшего основного приложения вам потребуется только узел для монтирования подприложений.
<div id="subViewport"></div>
тогда позвониregisterMicroApps
Способ сразу зарегистрировать приложение
registerMicroApps(
[
{
name: 'appA',
entry: appAEntryMap[process.env.NODE_ENV], // 根据运行环境,加载应用对应的入口,如 'http://localhost:3000/appA'
container: '#subViewport',
activeRule: '/appA'
},
{
name: 'appB', // app name registered
entry: appBEntryMap[process.env.NODE_ENV],
container: '#subViewport',
activeRule: '/appB'
}
]
);
setDefaultMountApp('/appA'); // 设置默认加载的应用,当路由匹配不到时会触发
start();
может появиться здесь#subViewport
Смонтированное подприложение не заполняет контейнер Проверьте официальную проблему и дайте решение для управления им через CSS, чтобы дочерний div, отображаемый под узлом, заполнил контейнер (в div будет введен хэш, поэтому его нельзя на основе идентификатора или класса для обработки)
#subViewport {
width: 100%;
height: 100%;
> div {
width: 100%;
height: 100%;
}
}
Подприложения предоставляют функции жизненного цикла, упакованные в формате UMD.
Вы можете обратиться к официальной документации для этого шага
Кроме того, если вы хотите, чтобы суб-приложения были доступны отдельно, вы можете добавить код в запись js
// 不是在 qiankun 框架中装载的时候,直接渲染
if (!window['__POWERED_BY_QIANKUN__']) {
bootstrap().then(mount);
}
междоменная проблема
Запускаем основное приложение, заходим на страницу, находим пробел, проверяем консоль, есть междоменная проблема
Access to fetch at 'http://localhost:3000/appA' from origin 'http://localhost:4001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
qiankun использует fetch для получения html-файла подприложения, поэтому возникает междоменная проблема. Он также относительно прост в обращении, так как он рендерится с помощью Nodejs, необходимо только добавитьkoa2-cors
Промежуточное программное обеспечение для решения проблемы
Обратите внимание, что если вы находитесь в режиме разработки, вам необходимоwebpack-dev-server
Междоменные также поддерживаются, см.эта статья
Проблемы с переадресацией запросов и проверкой API
Наконец, мы подошли к очень важной части, обработке запросов API, это также та часть, о которой не упоминается на официальном сайте и в демоверсии, но она является наиболее важной, от нее зависит, будет ли ваша трансформация микро-фронтенда успешной или нет.
Если основное приложение, подприложение и внутренний API имеют одно и то же доменное имя, эту проблему не нужно решать естественным путем.
Следующие решения основаны на основной предпосылке: основное приложение и подприложения имеют свою собственную сторону Node для обработки рендеринга страниц, проверки входа и переадресации API.
Прежде всего, должно быть ясно, что когда подприложение qiankun отправляет запрос API на стороне браузера, оно фактически запрашивает сторону узла основного приложения, а URL-адрес/appA_apis/xxx
/appB_apis/xxx
В этом формате на стороне Node основного приложения нет логики для обработки этих маршрутов, поэтому необходимо добавить логику пересылки для пересылки этих запросов на сторону Node подприложения.
Сначала добавьте конфигурацию подприложения в файл конфигурации основного приложения.
subApps: [
{
name: 'appA',
prefix: '/appA_apis',
// 子应用的 host,例如 http://localhost:3000
host: process.env['subApps.appA.host']
},
{
name: 'appB',
prefix: '/appB_apis',
host: process.env['subApps.appB.host']
}
]
Затем в настройках маршрутизации основного приложения добавьте переадресацию
subApps.forEach(subApp => {
router.all(`${subApp.prefix}/*`, (ctx, next) => {
// 转发请求到 `${subApp.host}/${ctx.url}`,注意参数要透传,content-type 也要保持一致,此处实现方式多种,不在此赘述
...
})
})
После переадресации будет обнаружено, что запрос API не может пройти проверку на стороне Node субприложения.Давайте сначала рассмотрим процесс проверки запроса API.
- Получить cookie из запроса
x-auth-token
(Этот ключ указан нашим проектом, не фиксирован) - С помощью этого токена определите, существует ли соответствующий ему действительный сеанс, и если да, извлеките информацию о пользователе.
- Сгенерируйте jwt из информации о пользователе, прозрачно передайте другие параметры и перенаправьте на реальный серверный API.
Нетрудно заметить, что основное приложение сгенерировано после входа в систему.x-auth-token
Сторона Node дочернего приложения не может признать его действительным.session id
Здесь есть два подхода
-
Основное приложение и все подприложения используют одно и то же хранилище сеансов.В нашем проекте используется Redis, поэтому все приложения используют один и тот же Redis.
-
Преимущества: простой и грубый, меньшая рабочая нагрузка
-
Дефекты: совместное хранилище может вызвать некоторые конфликты, а разработка подприложения может по ошибке перезаписать ключевые данные других подприложений; каждое подприложение не может иметь специальную информацию о пользователе (например, в информации о пользователе subA есть a Специальные поля, которых нет ни в основном приложении, ни в других подприложениях)
-
-
Подприложение предоставляет специальный SSO-интерфейс.После того, как основное приложение входит в систему, оно вызывает SSO-интерфейс всех подприложений и передает это
x-auth-token
и зашифрованная учетная запись пользователя, позволяющая каждому подприложению создавать свою собственную сессию-
Преимущества: разделение хранилища; каждое подприложение может хранить специальную информацию о пользователе по мере необходимости.
-
Дефект: Необходимо разработать новый интерфейс, при большом количестве подприложений время отклика действия входа в систему становится больше (необходимо убедиться, что SSO-интерфейс каждого подприложения работает успешно)
-
Исходя из существующего положения, мы выбрали второе решение, в котором используется симметричное шифрование RC4 для учетных записей пользователей, каждое подприложение поддерживает отдельную соль, основное приложение поддерживает все соли, а конфигурация подприложения становится
subApps: [
{
name: 'appA',
prefix: '/appA_apis',
salt: 'appA',
// 子应用的 host,例如 http://localhost:3000
host: process.env['subApps.appA.host']
}
]
Затем после завершения основного входа в действие интерфейс SSO, предоставленный поддержанием.
for (const subApp of subApps) {
// 阻塞调用接口,确保每个请求都正确
await ...
}
После вышеуказанных шагов наша проблема с запросом страницы в основном решена.
Переключение между дополнительными приложениями
Наконец, есть переключение между подприложениями. В начале я использовал тег Link React Router и обнаружил, что невозможно переключиться с одного подприложения на другое, потому что у каждого подприложения свой маршрут, а история каждого маршрута называетсяcreateBrowserHistory()
метод создан
Снова проверьте документацию qiankun и найдите предложение.
После регистрации информации о микроприложении, как только URL-адрес браузера изменится, будет автоматически запущена логика сопоставления qiankun, и все микроприложения, соответствующие правилу activeRule, будут вставлены в указанный контейнер, а микро- приложения будут вызываться по очереди для раскрытия ловушек жизненного цикла.
Ключ должен вызвать изменение URL-адреса браузера. используется здесьwindow.history.pushState
метод достижения
history.pushState(null, linkPath, linkPath);
После завершения переключения субприложений обнаружилось еще одно явление: когда субприложение A переключается на определенный маршрут, переключается на субприложение B и работает, затем снова переключается на субприложение A, url не тот суб -приложение A было только что удалено. Однако при перезагрузке подприложения A оно вернется на предыдущую страницу. Это удобно для пользователя, но URL-адрес не соответствует фактическому представленному интерфейсу.
Решение состоит в том, чтобы перейти к предыдущему маршруту при переключении на подприложение, поэтому текущий маршрут необходимо сохранить. Поскольку это может повлиять только на открытый в данный момент интерфейс, сохраните это значение вsessionStorage
середина
Во-первых, перед переключением подприложений запишите текущий маршрут
sessionStorage.setItem('appA-currentRoute', window.location.href);
Затем, после загрузки подприложения, получите текущий маршрут и прыгните, а затем удалите записанный маршрут.
const currentRoute = sessionStorage.getItem('appA-currentRoute');
if (currentRoute) {
history.pushState(null, currentRoute, currentRoute);
sessionStorage.setItem('appA-currentRoute', '');
}
С помощью приведенной выше схемы реализовано обслуживание состояния приложения и сопоставление URL-адресов переключения подприложений.
Суммировать
На данный момент мы завершили первоначальную практику микро-интерфейса.На основе микро-интерфейса фреймворка qiankun, путем преобразования исходной системы и разработки основного приложения в качестве контейнера, был достигнут эффект слияния нескольких приложений. реализовано, и пользовательский опыт при переключении между приложениями был значительно улучшен; в то же время он также учитывает вопросы совместимости, поддерживает независимый доступ подприложений, также совместим с исходными ссылками и автоматически перенаправляет на правильную ссылку
Микро-интерфейс — это не серебряная пуля, только когда вы действительно столкнетесь с бизнес-проблемами и вам нужно улучшить пользовательский опыт, подумайте о его внедрении. Однако на ранних стадиях разработки любого будущего приложения можно ожидать, что共享域名、微前端改造
и т. д., чтобы гарантировать, что все запросы имеют уникальные подпути