Владислав Калачев

Micro-frontend архитектура: когда она действительно нужна?

13

Когда я только начинал работать с фронтендом, micro-frontend звучал как buzzword из мира гигантов уровня Amazon или Netflix. Казалось: «Ну это же для тех, у кого тысячи разработчиков». Но после пары лет в команде, которая развивала продукт с десятками страниц и десятками разработчиков, я понял — micro-frontend реально решает проблемы, когда проект и команда растут.

В этой статье я разберу, когда MFE нужен, когда нет, и покажу реальные примеры на React с Module Federation и роутингом.

Почему вообще появляется интерес к micro-frontend?

Представьте: у вас есть огромный SPA на React. Команда выросла с 5 до 30 человек. Каждую неделю кто-то:
— меняет общие компоненты;
— обновляет библиотеки;
— выкатывает новые страницы.

В итоге:
CI/CD стал занимать по 30–40 минут, потому что всё пересобирается;
— баг в корзине может «положить» весь сайт;
— даже маленькое изменение в личном кабинете требует релиза всей системы.

Знакомо? Именно в этот момент бизнес и разработчики начинают задумываться: «А может разделить приложение на независимые части, как это делают бэкендеры с микросервисами?»

Что такое micro-frontend?

Micro-frontend — это архитектурный подход, при котором одно большое приложение разбивается на несколько независимых фронтендов. Каждый из них:
— может иметь свой собственный стек (React, Vue, Angular);
— разрабатывается своей командой;
— деплоится независимо от остальных.

Module Federation: стандарт де-факто

До Webpack 5 мы мучились с iframe или динамическими import() из CDN. Сейчас почти все используют Module Federation.

Пример: Host + Remote

Host (основное приложение)webpack.config.js:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
        cart: 'cart@http://localhost:3002/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

Remote (Dashboard)webpack.config.js:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard',
      filename: 'remoteEntry.js',
      exposes: {
        './Dashboard': './src/Dashboard',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

Remote (Cart)webpack.config.js:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'cart',
      filename: 'remoteEntry.js',
      exposes: {
        './Cart': './src/Cart',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

Пример React-роутинга для micro-frontend

Host (основное приложение)

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

const Dashboard = lazy(() => import('dashboard/Dashboard'));
const Cart = lazy(() => import('cart/Cart'));

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Главная</Link>
        <Link to="/dashboard">Dashboard</Link>
        <Link to="/cart">Cart</Link>
      </nav>

      <Suspense fallback={<div>Загрузка модуля...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard/*" element={<Dashboard />} />
          <Route path="/cart/*" element={<Cart />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

function Home() {
  return <h1>Главная страница</h1>;
}

export default App;

Remote (Dashboard)

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';

export default function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
      <nav>
        <Link to="analytics">Analytics</Link>
        <Link to="settings">Settings</Link>
      </nav>

      <Routes>
        <Route path="analytics" element={<Analytics />} />
        <Route path="settings" element={<Settings />} />
      </Routes>
    </div>
  );
}

function Analytics() {
  return <div>Страница Analytics</div>;
}

function Settings() {
  return <div>Страница Settings</div>;
}

Remote (Cart)

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';

export default function Cart() {
  return (
    <div>
      <h2>Cart</h2>
      <nav>
        <Link to="items">Items</Link>
        <Link to="checkout">Checkout</Link>
      </nav>

      <Routes>
        <Route path="items" element={<Items />} />
        <Route path="checkout" element={<Checkout />} />
      </Routes>
    </div>
  );
}

function Items() {
  return <div>Список товаров</div>;
}

function Checkout() {
  return <div>Страница оформления заказа</div>;
}

Итог: у каждого remote модуля свой собственный роутинг, а host управляет только корневыми маршрутами (/dashboard и /cart), не зная деталей внутренней структуры модулей.

Когда micro-frontend реально нужен

Мой опыт показывает, что micro-frontend оправдан:

Большая команда (5+ отдельных команд)
У нас был проект, где одна команда делала CRM, другая — биллинг, третья — портал клиентов. Обновления в одной части не должны ломать другие.

Разные технологии
Например, вы переписываете Angular-приложение на React, но нужно продолжать поддерживать старый код. MFE позволяет плавно мигрировать.

Сложные домены
Когда у приложения несколько разных бизнес-направлений, которые живут своей жизнью.

Разный релизный цикл
У нас был модуль «Оплата», который обновлялся раз в месяц (жёсткие регуляции), и модуль «Маркетинг», который менялся чуть ли не каждый день. MFE позволил не блокировать друг друга.

Когда micro-frontend — это лишнее?

Micro-frontend — не серебряная пуля. Если:
— у вас одна команда;
— приложение небольшое (до 50–70k строк кода);
— нет планов использовать разные технологии
то MFE принесёт больше боли:
— сложнее локальная разработка (поднять несколько приложений одновременно);
— сложнее DevOps (нужна инфраструктура для сборки и деплоя модулей);
— увеличивается размер бандла, потому что каждая часть тянет свои зависимости.

Пример из практики

Я видел команду из 4 человек, которые пытались запустить MFE, чтобы «быть как в больших компаниях». В итоге:
— они тратили по часу на запуск локальной разработки;
— появились баги с версионированием зависимостей;
— процесс деплоя стал в 3 раза сложнее.

Через полгода они отказались от MFE и вернулись к классическому SPA.

Пример структуры проекта (React + Module Federation)

/host-app
  ├── src/
  │   └── App.jsx
  └── webpack.config.js

/dashboard-app
  ├── src/
  │   └── Dashboard.jsx
  └── webpack.config.js

/cart-app
  ├── src/
  │   └── Cart.jsx
  └── webpack.config.js

Каждый модуль запускается на своём порту:
— Host: localhost:3000
— Dashboard: localhost:3001
— Cart: localhost:3002

Host динамически подключает Dashboard и Cart как независимые модули.

Вывод

Micro-frontend архитектура — это не про модули ради модулей. Это про:
— независимые команды;
— независимые релизы;
— гибкость в технологиях.

Если ваш проект маленький или у вас одна команда — оставайтесь на обычном SPA.
Если же вы растёте и упираетесь в монолит — MFE может дать нужную гибкость, но вместе с этим придётся вкладываться в инфраструктуру и DevOps.

Комментарии:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *