Реконструкция микроинтерфейсных приложений на основе qiankun

внешний фреймворк

предисловие

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

Система состоит из множества небольших модулей, и у большинства пользователей нет прав доступа ко всем модулям, поэтому наш первый метод оптимизации: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, путем преобразования исходной системы и разработки основного приложения в качестве контейнера, был достигнут эффект слияния нескольких приложений. реализовано, и пользовательский опыт при переключении между приложениями был значительно улучшен; в то же время он также учитывает вопросы совместимости, поддерживает независимый доступ подприложений, также совместим с исходными ссылками и автоматически перенаправляет на правильную ссылку

Микро-интерфейс — это не серебряная пуля, только когда вы действительно столкнетесь с бизнес-проблемами и вам нужно улучшить пользовательский опыт, подумайте о его внедрении. Однако на ранних стадиях разработки любого будущего приложения можно ожидать, что共享域名、微前端改造и т. д., чтобы гарантировать, что все запросы имеют уникальные подпути