Сериализация микроинтерфейса 5/7: планирование маршрутизации основного и подприложений микроинтерфейса

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

Конференция раннего чата по интерфейсу, новая отправная точка для развития интерфейса, была проведена совместно с Nuggets. Добавьте WeChat codingdreamer в эксклюзивную внутреннюю группу поддержки конференции и выиграйте на новой стартовой линии.


14-е | Сессия по развитию и продвижению интерфейса, 8-29 будет в прямом эфире, 9 лекторов (Ant Financial Services / Tax Friends и т. д.),Нажмите на меня, чтобы сесть в машину 👉 (Адрес регистрации):


Текст выглядит следующим образом

Эта статья — 40-я сессия фронтенда раннего чата, седьмого микрофронтенда, отКоманда открытой облачной платформы Alibaba — ФахайСовместное использование - Кратко организованная версия выступления (пожалуйста, смотрите записанное видео и PPT для полной версии, включая демонстрацию):


Как спроектировать планирование основной-подпрограммы в микроинтерфейсах

Даже в разделе ядра микроинтерфейса проблема маршрутизации не занимает много места... Но она включает в себя базовую логику, такую ​​как логика интеграции и песочница; с другой стороны, решить проблему маршрутизации в совершенстве непросто, и есть много Деталей, доступных для копания. Поэтому обсуждать этот вопрос нескучно, а в качестве особого ракурса можно и через него взглянуть на всю систему микрофронтенда.


Предыстория проблемы и желаемый эффект

Предыстория проблемы с доменом

Я полагаю, что читатели, перешедшие по этой статье, уже знакомы со сценариями проблем, которые необходимо решить с помощью микрофронтендов:

  • Разделяй и властвуй при разработке приложений Boulder.
  • В сети сосуществуют новые и старые проекты.
  • Трехсторонняя системная интеграция.
  • ...

Конкретные случаи описанных выше проблемных сценариев здесь не рассматриваются. Стоит отметить, что в соответствии с потребностями разных сценариев реализация микрофронтендов будет иметь разные детали, такие как:

  • В сценарии трехсторонней системной интеграции решение должно быть совместимо с разными технологическими стеками, для внутренних проектов Али вообще нужно рассматривать только кросс-версию React.
  • Приложения с несколькими вкладками, такие как почтовые ящики, должны иметь возможность изолировать вложенные приложения на каждой вкладке; другим приложениям с монопольными интерфейсами не нужно беспокоиться об этом, и они всегда поддерживают активный экземпляр вложенного приложения.
  • ...

- Проблема маршрутизации, которая будет обсуждаться здесь, может не иметь значения в определенных сценах, например, интеграция основного приложения является модулем детализации виджета.В настоящее время виджет не касается маршрутизации, поэтому только логика маршрутизации основного приложения себя. Однако в более общем случае само подприложение также является спа-центром, имеющим собственную логику маршрутизации. Таким образом, когда основное приложение интегрировано, обработка маршрута сталкивается с рядом новых проблем. Как решить эти проблемы, включить правильную основную логику маршрутизации? Это содержание, которое будет расширено позже.

Желаемый эффект мастер-подмаршрутизации

Так что же такое «правильная логика маршрутизации master-sub»? Независимо от конкретных методов реализации, мы надеемся, что основное приложение, наконец, достигнет эффекта, показанного на рисунке, после интеграции подприложений:0.目标效果.gifМы сделали вымышленное основное приложение под названием cow-and-chicken, которое будет интегрировать приложения для курицы и коровы и переключаться между вкладками. По умолчанию основное приложение будет указывать на приложение cow и будет следовать следующим эффектам маршрутизации:

  1. Доступ к основному приложению по определенному пути приведет к отправке соответствующего маршрута к подприложению. такие как доступcow-and-chicken.com/cow/detailВернитесь на страницу сведений о приложении для коров.
  2. После переключения маршрутов внутри подприложения синхронно также изменяются маршруты основного приложения. Например, после того, как приложение корова вернется на домашнюю страницу, в адресной строке браузера отобразитсяcow-and-chicken.com/cow/.
  3. Функции браузера вперед и назад работают нормально.

В то же время мы надеемся, что процесс интеграции не требует модификации субприложений, дополнительных соглашений и не нужно беспокоиться о том, что субприложения могут адаптироваться только к конкретным сценариям основного приложения, например эксклюзивным... Вкратце , чем более общее решение, тем лучше.

