Автор впервые столкнулся с микро-интерфейсом в июле 2020 года и услышал о нем от коллеги. Хотя это не ранний контакт, он продвигает и следит за развитием и реализацией крупномасштабного внутреннего проекта. Я также надеюсь поделиться с вами некоторыми ямами, через которые я прошел, и некоторыми мыслями. Все приложения, упомянутые в этой статье, являются веб-приложениями для ПК. В этой статье не обсуждается мобильная М-страница.
В то время информации в интернете было не много, и результатов в поиске Baidu по микрофронтендам было очень мало, большую часть страниц занимали Qiankun и Feibing, а еще был внутренний технический салон Meituan. Кто бы мог подумать, что уже через полгода термин микро-фронтенд будет становиться все популярнее, а различные решения и технологии будут реализовываться, как побеги бамбука после весеннего дождя, и ситуация раздора между сотней школ мысль постепенно формировалась.
Что такое микрофронтенд
В Интернете есть бесчисленное множество статей, посвященных микрофронтендам, большинство из которых цитируются изMicro FrontendsЭта статья. Эта статья подробно знакомит с идеей микроинтерфейса. Также продвигается решение для реализации микро-интерфейса (веб-компоненты).С течением времени и авторской практикой микро-интерфейса у меня также появилось некоторое собственное понимание концепции микро-интерфейса. надеюсь, это поможет вам лучше понять интерфейс Micro.
Что такое микро-интерфейс? Разделяй и властвуй и сиди на одной сцене. Микро, рассеянный также. В деталях, микроинтерфейс представляет собой архитектуру, которая делит все приложение Boulder на несколько небольших приложений (подприложений), которые можно независимо разрабатывать, развертывать, запускать и запускать, а консольное приложение (родительское приложение) выставляет наружу. Унифицированное управление рабочим статусом каждого подприложения, несколько подприложений переключаются туда и обратно без восприятия пользователем.
Термин «микроинтерфейс» впервые появился в «Технологическом радаре» ThoughtWorks за 2016 г.Technology Radar)середина. Однако автор считает, что идея микро-фронтенда ранее часто применялась к приложениям Boulder. Оглядываясь назад, в эпоху, когда передняя и задняя часть не были разделены, когда JSP все еще использовался для смешивания Java и HTML, страница представляла собой html-документ с файлом index.html в качестве записи через тег привязки (<a/>) для соединения страниц. Страницы используют функции в качестве индикаторов и относятся к разным проектам. Проекты не влияют друг на друга и могут распространяться и запускаться независимо друг от друга. Я думаю, что это самый ранний микро-фронтенд. С появлением SPA-приложений эта ранняя идея микроинтерфейса постепенно была похоронена.
Зачем использовать микрофронтенды
Теперь приложения SPA уже не могут соответствовать логической сложности и функциональному разнообразию современных веб-приложений. Представьте себе входной файл размером в несколько мегабайт, запрашивающий бесчисленное количество изображений и css-файлов при первом открытии.Сетевое время и время рендеринга браузера в этот период уже давно намного ниже ожиданий пользователя.
Хоть и есть бесчисленное множество решений типа ленивой загрузки, спрайтов, сжатия файлов, CDN и т.д., но суть все же принадлежит одному и тому же монолитному приложению, что также приводит к любым изменениям в приложении.Перепаковывать нужно весь проект, даже если happypack включен Многопоточная упаковка, ее инженерная эффективность не льстит. Полагаю, что читатели, имеющие опыт сопровождения масштабных проектов, тоже могут испытать бессильное чувство, что сердце летит.
С другой стороны, с взрывным ростом стека передовых технологий в этом году уже не та эра, когда вы можете держать jQuery и путешествовать по всему миру без страха. Vue достиг версии 3.0, а версия react достигла ужасающего числа 17. Хотя angular мало используется в Китае, он также является одним из трех основных фреймворков, и его версия также ранняя до 11. Даже в случае обратной совместимости различных версий, сколько людей осмеливается гарантировать, что базовый фреймворк проекта не будет заброшен временем. Длительная стабильная работа. Некоторые из этих людей прошли через обновление перезаписи angular2 и рефакторинг хуков реакции (хотя реакция не рекомендует этого).
Так что в долгосрочной перспективе разделите монолитное приложение на несколько небольших приложений. Очень реально добиться стабильного обновления и итерации проекта. Течение основного принципа выглядит следующим образом:
Болевые точки современных приложений:
- Компонентов и функциональных модулей в проекте будет все больше и больше, что приведет к более медленной упаковке всего проекта;
- Поскольку количество папок будет увеличиваться с увеличением функциональных модулей, поиск кода будет становиться все медленнее и медленнее;
- Если изменяется только один из модулей, необходимо переупаковать и выложить в сеть весь проект;
- Если уровень каталога и уровень модуля слишком глубоки и файлов много, поиск файлов будет происходить все медленнее и медленнее;
- Все проекты могут использовать только одну и ту же техническую структуру, такую как: react, vue и т. д.;
Преимущества микро-интерфейса:
- Стек технологий не имеет значения: основной фреймворк не ограничивает стек технологий приложения доступа, а микроприложение имеет полную автономию;
- Независимая разработка и независимое развертывание: git-репозиторий микроприложения является независимым, и страница, открытая родительским приложением, обновляется синхронно после завершения развертывания микроприложения;
- Инкрементное обновление: перед лицом различных сложных сценариев нам обычно сложно выполнить полное обновление стека технологий или рефакторинг существующей системы, а микро-интерфейс позволяет нам сделать хороший инкрементный рефакторинг;
- Независимая работа: каждый проект может выполняться как полноценный отдельный проект, это может быть просто функциональный модуль в большом фоновом проекте, и тогда все микро-приложения, объединенные вместе, представляют собой законченную функцию, которую хочет PM;
Техническое решение микроинтерфейса
Маршрутно-распределенные микрофронтенды
Как следует из названия, разные сервисы распределяются по разным приложениям посредством маршрутизации (например, конфигурации nginx).Это также простой метод «микроинтерфейса», но на самом деле этот метод больше похож на агрегирование нескольких приложений. -end приложения, чтобы они отображались как единое целое.
iFrame
iframe — это html-тег, предоставляемый браузером, элемент iframe создает встроенный фрейм (inline frame), который содержит другую страницу. Этот тег может эффективно завершить «микро-интерфейс», но если вы его используете, вам нужно учитывать две вещи:
- Проблема с загрузкой приложения: когда родительское приложение загружает и выгружает микроприложение, и какой эффект или анимация используется при переключении страниц, чтобы вся страница выглядела более естественно и приемлемо;
- Проблема связи приложения: это более упрощенный способ получения объекта окна элемента iFrame через HTMLIFrameElement.contentWindow, но необходимо определить набор спецификаций связи: в какой форме и как назвать ключ события, когда начинать прослушивание время и т.д. И другие вопросы;
Web Components
Веб-компоненты — это другой набор технологий, которые позволяют вам создавать повторно используемые пользовательские элементы (их функциональность инкапсулирована вне вашего кода) и использовать их в своих веб-приложениях. Он состоит из трех основных технологий, которые можно использовать вместе для создания пользовательских элементов, инкапсулирующих функциональность, и которые можно повторно использовать где угодно, не беспокоясь о конфликтах кода.
- Пользовательские элементы: набор API-интерфейсов JavaScript, которые позволяют вам определять пользовательские элементы и их поведение, которые затем можно использовать по мере необходимости в вашем пользовательском интерфейсе.
- Shadow DOM: набор API-интерфейсов JavaScript для прикрепления инкапсулированного «теневого» дерева DOM к элементу (отображаемому отдельно от DOM основного документа) и управления связанными с ним функциями. Таким образом, вы можете сохранить функциональность своих элементов в секрете, чтобы их можно было запрограммировать и стилизовать, не беспокоясь о конфликте с остальной частью документа.
- HTML-шаблоны:
<template>а также<slot>Элементы позволяют писать шаблоны разметки, которые не отображаются на отображаемой странице. Затем их можно повторно использовать несколько раз в качестве основы для пользовательских структур элементов.
Выбор фреймворка микро-фронтенда
Mooa
Mooa — это микроинтерфейсный фреймворк, обслуживающий Angular. Это решение для микроинтерфейса на основе единого спа-центра, но его проект обслуживает Angular, поэтому он может напрямую пройти этот фреймворк. Заинтересованные студенты могут изучить его самостоятельно.
летающий лед
FeiBing — это микроинтерфейсное решение для крупномасштабных систем.Он может осуществлять независимую разработку и выпуск каждого микроприложения на основе обеспечения опыта работы системы.Регистрация и рендеринг микроприложений управляются через icestark, и вся система полностью понятна. (это один из наших кандидатов)
Адрес официального сайта Feibing:ice.work/docs/ice это он…
qiankun
qiankun — это библиотека для реализации микроинтерфейса с одним спа.
- Простота: поскольку основное приложение и микроприложения могут быть независимыми от технологического стека, qiankun — это просто jQuery-подобная библиотека для пользователей.Вам нужно вызвать несколько API-интерфейсов qiankun, чтобы завершить преобразование микроинтерфейса приложения. В то же время, благодаря дизайну HTML-записи и песочницы qiankun, доступ к микроприложениям так же прост, как с помощью iframe;
- Нерелевантность разделения/стека технологий: основная цель микроинтерфейса состоит в том, чтобы разобрать монолитное приложение на несколько слабо связанных микроприложений, которые могут быть автономными, и многие проекты qiankun придерживаются этого принципа, такие как запись HTML, песочница и интерактивная среда. -приложение связи Подождите. Только таким образом микроприложения могут действительно развиваться и работать независимо;
Потому что его введение и концепция очень выдающиеся, простые, поэтому он стал нашим главным героем, но ему все еще нужно пройти через некоторые сравнения.
Адрес официального сайта qiankun:qiankun.umijs.org/zh
Сравнение qiankun и Feibing
Ниже приводится их приблизительное сравнение по состоянию на 03 декабря 2020 г.
летающий лед
- git: количество звезд 943;
- изоляция js: песочница;
- Изоляция стилей: используйте схему модулей CSS для управления стилями (обычно старайтесь использовать схему Shadow Dom)
- Связано с упаковкой: обязательная зависимость от ice.js, и приложения могут использовать только реакцию, и упаковка также должна быть упакована с использованием фреймворка ice.js;
- Цикл обновления: 1 неделя ~ месяц;
qiankun
- git: количество звездочек 7,8к;
- изоляция js: песочница;
- Изоляция стиля: Shadow Dom;
- Относительно упаковки: зависит от qiankun, официальная рекомендация — использовать упаковку посылок, но можно использовать и веб-пакет;
- Цикл обновления: 1 день ~ одна неделя;
После вышеупомянутой сортировки и проверки мы решили использовать qiankun для внутреннего использования. На это есть много причин. Поскольку в нашем проекте есть требования к упаковке результатов, использование парцеллы не может удовлетворить наши требования, поэтому мы используем веб-пакет для упаковки желаемых результатов с изоляцией стиля. По сравнению с летучим льдом он более зрелый, имеет большое количество звезд и так далее.
Введение в фактическое использование qiankun и запись проблем
После того, как фреймворк выбран, мы начинаем строить проект и строить два разных родительских приложения (также называемых базовыми приложениями), одно из которых — Vue of react. После этого я попробовал микро-приложение, представленное в демоверсии, а затем планировал унифицировать код.В конце концов, я решил, что все фоновые проекты были написаны с помощью реакции, поэтому я окончательно переключился на проект реакции.
Во время исследования я получил новость, что наши результаты онлайн-упаковки ограничены.Он должен быть в форме xxx, прежде чем он сможет выйти в онлайн, а затем есть много конфигураций.Из-за сжатого времени мы можем только заставить чтобы начать использовать встроенный zz-react-cli компании, который используется для создания реактивного проекта, поэтому в конце родительское приложение и микроприложение генерируются с использованием zz-react-cli. (Это наша внутренняя проблема, поэтому я не буду ее больше представлять), давайте начнем знакомить с проблемами, которые мы используем и с которыми столкнулись, и с тем, как их решить.
Базовая структура проекта выглядит следующим образом, включая основные и вспомогательные приложения, как показано ниже:
Основное использование qiankun
рамки проекта
И родительское приложение, и микроприложение генерируются с использованием официальных шаблонов React, а затем модификация и конфигурация будут обсуждаться ниже.
Модернизация родительского приложения
Родительское приложение должно установить два пакета зависимостей, одинhistoryОдинqiankun, тогда правильноindex.jsМодификации:
import { registerMicroApps, start } from 'qiankun';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
registerMicroApps(
[
{
name: 'app1',
entry: '//localhost:3002/',
container: '#container',
activeRule: '/app1',
props: {
name: 'kuitos',
}
}
],
{
beforeLoad: app => console.log('before load', app.name),
beforeMount: [
app => console.log('before mount', app.name),
],
},
);
start();
reportWebVitals();
тогда даapp.jsxМодификации:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
function App() {
// 子项的点击
const onMenuClick = (path) => {
history.push(path);
};
return (
<div className="layout">
<header className="layout-header">
<img src={logo} className="layout-logo" alt="logo" />
<div className="layout-link" onClick={() => { onMenuClick('/app1'); }}>点击加载app1微应用页面</div>
</header>
<div className="layout-main" id="container">
app1微应用展示区域
</div>
</div>
);
}
Модернизация микроприложений
Никаких дополнительных npm-пакетов для микроприложений устанавливать не нужно.Следующая модификация index.js:
const initAPP = container => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
container?container.querySelector('#root'):document.querySelector('#root')
)
}
//全局变量来判断环境,独立运行时
if(!window.__POWERED_BY_QIANKUN__){
initAPP()
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
const {container}=props
initAPP(container)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
reportWebVitals();
Это базовая конфигурация, но эта конфигурация не совсем подходит, вы столкнетесь с различными проблемами в середине, а затем, пожалуйста, не беспокойтесь, если у вас возникнут проблемы, пожалуйста, просмотрите следующие записи о проблемах, чтобы найти соответствующие решения.
На данный момент, вы можете спросить, подобных материалов в Интернете не так много, и случайное демо должно быть легко исчерпано. Тем не менее, следующее основано на этой архитектуре, проблемы, с которыми мы столкнулись, настоящие галантерейные товары, смотрите внимательно!
журнал проблем цянькунь
Проблема с хуком жизненного цикла не распознана
Цянькунь бросаетApplication died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entryЭта проблема представляет собой ошибку, которая будет выдана, когда qiankun будет введен в ваш собственный проект или официальный проект.Следующий код должен быть добавлен в часть упаковки веб-пакета микро-приложения:
const packageName = require('../package.json').name;
output: {
// 这里改成跟主应用中注册的一致
library: 'brokenSubApp',
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
Конкретная причина заключается в том, что qiankun выдает эту ошибку, потому что не может идентифицировать экспортированные обработчики жизненного цикла из записи js микроприложения. (Официальное введение)
Запрашивать ресурсы по проблемам
На начальном этапе проекта, т.к. нет фиксированного доменного имени, но в разработке все же необходимо разрешить кросс-доменность, как решить на данный момент:
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
}
Просто добавьте «Access-Control-Allow-Origin»: «*» в devServer веб-пакета микроприложения. Родительское приложение добавлять не нужно, так как родительское приложение загружает ресурсы микроприложения.
Другой способ — если у вас есть прокси-инструмент, такой как свисток, который мы используем.
127.0.0.1:8085 a.zhuanzhuan.com/manager
127.0.0.1:8084 a.zhuanzhuan.com
Также можно проксировать ресурсы родительского приложения и микроприложения на одно и то же доменное имя.
Проблема с обновлением микроприложения 404
Переключите режим маршрутизации родительского приложения и микроприложения в один и тот же режим маршрутизации.Микроприложение берет историю браузера в качестве примера:
<!-- config 设置 -->
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
<Provider store={store}>
<ConnectedRouter history={history}>
<div className="rooter-wrap">
<Route routeList={routeList}/>
</div>
</ConnectedRouter>
</Provider>
<!-- 另一个页面的配置 -->
<BrowserRouter
basename = {window.__POWERED_BY_QIANKUN__ ? '/qc_xxx' : '/hunter_qc_xxx'}
forceRefresh = {!window.__POWERED_BY_QIANKUN__}
>
Проект каждого человека или компании отличается, и это зависит от соответствующего кода проекта для обновления некоторых вещей. (Если вы используете его, вы должны изучить его самостоятельно, иначе это вызовет проблему 404 после обновления)
Проблемы с меню с родительскими и микроприложениями
Поскольку микроприложения могут работать независимо, микроприложения имеют собственную логику навигации по меню, а затем интегрируют все меню в родительское приложение. Тогда это расширит вопрос, как интегрировать меню всех микроприложений, мы придумали три идеи:
- Настройте набор навигации по меню в центре конфигурации, а затем родительское приложение запрашивает центр конфигурации, чтобы получить навигацию и отобразить ее. Микроприложение поддерживает собственную маршрутизацию внутри:
- Преимущества: локальная застройка удобна;
- Недостатки: поддерживать два набора маршрутов, один для центра конфигурации и один для локального;
- И родительское приложение, и микроприложение находятся в центре конфигурации, поэтому дочернее приложение также зависит от внешних ресурсов при самостоятельной разработке, что может сказаться на эффективности разработки:
- Преимущества: поддержка набора маршрутов, наличие текущего интерфейса центра конфигурации, отсутствие необходимости отдельной разработки;
- Недостатки: Неудобно разрабатывать микроприложения по отдельности, и необходимо поддерживать разные маршруты для разных окружений. И микроприложению, и родительскому приложению необходимо получить всю конфигурацию маршрутизации, а затем дочернее приложение должно отфильтровать и отфильтровать свою собственную маршрутизацию;
- При упаковке микроприложения упакуйте навигацию в отдельный
.jsonфайл, а затем настроить его на определенный адрес через nginx, и получить доступ к содержимому файла через адрес.Родительское приложение запрашивает файл, а затем интегрирует его, как показано ниже:
- Достоинства: локальная разработка удобна и не требует лишней настройки;
- Недостатки: Эту функцию нужно разрабатывать отдельно, родительское приложение должно тянуть несколько подприложений, но это повлияет на скорость рендеринга первого экрана;
Объединив три вышеупомянутых решения, после внутреннего обсуждения мы решили использовать сторонний метод.После разработки и реальной онлайн-операции мы обнаружили, что это вполне осуществимо.
Вот как это упаковать:
/**
* 需要一个 npm 包:generate-json-webpack-plugin
* webpack中写配置
*/
const GenerateJsonPlugin = require('generate-json-webpack-plugin');
const menuList = require('../src/router/menuData.js'); // 路由表
// 需要满足封装请求的格式
const menuConfig = {
code: 0,
msg: '',
data: menuList
};
/**
* 省略中间代码...
*/
/**
* 在 webpack 的 build 中增加 plugins:
* 生成指定的json文件
*/
plugins: [
// 生成指定的json文件
new GenerateJsonPlugin('webserver/config/menu.json', menuConfig),
],
// 之后在通过 nginx 配置一个单独的配置去指向这个文件
Это решает проблему с меню для родительских приложений и микроприложений.
Синхронизируйте данные между подприложениями
В реальных сценариях разработки всегда встречаются сложные и неожиданные требования. Синхронизация данных между подприложениями — одно из них. Теоретически, если субприложения достаточно независимы, бизнес-логика достаточно разделена. Не будет обмена данными между подприложениями. Все данные могут распространяться родительским приложением, что также является философией qiankun. смотрите подробностиissue412. Но на практике такие потребности существуют. Но в то же время автор также рекомендует минимизировать такие требования, потому что это может привести к путанице в потоке данных и вызвать неустранимые ошибки. Подробные решения описаны ниже
Родительское приложение использует прокси для создания глобального хранилища. Полный код выглядит следующим образом.
const GlobalStore = (function () {
if (window.__hunterMfeBase__) {
return window.__hunterMfeBase__;
}
// 初始化store
const store = Object.assign(Object.create(null), {
/**
* 赋值变量
* @param prop 属性名
* @param value 属性值
* @param options
* {
* readOnly: Boolean 只读
* }
*/
setValue(prop, value, options = {}) {
const { readOnly } = options;
// 非只读属性-直接赋值
if (!(readOnly === true)) {
this[prop] = value;
return;
}
// 只读属性-将值代理到__value__上
this[prop] = {
value,
readOnly
};
},
/**
* 初始化全局变量 (用于在父应用上初始化全局变量)
* @param global
*/
init(global = {}) {
Object.keys(global)
.forEach(item => (this[item] = global[item]));
}
});
// 初始化拦截器
const handler = {
// 拦截取值操作
get(target, prop) {
// const { activeApp = 'base' } = store;
// console.log(`[${activeApp}] get`, { activeApp, target, prop });
const primitive = target[prop]; // 获取原始值
// 只读属性-返回代理的 __value__
if ({}.toString.call(primitive) === '[object Object]' && primitive.readOnly === true) {
return primitive.__value__;
}
// 非只读属性-直接返回原始值
return primitive;
},
// 拦截赋值操作
set(target, prop, value) {
// const { activeApp = 'base' } = store;
// console.log(`[${activeApp}] set`, { activeApp, target, prop, value });
const oldVal = target[prop]; // 读取原始值
// 存在旧值且为只读属性-不可进行更改值操作
if ({}.toString.call(oldVal) === '[object Object]' && oldVal.readOnly === true) {
console.error(`GlobalStore.${prop}是只读属性,无法重复赋值`);
return true;
}
// 新值存在只读属性-数据代理到__value__
if ({}.toString.call(value) === '[object Object]' && value.readOnly === true) {
delete value.readOnly;
const proxyVal = {
__value__: value.value,
readOnly: true,
};
target[prop] = proxyVal;
return true;
}
// 非只读属性-直接赋值
target[prop] = value;
return true;
}
};
window.__hunterMfeBase__ = new Proxy(store, handler);
return window.__hunterMfeBase__;
}());
export default GlobalStore;
Глобальному хранилищу может быть присвоено значение при инициализации родительского приложения.
import GlobalStore from '@store';
GlobalStore.init({
// 常规属性
userInfo:{},
// 只读属性
permissionInfo:{
value: {},
readOnly: true
},
});
// 手动添加属性
GlobalStore.other = '';
// 手动添加只读属性
GlobalStore.setValue({
value: '',
readOnly: true
})
Ссылка на глобальное хранилище в подприложениях
const GlobalStore = (function () {
return window.__hunterMfeBase__ || {};
}());
export default GlobalStore;
Подприложение считывает данные из глобального хранилища.
import GlobalStore from '@/store/globalStore';
console.log(GlobalStore.userInfo);
Установите данные в глобальном хранилище в подприложении
import GlobalStore from '@/store/globalStore';
//...res为接口请求结果
const { userInfo = {}, permissionInfo = {} } = res;
GlobalStore.userInfo = userInfo;
получено из всех подприложений на данный моментuserInfoВсе обновлены.
На данный момент весь контент микро-фронтенда в этом выпуске опубликован, если у вас есть какие-либо вопросы, вы можете оставить сообщение ниже. Мы рассмотрим возможность продолжения обмена по мере необходимости.