Про микрофронты на коленке. Iframe
Мне в работе попадалась реализация микрофронтов на основе iframe. В целом, это вариант, когда надо «дёшево и сердито». Обычно такое используется, когда хост-приложение не одно и в каждом внутри своеобразная сборка, то есть федерацией модулей или монорепой всех проблем не решить.
Как это работает: по определённому адресу хостится мини-приложение, например, по урлу example.com/header грузится общее приложение хедера для всех возможных хост-сред. Во все хост-приложения хедер встраивается через iframe:
<iframe src="/header"></iframe>Важный момент: чтобы не возникало проблем с CORS, при таком подходе приложения должны находиться на одном домене, а также полностью должны совпадать порт и протокол.
Редко когда такое мини-приложение живёт само по себе. Обычно нужно, чтобы оно как-то общалось со внешним миром. Для этого есть система двунаправленных событий, которые отправляются с помощью метода postMessage:
window.parent.postMessage — отправляет сообщение изнутри фрейма наружу, в родителя.
iframe.contentWindow.postMessage — отправляет сообщение снаружи, от родителя, внутрь фрейма.
Получаются отправленные события подпиской на message (как изнутри iframe, так и в родительском приложении снаружи):
parent.html:
<iframe id="myFrame" src="/child"></iframe>
<script>const iframe = document.getElementById('myFrame');
// Отправка сообщения во фреймiframe.addEventListener('load', () => { iframe.contentWindow.postMessage({ type: 'fromParent', payload: 'Привет!' }, '*');});
// Получение сообщения от фреймаwindow.addEventListener('message', (event) => { if (event.data.type === 'fromChild') { console.log('Получено от фрейма:', event.data.payload); }});</script>child.html:
<script>// Отправка сообщения родителюwindow.parent.postMessage({ type: 'fromChild', payload: 'Ответ!' }, '*');
// Получение сообщения от родителяwindow.addEventListener('message', (event) => { if (event.data.type === 'fromParent') { console.log('Получено от родителя:', event.data.payload); }});</script>Эту идею можно раскрутить и дальше. Например, если одни и те же данные с сервера нужны в нескольких микрофронтах, то это может привести к дублированию запросов. Один из выходов — перенести этот дублирующийся запрос в хост-приложение и шарить его с фреймами. Ещё один вариант — сделать отдельный iframe, в котором визуально ничего нет (его можно даже скрыть), но есть данные. Данные этот стор-iframe может шарить наружу всем желающим, тоже через postMessage.
Также помимо общения через postMessage шаринг данных между контекстами можно построить на Broadcast Channel API или Channel Messaging API.
Broadcast Channel обеспечивает широковещательную связь между всеми контекстами одного origin, а Channel Messaging создает секьюрный двусторонний канал связи между двумя конкретными контекстами.
То есть этот самый «стор с шаренными данными» при получении данных может в зависимости от цели пулять данные или на всех, или точечно в конкретное место. Работают оба способа тоже на похожих postMessage.
В случае с Broadcast Channel надо как-то пошарить между потребителями созданный объект самого канала:
// Создание каналаconst bc = new BroadcastChannel("test_channel");
// Отправка сообщения в каналbc.postMessage("This is a test message.");
// Приём сообщенияbc.onmessage = (event) => { console.log(event);};В случае с Channel Messaging параметры для связи двух контекстов передаются в доп параметрах postMessage:
main-page:
const channel = new MessageChannel();const port1 = channel.port1;
// Подписка на загрузку iframeiframe.addEventListener("load", onLoad);
function onLoad() { // Слушание входящих сообщений на port1 port1.onmessage = onMessage;
// Отправка исходящего сообщения на port1 port1.postMessage(input.value);
// Отправка port2 в iframe iframe.contentWindow.postMessage("init", "*", [channel.port2]);}
// Обработка сообщений на port1function onMessage(e) { console.log(e.data);}child-page:
let port2;
// Слушание события инициализацииwindow.addEventListener("message", initPort);
// Настройка портаfunction initPort(e) { port2 = e.ports[0]; port2.onmessage = onMessage;}
// Обработка сообщений на port2function onMessage(e) { port2.postMessage(`Message received by IFrame: "${e.data}"`);}Отдельно отмечу наличие атрибута loading=lazy у iframe. Это чтобы не подгружать те iframe, которые находятся вне вьюпорта, для экономии ресурсов.
Также дополнительно подсвечу, что в хромиум-браузерах все iframe на одном домене будут обрабатываться в одном потоке, то есть могут потенциально блокировать друг друга, даже если не блокируют родителя (лайфхак: если нет нужды держать их на одном домене, то выносом на другой домен можно «распараллелить потоки»).