Положение проблемы планирования основной-подмаршрутизации в технологической системе микроинтерфейса

В этом разделе описывается положение проблемы планирования основной-под-маршрутизации во всей технологической системе микроинтерфейса.Если вас интересует только конкретное решение проблемы маршрутизации, вы можете пропустить этот раздел.

"

Решение проблемы маршрутизации не существует изолированно, оно связано с некоторыми другими элементами системы микроинтерфейса. Понимание логической взаимосвязи между этими элементами может помочь нам понять всю технологическую систему микроинтерфейса.

Панорама микроинтерфейсной системы

Это некоторая конструкция, выполненная командой Alibaba Cloud Open Platform, в которой я работаю над микроинтерфейсом.

Обратите внимание, что левый столбец — это возможности, связанные с платформой (настройка, управление и контроль), а верхний уровень — это некоторые пакеты решений на стороне консоли Alibaba Cloud… То есть большая часть работы строится в соответствии с бизнесом. . Для разных команд BU необходимо настроить соответствующие решения в соответствии с особенностями их собственного бизнеса. Часть среднего и нижнего уровней «микроинтерфейсного ядра» представляет собой более «чистую технологию» и может использоваться практически в любом бизнес-сценарии (на основе микроинтерфейса). Эта часть также проще для читателей, которые не иметь понимание бизнес-концепций Alibaba Cloud.

Части, связанные с ядром микроинтерфейса, расширяются

Выделенная часть рисунка выше — это технический момент, связанный с проблемой маршрутизации.

Здесь мы продолжаем расширять раздел «микроинтерфейсное ядро», который в основном включает в себя:

  1. Загрузка, программное обеспечение, связь и маршрутизация на стороне основного приложения.
  2. И среда выполнения JS Bundle и песочница на стороне суб (микро) приложения.

Другие «соответствующие части»:

  1. Чтобы правильно работать в контейнере подприложения и основной структуре приложения, подприложение необходимо изменить — модификация здесь обычно выполняется автоматически с помощью инженерных инструментов.
  2. Информация о подприложении, необходимая при инициализации основного приложения, предоставляется платформой управления конфигурацией.
  3. А также обычные строительные леса.

Выделенная часть на рисунке — это технический момент, связанный с маршрутизацией, о котором будет сказано позже.

Состояние страницы и интерфейсная маршрутизация

Давайте сначала подойдем к существовавшей ранее душевной пытке: зачем нужна фронтальная маршрутизация и какую проблему она решает? На самом деле технология маршрутизатора не создается за одну ночь, она постепенно превратилась в то, чем она является сегодня.

Controller

Более десяти лет назад классическая архитектура приложений MVC была в моде в бэкенде, и люди (включая меня), естественно, хотели практиковать ее и во внешнем интерфейсе. В то время различные роли, созданные в соответствии с MVC, были такими, как показано на рисунке выше. Среди них два уровня модели и представления относительно легко разобрать, и их соответствующие представления кода также очень ясны, но часть контроллера кажется неэлегантной, независимо от того, как она написана — необходимо написать большое количество связующего кода. в нем обеспечить нормальную работу страницы статуса.

Например, для упомянутого выше приложения «корова и курица» контроллер, вероятно, должен сделать следующее: связать нажатие кнопок коровы и курицы, определить соответствующую логику, а затем вызвать субконтроллер для дальнейшей обработки. Чтобы быть более элегантным, вы также можете определить конечный автомат, используяEventEmitterДля запуска потока событий. Форма кода:

// 维护当前的页面状态
var currentState = 'default';
 
// 类似于今天的路由表
var stateMap = {
  'default': () => {},
  'cow/home': () => {},
  'chicken/home': () => {}
};
 
// controller 是个 EventEmitter
controller.on('statechange', (e) => {
  stateMap[e.type](e.payload);
});
 
// 绑定页面的物理事件
document.on('click', (e) => {
  switch(e.target.className) {
    case 'cow-home-button':
      currentState = 'cow/home';
      break;
  }
  controller.emit('statechange', {
    type: currentState,
    payload: {}
  });
});

Router

Позже я познакомился с Backbone, классическим фреймворком MVC. Но одна вещь, которая меня поражает, это то, что у него нет контроллера, вместо него есть модуль маршрутизатора.

