All streams
Search
Write a publication
Pull to refresh
47
0
Дмитрий Казаков @DmitryKazakov8

Front-end архитектор

Send message

Идея-то неплохая, если SPA без SSR, но есть ряд сайд-эффектов.

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

Во-вторых, как вы это сериализуете если есть SSR? При хранении в глобальном сторе `modals: [{ message, type }]` это легко сериализуется, а если хранилище открытости локальное - все намного сложнее.

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

В-четвертых, afterHideCallback удобнее определять при открытии, а не при закрытии - если например используется в целях онбординга (симуляция пользовательских действий). Да и нужных данных при вызове закрытия может не быть.

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

Почему не позволит? В HOC можно сделать такие же propTypes, как в обернутом компоненте, просто если они не переданы использовать дефолтные значения return <OriginalComponent prop1={props.prop1 || 'default value'} />

Да, с половиной я работал, и с Firebase для no-server сайтов. Речь о том, насколько разное взаимодействие с одной и той же базой на разных серверных языках. Вряд ли переход на другой язык потребует внезапно изменить базу одного типа на другую — скорее всего останется та же самая, а переход на другую будет по причинам, не зависимым от языка.


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

SQL синтаксис запросов и работа с базой разве сильно отличаются? Если используются ORM, только в синтаксисе различия, поэтому это не в счет сложности миграции. Кафки и рэббиты тоже те же самые, адаптеры как правило пишутся в сопоставимом формате — разве нет? Та же история про целостность и непротиворечивость — паттерны реимплементируются. Конечно, будут некоторые локальные особенности и ограничения подходов в фреймворках на других языках — но концептуально все, в моем понимании, то же самое.


А вот многопоточность да, может очень сильно различаться. Если не прав, буду рад более подробному объяснению — сам только на php и node.js фулстачил, да на python по мелочи дорабатывал — каких-то концептуальных отличий не видел, кроме ноды — там можно писать изоморфный код бэк-фронт, делать единую схему моделей и валидаций апи бэк-фронт. В этом смысле удобство и переиспользуемость шикарные, но производительность по сравнению с более низкоуровневыми серверными языками оставляет желать лучшего (судя по обзорным статьям), хотя и на ноде под танком 1000 запросов / сек для ряда кейсов сервер за 10мс секунд стабильно отдавал ответ, если задача не требует подключения к базе. Существенные различия по скорости только в сложных сценариях, включающих походы в разные базы и микросервисы. Но у нас речь не о скорости, а о сложности перехода в бэке с одного стека на другой — и тут мой опыт говорит, что это намного проще, чем на фронте.

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


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


Даже если знаешь, как идеально все организовать с точки зрения логики — подобрать подходящие библиотеки и написать аналогичную систему на другом стеке — дело не месяцев, но лет, путем нескольких масштабных итераций рефакторинга. Для чего это нужно, если на своем стеке решишь бизнес-задачи за N времени, а на новом за N*X? Если люди приходят "получить новый опыт, расширить свои компетенции и прокачаться" — это хорошо, но компаниям нужно совершенно не это, а качественное и быстрое решение задач. Поэтому на вакансии на других стеках я даже не смотрю — самокритик меня живьем съест, если буду делать задачи за значительно более долгое время, не смогу планировать сроки даже приблизительно, и качество значительно упадет. Считаю, что это более ответственный подход, чем "пойду не знаю куда и будь что будет, там разберусь".

Попробуйте, должно понравиться — бойлерплейта в разы меньше (по факту только observer и проброс контекста, в остальном работа со стором — как с обычным объектом, только реактивным).


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


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


БЭМ — это один из вариантов решения проблемы глобальной области видимости, когда создаются околоуникальные именования (хотя нередко они все же пересекаются и возникают баги). Минусы — длинные названия, нет автодополнения, нет быстрого перехода в стилевые файлы на конкретный класс, сложнее отслеживать наличие неиспользованных или отсутствующих классов, сложнее придумывать названия. Так как в реакт-проектах практически везде используется сборщик, то намного эффективнее использовать CSS Modules и получать все преимущества — автоматические суффиксы и префиксы по названию файла в наименованиях (исключают возможность пересечения), быстрые переходы сразу на нужный класс в стилях, автодополнение, возможность проверки неиспользуемых или отсутствующих классов. Это намного удобнее.

Да выбросьте вы из головы Redux, крайне неэффективный стейт-менеджер. Понимаю, что взяли его скорее для опыта и понимания работы легаси-проектов, но на том же MobX сделать намного проще. Вот пример реализации на нем + TS.

Импорты и типы

