"действия этого скрипта НЕОТВРАТИМЫ" — точно сказано, почти все проекты, которые видел использующими CRA, сделали eject и перешли на кастомные конфиги по мере роста проекта. Он только для маленьких pet-проджектов, так как для проектов чуть больше нужно много дополнительного функционала, а возня с переопределением правил изначально плохая идея.
По телу статьи — вы забыли упомянуть, что в IDE может потребоваться специальным образом отметить папки, для которых сделаны алиасы — так, WebStorm умеет брать ts-алиасы в ts-файлах, но в стилевых файлах @import 'mixins.scss' не сработает без отметки Resource Root на папке src/components/styles. У вас вижу css-in-js, поэтому могли не столкнуться. Также в кодовых блоках лучше использовать двойной пробел для табуляции и переносить при достижении 80-100 символов — читать легче, не будет прокрутки.
За лесенку в const ... = require() уважение, выглядит аккуратно. Жаль, не разбито по семантическим группам (built-ins, external, internal etc.), в этом случае скорость восприятия кода еще улучшится.
По коду еще по неймингу захотелось пройтись. Вот, к примеру, функция
const slicePath = p => p.slice(0, p.indexOf('*') - 1)
Как видно из названия, она "нарезает путь". Из реализации не очень понятно, что ожидается на вход и что будет на выходе. Даже если найти пример входных данных в коде ("src/components/*"), все равно не сразу понятно, что будет на выходе — обрежется только звездочка или еще что-то. Только поразбиравшись станет понятно, что на выходе получится "src/components". Почему бы не сделать более читабельно:
Примерно то же и с .reduce((obj, x) => { ... }). Так как тут не используется TypeScript, то главным оружием в борьбе за понятность структур в коде является семантичность. И вариант с .reduce((acc, path) => { ... }) будет выглядеть лучше, а еще лучше — вынести это в отдельную функцию, чтобы следующий разработчик не тратил время на разбор, если его это не интересует. К тому же эти вездесущие нейминги x — в последнем кодовом блоке возник дубляж названий:
Должно уйти, потому что многословно, неудобно, низкопроизводительно и приводит к смешению концептуально разных сущностей и языков. Повторюсь, у всех реализаций при применении на практике громадное количество костылей и недостатков.
Управление стилями при помощи добавления-удаления классов очень эффективно, так как позволяет за раз переключать сразу несколько параметров и переиспользовать эти классы в других компонентах. Схема через template literals ${isActive => isActive ? 'margin: 0; color: red;' : 'margin: 1px; color: green;'} как раз выглядит хаком, а не &.active {margin: 0; color: red;}.
В целом не могу сказать, что что-то в отдельном CSS меня не устраивает — это долго развивавшийся язык, с отличной поддержкой в IDE, многочисленными инструментами для добавления функционала через пре- и пост-процессинг, линтерами/форматтерами. Часто говорят, что css-in-jss это мощно, потому что можно написать какие угодно инструменты на js — но они и так есть для CSS, и тотальное переписывание, чтобы было так же удобно работать, как и с CSS, приведет к тому, что получится то же самое. Просто файлы не с расширением .scss, а .ts и дополнительным оверхедом по синтаксису, экспортам и торможению основной сборки. Но для этого "светлого будущего" нужно еще написать сотни инструментов, исправить тысячи багов, интегрироваться во все IDE, стандартизировать параметры и значения (а текущий TS этого не позволяет в должной мере, string и все тут). В то время как все давно уже есть, и ради маленькой фичи "не хочу писать cn(styles.class1, isActive && styles.class2), хочу писать class1({ isActive }) и логику внедрять внутрь стилей, потому что так смогу все размазать в единый слой" все эти усилия… Стоит ли оно того, или пора все же в историю?
Скорей бы это смешение стилей и логики ушло в историю. Вот смешение разметки и логики (JSX) - это удобно, так как они очень тесно связаны, а стили - совсем другой слой и программе все равно есть они или нет, без них все будет работать так же, это чисто визуальная составляющая.
Я уже со счета сбился в перечислении недостатков, которые css-in-js вносит в проект, по сравнению с модулями. И тем не менее иногда встречаются проекты, использующие этот нежизнеспособный концепт, запутываясь в сотнях одинаковых с лица но кардинально разных компонентах Button / StyledButton (в лучшем случае), тоннах omitProps, смешении стилей и компонентов, ужаснейше выглядящих и не поддающихся форматированию template-вставках, постоянных "> * {}" для переписывания стилей любых чайлдов, и список этот настолько длинен, что на вот такую статью как наверху потянет, чисто перечисление...
Аналогичный опыт — долго провозился с Vite и Snowpack, пытаясь хотя бы самый простой рабочий проект завести. Но отсутствие возможности собирать нодовый сервер под SSR или без него сразу ограничило проекты до нескольких (экспериментальные плагины есть, но после долгой возни только с большими костылями завелись, плюс нет частичного обновления — серверная часть каждый раз прогоняется через бандлер, что в сотню раз медленнее). Часть из оставшихся использует асинхронные чанки, как вы описываете — с общими частями, lazy-подгрузкой, контентхешем в названиях, прелоадингом, экстрактом css-файлов и т.п. После долгой возни тоже корректно не завелись.
Остались простейшие SPA-монолиты из пет-проджектов, потому что энтерпрайз так не делается. Там да, завелось, но бандлер с автополифиллингом опять же нужен, конфигурирование через env-параметры, комменты в файлы и куча всего еще, что в Вебпаке по привычке за пару минут делается, а тут приходится "женить" дев-сборщик (довольно сырые причем оба и Vite и Snowpack — в их issues прямо поселиться пришлось) и бандлер. С этим можно справиться, что-то завелось корректно, и в дев-режиме действительно быстрее собирается и в основном хот-релоад работает лучше, чем в Вебпаке, но однозначно оно того не стоит. С кешированием и параллельностью Вебпак такие проекты тоже за 0.03-0.05с пересобирает вместо 0.01с у этих новичков. Поэтому дальше копаться не стал, подожду, пока хотя бы основные энтерпрайз-фичи будут хорошо поддерживаться и с бандлерами начнут крепко дружить.
Я посчитал, что это было банальной ошибкой в коде, так как смысла не оборачивать компонент не вижу, в комментарии слишком мало контекста и явно плохое понимание как работает observable. Если этот Page — компонент из сторонней библиотеки, то достаточно передать отдельные props, но тут еще упоминалась передача "state.auth в качестве пропса", так что это явно внутренний компонент.
В целом ветка с перепутанными смыслами, неявным контекстом и мешаниной подходов, поэтому я написал комментарий не к ветке, а в общем — что есть механизм toJS, но актуальность его использования крайне редка — есть решения лучше (обернуть дочерний в observable или передавать отдельные пропсы если компонент сторонний). Ваш вариант с toJS некой объемной структуры данных, часть из которых может быть не нужна в дочерних и вызовет лишние ререндеры плохой в любом случае.
Чем же оно прекрасное, зачем компоненту-обертке лишний раз ререндериться? И конкретные параметры тоже лучше не передавать, опять же, это нивелирует бенефиты от observable. Куда лучше в самом этом дочернем компоненте использовать store.auth.username — тогда только он и обновится. Реактивность работает именно так, как должна.
Если нужно реагировать на изменения во всех дочерних структурах, можно использовать метод toJS из MobX, пример
reaction(() => toJS(state.auth), function onAnyChangeInAuth() {
...
})
Через пропсы передавать с помощью toJS крайне нежелательно, так как родительский компонент будет ререндериться, когда это не нужно. В целом ситуации, когда этот механизм нужен, крайне редки — в голову приходит только синхронизация стейта из оперативной памяти и какого-нибудь persistent storage, того же localStorage, или онлайн-игр с сохранением состояния в Firebase. То есть при любом изменении в хранилище оно целиком (в реальности, конечно, через дифф-алгоритм) будет записываться в постоянное хранилище и не будет пропадать при перезагрузке страницы.
Спасибо за ответ, посмотрел подробнее — получается довольно удобно, каждый модуль собирается отдельно и вертится на своем порту, при этом можно красиво импортить отдельные компоненты, расшаривать общие библиотеки, прокидывать верхнеуровневое состояние и методы. Вебпаковский вариант выглядит намного лучше тех костыльных нагромождений, которые видел раньше.
Тем не менее, недостатки, присущие проектам, разложенным по разным репозиториям и имеющим общие части остаются, но для 4+ команд по-другому и не получится — толпиться всем в одном репозитории становится тесно даже при грамотном менеджеринге веток, так как из-за разницы в релизных циклах мердж реквесты начнут копиться и устаревать. Можно сделать еще удобные рецепты, чтобы собирались только рут + нужные модули, этим разрулится проблема с производительностью. SSR тоже вполне можно прикрутить, включив в useDynamicScript механизм require и кэш в global, хотя прикрутить ожидание асинхронных вызовов перед рендером будет непросто (но наверняка будет еще немало подводных камней).
В общем, Module Federation на первый взгляд — уже хоть что-то, похожее на удобство, по крайней мере для одностековых проектов.
Поясните, пожалуйста, чем данное решение помогло в "разделении зоны ответственности и распараллеливании разработки". Как я понял, все модули лежат в одном репозитории и ревью / мерджи производятся в одном потоке с соответствующим пересечением в релизных циклах? Или для каждого модуля-папки заведет отдельный гит-репозиторий, просто получается в одной папке можно собрать сразу много репозиториев через cli-инструмент, который подтягивает версии?
Так же, как вижу, в каждом из модулей могут использоваться разные версии npm-зависимостей, что приводит к рассинхрону и потенциальным багам, часть из которых описана в статье. Вскоре может понадобиться изменить конфиги вебпака для отдельного модуля — добавить другие лоадеры, плагины, сборку других фреймворков и т.п., но, как я понял, Module Federation опирается на единый конфиг. Как планируется разруливать этот кейс?
И по поводу "разделения зоны ответственности" непонятно, чем данная схема лучше монолита — если код разбит на асинхронно загружаемые модули, то команды, их разрабатывающие, и так не будут пересекаться по коду. Проблема только в том, что используется один гит-репозиторий, но если команд не больше 4, разруливается менеджерингом лейблов и веток — в одном проекте так делали, пересечений фактически не было.
В монолите, к сожалению, тоже есть одна существенная проблема — так как собирается все и сразу, то время сборки значительно увеличивается (хотя есть несколько стратегий для динамического билдинга), при Module Federation, как понимаю, будут собираться только переданные через cli модули или тоже все?
Да, там тоже многие еще сидят на старых компьютерах ввиду низкой покупательской способности или устаревшего отношения руководства компаний к интернету. Статистика сейчас разнится, но в районе 4% у IE9-11 все еще есть, а это ну почти 100млн машин если только ЮВА считать. Так что веб там очень специфичен и многие до сих пор придерживаются табличной верстки либо в качестве энтерпрайз решения — флоатов, что не требует полифиллинга. Благо очень многое полифиллится, но не все. У госконтор та же история, но с началом Медведевской "диджитализации" все-таки ситуация немного лучше, и хром-то даже на старую винду админы поставят.
Спасибо за пояснение, я в DI не хорош — видел реализации в проектах, но смысла для фронтенда так и не понял. Предпочитаю делать слои экшенов, апи и сайд-эффектов отдельно от хранилищ, с доступом к глобальному стору.
Сборка упадет и он поправит, это же обычная опечатка. Получается этот паттерн через getInstance неактуален? Ни разу не видел в современных проектах, везде просто готовый инстанс экспортируется
Может глупый вопрос, но чем отличается от export const dataBase = new DataBase()? При импорте из разных мест придет один и тот же инстанс по правилам ES Imports. И DI соответственно class { private dataBase = dataBase; } без контейнера, передающего значение через декоратор.
Вот это "подтягивание хвостов" только звучит как будто это осуществимо, на деле приходится обновлять компонент (селект к примеру) в библиотеке компонентов для реакта, вью, ангуляра, ставить задачи на все команды по подъему версии, причем они могут быть ниже приоритетом чем задачи от бизнеса, в итоге 5 из 10 микрофронтов обновились через неделю, еще два — через две, еще в трех эти задачи "потерялись" и остались в глубоком бэклоге. В итоге приложение несинхронизировано, баги остаются надолго, стили остаются разными. С десятью обновлениями или тем более редизайном получается такой треш, что смотреть на это все невозможно. Организационно это тоже не решить, так как у команд разные циклы поставок. В монолите же один апдейт и если используется TS, то он не даст оставить "хвосты", придется переводить сразу все — это займет больше времени на задачу, но сэкономит десятки человекочасов (считая время аналитиков, тестировщиков, разработчиков) по сравнению с микрофронтами. Не в идеальном мире живем, когда все всегда доводят задачи до конца.
Микрофронты всегда тесно связаны с рутом, в котором находятся глобальные компоненты и методы — не сделать серию нотификаций или модалок без доступа к информации, какие уже отображены, то же касается и авторизации, методов подключения/дестроя, роутинга. Изменения в каждом — критичны, если не поддерживать обратную совместимость, что сильно ограничивает возможности и раздувает код.
С этим не сталкивался, так как использую автоматический полифиллинг (babel preset-env), соответственно достаточно изменить browserslist в каждом микрофронте, но эту схему используют далеко не все и действительно может стать проблемой.
Микрофронты — не модный тренд, а метод обхода комплексного рефакторинга систем, чтобы можно было использовать легаси-части и постепенно их переводить на общую схему, имхо. А как по вашему, какое еще может быть у них назначение?
На моем опыте был только один случай, когда приложение состояло из двухуровневых iframe — виджет встраивался в сайты, и в самом виджете было несколько фреймов из разных доменных зон, связанных по postMessage. Но и там сделал единую точку рендеринга, в которую ходили разные бэкенды за получением локализованного шаблонизированного html, соответственно "зоопарка" не было, просто такой мультифреймовый конструкт с большим оверхедом по доступу к данным соседних "микрофронтендов".
В контекстном понимании, когда в едином поле страницы могут параллельно работать несколько разных фреймворков со своими компонентно-стилевыми системами, было только два проекта, и в обоих эта схема не была оправданной, так как приносила массу проблем и катастрофически замедляла разработку, несмотря на "параллелизацию поставок" — все равно в каждом релизе приходилось дорабатывать практически каждый микрофронтенд ввиду новых схем роутинга, подключения, общения с рутом, конфликтов лэйаута, обновления библиотек и дизайна и т.п. Поэтому я полностью отказался от подобных архитектур — оправдать их наличие перед бизнесом уже через пару месяцев становится нереально, так как профит только отрицательный. Думаю, что эта схема подходит только для легаси-проектов, как в статье, как переходный этап к новому монолиту, а проблема "Быстро писать код мешали устаревание подходов, фреймворков и зависимостей, а также связанность логики" решается планомерным рефакторингом — это обязательная часть разработки, не получится долго писать только бизнес-логику без архитектурных правок.
Можно еще писать компонент-тесты, делать подробную документацию к ним, обсуждать подходы типа двойные кавычки или одинарные, писать кастомные правила для ESLint, внедрять дополнительные статические анализаторы кода в CI/CD, делать обертки над многочисленными opensource-зависимостями для "абстракции от конкретной библиотеки", выносить каждое правило в стилях в миксины для "переиспользуемости", достигать какого-то уровня покрытия тестами кода, да мало ли еще бесполезной работы сейчас фронт-ендеры придумывают, лишь бы их бэк-енд был в тепле и достатке.
Клоакинг, как много в этом слове… Когда поисковики еще обращали внимание на meta description и не обращали на opacity: 0; width: 0; или цепочку js-редиректов, можно было представить в поисковике сайт как угодно… Сейчас такие схемы только в даркнете работают ввиду отсутствия продуманности в краулерах сайтов. Но в лайтнете такое не работает уже лет 7.
В подобной архитектуре намного больше проблем, чем описано, и код, обслуживающий их, становится настолько сложным и трудноподдерживаемым, что приходится выделять отдельную команду. Участвовал в двух проектах с подобной схемой. В числе других проблем — отсутствие общих компонентов либо большая сложность в их интеграции (обновить шрифт в 10-20 микрофронтах? Поднять версию библиотек, подтянуть иконки? В монолите — час работы, в подобной системе — больше месяца, кроме шуток); подтягивание актуальных версий для локальной разработки (придется писать сложный cli-инструмент с резолвом конфликтов и менеджерингом гит-версий в каждом микрофронте); на поддержку root-обертки тоже нужна команда, со своим флоу поставок и разрастающимися версиями для поддержки обратной совместимости, что выливается со временем в катастрофическое торможение новых релизов; про SSR даже можно не думать; в каждом микрофронте свои подходы и устаревающие конфиги, которые тоже нужно синхронизировать; кратное увеличение времени CI/CD; в браузере общий DOM, соответственно общая раскладка, и если не пользоваться iframe то очень велика вероятность конфликтов стилей или лэйаута; дублирование полифиллов. Перечислять можно еще долго, но закономерный итог — замедление поставок и тонна багов от синхронизации. А что считается "достоинством"? "Свободная реализация компонентов системы разными командами", то есть тотальный зоопарк. Про "независимость поставок", который часто тоже служит аргументом, лучше и не упоминать — в реальности этого нет, наоборот, приходится часто лезть во все репозитории, разбираться в их коде и подходах, и править, влезая в циклы поставок других команд.
За меньшее (!) время можно переписать легаси-код с полноценным рефакторингом и приведением к современным общим стандартам. Никто не мешает заложить архитектуру, в которой каждая страница — отдельный асинхронно загружаемый модуль, для развития которого не нужно лезть в рут и соответственно не будет конфликтов слияний, при этом он будет иметь доступ к глобальным методам и компонентам (модалки, нотификации, апи, роутинг), что уберет дубляж, будет поддерживать SSR, а апдейт зависимостей можно сразу протестировать на всем приложении. Не идите в эти дебри, тут вы не первопроходцы, а просто пополняете ряды страдальцев, занимающихся вместо продуктовой разработки решением самими же созданных проблем.
А я с вами и не спорил, читал внимательно — "Если вопрос про SSR то да", просто описал схему работы и показал, что это действительно ускоряет метрики загрузки интерфейса. Но в случае SPA, когда интерфейс описан на js, на сервере нужна среда, которая умеет его выполнять — так что нода практически без вариантов. Чтобы было более понятно, напишу пример:
В итоге получаем соответствующий странице html-код в первом запросе + объект вида window.INITIAL_DATA = { someData: 'test' };, который можно отправить в клиентский js-код и чисто гидрироваться с выданной сервером разметкой. Еще раз повторюсь, что говорю про SPA, когда один и тот же js изоморфно выполняется на клиенте и сервере, а не когда клиентский js вручную навешивает какие-то события и триггеры на шаблон, отданный бэком. В других серверных языках выполнение React.renderToString может быть только в виртуальном окружении.
"действия этого скрипта НЕОТВРАТИМЫ" — точно сказано, почти все проекты, которые видел использующими CRA, сделали eject и перешли на кастомные конфиги по мере роста проекта. Он только для маленьких pet-проджектов, так как для проектов чуть больше нужно много дополнительного функционала, а возня с переопределением правил изначально плохая идея.
По телу статьи — вы забыли упомянуть, что в IDE может потребоваться специальным образом отметить папки, для которых сделаны алиасы — так, WebStorm умеет брать ts-алиасы в ts-файлах, но в стилевых файлах
@import 'mixins.scss'
не сработает без отметки Resource Root на папкеsrc/components/styles
. У вас вижу css-in-js, поэтому могли не столкнуться. Также в кодовых блоках лучше использовать двойной пробел для табуляции и переносить при достижении 80-100 символов — читать легче, не будет прокрутки.За лесенку в
const ... = require()
уважение, выглядит аккуратно. Жаль, не разбито по семантическим группам (built-ins, external, internal etc.), в этом случае скорость восприятия кода еще улучшится.По коду еще по неймингу захотелось пройтись. Вот, к примеру, функция
Как видно из названия, она "нарезает путь". Из реализации не очень понятно, что ожидается на вход и что будет на выходе. Даже если найти пример входных данных в коде (
"src/components/*"
), все равно не сразу понятно, что будет на выходе — обрежется только звездочка или еще что-то. Только поразбиравшись станет понятно, что на выходе получится"src/components"
. Почему бы не сделать более читабельно:Примерно то же и с
.reduce((obj, x) => { ... })
. Так как тут не используется TypeScript, то главным оружием в борьбе за понятность структур в коде является семантичность. И вариант с.reduce((acc, path) => { ... })
будет выглядеть лучше, а еще лучше — вынести это в отдельную функцию, чтобы следующий разработчик не тратил время на разбор, если его это не интересует. К тому же эти вездесущие неймингиx
— в последнем кодовом блоке возник дубляж названий:а это уже тянет на вопрос юниору на собеседованиях — какие недостатки у подобного подхода) А так норм, добро пожаловать в писательское сообщество.
Должно уйти, потому что многословно, неудобно, низкопроизводительно и приводит к смешению концептуально разных сущностей и языков. Повторюсь, у всех реализаций при применении на практике громадное количество костылей и недостатков.
Управление стилями при помощи добавления-удаления классов очень эффективно, так как позволяет за раз переключать сразу несколько параметров и переиспользовать эти классы в других компонентах. Схема через template literals
${isActive => isActive ? 'margin: 0; color: red;' : 'margin: 1px; color: green;'}
как раз выглядит хаком, а не&.active {margin: 0; color: red;}
.В целом не могу сказать, что что-то в отдельном CSS меня не устраивает — это долго развивавшийся язык, с отличной поддержкой в IDE, многочисленными инструментами для добавления функционала через пре- и пост-процессинг, линтерами/форматтерами. Часто говорят, что css-in-jss это мощно, потому что можно написать какие угодно инструменты на js — но они и так есть для CSS, и тотальное переписывание, чтобы было так же удобно работать, как и с CSS, приведет к тому, что получится то же самое. Просто файлы не с расширением .scss, а .ts и дополнительным оверхедом по синтаксису, экспортам и торможению основной сборки. Но для этого "светлого будущего" нужно еще написать сотни инструментов, исправить тысячи багов, интегрироваться во все IDE, стандартизировать параметры и значения (а текущий TS этого не позволяет в должной мере, string и все тут). В то время как все давно уже есть, и ради маленькой фичи "не хочу писать
cn(styles.class1, isActive && styles.class2)
, хочу писатьclass1({ isActive })
и логику внедрять внутрь стилей, потому что так смогу все размазать в единый слой" все эти усилия… Стоит ли оно того, или пора все же в историю?Скорей бы это смешение стилей и логики ушло в историю. Вот смешение разметки и логики (JSX) - это удобно, так как они очень тесно связаны, а стили - совсем другой слой и программе все равно есть они или нет, без них все будет работать так же, это чисто визуальная составляющая.
Я уже со счета сбился в перечислении недостатков, которые css-in-js вносит в проект, по сравнению с модулями. И тем не менее иногда встречаются проекты, использующие этот нежизнеспособный концепт, запутываясь в сотнях одинаковых с лица но кардинально разных компонентах Button / StyledButton (в лучшем случае), тоннах omitProps, смешении стилей и компонентов, ужаснейше выглядящих и не поддающихся форматированию template-вставках, постоянных "> * {}" для переписывания стилей любых чайлдов, и список этот настолько длинен, что на вот такую статью как наверху потянет, чисто перечисление...
Аналогичный опыт — долго провозился с Vite и Snowpack, пытаясь хотя бы самый простой рабочий проект завести. Но отсутствие возможности собирать нодовый сервер под SSR или без него сразу ограничило проекты до нескольких (экспериментальные плагины есть, но после долгой возни только с большими костылями завелись, плюс нет частичного обновления — серверная часть каждый раз прогоняется через бандлер, что в сотню раз медленнее). Часть из оставшихся использует асинхронные чанки, как вы описываете — с общими частями, lazy-подгрузкой, контентхешем в названиях, прелоадингом, экстрактом css-файлов и т.п. После долгой возни тоже корректно не завелись.
Остались простейшие SPA-монолиты из пет-проджектов, потому что энтерпрайз так не делается. Там да, завелось, но бандлер с автополифиллингом опять же нужен, конфигурирование через env-параметры, комменты в файлы и куча всего еще, что в Вебпаке по привычке за пару минут делается, а тут приходится "женить" дев-сборщик (довольно сырые причем оба и Vite и Snowpack — в их issues прямо поселиться пришлось) и бандлер. С этим можно справиться, что-то завелось корректно, и в дев-режиме действительно быстрее собирается и в основном хот-релоад работает лучше, чем в Вебпаке, но однозначно оно того не стоит. С кешированием и параллельностью Вебпак такие проекты тоже за 0.03-0.05с пересобирает вместо 0.01с у этих новичков. Поэтому дальше копаться не стал, подожду, пока хотя бы основные энтерпрайз-фичи будут хорошо поддерживаться и с бандлерами начнут крепко дружить.
Я посчитал, что это было банальной ошибкой в коде, так как смысла не оборачивать компонент не вижу, в комментарии слишком мало контекста и явно плохое понимание как работает observable. Если этот Page — компонент из сторонней библиотеки, то достаточно передать отдельные props, но тут еще упоминалась передача "state.auth в качестве пропса", так что это явно внутренний компонент.
В целом ветка с перепутанными смыслами, неявным контекстом и мешаниной подходов, поэтому я написал комментарий не к ветке, а в общем — что есть механизм toJS, но актуальность его использования крайне редка — есть решения лучше (обернуть дочерний в observable или передавать отдельные пропсы если компонент сторонний). Ваш вариант с toJS некой объемной структуры данных, часть из которых может быть не нужна в дочерних и вызовет лишние ререндеры плохой в любом случае.
Это-то тут причем? Конечно, он должен быть обернут в observer, разве об этом разговор? export const Home = observer(...).
Чем же оно прекрасное, зачем компоненту-обертке лишний раз ререндериться? И конкретные параметры тоже лучше не передавать, опять же, это нивелирует бенефиты от observable. Куда лучше в самом этом дочернем компоненте использовать store.auth.username — тогда только он и обновится. Реактивность работает именно так, как должна.
Если нужно реагировать на изменения во всех дочерних структурах, можно использовать метод toJS из MobX, пример
Через пропсы передавать с помощью toJS крайне нежелательно, так как родительский компонент будет ререндериться, когда это не нужно. В целом ситуации, когда этот механизм нужен, крайне редки — в голову приходит только синхронизация стейта из оперативной памяти и какого-нибудь persistent storage, того же localStorage, или онлайн-игр с сохранением состояния в Firebase. То есть при любом изменении в хранилище оно целиком (в реальности, конечно, через дифф-алгоритм) будет записываться в постоянное хранилище и не будет пропадать при перезагрузке страницы.
Спасибо за ответ, посмотрел подробнее — получается довольно удобно, каждый модуль собирается отдельно и вертится на своем порту, при этом можно красиво импортить отдельные компоненты, расшаривать общие библиотеки, прокидывать верхнеуровневое состояние и методы. Вебпаковский вариант выглядит намного лучше тех костыльных нагромождений, которые видел раньше.
Тем не менее, недостатки, присущие проектам, разложенным по разным репозиториям и имеющим общие части остаются, но для 4+ команд по-другому и не получится — толпиться всем в одном репозитории становится тесно даже при грамотном менеджеринге веток, так как из-за разницы в релизных циклах мердж реквесты начнут копиться и устаревать. Можно сделать еще удобные рецепты, чтобы собирались только рут + нужные модули, этим разрулится проблема с производительностью. SSR тоже вполне можно прикрутить, включив в useDynamicScript механизм require и кэш в global, хотя прикрутить ожидание асинхронных вызовов перед рендером будет непросто (но наверняка будет еще немало подводных камней).
В общем, Module Federation на первый взгляд — уже хоть что-то, похожее на удобство, по крайней мере для одностековых проектов.
Поясните, пожалуйста, чем данное решение помогло в "разделении зоны ответственности и распараллеливании разработки". Как я понял, все модули лежат в одном репозитории и ревью / мерджи производятся в одном потоке с соответствующим пересечением в релизных циклах? Или для каждого модуля-папки заведет отдельный гит-репозиторий, просто получается в одной папке можно собрать сразу много репозиториев через cli-инструмент, который подтягивает версии?
Так же, как вижу, в каждом из модулей могут использоваться разные версии npm-зависимостей, что приводит к рассинхрону и потенциальным багам, часть из которых описана в статье. Вскоре может понадобиться изменить конфиги вебпака для отдельного модуля — добавить другие лоадеры, плагины, сборку других фреймворков и т.п., но, как я понял, Module Federation опирается на единый конфиг. Как планируется разруливать этот кейс?
И по поводу "разделения зоны ответственности" непонятно, чем данная схема лучше монолита — если код разбит на асинхронно загружаемые модули, то команды, их разрабатывающие, и так не будут пересекаться по коду. Проблема только в том, что используется один гит-репозиторий, но если команд не больше 4, разруливается менеджерингом лейблов и веток — в одном проекте так делали, пересечений фактически не было.
В монолите, к сожалению, тоже есть одна существенная проблема — так как собирается все и сразу, то время сборки значительно увеличивается (хотя есть несколько стратегий для динамического билдинга), при Module Federation, как понимаю, будут собираться только переданные через cli модули или тоже все?
Да, там тоже многие еще сидят на старых компьютерах ввиду низкой покупательской способности или устаревшего отношения руководства компаний к интернету. Статистика сейчас разнится, но в районе 4% у IE9-11 все еще есть, а это ну почти 100млн машин если только ЮВА считать. Так что веб там очень специфичен и многие до сих пор придерживаются табличной верстки либо в качестве энтерпрайз решения — флоатов, что не требует полифиллинга. Благо очень многое полифиллится, но не все. У госконтор та же история, но с началом Медведевской "диджитализации" все-таки ситуация немного лучше, и хром-то даже на старую винду админы поставят.
Спасибо за пояснение, я в DI не хорош — видел реализации в проектах, но смысла для фронтенда так и не понял. Предпочитаю делать слои экшенов, апи и сайд-эффектов отдельно от хранилищ, с доступом к глобальному стору.
Сборка упадет и он поправит, это же обычная опечатка. Получается этот паттерн через getInstance неактуален? Ни разу не видел в современных проектах, везде просто готовый инстанс экспортируется
Может глупый вопрос, но чем отличается от
export const dataBase = new DataBase()
? При импорте из разных мест придет один и тот же инстанс по правилам ES Imports. И DI соответственноclass { private dataBase = dataBase; }
без контейнера, передающего значение через декоратор.Вот это "подтягивание хвостов" только звучит как будто это осуществимо, на деле приходится обновлять компонент (селект к примеру) в библиотеке компонентов для реакта, вью, ангуляра, ставить задачи на все команды по подъему версии, причем они могут быть ниже приоритетом чем задачи от бизнеса, в итоге 5 из 10 микрофронтов обновились через неделю, еще два — через две, еще в трех эти задачи "потерялись" и остались в глубоком бэклоге. В итоге приложение несинхронизировано, баги остаются надолго, стили остаются разными. С десятью обновлениями или тем более редизайном получается такой треш, что смотреть на это все невозможно. Организационно это тоже не решить, так как у команд разные циклы поставок. В монолите же один апдейт и если используется TS, то он не даст оставить "хвосты", придется переводить сразу все — это займет больше времени на задачу, но сэкономит десятки человекочасов (считая время аналитиков, тестировщиков, разработчиков) по сравнению с микрофронтами. Не в идеальном мире живем, когда все всегда доводят задачи до конца.
Микрофронты всегда тесно связаны с рутом, в котором находятся глобальные компоненты и методы — не сделать серию нотификаций или модалок без доступа к информации, какие уже отображены, то же касается и авторизации, методов подключения/дестроя, роутинга. Изменения в каждом — критичны, если не поддерживать обратную совместимость, что сильно ограничивает возможности и раздувает код.
С этим не сталкивался, так как использую автоматический полифиллинг (babel preset-env), соответственно достаточно изменить browserslist в каждом микрофронте, но эту схему используют далеко не все и действительно может стать проблемой.
Микрофронты — не модный тренд, а метод обхода комплексного рефакторинга систем, чтобы можно было использовать легаси-части и постепенно их переводить на общую схему, имхо. А как по вашему, какое еще может быть у них назначение?
На моем опыте был только один случай, когда приложение состояло из двухуровневых iframe — виджет встраивался в сайты, и в самом виджете было несколько фреймов из разных доменных зон, связанных по postMessage. Но и там сделал единую точку рендеринга, в которую ходили разные бэкенды за получением локализованного шаблонизированного html, соответственно "зоопарка" не было, просто такой мультифреймовый конструкт с большим оверхедом по доступу к данным соседних "микрофронтендов".
В контекстном понимании, когда в едином поле страницы могут параллельно работать несколько разных фреймворков со своими компонентно-стилевыми системами, было только два проекта, и в обоих эта схема не была оправданной, так как приносила массу проблем и катастрофически замедляла разработку, несмотря на "параллелизацию поставок" — все равно в каждом релизе приходилось дорабатывать практически каждый микрофронтенд ввиду новых схем роутинга, подключения, общения с рутом, конфликтов лэйаута, обновления библиотек и дизайна и т.п. Поэтому я полностью отказался от подобных архитектур — оправдать их наличие перед бизнесом уже через пару месяцев становится нереально, так как профит только отрицательный. Думаю, что эта схема подходит только для легаси-проектов, как в статье, как переходный этап к новому монолиту, а проблема "Быстро писать код мешали устаревание подходов, фреймворков и зависимостей, а также связанность логики" решается планомерным рефакторингом — это обязательная часть разработки, не получится долго писать только бизнес-логику без архитектурных правок.
Можно еще писать компонент-тесты, делать подробную документацию к ним, обсуждать подходы типа двойные кавычки или одинарные, писать кастомные правила для ESLint, внедрять дополнительные статические анализаторы кода в CI/CD, делать обертки над многочисленными opensource-зависимостями для "абстракции от конкретной библиотеки", выносить каждое правило в стилях в миксины для "переиспользуемости", достигать какого-то уровня покрытия тестами кода, да мало ли еще бесполезной работы сейчас фронт-ендеры придумывают, лишь бы их бэк-енд был в тепле и достатке.
Клоакинг, как много в этом слове… Когда поисковики еще обращали внимание на
meta description
и не обращали наopacity: 0; width: 0;
или цепочку js-редиректов, можно было представить в поисковике сайт как угодно… Сейчас такие схемы только в даркнете работают ввиду отсутствия продуманности в краулерах сайтов. Но в лайтнете такое не работает уже лет 7.В подобной архитектуре намного больше проблем, чем описано, и код, обслуживающий их, становится настолько сложным и трудноподдерживаемым, что приходится выделять отдельную команду. Участвовал в двух проектах с подобной схемой. В числе других проблем — отсутствие общих компонентов либо большая сложность в их интеграции (обновить шрифт в 10-20 микрофронтах? Поднять версию библиотек, подтянуть иконки? В монолите — час работы, в подобной системе — больше месяца, кроме шуток); подтягивание актуальных версий для локальной разработки (придется писать сложный cli-инструмент с резолвом конфликтов и менеджерингом гит-версий в каждом микрофронте); на поддержку root-обертки тоже нужна команда, со своим флоу поставок и разрастающимися версиями для поддержки обратной совместимости, что выливается со временем в катастрофическое торможение новых релизов; про SSR даже можно не думать; в каждом микрофронте свои подходы и устаревающие конфиги, которые тоже нужно синхронизировать; кратное увеличение времени CI/CD; в браузере общий DOM, соответственно общая раскладка, и если не пользоваться iframe то очень велика вероятность конфликтов стилей или лэйаута; дублирование полифиллов. Перечислять можно еще долго, но закономерный итог — замедление поставок и тонна багов от синхронизации. А что считается "достоинством"? "Свободная реализация компонентов системы разными командами", то есть тотальный зоопарк. Про "независимость поставок", который часто тоже служит аргументом, лучше и не упоминать — в реальности этого нет, наоборот, приходится часто лезть во все репозитории, разбираться в их коде и подходах, и править, влезая в циклы поставок других команд.
За меньшее (!) время можно переписать легаси-код с полноценным рефакторингом и приведением к современным общим стандартам. Никто не мешает заложить архитектуру, в которой каждая страница — отдельный асинхронно загружаемый модуль, для развития которого не нужно лезть в рут и соответственно не будет конфликтов слияний, при этом он будет иметь доступ к глобальным методам и компонентам (модалки, нотификации, апи, роутинг), что уберет дубляж, будет поддерживать SSR, а апдейт зависимостей можно сразу протестировать на всем приложении. Не идите в эти дебри, тут вы не первопроходцы, а просто пополняете ряды страдальцев, занимающихся вместо продуктовой разработки решением самими же созданных проблем.
А я с вами и не спорил, читал внимательно — "Если вопрос про SSR то да", просто описал схему работы и показал, что это действительно ускоряет метрики загрузки интерфейса. Но в случае SPA, когда интерфейс описан на js, на сервере нужна среда, которая умеет его выполнять — так что нода практически без вариантов. Чтобы было более понятно, напишу пример:
Клиентский код:
Серверный код:
В итоге получаем соответствующий странице html-код в первом запросе + объект вида
window.INITIAL_DATA = { someData: 'test' };
, который можно отправить в клиентский js-код и чисто гидрироваться с выданной сервером разметкой. Еще раз повторюсь, что говорю про SPA, когда один и тот же js изоморфно выполняется на клиенте и сервере, а не когда клиентский js вручную навешивает какие-то события и триггеры на шаблон, отданный бэком. В других серверных языках выполнение React.renderToString может быть только в виртуальном окружении.