Вышеупомянутая реализация кода может быть элегантно заменена маршрутизатором:

  1. currentStateНикакого дополнительного обслуживания, потому что это всегда равноlocation.hash;
  2. stateMapв таблицу маршрутизации;
  3. События больше не нужно привязывать вручную, просто используйте естественную привязку.

Это просто гений по изобретению: наслаждайтесь прозрачной архитектурой приложения в стиле MVC, а также нужно только работать с таблицей маршрутизации. После этого бой я никогда не видел, чтобы кто-то вручную внедрил контроллер, так что к MVVM более позднюю эру, маршрутизатор или в качестве страницы госфраметрового устройства просмотруют место.

History API

Позже History API был представлен как стандартный инструмент для управления состоянием страницы. По сравнению с волшебным образом используемым хэшем, официальный History API может полностью покрыть возможности вышеупомянутого контроллера и является более мощным. Но обратите внимание, что History API строго не эквивалентен «интерфейсной маршрутизации», фактически он поддерживает изменение состояния страницы без изменения текущего URL-адреса. В настоящее время информация о состоянии страницы сохраняется только в памяти.

// 不影响 URL 变化的 pushState
history.pushState('new state', '', undefined);

С этой точки зрения front-end роутинг не является необходимой опцией, пока приложение может корректно поддерживать состояние страницы, ему не нужно явно выводить состояние страницы... Но в реальной разработке мы почти никогда не используем Изменения без URL.pushState. Это связано с тем, что мы обычно хотим показать состояние страницы пользователю, чтобы пользователь мог видеть (понимать текущее состояние) и вводить (влиять на текущее состояние). Так что, грубо говоря, интерфейсная маршрутизация необходима и важна как для разработчиков, так и для пользователей. Таким образом, давайте перейдем от простого метода планирования маршрутизации к более сложной ситуации и, наконец, добьемся максимально совершенного решения.

Постепенно внедряйте идеальную логику планирования мастер-подпрограмм.

Основное приложение вообще не обрабатывает маршрутизацию

Основное приложение не планирует подчиненные приложения в соответствии с маршрутом, а изменения состояния внутри вспомогательного приложения отражаются на основном маршруте приложения как есть.

1.主应用不作为.gif

1. Основное приложение не действует .gif

По умолчанию основное приложение действует как контейнер, запуская загрузку и рендеринг подприложений. На этом этапе определение загрузки основного приложения для подприложения может быть жестко закодировано в карте конфигурации и запускаться путем ручной привязки внешних событий (например, нажатия на вкладку). И что должна делать собственная логика маршрутизации подприложения, изменение URL-адреса напрямую возвращается в текущую адресную строку.

Суммируйте отношения между мастером и дочерним элементом: параметры передаются в одном направлении (параметры записываются до смерти), а изменения состояния последующих подприложений выполняются в соответствии с исходной логикой. В это время мы можем легко найти следующие проблемы:

  1. Если у разных подприложений один и тот же маршрут, они будут конфликтовать.
  2. Так как основное приложение не занимается роутингом, то при входе на страницу с роутингом оно не подействует (но войдет в начальное состояние).

Дочернее приложение и основное приложение используют общие маршруты

Основное приложение планирует работу подприложений в соответствии с маршрутом, а изменения состояния внутри подприложений отражаются на маршрутах основного приложения в соответствии с определенными правилами.

2.共享路由.gif

2. Общая маршрутизация.gif

Для типичной проблемы «преимущественного использования общих ресурсов» и «конфликта маршрутизации» в предыдущем решении решение очень простое — разделить общие ресурсы, избежать проблемы вытеснения ресурсов с помощью общих соглашений о префиксах и т. д. и изолировать различные подсистемы. , приложение. То есть добавьте определенные префиксы в соответствующие таблицы маршрутизации (и ссылки) (например, используя имя текущего подприложения):

// 使用子应用名称来对共享资源切片
const APP_NAME = 'cow';
 
<Route path={`/${APP_NAME}/home`} component={Home} />
<Route path={`/${APP_NAME}/detail`} component={Detail} />

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

Эффект щелчка назад на рисунке и щелчка/cow/homeОни почти одинаковые, поэтому не указаны. Короче говоря, (даже) удалось достичь желаемого конечного эффекта.

Суммируйте отношения между основным и дочерним: параметры передаются одним способом, параметры поступают из URL-адреса, главное приложение управляет крупномасштабной маршрутизацией, а изменения состояния последующих дочерних приложений слегка модифицируются и запускаются.