import _ from 'lodash';
import axios from 'axios';
import { observer } from 'mobx-react';
import { autorun, observable, runInAction, toJS } from 'mobx';
import { ChangeEvent, Component, ContextType, createContext } from 'react';
import Selectable from 'selectable.js';

import styles from './Table.scss';

type TypeStarship = {
  url: string;
  name: string;
  crew: string;
  model: string;
  length: string;
  edited: string;
  created: string;
  passengers: string;
  consumables: string;
  manufacturer: string;
  vehicle_class: string;
  cargo_capacity: string;
  cost_in_credits: string;
  max_atmosphering_speed: string;
};

Контекст для прямого доступа из дочерних компонентов

// eslint-disable-next-line @typescript-eslint/naming-convention
const StarshipsStore = createContext<{ starships: Array<TypeStarship> }>({ starships: [] });

type TypeStarshipsStoreContext = ContextType<typeof StarshipsStore>;

class ConnectedComponentStarship<TProps = any> extends Component<TProps> {
  static context: TypeStarshipsStoreContext;
  static contextType = StarshipsStore;
  declare context: TypeStarshipsStoreContext;
}

Верхняя обертка с пробросом контекста

export class App extends Component {
  render() {
    return (
      <StarshipsStore.Provider value={observable({ starships: [] })}>
        <Table />
      </StarshipsStore.Provider>
    );
  }
}

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

@observer
class Table extends ConnectedComponentStarship {
  localState: { prevStarships: Array<TypeStarship> } = observable({
    prevStarships: [],
  });

  select: any;
  trackDisposer: IReactionDisposer | null = null;

  componentDidMount() {
    void axios({
      method: `get`,
      url: `http://swapi.dev/api/vehicles`,
    }).then((result: any) => {
      runInAction(() => {
        this.context.starships = result.data.results;
        this.localState.prevStarships = result.data.results;
      });

      this.select = new Selectable({
        appendTo: `.${styles.table}`,
        filter: `.${styles.tableCell}`,
        autoRefresh: false,
        lasso: {
          border: '1px solid blue',
          backgroundColor: 'rgba(52, 152, 219, 0.1)',
        },
        ignore: [`input`],
      });

      document.addEventListener(`keydown`, this.escKeyDownHandler);

      this.trackChangedStarships();
    });
  }

  escKeyDownHandler = (evt: any) => {
    if (evt.key === `Escape` || evt.key === `Esc`) {
      evt.preventDefault();
      this.select.clear();
    }
  };

  trackChangedStarships = () => {
    this.trackDisposer = autorun(() => {
      if (_.isEqual(this.localState.prevStarships, this.context.starships)) return;

      const changedStarships = _.differenceWith(
        this.localState.prevStarships,
        this.context.starships,
        _.isEqual
      );

      changedStarships.forEach((starship) => {
        console.log('starship data changed', toJS(starship));
      });

      runInAction(() => {
        this.localState.prevStarships = toJS(this.context.starships);
      });
    });
  };

  componentWillUnmount() {
    document.removeEventListener(`keydown`, this.escKeyDownHandler);
    this.trackDisposer?.();
  }

  render() {
    const { starships } = this.context;

    const renderedKeys: Array<keyof TypeStarship> = [
      'cargo_capacity',
      'cost_in_credits',
      'max_atmosphering_speed',
      'name',
    ];

    return (
      <div className={styles.table}>
        <TableHeader renderedKeys={renderedKeys} />
        {starships.map((starship, index) => (
          <TableRow key={index} starship={starship} renderedKeys={renderedKeys} />
        ))}
      </div>
    );
  }
}

Заголовки и строки таблицы. Добавил динамический вывод столбцов, по сравнению с оригинальным кодом

@observer
class TableHeader extends ConnectedComponentStarship<{ renderedKeys: Array<keyof TypeStarship> }> {
  render() {
    const { renderedKeys } = this.props;

    return (
      <div className={styles.tableRowHeader}>
        {renderedKeys.map((param) => (
          <div key={param} className={styles.tableCell}>
            {param}
          </div>
        ))}
      </div>
    );
  }
}

@observer
class TableRow extends ConnectedComponentStarship<{
  starship: TypeStarship;
  renderedKeys: Array<keyof TypeStarship>;
}> {
  render() {
    const { starship, renderedKeys } = this.props;

    return (
      <div className={styles.tableRow}>
        {renderedKeys.map((param) => (
          <TableCell key={param} starship={starship} param={param} />
        ))}
      </div>
    );
  }
}

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

