WSO2 Identity Server всегда был известен своим техническим совершенством и богатством функций. Но этого было недостаточно, чтобы нас насытить. Чтобы обеспечить беспрецедентный пользовательский опыт, мы хотели добавить в наш колчан еще одну вещь. Итак, мы представляем наше новое консольное приложение!
Бета-версия нашего консольного приложения доступна с версией 5.11.0 WSO2 Identity Server. Это приложение обеспечивает значительно улучшенный пользовательский интерфейс, позволяя администраторам и разработчикам выполнять свои задачи с помощью интуитивно понятного и тщательно продуманного пользовательского интерфейса.
На протяжении всего цикла выпуска приложение претерпевало множество итераций дизайна и редизайна, поскольку наша команда обдумывала различные идеи и экспериментировала с различными реализациями. Это история происхождения консольного приложения.
Первые дни
Вскоре после завершения работы над нашим приложением User Portal, которое с 5.11.0 будет известно как приложение My Account, мы приступили к работе над приложением Console. Когда мы начинали, мы были уверены в одном: как и наше приложение «Моя учетная запись», оно будет написано на React.
Виртуальная модель DOM React, которая делает обновления состояния более быстрыми и эффективными, превосходное взаимодействие с разработчиками и, конечно же, JSX сделали этот выбор для нас автоматическим.
Мы остановились на семантическом пользовательском интерфейсе для создания наших пользовательских интерфейсов, поскольку он позволяет нам использовать созданную нами тему и вне наших приложений React. Это позволило нам использовать одну и ту же тему как в наших приложениях React, так и в наших порталах аутентификации, которые были написаны с использованием JSP.
Использование монофонического репо
Поскольку наше приложение My Account также использует React и структуру Semantic UI, мы хотели повторно использовать некоторые утилиты и компоненты в приложении Console. Однако, поскольку мы продолжали работать над улучшением кодовой базы My Account, нам нужен был способ изменить повторно используемый код, продолжая использовать его в приложении Console.
Мы могли бы переместить повторно используемый код в отдельный репозиторий и опубликовать его в реестре NPM, а затем добавить его как зависимость к нашему консольному приложению. Но это сильно замедлило бы наш рабочий процесс разработки. Представьте себе, если бы мы изменили повторно используемый код, нам пришлось бы изменить код, протестировать его, опубликовать, а затем нам пришлось бы поднять версию зависимости в приложении Console, чтобы получить новые изменения.
Даже тогда не было гарантии, что изменения будут работать должным образом в приложениях. Лучший способ убедиться, что повторно используемый код делает то, что он должен, - это протестировать его в самих приложениях.
Здесь нам на помощь пришла концепция монорепо. Моно-репо позволяет иметь несколько пакетов в одном репозитории и позволяет использовать один пакет в качестве зависимости в другом. Это полностью соответствовало нашим требованиям, и после беглого исследования в Интернете мы остановились на Lerna, стабильном и популярном инструменте монорепо.
Итак, теперь у нас есть код приложения My Account и Console в одном репозитории. Повторно используемые компоненты React были превращены в библиотеку компонентов и существуют в нашем каталоге модулей в том же репозитории. Повторно используемый код TypeScript встроен в модуль Core.
Следовательно, теперь у вас есть два разных интерфейсных приложения, использующих код в одном репо. Это упрощает обслуживание, улучшает рабочий процесс разработки и делает наш код более управляемым. Будьте счастливы, моно репо!
В Redux или нет в Redux
Один вопрос, который мы постоянно задавали себе и изо всех сил пытались ответить, заключался в том, нужен ли нам Redux. Конечно, наша мышечная память подсказывала нам установить Redux, как только мы запускаем проект React. Но действительно ли нам нужен Redux?
Ответ на этот вопрос зависит от того, нужен ли вам единый глобальный источник истины. У каждого компонента React есть источник истины в виде состояний, и реквизиты могут использоваться для передачи данных вниз по дереву компонентов. Грамотно спроектировав дерево компонентов или думая в React (как любят называть его ребята из React), вы можете уменьшить потребность в едином глобальном источнике истины.
Чаще всего компоненты обращаются к API, чтобы сохранить свои данные в бэкэнде, и эти бэкэнд-данные будут служить единственным источником истины, в которой вы нуждаетесь. Если нет острой необходимости уменьшить количество вызовов API или вам нужно преобразовать возвращаемое перед использованием, этого метода будет достаточно.
Однако могут быть сценарии, в которых вы хотите, чтобы все компоненты имели доступ к определенным данным. В таком случае будет излишним передавать данные в качестве свойств всем компонентам. Здесь есть веские основания использовать такую библиотеку, как Redux. Но это еще не все.
Новый контекстный API React позволяет любому компоненту подписываться на данные, тем самым позволяя компонентам получать доступ к данным из одного источника без реквизитов. Это похоже на то, что предлагает Redux. Итак, какой из них вы выберете?
Изначально нам удалось избежать необходимости в едином глобальном источнике истины. Однако вскоре мы поняли, что нам нужен глобальный доступ к информации профиля пользователя. Поскольку вариант использования был прост, мы решили использовать Context API. Однако наши потребности стали усложняться. Нам также нужно было хранить детали конфигурации среды выполнения (подробнее об этом позже) и информацию о локализации.
Поскольку мы пытались свести к нулю один из двух, мы также столкнулись с проблемами производительности React-Redux после перехода на Context API и советом разработчика React не рассматривать Context API как замену Flux-подобным архитектурам.
И, наконец, пришел решающий довод. Когда мы хотели получить доступ к единому источнику истины за пределами компонентов React / файлов JSX. Redux предоставляет API, позволяющий нам получать доступ к состоянию вне React, тогда как с Context API это невозможно. Итак, мы продвинулись вперед с Redux.
Функции или классы?
С помощью хуков React сделал функциональные компоненты такими же мощными, как и компоненты на основе классов, и вскоре нам пришлось решить, какой из них мы собираемся использовать. Мне лично понравился ассортимент предлагаемых компонентов на основе классов с методами жизненного цикла, и у меня были опасения по поводу способности хуков заменять функциональные возможности компонента на основе классов.
Однако мы хотели избежать ада оболочки компонентов высшего порядка в компонентах на основе классов, и отказ React от некоторых методов жизненного цикла означал, что будущее за функциональными компонентами. Итак, мы решили продолжить работу с функциональными компонентами.
Изначально мы обнаружили, что вместо удушающих методов жизненного цикла нам нужно использовать ловушку useEffect
, но со временем, надо признать, функциональные компоненты начали расти в нас. Когда мы разобрались с хуками, мы обнаружили, что функции намного проще и понятнее, чем классы. Поначалу миграция была сложной задачей, но как только вы начнете думать о функциональных компонентах, будьте уверены, вы попадетесь на нее.
Это не означает, что функциональные компоненты не лишены недостатков, как мы вскоре узнали, когда нам потребовались границы ошибок. Границы ошибок гарантируют, что ошибка в компоненте не нарушит работу всего приложения. Вместо этого вы можете обработать ошибку в компоненте и показать резервный пользовательский интерфейс. Но проблема заключалась в том, что границы ошибок в функциональных компонентах невозможны.
Итак, мы решили обернуть функциональные компоненты на данной странице компонентом на основе классов, чтобы в лучшем случае это была страница, которая сломалась.
Ленивая загрузка
Когда наше приложение приблизилось к зрелости, мы поняли, что размер окончательного пакета составляет несколько мегабайт. Такой большой файл сценария вызовет значительную задержку при загрузке приложения, что негативно повлияет на работу пользователя с приложением. Мы хотели уменьшить размер пакета, чтобы ускорить загрузку.
Конечно, мы сделали очевидную вещь - проанализировали наши зависимости и попытались заменить более крупные альтернативы меньшими. Но это не привело к значительному увеличению размера пакета. Вскоре мы смирились с тем фактом, что наше приложение было огромным, и в результате наш пакет должен был быть большим.
Итак, нам пришлось разбить наш код на несколько частей. Динамический импорт помогает нам, создавая фрагменты нашего кода и обеспечивая их загрузку только тогда, когда они требуются пользователю. Итак, когда пользователь загружает приложение, загружается только код, необходимый для отображения целевой страницы. Когда пользователь перемещается по приложению, необходимые фрагменты могут загружаться динамически. Это решает проблему, связанную с длительной загрузкой приложения.
React идет еще дальше и помогает нам визуализировать динамический импорт как обычный компонент с помощью функции React Lazy. Все, что нам нужно было сделать, это передать функцию, которая возвращала бы динамический импорт в качестве аргумента в метод React.lazy ().
Но здесь возникает новая проблема. Представьте, что вы загружаете компонент лениво. Приложению придется дождаться загрузки необходимого фрагмента кода, прежде чем компонент можно будет отобразить. До тех пор вы не увидите ничего на месте компонента. Это повлияет на пользовательский опыт. В идеале мы должны показывать загрузчик, пока компонент не загрузится. Как мы можем сделать это?
Не волнуйтесь, React (снова) вас поддержал. Компонент с отложенной загрузкой фактически должен отображаться внутри компонента Suspense. Резервная опора компонента Suspense принимает резервный компонент, который будет отображаться до тех пор, пока компонент с отложенной загрузкой не будет готов.
Использование отложенной загрузки React позволило нашему приложению значительно улучшить время загрузки, и это необходимо во всех крупных приложениях.
Конфигурация среды выполнения
Мы хотели, чтобы наше приложение было настраиваемым. Нет, мы не хотели, чтобы наши пользователи копались в коде только для того, чтобы изменить брендинг. Мы хотели сделать это без кода. И это нужно делать во время выполнения.
Итак, мы создали файл JSON, в котором будут храниться все детали конфигурации развертывания. Приложение отправляет запрос GET во время инициализации для загрузки этого файла. После получения ответа мы анализируем этот файл JSON и сохраняем полученный объект в глобальной переменной.
Ребрендинг приложений на лету
Приложение получит доступ к этой глобальной переменной для загрузки соответствующей конфигурации. Например, имя приложения получается из этой конфигурации. Итак, теперь вы знаете, что нужно сделать, чтобы изменить название приложения. Измените имя в файле JSON, перезагрузите приложение и вуаля, имя в заголовке тоже изменится!
Поддержка Internet Explorer
Некоторые из наших клиентов продолжают использовать Microsoft Internet Explorer и Microsoft Edge Legacy, и нам важно убедиться, что наше приложение нормально работает в этих браузерах. Поскольку некоторые из собственных API-интерфейсов, которые работают в большинстве других браузеров, не работают в устаревших браузерах Microsoft, нам пришлось отказаться от работы.
Сначала мы попробовали полифиллинг API, которые не поддерживаются Internet Explorer, но быстро обнаружили, что вручную полифиллинг всех недостающих API браузера практически невозможно. И у нас также были проблемы с некорректным отображением некоторых CSS в Microsoft Internet Explorer. Итак, пришлось искать альтернативное решение.
Войдите в Вавилон. Переход с ts-loader на babel-loader позволил нам перенести наш код для работы в Internet Explorer. Кроме того, мы настроили babel для использования core-js для полифилла API, которые не работают в Explorer.
Но это не решает проблем с CSS. Итак, мы использовали пакет autoprefixer вместе с postcss-loader для webpack, чтобы преобразовать CSS для правильного рендеринга в Internet Explorer.
Однако полифиллинг может привести к увеличению размера конечного пакета. Вам всегда придется искать компромисс между поддержкой разных браузеров и тем, чтобы убедиться, что это не влияет на производительность приложения в наиболее часто используемых браузерах. Что касается того, где вы подведете черту и скажете, что мы не будем поддерживать эти браузеры, решать разработчикам.
Но как вы сообщите Babel и autoprefixer, что вы собираетесь поддерживать именно эти браузеры? Что ж, атрибут browserslist в файле package.json позволяет нам очень просто передать эту информацию. Вы можете указать имена браузеров, которые хотите поддерживать, в массиве, или, как то, что мы сделали, вы можете принять решение о поддержке браузеров в зависимости от их доли на рынке. Мы установили его значение более 0,2%, что означает, что наше приложение будет работать в браузерах, занимающих долю рынка более 0,2%.
Подведение итогов
Путешествие еще не окончено. В конце концов, мы выпустили только бета-версию приложения. Поскольку мы постоянно стремимся улучшать все приложения и добавлять новые функции, мы уверены, что нас ждет еще много приключений и извлеченных уроков. На данный момент наше консольное приложение готовится пройти еще один цикл выпуска усовершенствований и дополнений.