Хотя с эффектом рендеринга проблем нет, очень неэлегантно, что соглашение нужно вторгаться в логику субприложения.

Сноска: песочница JavaScript

Когда дело доходит до «вторжения», естественно думать об «изоляции». Далее мы должны представить механизм песочницы, вот краткое введение в изоляцию JS.

Проблему, которую должно решить ядро ​​изоляции, можно объяснить одним предложением: необходимо контролировать доступ различных подприложений к глобальным ресурсам. В контексте браузеров это означает две вещи:

  1. Побочные эффекты в глобальном контексте должны быть изолированы.
  2. В частности, для изоляции объектов DOM/BOM.

Основное решение для изоляции глобальных переменных состоит в том, чтобы свести глобальные переменные к локальным переменным.Традиционный подход заключается в создании замыкания для включения подприложения, а глобальная переменная подприложения становится замыканием.arguments.

// Node.js 中的「The Module Wrapper」
(function(exports, require, module, __filename, __dirname) {
  // 实际的 Module 代码
});

При создании песочницы передайте копию глобальной переменной (например,g_config). Если вам действительно нужно иметь побочные эффекты в глобальном масштабе, вам нужно установить белый список и передать ссылку.

Для глобальных переменных, таких как API браузера, невозможно полностью смоделировать копию, что нам делать? В этот момент мы думали, что iframes в браузере могут обеспечить полностью изолированноеwindow,documentОжидание контекста, в конце концов, создание iframe — это создание нового дерева DOM. Следовательно, мы можем создать iframe в том же домене, позволить ему нормально вернуть 200 ответов пустого HTML, а затем передать их подприложению в качестве входных параметров замыкания.

// 子应用被包上 wrapper
__CONSOLE_OS_GLOBAL_HOOK(id, function(exports, require, module, {
  window
}) {
  // 实际的子应用代码
});

// wrapper 的实现
const frame = document.createElement('iframe');
const _window = frame.contentWindow;

function __CONSOLE_OS_GLOBAL_HOOK(id, entry) {
  entry(exports, require, module, {
    window: _window
  });
}

Получив изолированный объект DOM/BOM, мы не используем его напрямую, а проксируем какие-то объекты для вставки нужной нам логики, либо делегируем какую-то логику основному приложению, либо просто отключаем какой-то метод.

class History {
  constructor(_history) {
    return new Proxy(_history, {
      get(target, name) {
        switch(name) {
          case 'pushState':
            // 在这里魔改掉            
            break;
        }
      }
    });
  }
}

Для получения дополнительной интерпретации нашей песочницы вы можете перейти к ">Внедрение в песочнице решения Alibaba Cloud Open Platform Micro Front-end".

Подприложения поддерживают изолированные маршруты

Основное приложение планирует подчиненные приложения в соответствии с маршрутом, и изменения состояния внутри вспомогательных приложений отражаются в соответствующих песочницах, не мешая друг другу, но они не будут отражаться в маршрутизации основного приложения.

3.子应用隔离路由.gif

3. Изоляция подприложений routing.gif

После использования песочницы работа подприложенияlocation / history / documentВсе становятся фреймами. В это время всем уже не нужно конкурировать за ресурсы основного приложения, и не нужно договариваться заранее, и можно без происшествий выполнять свою логику маршрутизации; и естественно маршруты подприложения и основного Приложение также изолировано в это время.

Щелкните ссылку в подприложении, поведение перехода по ссылке по умолчанию перехватывается фреймворком, измените наpushState, так как подприложение находится в песочницеhistory, так что в конечном итоге вы фактически изменяете URL-адрес iframe. Если используется хэш-схема, клик привязки блокируется и изменяется наpushState. Будет ли в это время нажатие на нее запускать основную часть приложения?popstateШерстяная ткань? Ответ — нет, потому что события кликов в DOM дочернего приложения в конечном итоге всплывут в песочнице.document, который не связан с основным приложением.

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

Маршруты поддержания синхронизируются обратно в основное приложение

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

4.主应用同步回路由.gif

4. Основной цикл синхронизации приложения routing.gif

Когда дело доходит до синхронизации информации между различными объектами, без механизма обмена сообщениями не обойтись. Идея естественна и проста:

  1. Субприложение дополнительно отслеживает изменение маршрута и отправляет адрес в виде тела сообщения в основное приложение после генерации нового маршрута.
  2. Основное приложение прослушивает сообщения от подприложений, получает новый маршрут, а затем меняет свой собственный маршрут.