@observer
class TableCell extends ConnectedComponentStarship<{
  starship: TypeStarship;
  param: keyof TypeStarship;
}> {
  handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { starships } = this.context;
    const { starship, param } = this.props;

    const selectedFields = document.querySelectorAll(`.ui-selected`);

    if (!selectedFields.length) {
      runInAction(() => (starship[param] = event.target.value));

      return;
    }

    runInAction(() => {
      selectedFields.forEach(({ dataset }) => {
        const targetStarship = starships.find((s) => s.url === dataset.url);

        if (targetStarship) targetStarship[dataset.param] = event.target.value;
      });
    });
  };

  render() {
    const { starship, param } = this.props;

    return (
      <div data-url={starship.url} data-param={param} className={styles.tableCell}>
        <input onChange={this.handleChange} value={starship[param]} type={'text'} />
      </div>
    );
  }
}

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

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

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

Также присмотритесь к CSS Components вместо строкового указания глобальных классов. К теме статьи не относится, но видеть БЭМ в 2021 как-то очень удивительно.

Чтобы сделать изменение только одной колонки, достаточно добавить фильтр

selectedFields.filter(({ dataset }) => dataset.param === param)

"Я про случай когда единственный повод не использовать CDN для разработчика — лень скачивать и собирать библиотеку"


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


Остальные минусы вида "на случай работы в дороге", "дополнительный поиск DNS" и "удаление древнейших версий библиотек" локальны и их можно обойти.


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

Под "блокирующим" я подразумевал ts-loader без transpileOnly, т.е. билд не будет собираться, если есть ошибки в типах — это на удивление распространенный, но на мой взгляд неэффективный подход. Под неблокирующим имел в виду как раз ваш вариант.


Сам люблю часто рефакторить, но тут все зависит от архитектуры. Если в приложении нет связки по строкам и динамических названий структур, то в 95% случаев достаточно делать findUsages в IDE и при рефакторинге интерфейсов и параметров все места легко открыть и поправить. Поэтому ситуация, когда на pre-commit ругается ts у меня крайне редкая, и в целом намного удобнее использовать IDE чем потом в консоли смотреть, что развалилось, и вручную переходить на каждый файл и искать строчку с ошибкой.


Если архитектура не подходит и findUsages не работает корректно — тогда да, ваш вариант имеет место быть, и поможет найти развалившиеся места.

Я вот давно отказался от проверки типов по всему проекту при каждой пересборке — при блокирующем режиме это блочит билд и отображение в браузере, при неблокирующем — медленнее, чем бабель с вырезанием ts-типов, при неблокирующем параллельном — отстает от реального кода, который мог несколько раз измениться, а ошибки выдадутся нерелевантные.


При необходимости (после завершения куска логики) можно вручную вызвать проверку типов, и обязательно — на pre-commit+CI. Это максимально удобно, и скорость инкрементальной сборки на современных сборщиках почти мгновенная.


Еще в Бабеле запилили хороший кеш, который тоже значительно сокращает время сборки (перешел на него с happypack/thread-loader и стало быстрей, чем параллельная сборка в нескольких процессах).


А про один AST на разные инструменты, конечно, согласен, но вряд ли в обозримом будущем смогут договориться Eslint+TS+babel+IDE на единый всем подходящий формат, так как это будет блочить развитие и оптимизацию каждого инструмента в отдельности.

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


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


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

Эта эпоха никуда не делась, просто "ушла в тень". Но для даркнета не подойдет такая либа, т.к. завязана на js

"Я сам не слышал, но мне так сказали". Я работал только с первым ангуляром, про ломающие обновления слышал от коллег, поэтому аргументированно не смогу ответить.

Хотел бы я посмотреть на код автора этой статьи… Все равно ему придется писать и механизм рендеринга в DOM, и механизм обновления данных патчами (т.к. переписывание innerHTML собьет listeners), и жизненный цикл компонентов (т.к. нужно где-то очищать listeners и timeouts), и механизм обработки пользовательских событий (onclick="window.myFunc" не подойдет из-за засорения глобальной области видимости и недоступности локального контекста данных), и экстракт состояния, отрендеренного на сервере, из разметки, и много чего еще. Не собирается же он бэкендовым twig рендерить модалки, графики, селекты, валидации форм и т.п., чтобы при каждом клике на элемент страница перезагружалась?


Насчет недостатков Angular я скорее согласен с его ломающими обновлениями и обилием встроенных библиотек, но про "другие модные библиотеки" он так зря.


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

Декорирование — древний концепт, и нападок на него как на паттерн я не понимаю. Это оборачивание функции в новую функцию, которая делает что-то дополнительное, а затем вызывает оригинал [+ что-то дополнительное]. Если нападки на синтаксис с "собачкой" над свойствами класса — так это можно воспринимать как сахар, который стабильно корректно транспайлится бабелем уже много лет и не вызывает проблем.