Вот подробный вопрос: подприложение прослушивает изменения маршрутизации, а затем уведомляет основное приложение, не вторгается ли это снова в код подприложения? Помните упомянутую ранее песочницу? Мы проксиhistoryПост для грязной работы! Прокси будет слушать в слое песочницыhistoryРазнообразие,postMessageвыйди;

switch(name) {
  case 'pushState':
    return (...args) => {
      const returnValue = _pushState.call(_history, ...args);
      // 插入消息通信逻辑      
      frame.postMessage({
        type: 'statechange',
        data: {
          location: frame.location,
          state: _history.state
        }
      }, '*');
      return returnValue;
    };
}

Сторона основного приложения прослушивает это сообщение, а затем прописывает маршрутreplaceState.

frame.contentWindow.addEventListener('message', (e) => {
  const payload = e.data;
  switch(payload.type) {
    case 'statechange':
      const { state, title, location } = payload.data;
      const url = location.href;
      window.history.replaceState(state, title, url);
      break;
  }
});

Основное приложение изменяет свой собственный маршрут после получения сообщения, в результате чего «основное приложениеpopstate подприложениеpopstate' бесконечная петля? Ответ - нет. потому чтоpushState / replaceStateне сработаетpopstateсобытий, поэтому на этапе «синхронизации маршрутизации субприложения с основным приложением» основное приложение «отображает» только текущее состояние маршрутизации без какой-либо другой логики. Все выглядит нормально, последний вопрос, почему основная часть приложения используетreplaceStateвместоpushState?

Сноска: история совместной сессии

Когда iframe на странице перескакивает, адресная строка родительской страницы,historyКак это изменится? Явление таково: родительская страница может использовать кнопки «вперед» и «назад» (илиhistory.forward() / history.back()) управляет iframe; в свою очередь, изменение состояния самого iframe повлияет на родительскую страницу.history.

Это явление является реализацией стандарта, называемогоJoint Session History.

The joint session history of a > History object is the union of all the > session histories of all > browsing contexts of all the > fully active > Document objects that share the > History object's > top-level browsing context, with all the entries that are > current entries in their respective > session histories removed except for the > current entry of the joint session history.

Проще говоря, все страницы iframe будут иметь общийhistory.

Следовательно, когда меняется маршрут подприложения, это означает, что меняется адрес iframe песочницы, а также собственный адрес основного приложения.history.lengthТакже +1. Если вы не синхронизируете маршрут обратно к основному приложению в это время, URL-адрес основного приложения не изменится; если вы нажмете «Назад», URL-адрес основного приложения останется неизменным, но подприложение вернется в предыдущее состояние.

поведение основное приложение дополнительное приложение
Дочернее приложение переходит из /detail в /home URL не меняется страница становится дома
нажмите назад URL не меняется страница становится деталью

Если используется в основном приложенииpushStateСинхронная маршрутизация, тогда:

поведение основное приложение дополнительное приложение
Дочернее приложение переходит из /detail в /home URL становится /cow/home страница становится домашней
нажмите назад URL становится /cow/detail страница не меняется
нажмите назад URL не меняется страница становится деталью

использоватьreplaceState,но:

поведение основное приложение дополнительное приложение
Дочернее приложение переходит из /detail в /home URL становится /cow/home страница становится домашней
нажмите назад URL становится /cow/detail страница становится деталью

Итак, на стороне основного приложения используйтеreplaceStateчтобы перезаписать текущий URL, и он отлично работает!

Суммировать

Просмотрите эту статью:

  1. Во-первых, взгляд с высоты птичьего полета на положение проблемы маршрутизации во всей технологической системе микроинтерфейса.
  2. Понимание существования внешней маршрутизации и уточнение синхронизации маршрутизации действительно является проблемой.
  3. Начните с самого простого и естественного способа справиться с этим и найдите проблему, стоящую за решением.
  4. Предлагайте решения проблем, заменяйте их, исследуйте, не возникают ли новые проблемы.
  5. Если да, вернитесь к предыдущему шагу, в противном случае завершите решение.

Конечно, если при решении задачи используются другие методы, например, реализация песочницы не использует iframe, то дальнейшие решения в этой статье уже не будут актуальны. Однако, если цели и эффекты гарантированно непротиворечивы, в конкретной реализации может расцвести сотня цветов.

Цитировать