Пожалуй, отказ от этого сахара может быть только по одной более-менее разумной причине — желании сохранять итоговый код как можно ближе по синтаксису к исходному. Но тогда исходный надо писать на ES2015 — на Хабре есть такие любители, даже если профессионалы, но все равно встречаются нечасто. А тут вот в статье еще один — никаких классов (для сторов и для компонентов), никаких типов, никакого Prettier с форматированием с отступами. Удивительно, что Proxy со скрипом, но приняты к использованию...

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


Независимость релизов от core-обертки иногда благо, иногда — трагедия. Тоже были случаи, когда в core были breaking changes и надо было все микрофронты обновить (и не раз такие случаи), и если бы в одной репе это выглядело бы в основном как "пробежался автозаменой — проверил — выложил", то при многих командах выглядело как "фриз core -> ждешь неделю апдейта от 3 команд -> фриз этих 3 команд -> ждешь 2 недели остальные команды либо лезешь сам разбираться в их зоопарке -> общий тест -> еще несколько правок, пока все команды зафрижены -> релиз". Сколько тут ресурсов и времени разработчиков терялось — не сосчитать, а опыт взят из богатой конторы с большим количеством фронтов. Ошибка была только одна — решили делать микрофронтенды. Таких историй у меня выше крыши, но статьи по ним не пишут)


Если и не полный фриз а команды продолжают работу пока другие подтягиваются — то в момент когда все должны сойтись в одной точке прилетают критичные баги по несвязанным задачам сразу из нескольких микрофронтов + неготовность некоторых сервисов бэка под новые изменения, что может привести вплоть до отката breaking changes в core и всех подпроектах с колоссальными усилиями и увольнением разработчиков, которые вынуждены возиться в подобном.


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

Редко появляются технические статьи из фронтового энтерпрайза, интересно почитать. Первые две схемы действительно полны недостатков, третья — выглядит удобнее, но возиться с ее настройкой ввиду новизны приходится немало. Насчет стрелочных функций в IE, кстати, так как модули — отдельно собранные файлы, то при их загрузке код можно выполнить в try-catch с отслеживанием превышения времени выполнения в секунды 3 (для защиты от бесконечных циклов) и логировать случаи жестких ошибок или некорректного синтаксиса. Если уж совсем независимые команды делают, то это дополнит защиту ErrorBoundary.


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


При этом в монорепе:


"Команды разрабатывают модули изолированно и не влияют друг на друга" — если страницы сделаны через вебпаковые асинхронные импорты, то проблемы нет. В каждой папке страницы может быть много модульных сторов, экшенов, компонентов, роутов, апи-запросов — и для разработки сложных страниц не нужно лезть в окружающий код, при необходимости изоляцию (запрет глобальных импортов) можно включить в eslint-правила. Не так уж сложно проконтролировать то, что "команда не может внести изменения в архитектуру системы" — а с точки зрения безопасности, если захотят, то и через iframe, npm или module federation встроят любой код типа картинки, делающей гет-запрос с шифрованным auth-токеном и базой юзеров. Микрофронты тут никак не защищают, и вообще встраивать код быстро меняющихся чужих команд которые пишут как хотят внутри своих реп — никак не по энтерпрайзу.


"Изолированность позволяет каждой команде работать в привычном режиме" — в монорепе вполне можно сделать удобные релизные ветки и менеджерить общие релизы в мастер. Есть проблема с откатами, но с микрофронтами там тоже все непросто.


"Команды придерживаются отличающихся практик и требований к написанию" — если так уж нужно, можно отключить для определенных папок-страниц проверки ESLint и даже TS, если некая аутсорс команда не хочет автоформатирования, типизации, проверки ошибок в рантайме и ей обязательно нужны табы вместо пробелов для отступов и длинные строки инструкций.


"Бизнес-процессы в некоторых модулях очень сложны" — никак не связано с микрофронтендами, почему написано в списке причин их заводить — непонятно.


Итого — вместо менеджеринга фиче- и релизных веток в монорепе было решено пойти во все тяжкие, потратив большое количество ресурсов компании и получив все возможные "болячки" микрофронтов, о которых уже много было написано. При этом при выделении в монорепе просто изолированных папок-модулей и при асинхронной их подгрузке, не было бы проблем с кешем, полифиллингом (в микрофронтах полифиллы дублируются, т.к. не весь код прогоняется через корневой бандлер), передачей глобальных данных (сторов) и методов (контекст, пропсы), расхождением или дублированием версий uikit и других библиотек от модуля к модулю, дублированием, выделением общих сложных компонентов (типа панели выбора сотрудника), настройкой ci/cd, локальной подгрузкой актуальных модулей (т.к. есть общий master), возней с версионированием и откатом npm-пакетов, типизацией, таких проблем с безопасностью этих "черных ящиков"...


Не проще ли выбрать пару понятных проблем монореп — необходимости аккуратного менеджеринга веток разными командами и оптимизации первичной сборки и пересборки, вместо всего набора этих и многих других проблем (особенно если вдруг станут не моностековые микрофронты)?

Хотя я не согласен с MaZaAa, что код читается слева-направо, в вашем continueBtnActive много недостатков.


Во-первых, использовать специальные конструкции языка — значит ухудшать читаемость и скорость восприятия для разработчиков в целом. Если вы видели конструкции Elixsir или Ruby, тогда не нужно объяснять, если не видели — то Boolean() всегда предпочтительнее !!. Описание логики "не на языке, а с помощью языка" — одно из ключевых требований к синьорскому коду, по крайней мере в проектах с долгосрочной поддержкой.


Во-вторых, приведение типов в JS — очень сомнительное "удовольствие", так как часто результат будет не тем, что ожидается. TS решает эту проблему только частично, оставляя пространство для логических багов. Поэтому чистое сравнение this.title.value.trim() === '' выглядит намного лучше.


В-третьих, работа с операторами && и || требует хорошего знания работы языка, так как сравнение производится как boolean, а результат возвращается как значение, соответственно 0 && 1 выведет 0, а не false. В конкретном коде это не играет роли, но такие ошибки предикции распространены очень сильно. И даже в примере — чуть удлинится код, и предсказать возвращаемое значение будет сложно как путем визуального анализа, так и статическим анализом, либо результатом выполнения.


Что касается темы "кнопки не должны сейчас срабатывать" — в любом случае, стейт машины запущены или отдельные функции, придется описывать эту логику отдельно. Если, конечно, в XState нет проверки disabled={someStateMachine.isTransitioning}, так как добрая половина логики в приложениях — асинхронная. С MobX это описывается очень просто.


Несмотря на быковатый русский у MaZaAa во всем треде я не вижу его явных ошибок в суждениях. В концепте change-reaction не может быть недостатков, есть только непродуманное ее использование.

Если в проекте используется Webpack, то он при билде строит граф зависимостей, который можно отобразить в любом формате. Например, такой миниатюрный плагин позволяет описывать в .dot типа


digraph sources {
        rankdir=LR;
    "node_modules/@sentry/hub/esm/index.js" -> "node_modules/@sentry/hub/esm/session.js";
    "node_modules/@sentry/hub/esm/index.js" -> "node_modules/@sentry/hub/esm/scope.js";
    "node_modules/@sentry/hub/esm/index.js" -> "node_modules/@sentry/hub/esm/hub.js";
    "src/components/Footer/Footer.tsx" -> "src/components/Footer/Footer.scss";
    "src/components/Header/Header.tsx" -> "src/components/Header/Header.scss";
    "src/assets/icons.ts" -> "src/assets/icons/arrow-left-bold.svg";
    "src/assets/icons.ts" -> "src/assets/icons/arrow-left.svg";
    "src/assets/icons.ts" -> "src/assets/icons/arrow-right-bold.svg";
    "src/assets/icons.ts" -> "src/assets/icons/arrow-right.svg";
    "src/assets/icons.ts" -> "src/assets/icons/auth.svg";
}

который можно отобразить графически в одном из многочисленных онлайн просмотрщиков, либо в плагине к IDE (для WebStorm тот же dotplugin). Плюс в том, что можно самостоятельно выбирать итоговый формат, фильтровать, конкатенировать, включать в git для просмотра из онлайн-утилит, делать статический анализ зависимостей по сгенерированному графу в гит-хуках или CI. Минус в том, что учитываются только импорты, а не динамически используемые зависимости (через IoC, Context), поэтому для полноценной демонстрации архитектуры не подходит, но полноценный граф по реальным динамическим usages статическим анализом сгенерировать сложно.


Как-то медитировал над задачей "найти все использования параметров хранилищ в компонентах", но для этого нужен стейт, отслеживающий использование параметров (как MobX) и прогон полного автоматизированного регресса сайта. Хотя это вполне реально сделать, на практике фактически бесполезно — только для проверки, правильно ли используется архитектура в проекте.

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


В тексте статьи у вас увидел parallel-webpack, сейчас бы я не стал его рекомендовать — там накопилось довольно много багов и репозиторий заброшен, проще написать свое решение типа такого webpack-parallel-simple

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity